Android自定义控件:文字闪光移动效果

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


Android自定义控件:文字闪光移动效果_第1张图片
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();
        }
    }

实现方式 二

Android自定义控件:文字闪光移动效果_第2张图片
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的位置矩阵matrixdx是矩阵的左右位移。然后设置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变化到两倍的字符串宽度距离。

参考文章

  • 自定义控件之绘图篇( 五):drawText()详解

你可能感兴趣的:(Android自定义控件:文字闪光移动效果)