自定义View之歌词渐变文本控件
来源:互联网 发布:淘宝联盟新手怎么推广 编辑:程序博客网 时间:2024/04/29 17:16
自定义view之歌词渐变文本控件
前言
本文是自定义view的练习,默认读者掌握了自定义view的知识
本文的参考文章
http://blog.csdn.net/lmj623565791/article/details/44098729
使用方法:
https://github.com/CCY0122/lyrictextview
效果
实现
第一步吧我们要对外提供属性,让用户自由设置,这个一般是自定义view的老套路。在res下的values下新建的attrs.xml里定义好可以自定义的属性:
<declare-styleable name="LyricTextView"> <attr name="text" format="string" /> <attr name="text_size" format="dimension" /> <attr name="default_color" format="color|reference" /> <attr name="changed_color" format="color|reference" /> <attr name="progress" format="float" /> <attr name="direction"> <enum name="left" value="0" /> <enum name="right" value="1" /> </attr> </declare-styleable>
以上属性分别是文本、文本大小、默认颜色、渐变颜色、渐变百分比、渐变方向。不多说啦。然后新建LyricTextView继承view,重写前三个构造,在构造里获取属性:
public LyricTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray t = context.obtainStyledAttributes(attrs, R.styleable.LyricTextView); text = t.getString(R.styleable.LyricTextView_text); if(text == null){text = "";} textSize = t.getDimension(R.styleable.LyricTextView_text_size, sp2px(16)); defaultColor = t.getColor(R.styleable.LyricTextView_default_color, DEFAULT_COLOR); changeColor = t.getColor(R.styleable.LyricTextView_changed_color, CHANGED_COLOR); direction = t.getInt(R.styleable.LyricTextView_direction, LEFT); progress = t.getFloat(R.styleable.LyricTextView_progress,0); t.recycle(); initPaint(); measureText(); }
可以看到首先是获取了xml里自定义好的属性,设置给对应变量。然后这个构造方法里还调用了两个方法initPaint(); measureText();
一个是用来初始化画笔的,一个是用来测量文本宽高的。代码如下。
private void initPaint() { paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setTextSize(textSize); //必须在measureText前设置 } /** * 测量内容文本的宽高,当改变textSize时应重新调用此方法 */ private void measureText() { Rect r = new Rect(); paint.getTextBounds(text, 0, text.length(), r);//一个坑:rect.width(或者说rect.right-rect.left)得到的值会比实际字长度小一点点,因此这里使用paint.measureText方法获取宽度 textHeight = r.bottom - r.top; textWidth = (int) paint.measureText(text, 0, text.length()); }
在初始化画笔时,首先要给paint设置好字体大小,因为后面measureText
里调用的方法都是在文字大小设置好的前提下获取的才是正确的值。 paint.getTextBounds
是测量文字的宽高值然后放入rect里,但是实测发现一个坑:宽度会比实际宽度小一点,这会导致后面颜色渐变到100%时还有一丢丢字体没变色。因此获取文字宽度使用了paint.measureText
方法。
接下来是onMeasure方法。先看代码。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); width = measure(widthMeasureSpec, true); height = measure(heightMeasureSpec, false); setMeasuredDimension(width, height); } private int measure(int measureSpec, boolean isWidth) { int mode = MeasureSpec.getMode(measureSpec); int size = MeasureSpec.getSize(measureSpec); switch (mode) { case MeasureSpec.EXACTLY: break; case MeasureSpec.AT_MOST: case MeasureSpec.UNSPECIFIED: if (isWidth) { size = textWidth; } else { size = textHeight; } break; } return isWidth ? (size + getPaddingLeft() + getPaddingRight()) : (size + getPaddingTop() + getPaddingBottom()); }
如果设定了确定值(EXACTLY),那就设置之,如果没设,则控件大小就是文本的大小。
好了,核心部分onDraw来了.
本控件其实知识点就一个,怎么实现字体渐变?
答:画布canvas有一个方法canvas.clipRect()
,调用了这个方法后接下来只会在这个区域内画内容,超出这个区域的内容就不画了。那么对于我们歌词渐变,我们先用默认颜色画出全部文本,然后呢,根据变量progress(渐变比例,范围[0,1])和方向direction(确定从左到右渐变还是从右到左)计算出要变色的区域,然后用渐变颜色再画一次文本即可。
先放上代码。
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); drawText(canvas, direction, progress); } private void drawText(Canvas canvas, int direction, float progress) { int startX; int endX; int realWidth = (getMeasuredWidth() - getPaddingLeft() - getPaddingRight()); int realHeight = (getMeasuredHeight() - getPaddingTop() - getPaddingBottom()); int textLeft = getPaddingLeft() + realWidth / 2 - textWidth / 2; //文本在控件中的起始x位置 int textRight = getPaddingLeft() + realWidth / 2 + textWidth / 2; // 文本在控件中的结束x位置 int textBottom = getPaddingTop() + realHeight / 2 + textHeight / 2; //文本在控件中的结束y位置 if(progress < 0 ){progress = 0;} if(progress > 1 ){progress = 1;} int changedWidth = (int) (textWidth * progress); if (direction == LEFT) { startX = textLeft; endX = textLeft + changedWidth; } else { startX = textRight - changedWidth; endX = textRight; } //画正常的文字内容 paint.setTextSize(textSize); Paint.FontMetrics fontMetrics = paint.getFontMetrics(); canvas.save(); paint.setColor(defaultColor); canvas.drawText(text, textLeft, textBottom, paint); canvas.restore(); //画渐变部分的文字 canvas.save(Canvas.CLIP_SAVE_FLAG); paint.setColor(changeColor); canvas.clipRect(startX, 0, endX, getMeasuredHeight()); canvas.drawText(text, textLeft, textBottom, paint); canvas.restore(); }
哇,贼简单。一些位置的计算,细心读一下应该能懂。如果你没理解,那可以去看看开头的参考文章,咱们的鸿洋大大写的。
小优化
我们先直接看看目前的效果吧:
<com.example.lyrictextview.LyricTextView android:id="@+id/lyric" android:layout_width="match_parent" android:layout_height="60dp" android:background="#44000000" app:changed_color="#ff0000" app:default_color="#000000" app:direction="left" app:progress="0.7" app:text="按时大大的飒飒的" app:text_size="26sp" />
破费特,效果可以
那我们在用wrap_content看看。
<com.example.lyrictextview.LyricTextView android:id="@+id/lyric" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#44000000" app:changed_color="#ff0000" app:default_color="#000000" app:direction="left" app:progress="0.7" app:text="按时大大的飒飒的" app:text_size="26sp" />
好了,问题来了,文字下面有一小部分没显示呀。
对于这个问题,看图(图片来自他人博客):
上图是android中文本绘制的各种线,我们平时绘制一个文本时呢,就是从baseLine最左端开始绘制的(上图中红点)。关于这个文本绘制的细节,已经有很多完整分析的文章了。
看上面这张图,字母g、j、p都是会超出baseLine的,这也就是为什么当我们宽高为wrap_content时文字下面一小部分不显示的原因了。
解决方法1:如果你不需要非常精细,直接给控件设置个默认padding就可以,就几个dp差不多就能把底部显示出来。
解决方法2:
1)修改measureText
:
private void measureText() { Rect r = new Rect(); paint.getTextBounds(text, 0, text.length(), r);//一个坑:rect.width会比实际字长度小一点点// textHeight = r.bottom - r.top; Paint.FontMetrics fontMetrics = paint.getFontMetrics(); textHeight = (int) (-fontMetrics.ascent + fontMetrics.descent); textWidth = (int) paint.measureText(text, 0, text.length()); }
Paint.FontMetric里包含了文本绘制的各种线。因为绘制基线是baseLine,基线为0,坐标轴向下为正,故在其之上的是负数。
2)修改onDraw绘制部分:
//画正常的文字内容 paint.setTextSize(textSize); Paint.FontMetrics fontMetrics = paint.getFontMetrics(); canvas.save(); paint.setColor(defaultColor);// canvas.drawText(text, textLeft, textBottom, paint); canvas.drawText(text, textLeft, textBottom - fontMetrics.descent, paint); canvas.restore(); //画渐变部分的文字 canvas.save(Canvas.CLIP_SAVE_FLAG); paint.setColor(changeColor); canvas.clipRect(startX, 0, endX, getMeasuredHeight());// canvas.drawText(text, textLeft, textBottom , paint); canvas.drawText(text, textLeft, textBottom - fontMetrics.descent, paint); canvas.restore();
好了,就是绘制文字时向上偏移一定距离,即fontMetrics.descent
这样,当控件宽高为wrap_content时文字也能显示完整啦
最后再贴上对外公开的setter、getter方法:
/以下settre getter供外部设置属性,别忘记invalidate(); //ps:若要使用属性动画控制progress,前提得有progress的setter getter public void setProgress(float progress) { this.progress = progress; invalidate(); } public float getProgress() { return progress; } public void setTextSize(float size) { textSize = size; initPaint(); measureText(); requestLayout();//wrap_content情况下文字大小改变后需重新onMeausre invalidate(); } public float getTextSize() { return textSize; } public int getDirection() { return direction; } public void setDirection(int direction) { this.direction = direction; invalidate(); } public String getText() { return text; } public void setText(String text) { this.text = text; requestLayout(); //wrap_content情况下文字长度改变后需重新onMeausre invalidate(); } public int getDefaultColor() { return defaultColor; } public void setDefaultColor(int defaultColor) { this.defaultColor = defaultColor; invalidate(); } public void setAll(float progress, String text, float textSize, int defaultColor, int changeColor, int direction) { this.progress = progress; this.text = text; this.textSize = textSize; this.defaultColor = defaultColor; this.changeColor = changeColor; this.direction = direction == LEFT ? LEFT : RIGHT; initPaint(); measureText(); requestLayout(); invalidate(); } public int getChangeColor() { return changeColor; } public void setChangeColor(int changeColor) { this.changeColor = changeColor; invalidate(); } //工具 private float dp2px(int dp) { return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics()); } private float sp2px(int sp) { return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics()); }
这是一个简单的小控件,写它主要是打算写一个仿今日头条指示器的控件~过些天应该会写吧~~~
源码https://github.com/CCY0122/lyrictextview
- 自定义View之歌词渐变文本控件
- 自定义View强势来袭,用自定义View实现歌词显示控件上篇之实现歌词文件解析
- 自定义View强势来袭,用自定义View实现歌词显示控件下篇之自定义LyricView的实现
- 自定义文本横向渐变消失的控件
- 自定义View 之 图标&文本 标题信息小控件
- Android自定义View,高仿QQ音乐歌词滚动控件!
- 歌词显示控件的实现下——自定义View
- 自定义View之渐变色圆形进度条
- 自定义View之实用渐变色进度条
- 自定义View之颜色渐变折线图
- Android 自定义View之矩形渐变表格
- 自定义View之颜色渐变折线图
- 自定义view之Text文本
- 自定义歌词展示控件
- android自定义view显示歌词
- 自定义View之GradualView文字渐变-颜色渐变-图像渐变
- 自定义控件之 继承 View
- 自定义View之组合控件
- Echarts入门使用
- 从零开始学_JavaScript_系列(43)——Symbol简述
- xxxxxxxx
- POJ 3680 <离散化+最小费用最大流模版>
- javascript中const/let/var区别浅析
- 自定义View之歌词渐变文本控件
- Search Algorithm introduction
- python 关于seek
- JDBC连接数据库学习心得2
- 开心一下
- Java多态性
- Android 实时监测(监听)网络连接状态变化
- 20170605 宁愿多花点时间去想好
- 软件工程之功能性需求和非功能性需求