android 字体 动画,android 对绘制的文本添加动画

场景:

存在较多绘制内容的区域需要某些动画效果,

需要尽量少修改视图的绘制方法,做到动画与绘制分离。

看个简单例子:

d5b786a19d5c

image

我在一个视图上绘制了一行文字,先看一下绘制部分的代码:

public class MyLayout extends LinearLayout

{

private String mText = "show me the money";

//……

@Override

protected void dispatchDraw(Canvas canvas)

{

super.dispatchDraw(canvas);

doPaint(canvas, null);

}

public void doPaint(Canvas canvas, TextAnimationController.AnimationView aniView)

{

Paint paint = new Paint();

paint.setColor(Color.RED);

paint.setTextSize(80);

paint.setStyle(Paint.Style.FILL);

//切分字符串

String[] words = mText.split("\\s+");

//起始位置

int x_start = 100;

int y_start = 400;

for (String subText : words)

{

//绘制文本

canvas.drawText(subText, x_start, y_start, paint);

float width = paint.measureText(subText);

float height = paint.descent() - paint.ascent();

x_start += width;

//绘制空格

String blank = " ";

canvas.drawText(blank, x_start, y_start, paint);

x_start += paint.measureText(blank);

}

}

}

现在需要做删除最后一个单词的动作并为之添加动画。

一个实现方法:

将属性动画结合到自己的视图(View)中,通过数值发生器(ValueAnimator)和估值器(TypeEvaluator)不断产生出的位置信息,修改单词绘制时的位置和大小,进行重新绘制。

这种做法会产生两个问题:

动画和绘制结合得太紧密,绘制的逻辑和动画的逻辑搅和在一起,导致代码复杂且混乱

频繁的直接绘制会导致屏幕闪烁。(当然,这个可以用双缓冲解决)

解决思路:

在执行动画时,可以在视图上再盖一层PopupWindow。强制调用原视图的绘制方法,让其在PopupWindow视图的canvas上重新绘制一遍文本,来参与动画的显示(双缓冲):

d5b786a19d5c

image

对于动画的绘制思路可参考下图来理解:

d5b786a19d5c

image

说明:我们先把最终的状态的Bitmap对象绘制到动画视图(PopupWindow的视图)上,然后再把原始状态Bitmap上的动画字符区域经过缩放绘制到动画数值发生器给出的具体位置上

为了实现动画,我们需要一些数据的支持:

动画文本在默认绘制时的坐标位置及宽(原始位置)

通过数值发生器(ValueAnimator)和估值器(TypeEvaluator)不断产生出的动画文本的位置信息

首先,我们需要修改原始视图的绘制方法,添加文本位置的搜集器:

public void doPaint(Canvas canvas, TextAnimationController.AnimationView aniView)

{

……

for (String subText : words)

{

//绘制文本

canvas.drawText(subText, x_start, y_start, paint);

float width = paint.measureText(subText);

float height = paint.descent() - paint.ascent();

//==============================================================================================

if (aniView != null)

{

TextAnimationController.TextObject textObj = new TextAnimationController.TextObject();

textObj.rcRangeSrc = new RectF(x_start, y_start + paint.descent() - height, x_start + width,

y_start + paint.descent());

aniView.addTextObject(textObj);

}

//==============================================================================================

x_start += width;

//绘制空格

String blank = " ";

canvas.drawText(blank, x_start, y_start, paint);

x_start += paint.measureText(blank);

}

}

为了方便控制,我们创建一个动画控制器类:TextAnimationController,所有的数据导入、动画生成、弹层、控制都在这个类里。

public class TextAnimationController

{

private MyLayout mOriginalView = null; //PopupWindow的视图

private AnimationView mAnimationView = null;

private AnimationWindow mAnimationWindow = null;

public TextAnimationController(MyLayout view)

{

mOriginalView = view;

}

//开始动画

public void startAnimation(Point target)

{

//创建动画视图层

mAnimationView = new AnimationView(mOriginalView.getContext(), target);

mAnimationWindow = new AnimationWindow(mAnimationView, mOriginalView.getWidth(), mOriginalView.getHeight());

mAnimationWindow.setFocusable(true);

//加载动画视图层

int[] location = new int[2];

mOriginalView.getLocationOnScreen(location);

mAnimationWindow.showAtLocation(mOriginalView, Gravity.TOP | Gravity.LEFT, location[0], location[1]);

}

public void endAnimation()

{

//关闭动画层

if (mAnimationWindow != null && mAnimationWindow.isShowing())

{

mAnimationWindow.dismiss();

mAnimationWindow = null;

}

}

static class TextObject

{

//原始尺寸

RectF rcRangeSrc;

//目标尺寸

RectF rcRangeDes;

//中间步骤尺寸

RectF rcRangeStep;

}

//PopupWindow弹层

public class AnimationWindow extends PopupWindow

{

public AnimationWindow(View contentView, int width, int height)

{

super(contentView, width, height);

setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));

}

}

//动画视图(PopupWindow的视图)

class AnimationView extends View

