如图的文字闪光效果,下面分别用两种方式来实现。
shimmer_text.gif
实现方式 一
由于要实现文字的闪光移动,自定义控件直接继承TextView。在文字上面绘制一个矩形框,矩形框和文件相交处显示矩形框的颜色,不断移动矩形框的位置,从而实现闪光不断移动的效果。
public class BlinkTextView extends TextView {
...
}
通常字符串并不会完全填充View,因此需要计算字符串实际所占区域位置,在onMeasure
方法中计算绘制内容的实际区域。需要的绘制的前景闪光效果矩形框初始位置在字符串区域的左边,然后移动两倍的字符串宽度到右边。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 基线位置
int baseLine = getBaseline();
// 绘制内容所占的区域
mBlinkPaint.getTextBounds(getText().toString(), 0, getText().length(), textRect);
// 传入基线位置,计算实际的位置区域
textRectf.set(textRect.left, textRect.top + baseLine, textRect.right, textRect.bottom + baseLine);
L.d(TAG, "text region: " + textRectf.toString());
// 前景闪光效果的矩形框
rectF.set(textRectf.left - mMoveWidth, textRectf.top,
textRectf.left, textRectf.bottom);
L.d(TAG, "draw region: " + rectF.toString());
}
在onDraw
方法中实现绘制逻辑。采用PorterDuffXfermode(图像过度模式)实现字符串内容和前景闪光合成效果。这里需要用到PorterDuff.Mode.SRC_IN
模式,在两者相交的地方绘制源图像,并且绘制的效果会受到目标图像对应地方透明度的影响(可参考https://www.jianshu.com/p/d11892bbe055)。
这里保持前景闪光矩形框的位置参数不变,通过设置canvas的位移矩阵从而实现矩形框的移动效果。
@Override
protected void onDraw(Canvas canvas) {
// 离屏缓冲
int saveCount = canvas.saveLayer(dstRect, mBlinkPaint, Canvas.ALL_SAVE_FLAG);
// 调用父方法绘制字符串
super.onDraw(canvas);
mBlinkPaint.setAntiAlias(true);
// 设置画笔的颜色混合模式
mBlinkPaint.setXfermode(mXfermode);
mBlinkPaint.setColor(getResources().getColor(R.color.green));
mBlinkPaint.setStyle(Paint.Style.FILL);
// 设置矩阵左右位移dx,dx随着时间变化
matrix.reset();
matrix.setTranslate(dx, 0);
// canvas.skew(0.5f, 0);
// 画布设置位置矩阵
canvas.setMatrix(matrix);
// 绘制前景矩形框
canvas.drawRect(rectF, mBlinkPaint);
// 清除Xfermode
mBlinkPaint.setXfermode(null);
canvas.restoreToCount(saveCount);
}
使用属性动画ValueAnimator来实现动画效果,每一帧输出一个位移值dx,调用invalidate()
重新绘制View,dx值从0逐渐增加到两倍的字符串宽度。
public void start() {
if (valueAnimator != null && valueAnimator.isStarted()) {
return;
}
valueAnimator = ValueAnimator.ofFloat(0, 2 * (textRectf.right - textRectf.left));
valueAnimator.setDuration(1000);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
dx = (float) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.start();
}
在onWindowFocusChanged
方法中开启或者取消动画播放。需要注意的是View
的生命周期,View
只有在Activity
执行完onResume
方法之后才会调用onMeasure()
,在onResume
方法中主动调用start()
开启动画,此时字符串宽度值为0,会导致动画效果不显示。
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
L.d(TAG, "onWindowFocusChanged(), hasWindowFocus = %b", hasWindowFocus);
if (hasWindowFocus) {
start();
} else {
cancel();
}
}
实现方式 二
blink_shimmer_text_3s.gif将动画周期调大一些会发现,左边的动画当闪光移动比较慢时,方式一实现的的前景矩形框和背景字符串过度比较突兀,这里我们用LinearGradient
颜色梯度来实现右边带有颜色渐变的动画效果。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int baseLine = getBaseline();
mPaint.getTextBounds(getText().toString(), 0, getText().length(), textRect);
textRectf.set(textRect.left, textRect.top + baseLine, textRect.right, textRect.bottom + baseLine);
textWidth = getMeasuredWidth();
int textHeight = getMeasuredHeight();
L.d(TAG, "onMeasure(), width = %d, height = %d", textWidth, textHeight);
L.d(TAG, "onMeasure(), " + textRectf.toString());
linearGradient = new LinearGradient(-(textRectf.right- 2 * textRectf.left), textRectf.top,
textRectf.left, textRectf.bottom,
new int[] {getCurrentTextColor(), 0xff00ff00, getCurrentTextColor()},
new float[] {0, 0.5f, 1f}, Shader.TileMode.CLAMP);
}
在onMeasure
方法中,计算好字符串的实际位置后,新建一个LinearGradient
对象,颜色渐变方式是左、中、右的位置分别对应字体的颜色、闪光的颜色、字体的颜色。
@Override
protected void onDraw(Canvas canvas) {
L.d(TAG, "onDraw(), dx = " + dx);
matrix.reset();
matrix.setTranslate(dx, 0);
linearGradient.setLocalMatrix(matrix);
mPaint.setShader(linearGradient);
super.onDraw(canvas);
}
重写onDraw
方法,设置linearGradient
的位置矩阵matrix
,dx
是矩阵的左右位移。然后设置mPaint
的着色器,这里的mPaint
即当前绘制的TextView
的Paint对象,可通过调用getPaint()
获得。最后调用父方法绘制字符串。
public void start() {
L.d(TAG, "start()");
if (valueAnimator != null && valueAnimator.isStarted()) {
return;
}
valueAnimator = ValueAnimator.ofFloat(0, 2 * (textRectf.right - textRectf.left));
valueAnimator.setDuration(3000);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
dx = (Float) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.start();
}
LinearGradient
的位移dx
从0变化到两倍的字符串宽度距离。
网友评论