王学岗高级UI(十)————属性动画

属性动画核心概念

  • 核心:
  • 1.ObjectAnimator 对象动画(重点)
  • 2.ValueAnimator 值动画(重点)
  • 3.PropertyValueHolder 用于多个动画同时执行
  • 4.TypeEvaluator 估值器
  • 5.Interpolator 插值器
  • 6.AnimatorSet 动画集合

package com.dn_alan.myapplication;

import android.os.Bundle;
import android.os.Handler;
import android.widget.FrameLayout;

import androidx.appcompat.app.AppCompatActivity;

/**
 * 属性动画,API3.0之后提出的动画模式
 * 优点:
 * 1.不在局限于View 对象,无对象也可以进行动画处理
 * 2.不再局限于4种基本变换:平移、旋转、缩放 、透明度
 * 3.可以灵活的操作任意对象属性,根据自己业务来实现自己想要的结果
 * 核心:
 * 1.ObjectAnimator  对象动画(重点)
 * 2.ValueAnimator	  值动画(重点)
 * 3.PropertyValueHolder 用于多个动画同时执行
 * 4.TypeEvaluator 估值器
 * 5.Interpolator 插值器
 * 6.AnimatorSet 动画集合
 *
 * 属性动画要求动画作用的对象提供该属性的set方法,属性动画根据传递的该属性的初始值和最终值,
 * 以动画的效果多次去调用set方法。每次传给set方法的值都不一样,确切来说是随着时间的推移,所
 * 传递的值越来越接近最终值。
 */
public class MainActivity extends AppCompatActivity {
    private FrameLayout mMainView;
    private SplashView splashView;

    //    ------------------------加载动画--------------------------------------
    Handler handler = new Handler();
    private void startLoadData() {
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                //数据加载完毕,进入主界面--->开启后面的两个动画
                splashView.splashDisappear();
            }
        },5000);//延迟时间
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);


        //将动画层盖在实际的操作图层上
        mMainView = new FrameLayout(this);
        ContentView contentView = new ContentView(this);
        mMainView.addView(contentView);
        splashView = new SplashView(this);
        mMainView.addView(splashView);

        setContentView(mMainView);

        //后台开始加载数据
        startLoadData();

    }
}

package com.dn_alan.myapplication;

import android.content.Context;
import android.widget.ImageView;

import androidx.appcompat.widget.AppCompatImageView;

/**
 * 了解 VSync 的同学都知道,Andorid 中的重绘就是由Choreographer在 1 秒内产生
 * 60 个 vsync 来通知 view tree 进行 view 的重绘的。而 vsync 产生后会调用它的监听者
 * 回调接口 Choreographer.FrameCallback,也就是说,只要向Choreographer注册了这个接口,
 * 就会每 1 秒里收到 60 次回调。因此,在这里就实现了不断地调用 doAnimationFrame()
 * 来驱动动画了。想必看到这里,你应该明白了同学们常说的动画掉帧的原因了吧。如果 view
 * 的绘制过于复杂,即在 15 ms 内无法完成,那么就会使得中间某些帧跳过从而造成掉帧。
 */

public class ContentView extends AppCompatImageView {
    public ContentView(Context context) {
        super(context);
        setImageResource(R.mipmap.content);
    }
}

package com.dn_alan.myapplication;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.Log;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.view.animation.OvershootInterpolator;

public class SplashView extends View {
    private ValueAnimator mAnimator;
    // 大圆(里面包含很多小圆的)的半径
    private float mRotationRadius = 90;
    // 每一个小圆的半径
    private float mCircleRadius = 18;
    // 小圆圈的颜色列表,在initialize方法里面初始化
    private int[] mCircleColors;
    // 大圆和小圆旋转的时间
    private long mRotationDuration = 1200; //ms
    // 第二部分动画的执行总时间(包括二个动画时间,各占1/2)
    private long mSplashDuration = 1200; //ms
    // 整体的背景颜色
    private int mSplashBgColor = Color.WHITE;

    /**
     * 参数,保存了一些绘制状态,会被动态地改变* */
    //空心圆初始半径
    private float mHoleRadius = 0F;
    //当前大圆旋转角度(弧度)
    private float mCurrentRotationAngle = 0F;
    //当前大圆的半径
    private float mCurrentRotationRadius = mRotationRadius;

    // 绘制圆的画笔
    private Paint mPaint = new Paint();
    // 绘制背景的画笔
    private Paint mPaintBackground = new Paint();

    // 屏幕正中心点坐标
    private float mCenterX;
    private float mCenterY;
    //屏幕对角线一半
    private float mDiagonalDist;

    public SplashView(Context context) {
        super(context);
        init(context);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //当前View的中心店位置
        mCenterX = w/2f;
        mCenterY = h/2f;
        mDiagonalDist = (float) Math.sqrt((w*w+h*h))/2f;//勾股定律
    }

    private void init(Context context) {


        mCircleColors = context.getResources().getIntArray(R.array.splash_circle_colors);
        //画笔初始化
        //消除锯齿
        mPaint.setAntiAlias(true);
        mPaintBackground.setAntiAlias(true);
        //设置样式---边框样式--描边
        mPaintBackground.setStyle(Paint.Style.STROKE);
        mPaintBackground.setColor(mSplashBgColor);
    }

