前段时间看红橙Darren写的关于自定义View的相关文章,决定自己撸一撸,让记忆更深刻
先看下这两个动画的效果
效果.gif点赞动画
1.将可爱的点赞图标(ImageView)添加到自定义View中
2.利用贝塞尔估值器为每一个添加的ImageView设置路径,并设置插值器
3.动画完成之后将ImageView从View中移除
来分析下源码,100多行
//自定义View继承RelativeLayout
public class LoveIconView extends RelativeLayout {
//View自身的宽度和高度
private int width, height;
//ImageView的宽高
private int iconWidth, iconHeight;
//插值器列表
private Interpolator[] interpolators;
//随机数
private Random random = new Random();
public LoveIconView(Context context) {
this(context, null);
}
public LoveIconView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LoveIconView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public LoveIconView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
initInterpolator();
}
//初始化了一些插值器,这样每一个ImageView就可以有不同的移动速率了
private void initInterpolator() {
interpolators = new Interpolator[]{
new LinearInterpolator(),
new AccelerateDecelerateInterpolator(),
new AccelerateInterpolator(),
new DecelerateInterpolator(),
};
}
//获取View的宽高
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = getMeasuredWidth();
height = getMeasuredHeight();
}
//在离开窗口后移除View
@Override
protected void onDetachedFromWindow() {
removeAllViews();
super.onDetachedFromWindow();
}
private void startAnimator(ImageView view) {
//曲线的两个顶点
PointF pointF1 = new PointF(
random.nextInt(width),
random.nextInt(height / 2) + height / 2);
PointF pointF2 = new PointF(
random.nextInt(width),
random.nextInt(height / 2));
//曲线的开始和结束点
PointF pointStart = new PointF((width - iconWidth) / 2,
height - iconHeight);
PointF pointEnd = new PointF(random.nextInt(width), random.nextInt(height / 2));
//贝塞尔估值器
BezierEvaluator evaluator = new BezierEvaluator(pointF1, pointF2);
ValueAnimator animator = ValueAnimator.ofObject(evaluator, pointStart, pointEnd);
animator.setTarget(view);
animator.setDuration(3000);
animator.addUpdateListener(new UpdateListener(view));
animator.addListener(new AnimatorListener(view, this));
animator.setInterpolator(interpolators[random.nextInt(4)]);
animator.start();
}
//添加ImageView并开始动画
public void addLoveIcon(int resId) {
ImageView view = new ImageView(getContext());
view.setImageResource(resId);
iconWidth = view.getDrawable().getIntrinsicWidth();
iconHeight = view.getDrawable().getIntrinsicHeight();
addView(view);
startAnimator(view);
}
public static class UpdateListener implements ValueAnimator.AnimatorUpdateListener {
private WeakReference<ImageView> iv;
public UpdateListener(ImageView iv) {
this.iv = new WeakReference<>(iv);
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//更新ImageView的透明度
PointF pointF = (PointF) animation.getAnimatedValue();
ImageView view = iv.get();
if (null != view) {
view.setX(pointF.x);
view.setY(pointF.y);
view.setAlpha(1 - animation.getAnimatedFraction() + 0.1f);
}
}
}
public static class AnimatorListener extends AnimatorListenerAdapter {
private WeakReference<ImageView> iv;
private WeakReference<LoveIconView> parent;
public AnimatorListener(ImageView iv, LoveIconView parent) {
this.iv = new WeakReference<>(iv);
this.parent = new WeakReference<>(parent);
}
@Override
public void onAnimationEnd(Animator animation) {
//动画结束时移除View
ImageView view = iv.get();
LoveIconView parent = this.parent.get();
if (null != view
&& null != parent) {
parent.removeView(view);
}
}
}
}
这里用到了贝塞尔三次方公式
贝塞尔公式.jpg下面是基于这个公式来实现的贝塞尔估值器实现
public class BezierEvaluator implements TypeEvaluator<PointF> {
private PointF point1, point2;
private PointF point;
public BezierEvaluator(PointF point1, PointF point2) {
this.point1 = point1;
this.point2 = point2;
point = new PointF();
}
@Override
public PointF evaluate(float t, PointF startValue, PointF endValue) {
point.x = startValue.x * (1 - t) * (1 - t) * (1 - t)
+ 3 * point1.x * t * (1 - t) * (1 - t)
+ 3 * point2.x * t * t * (1 - t)
+ endValue.x * t * t * t;
point.y = startValue.y * (1 - t) * (1 - t) * (1 - t)
+ 3 * point1.y * t * (1 - t) * (1 - t)
+ 3 * point2.y * t * t * (1 - t)
+ endValue.y * t * t * t;
return point;
}
}
这个动画主要涉及到的知识点
1.贝塞尔曲线公式
2.ValueAnimator的使用(自定义属性时的使用)
简单说下ValueAnimator,属性动画ObjectAnimator就是继承自这个类,这个类就是一个数值动画类,用来计算具体的动画数据值,除了可以使用ofInt,ofFloat等基本属性外,还可以实现自定义属性,实现数据的变化(ofObject)需要传入一个参数(实现TypeEvaluator),这里BezierEvaluator实现了TypeEvaluator,利用贝塞尔三次方公式来计算两个PointF的估值.
Yahoo新闻摘要动画
1.绘制小圆,并让一直旋转
2.小圆聚合动画
3.圆圈扩展动画,让下面的视图展现出来
OK来分析源码,200行左右,比较简单
首先看下类成员定义
//因为View继承自SurfaceView,所以需要这个
private SurfaceHolder surfaceHolder;
//View width/2 height/2 center point(这里的宽度和高度就是中心点坐标位置,不是View的宽高)
private int width, height;
//Draw small circle(绘制小圆)
private Paint paint;
//Draw expanded circle(绘制最后扩展圆来展现下层控件)
private Paint expandPaint;
//Small color list(可以定义一些小圆的颜色列表)
private int[] colors;
//小圆转圈动画
private ValueAnimator circleAnimator;
//小圆向外扩展动画
private ValueAnimator expandAnimator;
//小圆向内聚合动画
private ValueAnimator collapsingAnimator;
//最后的扩展显示下层控件的动画
private ValueAnimator filterAnimator;
//Current rotate angle(当前的旋转角度)
private float rotateAngle;
//分别为小圆半径,大圆半径,扩展动画的圆的半径(变化的)
private int innerRadius, outerRadius, expandRadius = -1;
//The angle between each circle(每个小圆之间的间隔角度)
private float gapAngle;
//The factor for collapsing and expand(小圆聚合的乘数因子)
private float factor = 1.0f;
//Status (Loading状态)
private Status status = Status.IDLE;
//Expand animator set(小圆集合以及最后铺开显示下层控件的动画集合)
private AnimatorSet animatorSet;
//Circle animator set(小圆转圈的动画集合)
private AnimatorSet circleAnimatorSet;
//绘图的模式
private PorterDuffXfermode mode = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);
定义了一个状态枚举类
public enum Status {
IDLE,//初始状态
LOADING,//正在加载
COMPLETE,//加载完成
}
初始化
public YahooLoadingView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
//这里定义了两个自定义属性,可以设置小圆的大圆的半径
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.YahooLoading);
innerRadius = a.getDimensionPixelSize(R.styleable.YahooLoading_innerRadius, 15);
outerRadius = a.getDimensionPixelSize(R.styleable.YahooLoading_outerRadius, 160);
a.recycle();
surfaceHolder = getHolder();
initData();
initAnimator();
//这句话需要,否则图层最后会显示黑色
setLayerType(LAYER_TYPE_HARDWARE, null);
//需要设置背景色,否则View无效果
setBackgroundColor(Color.WHITE);
//SurfaceView需要设置这句话,能让图层在最上层
setZOrderOnTop(true);
surfaceHolder.addCallback(this);
}
初始化一些数据
private void initData() {
//默认给了小圆6个颜色,并计算了小圆的间隔角度
colors = new int[]{Color.RED, Color.BLUE, Color.GREEN, Color.CYAN, Color.DKGRAY, Color.GRAY};
gapAngle = (float) Math.PI * 2 / colors.length;
paint = new Paint();
paint.setAntiAlias(true);
expandPaint = new Paint();
expandPaint.setAntiAlias(true);
}
核心的动画效果定义
private void initAnimator() {
circleAnimator = ValueAnimator.ofFloat(0, (float) Math.PI * 2);
circleAnimator.setDuration(1600);
circleAnimator.setRepeatCount(ValueAnimator.INFINITE);
circleAnimator.setInterpolator(new LinearInterpolator());
circleAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
expand();
}
});
circleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if (Status.COMPLETE == status) {
animation.cancel();
return;
}
rotateAngle = (float) animation.getAnimatedValue();
invalidate();
}
});
expandAnimator = ValueAnimator.ofFloat(1, 1.5f);
expandAnimator.setDuration(200);
expandAnimator.setRepeatCount(0);
expandAnimator.setInterpolator(new DecelerateInterpolator());
expandAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
factor = (float) animation.getAnimatedValue();
invalidate();
}
});
collapsingAnimator = ValueAnimator.ofFloat(1.5f, 0f);
collapsingAnimator.setDuration(300);
collapsingAnimator.setRepeatCount(0);
collapsingAnimator.setInterpolator(new AccelerateInterpolator());
collapsingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
factor = (float) animation.getAnimatedValue();
invalidate();
}
});
WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
Point point = new Point();
windowManager.getDefaultDisplay().getSize(point);
int width = point.x / 2;
int height = point.y / 2;
filterAnimator = ValueAnimator.ofInt(0, (int) Math.sqrt(width * width + height * height));
filterAnimator.setDuration(2000);
filterAnimator.setRepeatCount(0);
filterAnimator.setInterpolator(new LinearInterpolator());
filterAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
expandRadius = (int) animation.getAnimatedValue();
invalidate();
}
});
filterAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
resetData();
setVisibility(GONE);
}
});
animatorSet = new AnimatorSet();
animatorSet.playSequentially(expandAnimator, collapsingAnimator, filterAnimator);
circleAnimatorSet = new AnimatorSet();
circleAnimatorSet.playTogether(circleAnimator);
}
核心的绘制方法
@Override
protected void onDraw(Canvas canvas) {
if (expandRadius > -1) {
expandPaint.setColor(Color.WHITE);
expandPaint.setXfermode(mode);
canvas.drawCircle(width, height, expandRadius, expandPaint);
}
for (int i = 0; i < colors.length; i++) {
paint.setColor(colors[i]);
float radius = (outerRadius - innerRadius) * factor;
float cx = (float) (radius * Math.sin((double) (rotateAngle + i * gapAngle)) + width);
float cy = (float) (height - radius * Math.cos((double) (rotateAngle + i * gapAngle)));
if (radius > innerRadius) {
canvas.drawCircle(cx, cy, innerRadius, paint);
} else {
canvas.drawCircle(cx, cy, radius, paint);
}
}
}
这里遇到的一个坑就是PorterDuffXfermode
这个模式的官方的那张图有些误导,需要自己亲自实践一下,理解每种模式的含义。关于PorterDuffXfermode的具体解释网上有很多
最后贴上GitHub源码地址
https://github.com/ly85206559/AndroidCustomizeView/tree/master
欢迎Fork和Star,这个项目有新的动画就会往里面添加,也可以在评论区留言看那些动画比较不错,我来实现并往里面添加
网友评论