場景:
- 存在較多繪制內(nèi)容的區(qū)域需要某些動畫效果怠苔,
- 需要盡量少修改視圖的繪制方法仪糖,做到動畫與繪制分離。
看個簡單例子:
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);
}
}
}
現(xiàn)在需要做刪除最后一個單詞的動作并為之添加動畫故爵。
一個實現(xiàn)方法:
將屬性動畫結(jié)合到自己的視圖(View)中玻粪,通過數(shù)值發(fā)生器(ValueAnimator)和估值器(TypeEvaluator)不斷產(chǎn)生出的位置信息诬垂,修改單詞繪制時的位置和大小,進行重新繪制结窘。
這種做法會產(chǎn)生兩個問題:
- 動畫和繪制結(jié)合得太緊密,繪制的邏輯和動畫的邏輯攪和在一起隧枫,導(dǎo)致代碼復(fù)雜且混亂
- 頻繁的直接繪制會導(dǎo)致屏幕閃爍谓苟。(當(dāng)然协怒,這個可以用雙緩沖解決)
解決思路:
在執(zhí)行動畫時,可以在視圖上再蓋一層PopupWindow斤讥。強制調(diào)用原視圖的繪制方法,讓其在PopupWindow視圖的canvas上重新繪制一遍文本芭商,來參與動畫的顯示(雙緩沖):
image
對于動畫的繪制思路可參考下圖來理解:
image
說明:我們先把最終的狀態(tài)的Bitmap對象繪制到動畫視圖(PopupWindow的視圖)上搀缠,然后再把原始狀態(tài)Bitmap上的動畫字符區(qū)域經(jīng)過縮放繪制到動畫數(shù)值發(fā)生器給出的具體位置上
為了實現(xiàn)動畫,我們需要一些數(shù)據(jù)的支持:
- 動畫文本在默認(rèn)繪制時的坐標(biāo)位置及寬(原始位置)
- 通過數(shù)值發(fā)生器(ValueAnimator)和估值器(TypeEvaluator)不斷產(chǎn)生出的動畫文本的位置信息
首先艺普,我們需要修改原始視圖的繪制方法,添加文本位置的搜集器:
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);
}
}
為了方便控制岸浑,我們創(chuàng)建一個動畫控制器類:TextAnimationController瑰步,所有的數(shù)據(jù)導(dǎo)入矢洲、動畫生成缩焦、彈層、控制都在這個類里袁滥。
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)
{
//創(chuàng)建動畫視圖層
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()
{
//關(guān)閉動畫層
if (mAnimationWindow != null && mAnimationWindow.isShowing())
{
mAnimationWindow.dismiss();
mAnimationWindow = null;
}
}
static class TextObject
{
//原始尺寸
RectF rcRangeSrc;
//目標(biāo)尺寸
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<TextObject> 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<Animator> 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<Point>
{
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:其中動畫文本的運動軌跡使用了貝塞爾曲線,關(guān)于貝塞爾曲線的TypeEvaluator嵌赠,參考了
http://www.reibang.com/p/d9a3ae9e806d