Android动画学习分析与实践(1)


title: Android动画学习分析与实践(1)
date: 2016-11-02 10:36:01
tags:


前言

本文首发于本人博客--精分道长的博客
Android进阶必须学习的就是动画和自定义View(ViewGroup)了。在学习完基本概念之后,仍然发现自己不能很好的写出动画。
我觉的分析和模仿别人的动画是一个很好的思路。

关于基础可以看教程:
Android 动画总结

以及属性动画:

Android 属性动画(Property Animation) 完全解析 (上)

Android 属性动画(Property Animation) 完全解析 (下)

Android 自定义View (一)

Android 自定义View (二) 进阶

Android 自定义View (三) 圆环交替 等待效果

Android 自定义View (四) 视频音量调控

实例分析1---直播点赞动画

参考资料android 飘心动画(直播点赞)效果

效果图

Android动画学习分析与实践(1)_第1张图片
图片描述

代码分析

FlowLikeView.java

首先重写一个GroupView继承RelativeLayout 以及变量添加

public class FlowLikeView extends RelativeLayout {
private List mLikeDrawables; // 图片的集合
    private LayoutParams mLayoutParams; // 用于设置动画对象的位置参数
    private Random mRandom; // 用于产生随机数,如生成随机图片

    private int mViewWidth; // 控件的宽度
    private int mViewHeight; // 控件的高度

    private int mPicWidth; // 图片的宽度
    private int mPicHeight; // 图片的高度

    private int mChildViewHeight; // 在 XML 布局文件中添加的子View的总高度

三个构造函数:在第三个初始化数据

public FlowLikeView(Context context) {
        this(context, null);
    }

    public FlowLikeView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public FlowLikeView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        initParams();
    }
    
    private void initParams() {
        mLikeDrawables = new ArrayList<>();
        mLikeDrawables.add(generateDrawable(R.drawable.heart0));
        mLikeDrawables.add(generateDrawable(R.drawable.heart1));
        mLikeDrawables.add(generateDrawable(R.drawable.heart2));
        mLikeDrawables.add(generateDrawable(R.drawable.heart3));
        mLikeDrawables.add(generateDrawable(R.drawable.heart4));
        mLikeDrawables.add(generateDrawable(R.drawable.heart5));
        mLikeDrawables.add(generateDrawable(R.drawable.heart6));
        mLikeDrawables.add(generateDrawable(R.drawable.heart7));
        mLikeDrawables.add(generateDrawable(R.drawable.heart8));

        // 获取图片的宽高, 由于图片大小一致,故直接获取第一张图片的宽高
        mPicWidth = mLikeDrawables.get(0).getIntrinsicWidth();
        mPicHeight = mLikeDrawables.get(0).getIntrinsicHeight();

        // 初始化布局参数
        mLayoutParams = new LayoutParams(mPicWidth, mPicHeight);
        mLayoutParams.addRule(CENTER_HORIZONTAL);
        mLayoutParams.addRule(ALIGN_PARENT_BOTTOM);

        //随机数
        mRandom = new Random();
    }

    private Drawable generateDrawable(int resID) {
        return ContextCompat.getDrawable(getContext(), resID);
    }

重写onMeasure使显示之前进行测量,主要是为了让动画显示在其他View上面

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        if (mChildViewHeight <= 0) {
            for (int i = 0, size = getChildCount(); i < size; i++) {
                View childView = getChildAt(i);
                measureChild(childView, widthMeasureSpec, heightMeasureSpec);
                mChildViewHeight += childView.getMeasuredHeight();
            }

            // 设置底部间距
            mLayoutParams.bottomMargin = mChildViewHeight;
        }
    }

重写onSizeChange函数,为了能够及时获取View的高度

@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        mViewWidth = getWidth();
        mViewHeight = getHeight();
    }

动态添加ImageView(设置为TextView的点击事件)并设置动画

public void addLikeView() {
        ImageView likeView = new ImageView(getContext());
        likeView.setImageDrawable(mLikeDrawables.get(mRandom.nextInt(mLikeDrawables.size())));
        likeView.setLayoutParams(mLayoutParams);
        addView(likeView);
        startAnimation(likeView);
    }

动画分为两部分:

第一部分出场:

private AnimatorSet generateEnterAnimation(View target) {
        ObjectAnimator alpha = ObjectAnimator.ofFloat(target, "alpha", 0.2f, 1f);
        ObjectAnimator scaleX = ObjectAnimator.ofFloat(target, "scaleX", 0.5f, 1f);
        ObjectAnimator scaleY = ObjectAnimator.ofFloat(target, "scaleY", 0.5f, 1f);
        AnimatorSet enterAnimation = new AnimatorSet();
        enterAnimation.playTogether(alpha, scaleX, scaleY);
        enterAnimation.setDuration(150);
        enterAnimation.setTarget(target);
        return enterAnimation;
    }