{

private LinkedList objectList = new LinkedList<>();

private Paint paint = new Paint();

private Rect rectSrc = new Rect();

private Bitmap bitmapSrc = Bitmap

.createBitmap(mOriginalView.getWidth(), mOriginalView.getHeight(), Bitmap.Config.ARGB_8888);

private Bitmap bitmapFinal = Bitmap

.createBitmap(mOriginalView.getWidth(), mOriginalView.getHeight(), Bitmap.Config.ARGB_8888);

public AnimationView(Context context, final Point target)

{

super(context);

Canvas bmpCanvas = new Canvas(bitmapSrc);

mOriginalView.showStartText();

mOriginalView.doPaint(bmpCanvas, this);

bmpCanvas = new Canvas(bitmapFinal);

mOriginalView.showEndText();

mOriginalView.doPaint(bmpCanvas, null);

post(new Runnable()

{

@Override

public void run()

{

startAnimation(target.x, target.y);

}

});

}

public void addTextObject(TextObject object)

{

objectList.add(object);

}

@Override

protected void onDraw(Canvas canvas)

{

super.onDraw(canvas);

//绘制底图

canvas.drawBitmap(bitmapFinal, 0, 0, paint);

//绘制动画文本图

int lastIndex = objectList.size() - 1;

RectF rcSrc = objectList.get(lastIndex).rcRangeSrc;

rectSrc.set((int) rcSrc.left, (int) rcSrc.top, (int) rcSrc.right, (int) rcSrc.bottom);

RectF rcStep = objectList.get(lastIndex).rcRangeStep;

if (rcStep != null)

{

canvas.drawBitmap(bitmapSrc, rectSrc, rcStep, paint);

}

}

private void startAnimation(int desX, int desY)

{

for (int i = 0; i < objectList.size(); i++)

{

RectF src = objectList.get(i).rcRangeSrc;

objectList.get(i).rcRangeDes = new RectF(desX, desY, desX + src.width(), desY + src.height());

objectList.get(i).rcRangeStep = new RectF(src);

}

//==========================================================================================================

ArrayList animators = new ArrayList<>();

for (int i = 0; i < objectList.size(); i++)

{

final RectF src = objectList.get(i).rcRangeSrc;

RectF des = objectList.get(i).rcRangeDes;

AnimatorSet aniDisappear = new AnimatorSet();

final int index = i;

//贝塞尔曲线动画

final int pointX = (int) (src.left + 200);

final int pointY = (int) (src.top + (des.top - src.top) / 2);

Point controlPoint = new Point(pointX, pointY); //控制点

BezierEvaluator bezierEvaluator = new BezierEvaluator(controlPoint);

ValueAnimator animBezier = ValueAnimator.ofObject(bezierEvaluator,

new Point((int)src.left, (int)src.top),

new Point((int)des.left, (int)des.top));

animBezier.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()

{

@Override

public void onAnimationUpdate(ValueAnimator animation)

{

Point point = (Point) animation.getAnimatedValue();

RectF step = new RectF(objectList.get(index).rcRangeStep);

step.set(point.x, point.y, point.x + src.width(), point.y + src.height());

objectList.get(index).rcRangeStep.set(step);

invalidate();

}

});

//缩放动画

ValueAnimator animScale = ValueAnimator.ofFloat(1.0f, 0.1f);

animScale.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()

{

@Override

public void onAnimationUpdate(ValueAnimator animation)

{

float scaleSize = (Float) animation.getAnimatedValue();

RectF step = new RectF(objectList.get(index).rcRangeStep);

step.set(step.left, step.top, step.left + step.width() * scaleSize,

step.top + step.height() * scaleSize);

objectList.get(index).rcRangeStep.set(step);

}

});

aniDisappear.setDuration(1200);

aniDisappear.setInterpolator(new AccelerateInterpolator());

aniDisappear.play(animBezier).with(animScale);

animators.add(aniDisappear);

}

AnimatorSet animatorSet = new AnimatorSet();

animatorSet.addListener(new Animator.AnimatorListener()

{

@Override

public void onAnimationStart(Animator animation)

{

}

@Override

public void onAnimationEnd(Animator animation)

{

endAnimation();

}

@Override

public void onAnimationCancel(Animator animation)

{

endAnimation();

}

@Override

public void onAnimationRepeat(Animator animation)

{

}

});

animatorSet.playTogether(animators);

animatorSet.start();

}

}

public class BezierEvaluator implements TypeEvaluator

{

private Point controlPoint;

public BezierEvaluator(Point controlPoint)

{

this.controlPoint = controlPoint;

}

@Override

public Point evaluate(float t, Point startValue, Point endValue)

{

int x = (int) ((1 - t) * (1 - t) * startValue.x + 2 * t * (1 - t) * controlPoint.x + t * t * endValue.x);

int y = (int) ((1 - t) * (1 - t) * startValue.y + 2 * t * (1 - t) * controlPoint.y + t * t * endValue.y);

return new Point(x, y);

}

}

}

ps:其中动画文本的运动轨迹使用了贝塞尔曲线,关于贝塞尔曲线的TypeEvaluator,参考了

https://www.jianshu.com/p/d9a3ae9e806d

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值