    public void splashDisappear(){
        //开启后面两个动画
        //换模板--换状态
        if(mState!=null && mState instanceof RotateState){
            //结束旋转动画
            RotateState rotateState = (RotateState) mState;
            rotateState.cancel();
            post(new Runnable() {
                @Override
                public void run() {
                    mState = new MergingState();
                }
            });
        }
    }

    private SplashState mState = null;
    //策略模式:State---三种动画状态
    private abstract class SplashState{
        //绘制不同的状态
        public abstract  void drawState(Canvas canvas);
        public void cancel(){

            mAnimator.cancel();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(mState==null){
            //开启第一个旋转动画
            mState = new RotateState();
        }
        //调用绘制方法
        mState.drawState(canvas);
    }

    private void drawCircles(Canvas canvas) {
        //每个小圆之间的间隔角度 = 2π/小圆的个数
        float rotationAngle = (float) (2*Math.PI/mCircleColors.length);
        Log.i("barry","length------:"+mCircleColors.length);
        for (int i=0; i < mCircleColors.length; i++){
            /**
             * x = r*cos(a) +centerX
             * y=  r*sin(a) + centerY
             * 每个小圆i*间隔角度 + 旋转的角度 = 当前小圆的真是角度
             */
            double angle = i*rotationAngle + mCurrentRotationAngle;
            float cx = (float) (mCurrentRotationRadius*Math.cos(angle) + mCenterX);
            float cy = (float) (mCurrentRotationRadius*Math.sin(angle) + mCenterY);
            mPaint.setColor(mCircleColors[i]);
            canvas.drawCircle(cx,cy,mCircleRadius,mPaint);
        }
    }

    private void  drawBackground(Canvas canvas) {
        if(mHoleRadius>0f){
            //得到画笔的宽度 = 对角线/2 - 空心圆的半径
            float strokeWidth = mDiagonalDist - mHoleRadius;
            mPaintBackground.setStrokeWidth(strokeWidth);
            //画圆的半径 = 空心圆的半径 + 画笔的宽度/2
            float radius = mHoleRadius + strokeWidth/2;
            canvas.drawCircle(mCenterX,mCenterY,radius,mPaintBackground);
        }else {
            canvas.drawColor(mSplashBgColor);
        }
    }


    /**
     * 1.旋转动画
     * 控制各个小圆的坐标---控制小圆的角度变化----属性动画ValueAnimator
     */
    private class RotateState extends SplashState{
        public RotateState() {
            //1.动画的初始工作;2.开启动画
            //花1200ms,计算某个时刻当前的角度是多少? 0~2π
            mAnimator = ValueAnimator.ofFloat(0f,(float)Math.PI*2);
            mAnimator.setInterpolator(new LinearInterpolator());
            mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    //计算某个时刻当前的大圆旋转了的角度是多少?
                    mCurrentRotationAngle = (float) valueAnimator.getAnimatedValue();
                    postInvalidate();
                }
            });
            mAnimator.setDuration(mRotationDuration);
            mAnimator.setRepeatCount(ValueAnimator.INFINITE);
            mAnimator.start();
        }


        @Override
        public void drawState(Canvas canvas) {
            //1.背景--擦黑板,涂成白色
            drawBackground(canvas);
            //2.绘制小圆
            drawCircles(canvas);
        }
    }


    /**
     * 2.聚合动画
     * 要素:大圆的半径不断地变大--变小----》小圆的坐标
     */
    private class MergingState extends SplashState{
        public MergingState() {
            //花1200ms,计算某个时刻当前的大圆半径是多少? r~0中的某个值
            mAnimator = ValueAnimator.ofFloat(0, mRotationRadius);
            mAnimator.setDuration(mRotationDuration);
            mAnimator.setInterpolator(new OvershootInterpolator(10f));
            mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
//                    某个时刻当前的大圆半径是多少?
                    mCurrentRotationRadius = (float)valueAnimator.getAnimatedValue();
                    invalidate();
                }
            });
            mAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    mState = new ExpandState();
                }
            });
//            mAnimator.start();
            mAnimator.reverse();;
        }

        @Override
        public void drawState(Canvas canvas) {
            //1.背景--擦黑板,涂成白色
            drawBackground(canvas);
            //2.绘制小圆
            drawCircles(canvas);
        }
    }

    /**
     * 3.水波纹扩散动画
     * 画一个空心圆----画一个圆,让它的画笔的粗细变成很大---不断地减小画笔的粗细。
     * 空心圆变化的范围:0~ 对角线/2
     */
    private class ExpandState extends SplashState{
        public ExpandState() {
            //花1200ms,计算某个时刻当前的空心圆的半径是多少? r~0中的某个值
            mAnimator = ValueAnimator.ofFloat(mCircleRadius, mDiagonalDist);
            Log.i("zhang_xin","mCircleRadius:"+mCircleRadius+",mDiagonalDist:"+mDiagonalDist);
            mAnimator.setDuration(mRotationDuration);
            mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    //当前的空心圆的半径是多少?
                    mHoleRadius = (float) valueAnimator.getAnimatedValue();
                    Log.i("zhang_xin","mHoleRadius:"+mHoleRadius);
                    invalidate();
                }
            });
            mAnimator.start();
        }

        @Override
        public void drawState(Canvas canvas) {
            drawBackground(canvas);
        }
    }

}

旋转效果
在这里插入图片描述
放大缩小效果
在这里插入图片描述
圆形效果
在这里插入图片描述

发布了208 篇原创文章 · 获赞 15 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/qczg_wxg/article/details/103809858