今天做的是一个简单支持文字部分渐变效果的控件,还是先放上成果:
如上图,这个控件可以做特殊的loading动画,比如下载、上传、等,也可以用在viewpager切换时的tab,实现文字部分变色等。
-
实现原理:
拿到文字,先把它渲染在画布上,作为底色
然后对画布进行矩形裁剪clipRect(),paint换一种颜色,再把文字绘制一遍,即可
-
裁剪的尺寸是根据外部传入的progress、渐变方向、以及文字的宽高确定
-
先铺代码:
-
attrs.xml 比较简单,定义两种对比的颜色、文字大小和文字内容 :
- 写一个view的子类,(其实继承TextView更方便一点)
全局变量
private int mDirection ; //渐变的方向
public static final int DIRECTION_LEFT = 0;
public static final int DIRECTION_RIGHT = 1;
public static final int DIRECTION_TOP = 2;
public static final int DIRECTION_BOTTOM = 3;
public void setDirection(int direction) {
mDirection = direction;
postInvalidate();
}
private Paint mPaint;
private String mText;//显示的文字
private int mTextSize;//文字大小
private float mProgress;//渐变位置
private int mFirstColor;//base文字颜色
private int mSecondColor;//变化的文字颜色
public void setmProgress(float mProgress) {
this.mProgress = mProgress;
postInvalidate();
}
构造器,我的千篇一律的写法~~
public ShadeTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ShadeTextView, 0, defStyleAttr);
for (int i = 0; i < typedArray.length(); i++) {
int attr = typedArray.getIndex(i);
switch (attr) {
case R.styleable.ShadeTextView_text:
mText = typedArray.getString(attr);
break;
case R.styleable.ShadeTextView_firstColor:
mFirstColor = typedArray.getColor(attr, Color.BLACK);
break;
case R.styleable.ShadeTextView_secondColor:
mSecondColor = typedArray.getColor(attr, Color.BLUE);
break;
case R.styleable.ShadeTextView_textSize:
mTextSize = typedArray.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
break;
/*case R.styleable.ShadeTextView_progress:
mProgress = typedArray.getInteger(attr, 30);
break;*/
}
}
typedArray.recycle();
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setTextSize(mTextSize);
}
onMeasure()的处理,如果继承TextView的话可以省略很多代码。。。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
int width = 0, height = 0;
switch (specMode) {
case MeasureSpec.EXACTLY:
width = specSize + getPaddingRight() + getPaddingLeft();
break;
case MeasureSpec.AT_MOST:
width = (int) (mPaint.measureText(mText) + getPaddingLeft() + getPaddingRight());//先确定mPaint是否已经设置textsize
break;
}
specMode = MeasureSpec.getMode(heightMeasureSpec);
specSize = MeasureSpec.getSize(heightMeasureSpec);
switch (specMode) {
case MeasureSpec.EXACTLY:
height = specSize + getPaddingTop() + getPaddingBottom();
break;
case MeasureSpec.AT_MOST:
height = (int) (Math.abs(mPaint.getFontMetrics().bottom - mPaint.getFontMetrics().top) + getPaddingTop() + getPaddingBottom());
break;
}
setMeasuredDimension(width, height);
}
重头戏 onDraw(),本篇的思想精华所在。
@Override
protected void onDraw(Canvas canvas) {
float textWidth = mPaint.measureText(mText);
float mLeft = (getMeasuredWidth() - textWidth) / 2;
float textHeight = Math.abs(mPaint.getFontMetrics().bottom - mPaint.getFontMetrics().top);
float mTop = (getMeasuredHeight() - textHeight) /2 ;
//先画底层的文字
mPaint.setColor(mFirstColor);
canvas.drawText(mText, mLeft, getY(), mPaint);
mPaint.setColor(mSecondColor);
// 接着根据传入的渐变方向、颜色等来裁剪,接着在同样的位置重新渲染文字
if (mDirection == DIRECTION_LEFT) {
canvas.save(Canvas.CLIP_SAVE_FLAG);
canvas.clipRect(mLeft, 0, textWidth * mProgress + mLeft, getMeasuredHeight());
canvas.drawText(mText, mLeft, getY(), mPaint);
canvas.restore();
} else if (mDirection == DIRECTION_RIGHT){
canvas.save(Canvas.CLIP_SAVE_FLAG);
canvas.clipRect(textWidth - textWidth * mProgress + mLeft , 0 , textWidth + mLeft, getMeasuredHeight());
canvas.drawText(mText, + mLeft, getY(), mPaint);
canvas.restore();
} else if (mDirection == DIRECTION_TOP){
canvas.save(Canvas.CLIP_SAVE_FLAG);
canvas.clipRect(0, mTop , getWidth(), textHeight * mProgress + mTop);
canvas.drawText(mText, + mLeft, getY(), mPaint);
canvas.restore();
}else if (mDirection == DIRECTION_BOTTOM){
canvas.save(Canvas.CLIP_SAVE_FLAG);
canvas.clipRect(0, textHeight - textHeight * mProgress + mTop, getWidth(), textHeight + mTop );
canvas.drawText(mText, + mLeft, getY(), mPaint);
canvas.restore();
}
}
public float getY() {
Paint.FontMetricsInt fm = mPaint.getFontMetricsInt();
return (getHeight() + fm.descent - fm.ascent) / 2 - fm.descent;
}
值得说明的是,
float textHeight = Math.abs(mPaint.getFontMetrics().bottom - mPaint.getFontMetrics().top);
return (getHeight() + fm.descent - fm.ascent) / 2 - fm.descent;
这里借鉴了Android 自定义View-怎么绘制居中文本?的研究成果,怒学。
-
View基本写完,可以先测试了,先用seekbar测试四个方向的渐变效果,代码比较简单,只贴一段根据seekbar的progress值设置我们自定义View的渐变方向和渐变位置的代码:
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
shadeTextView.setDirection(ShadeTextView.DIRECTION_TOP);
shadeTextView.setmProgress(progress * 1.0f / 100);
}
运行测试,基本OK
运用到viewpager中的代码也比较简单,只贴核心调用部分,其他大家都很熟悉了。
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (positionOffset > 0){
ShadeTextView left = shadeTextViews.get(position);
ShadeTextView right = shadeTextViews.get(position + 1);
left.setDirection(1);
right.setDirection(0);
left.setmProgress(1-positionOffset);
right.setmProgress(positionOffset);
}
}
然后效果就是文首的viewpager切换效果了。
源码点击查看