如图的文字闪光效果,下面分别用两种方式来实现。

c746333cc5f7

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();

}

}

实现方式 二

c746333cc5f7

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变化到两倍的字符串宽度距离。

参考文章

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