第二部分:贝塞尔曲线运动动画--属性动画的ValueObjec

/**
     * 生成曲线运动动画
     *
     * @return 动画集合
     */
    private ValueAnimator generateCurveAnimation(View target) {
        CurveEvaluator evaluator = new CurveEvaluator(generateCTRLPointF(1), generateCTRLPointF(2));
        ValueAnimator valueAnimator = ValueAnimator.ofObject(evaluator,
                new PointF((mViewWidth - mPicWidth) / 2, mViewHeight - mChildViewHeight - mPicHeight),
                new PointF((mViewWidth) / 2 + (mRandom.nextBoolean() ? 1 : -1) * mRandom.nextInt(100), 0));
        valueAnimator.setDuration(3000);
        valueAnimator.addUpdateListener(new CurveUpdateLister(target));
        valueAnimator.setTarget(target);

        return valueAnimator;
    }

    /**
     * 生成贝塞儿曲线的控制点
     *
     * @param value 设置控制点 y 轴上取值区域
     * @return 控制点的 x y 坐标
     */
    private PointF generateCTRLPointF(int value) {
        PointF pointF = new PointF();
        pointF.x = mViewWidth / 2 - mRandom.nextInt(100);
        pointF.y = mRandom.nextInt(mViewHeight / value);

        return pointF;
    }

    /**
     * 自定义估值算法, 计算对象当前运动的具体位置 Point
     */
    private class CurveEvaluator implements TypeEvaluator {

        // 由于这里使用的是三阶的贝塞儿曲线, 所以我们要定义两个控制点
        private PointF ctrlPointF1;
        private PointF ctrlPointF2;

        public CurveEvaluator(PointF ctrlPointF1, PointF ctrlPointF2) {
            this.ctrlPointF1 = ctrlPointF1;
            this.ctrlPointF2 = ctrlPointF2;
        }

        @Override
        public PointF evaluate(float fraction, PointF startValue, PointF endValue) {

            // 这里运用了三阶贝塞儿曲线的公式, 请自行上网查阅
            float leftTime = 1.0f - fraction;
            PointF resultPointF = new PointF();

            // 三阶贝塞儿曲线
            resultPointF.x = (float) Math.pow(leftTime, 3) * startValue.x
                    + 3 * (float) Math.pow(leftTime, 2) * fraction * ctrlPointF1.x
                    + 3 * leftTime * (float) Math.pow(fraction, 2) * ctrlPointF2.x
                    + (float) Math.pow(fraction, 3) * endValue.x;
            resultPointF.y = (float) Math.pow(leftTime, 3) * startValue.y
                    + 3 * (float) Math.pow(leftTime, 2) * fraction * ctrlPointF1.y
                    + 3 * leftTime * fraction * fraction * ctrlPointF2.y
                    + (float) Math.pow(fraction, 3) * endValue.y;
            return resultPointF;
        }

    }

    /**
     * 动画曲线路径更新监听器, 用于动态更新动画作用对象的位置
     */
    private class CurveUpdateLister implements ValueAnimator.AnimatorUpdateListener {
        private View target;

        public CurveUpdateLister(View target) {
            this.target = target;
        }

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            // 获取当前动画运行的状态值, 使得动画作用对象沿着曲线(涉及贝塞儿曲线)运动
            PointF pointF = (PointF) animation.getAnimatedValue();
            ViewCompat.setX(target, pointF.x);
            ViewCompat.setY(target, pointF.y);
            // 改变对象的透明度
            ViewCompat.setAlpha(target, 1 - animation.getAnimatedFraction());
        }
    }

重写回收监听类:

/**
     * 动画结束监听器,用于释放无用的资源
     */
    private class AnimationEndListener extends AnimatorListenerAdapter {
        private View target;

        public AnimationEndListener(View target) {
            this.target = target;
        }

        @Override
        public void onAnimationEnd(Animator animation) {
            super.onAnimationEnd(animation);

            removeView(target);
        }
    }

最后要组合两个动画,为一个,返回给ingview调用:

private void startAnimation(View target) {
        // 设置进入动画
        AnimatorSet enterAnimator = generateEnterAnimation(target);
        // 设置路径动画
        ValueAnimator curveAnimator = generateCurveAnimation(target);

        // 设置动画集合, 先执行进入动画,最后再执行运动曲线动画
        AnimatorSet finalAnimatorSet = new AnimatorSet();
        finalAnimatorSet.setTarget(target);
        finalAnimatorSet.playSequentially(enterAnimator, curveAnimator);
        finalAnimatorSet.addListener(new AnimationEndListener(target));
        finalAnimatorSet.start();
    }

