android 酷炫文案随机归位动效实现
记录一次自定义控件的实现原理
动画效果分解为一个随机位置归位动效和一个渐变的动效
1.根据文案在onSizechange方法里生成一个随机位置的集合和一个最终位置的集合 用于记录每个字符的位置
2.监听valueanimation动态计算随机位置当前应该到达的位置然后重绘界面
3.监听动效结束开始初始化一个渐变的动效 使用LinearGradient和Matrix实现渐变效果 渐变效果实现参考文末链接
关键代码如下
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
initTranslateAnimation(w, h);
}
private void initTranslateAnimation(int w, int h) {
int startX;
int centerY;
float contenWith = mPaint.measureText(contentStr);
Rect rect = new Rect();
mPaint.getTextBounds(contentStr, 0, contentStr.length(), rect);
startX = (int) ((w - contenWith) / 2);
centerY = (h + rect.height()) / 2;
queitList.clear();
randowList.clear();
for (int i = 0; i < contentStr.length(); i++) {
char c = contentStr.charAt(i);
float singleW = mPaint.measureText(String.valueOf(c));
//记录每个字符的开始坐标 (文字绘制的左下角坐标)
queitList.add(new PointF(startX, centerY));
// 生成随机位置坐标
randowList.add(new PointF((float) Math.random() * (w - singleW) + singleW, (float) Math.random() * (h - rect.height()) + rect.height()));
startX += (singleW + 3);
}
animator = ValueAnimator.ofFloat(0, 1);
animator.setDuration(2000);
TimeInterpolator intercept = new DecelerateInterpolator(0.05f);
animator.setInterpolator(intercept);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 计算当前文字的坐标并重绘
float value = (float) animation.getAnimatedValue();
for (int i = 0; i < randowList.size(); i++) {
PointF pointF = randowList.get(i);
PointF pointF1 = queitList.get(i);
pointF.x = pointF.x + (pointF1.x - pointF.x) * value;
pointF.y = pointF.y + (pointF1.y - pointF.y) * value;
}
postInvalidate();
}
});
// 检听归位动画结束开始渐变动画
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
isTranslateEnd = false;
}
@Override
public void onAnimationEnd(Animator animation) {
isTranslateEnd = true;
initLightAnimation();
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
animator.start();
}
private void initLightAnimation() {
float strW = mPaint.measureText(contentStr);
PointF pointF = queitList.get(0);
// 从文案内容前面开始渐变 注意TileMode.CLAMP 时为了文字颜色不会突变 开始和结束的颜色和textColor保持一致
final Shader shader = new LinearGradient(pointF.x-strW, 0, pointF.x, 0, new int[]{Color.BLACK,Color.RED,Color.WHITE,Color.BLACK}, null,Shader.TileMode.CLAMP);
final Matrix matrix = new Matrix();
shader.setLocalMatrix(matrix);
mPaint.setShader(shader);
//
ValueAnimator animator = ValueAnimator.ofFloat(0, getWidth()*2);
animator.setDuration(1000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//计算Matrix translate距离
float value = (float) animation.getAnimatedValue();
matrix.setTranslate(value, 0);
shader.setLocalMatrix(matrix);
postInvalidate();
}
});
animator.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(0, 0, getWidth(), getHeight(), mPaints);
for (int i = 0; i < contentStr.length(); i++) {
PointF pointF = isTranslateEnd ? queitList.get(i) : randowList.get(i);
canvas.drawText(String.valueOf(contentStr.charAt(i)), pointF.x, pointF.y, mPaint);
}
}
//开始动效方法
public void start() {
if (animator.isRunning()) {
animator.cancel();
}
initTranslateAnimation(getWidth(), getHeight());
}
参考文献