步骤图解

Android动画学习分析与实践(1)_第2张图片
Android动画

关键就在两个动画的设置:
这里分别用到了属性动画里的两个类:

ObjectAnimator和ValueAnimator

ObjectAnimator动画

private AnimatorSet generateEnterAnimation(View target) {
        ObjectAnimator alpha = ObjectAnimator.ofFloat(target, "alpha", 0.2f, 1f);
        ObjectAnimator scaleX = ObjectAnimator.ofFloat(target, "scaleX", 0.5f, 1f);
        ObjectAnimator scaleY = ObjectAnimator.ofFloat(target, "scaleY", 0.5f, 1f);
        AnimatorSet enterAnimation = new AnimatorSet();
        enterAnimation.playTogether(alpha, scaleX, scaleY);
        enterAnimation.setDuration(150);
        enterAnimation.setTarget(target);
        return enterAnimation;
    }
  1. 传入View
  2. ObjectAnimator.ofFloat(target, "alpha", 0.2f, 1f);方法实例化一个ObjectAnimator。这几个参数分别为(View,View的某个属性,变化初始值,变化结束值)
  3. 使用AnimatorSet组合多个ObjectAnimator
  4. 设置其他属性
    这里也可以不设置真正的属性,在监听事件里修改属性
public void rotateyAnimRun(final View view)  
{  
    ObjectAnimator anim = ObjectAnimator//  
            .ofFloat(view, "zhy", 1.0F,  0.0F)//  
            .setDuration(500);//  
    anim.start();  
    anim.addUpdateListener(new AnimatorUpdateListener()  
    {  
        @Override  
        public void onAnimationUpdate(ValueAnimator animation)  
        {  
            float cVal = (Float) animation.getAnimatedValue();  
            view.setAlpha(cVal);  
            view.setScaleX(cVal);  
            view.setScaleY(cVal);  
        }  
    });  
}  

ValueAnimator动画

看一个简化的版本

public void paowuxian(View view)  
    {  
  
        ValueAnimator valueAnimator = new ValueAnimator();  
        valueAnimator.setDuration(3000);  
        valueAnimator.setObjectValues(new PointF(0, 0));  
        valueAnimator.setInterpolator(new LinearInterpolator());  
        valueAnimator.setEvaluator(new TypeEvaluator()  
        {  
            // fraction = t / duration  
            @Override  
            public PointF evaluate(float fraction, PointF startValue,  
                    PointF endValue)  
            {  
                Log.e(TAG, fraction * 3 + "");  
                // x方向200px/s ,则y方向0.5 * 10 * t  
                PointF point = new PointF();  
                point.x = 200 * fraction * 3;  
                point.y = 0.5f * 200 * (fraction * 3) * (fraction * 3);  
                return point;  
            }  
        });  
  
        valueAnimator.start();  
        valueAnimator.addUpdateListener(new AnimatorUpdateListener()  
        {  
            @Override  
            public void onAnimationUpdate(ValueAnimator animation)  
            {  
                PointF point = (PointF) animation.getAnimatedValue();  
                mBlueBall.setX(point.x);  
                mBlueBall.setY(point.y);  
  
            }  
        });  
    }  

也是只在UpdateListener事件里更新对象的属性。

模仿

雪花飘落

核心代码:

private ValueAnimator generateCurveAnimation(final View target) {
        final ValueAnimator valueAnimator = new ValueAnimator();
        final float wRadom = mRandom.nextFloat();
        valueAnimator.setDuration(3000);
        valueAnimator.setObjectValues(new PointF(0, 0));
        valueAnimator.setInterpolator(new DecelerateInterpolator());
        valueAnimator.setEvaluator(new TypeEvaluator() {

            @Override
            public PointF evaluate(float fraction, PointF startValue,
                                   PointF endValue) {

                PointF point = new PointF();
                float WH = mViewWidth - mPicWidth;

                point.y = fraction * (mViewHeight - mPicHeight);
                point.x = (float) Math.cos(fraction * 2 * Math.PI) * WH / 4 + WH*wRadom;
                return point;
            }
        });
        valueAnimator.setTarget(target);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                PointF point = (PointF) animation.getAnimatedValue();
                target.setX(point.x);
                target.setY(point.y);
            }
        });
        return valueAnimator;
    }

效果图

Android动画学习分析与实践(1)_第3张图片
图片描述

总结

仔细观察,数学要好。

你可能感兴趣的:(Android动画学习分析与实践(1))