深入了解一些Android动画

一、PropertyValuesHolder

阅读本文需要上一文Android属性动画的基础,这样才可以明白接下来要讲什么。

1.理解和使用

PropertyValuesHolder 是ObjectAnimation类似的一个方法,只是少了一个target,就是要执行的控件。看看正常的使用方法:会同时执行全部的Holder

public void  doPropertyValuesHolder(){
        //定义一个旋转Holder
        PropertyValuesHolder rotationHolder=
                PropertyValuesHolder.ofFloat(
                        "rotation",
                        60f,40f,100f,-60f,40f,88f,77f);

        //定义一个透明Holder
        PropertyValuesHolder alphaHolder=
                PropertyValuesHolder.ofFloat(
                        "alpha",
                        0.01f,0.5f,1.0f,0.8f,0.2f,0.0f);
        
    	//加载进ObjectAnimator
        ObjectAnimator objectAnimator=ObjectAnimator.ofPropertyValuesHolder(ballImageView,rotationHolder,alphaHolder);
        objectAnimator.setDuration(3000);
        objectAnimator.start();
    }

2.方法和参数

可以看看这个方法的参数:

ObjectAnimator ofPropertyValuesHolder(Object target,PropertyValuesHolder... values)
PropertyValuesHolder ofFloat(String propertyName, float... values)

Object target 是要显示动画的控件

PropertyValuesHolder... values 装载多个PropertyValuesHolder

String propertyName 代表要反射的参数,跟ObjectAnimation的参数是一样的

float... values 代表是可变长参数
这样的方法还有以下图片这些:

图片.png

其中ofObject()方法 ,也是跟ObjectAnimation的相似,也是要自定义TypeEvaluator。
图片.png

二、Keyframe

1.理解和使用

看名字,就是理解为关键帧的意思,在动画中,在某帧做一些操作,从而实现对比效果比较明显的效果。
关键帧表示是某个物体在哪个时间点应该在哪个位置上。
具体使用:

 public void  doPropertyValuesHolderKeyFrame(){

        //头keyframe1,从进度0.6开始,在进度60%的时候,数值是0.1f
        Keyframe keyframe1=Keyframe.ofFloat(0.6f,0.1f);

        //中间keyframe2
        Keyframe keyframe2=Keyframe.ofFloat(0.1f,0.8f);

        //尾部keyframe3,以50%进度作为结束,这时候的数值为0.2f
        Keyframe keyframe3=Keyframe.ofFloat(0.5f,0.2f);

        //装载到Holder中,并设置要反射的方法,这是反射的是setAlpha()方法,控制透明度
        PropertyValuesHolder alphaHolder=PropertyValuesHolder.ofKeyframe("alpha",keyframe1,keyframe2,keyframe3);

        //把装载到Holder中装载到ObjectAnimator或者ValueAnimation
        ObjectAnimator objectAnimator=ObjectAnimator.ofPropertyValuesHolder(ballImageView,alphaHolder);
        objectAnimator.setDuration(3000);
        objectAnimator.start();
    }

2.方法和参数

Keyframe ofFloat(float fraction, float value)

float fraction 表示进度

float value 表示在这个进度下的数值

 PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values)

String propertyName 要反射的set方法

Keyframe... values 传入Keyframe

Keyframe的方法,也是和其他的类似的。
图片.png

Keyframe的set方法,设置进度,插值器,数值。
没有设置插值器的时候,默认是线性插值器

 keyframe1.setInterpolator(new LinearInterpolator()); //默认线性插值器

图片.png

3.帧的操作

直接写结论:

  • 如果去掉0帧,则以第一个关键帧为起始位置
  • 如果去掉结束帧(进度为1),则以最后一个关键帧为结束位置
  • 使用keyframe来构建动画,至少需要2帧

三、ViewPropertyAnimator

1.理解和使用

可以通过串行的形式,快速定义动画,省去一些定义,在每次界面绘制的时候,启动动画,比其他的更节省消耗。
比如:

 ballImageView.animate().alpha(0.5f).rotation(360f).scaleX(1.5f).translationX(100f);

2.参数和方法

可以看到这些方法的返回值,基本都是ViewPropertyAnimator
图片.png
图片.png
再引用一张表格:

函数含义
alpha(float value)设置透明度
scaleY(float value)设置 Y轴方向的缩放大小
scaleX(float value)设置X轴方向的缩放大小
translationY(float value)设置Y轴方向的移动值
translationX(float value)设置X轴方向的移动值
rotation(float value)设置绕Z轴旋转度数
rotationX(float value)设置绕x轴旋转度数
rotationY(float value)设置绕 Y 轴旋转度数
x(float value)相对于父容器的左上角坐标在 X轴方向的最终位置
y(float value)相对于父容器的左上角坐标在Y轴方向的最终位置
alphaBy(float value)设置透明度增量
rotationBy(float value)设置绕Z轴旋转增量
rotationXBy(float value)设置绕 X 油旋转增量
rotationYBy(float value)设置统Y轴旋转增量
translationXBy(float value)设置X轴方向的移动值增量
translationYBy(float value)设置Y轴方向的移动值增量
scaleXBy(float value)设置X轴方向的缩放大小增量
scaleYBy(float value)设置 Y轴方向的缩放大小增量
xBy(float value)相对于父容器的左上角坐标在 X轴方向的位置增量
yBy(float value)相对于父容器的左上角坐标在 Y轴方向的位置增量
setlnterpolator(Timelnterpolator interpolator)设置插值器
setStartDelay(long startDelay)设置开始延时
setDuration(long duration)设置动画时长

四、animateLayoutChanges

android:animateLayoutChanges="true"
在Layout加入控件,或者移除控件的时候,添加动画,但是只能使用默认动画。

 <LinearLayout
            android:animateLayoutChanges="true"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"/>

五、LayoutTransition

LayoutTransition可以控制ViewGroup的动画,可以使用自定义的动画。
具体使用:

 public void doLayoutTransition(){

        LinearLayout linearLayout=new LinearLayout(this);

        //1.创建实例
        LayoutTransition transition=new LayoutTransition();

        //2.创建动画
        ObjectAnimator objectAnimator=ObjectAnimator.ofFloat(null,"rotation",0f,90f,0f);

        //3.动画出现形式进行设置
        transition.setAnimator(LayoutTransition.DISAPPEARING,objectAnimator);

        //4.将LayoutTransition设置到ViewGroup中
        linearLayout.setLayoutTransition(transition);
     
      	//5.开源动画库 NineOldAndroids
     
    }

setAnimator(int transitionType, Animator animator)

这个方法中,transitionType有五个选项

image.png

CHANGE_APPEARING 由于容器中要显示一个新的元素,其他需要变化的元素所应用的动画(问题多,不常用)

_CHANGE_DISAPPEARING_ 当个容器中某个元素要消失时,其他需要变化的元素所应用的动画(问题多,不常用)

_CHANGING_ 容器中正在更改的元素的动画变化

_APPEARING_ 元素在容器中出现时所定义的动画

_DISAPPEARING_ 元素在容器中消失时所定义的动画

六、PathMeasure

PathMeasure类似一个计算器,可以计算出目标path的坐标,长度等

1.初始化

  public void doPathMeasure(){
        Path path=new Path();

        //初始化方法1
        PathMeasure pathMeasure1=new PathMeasure();
        pathMeasure1.setPath(path,true);

        //初始化方法2
        PathMeasure pathMeasure2=new PathMeasure(path,false);
    }

setPath(Path path, boolean forceClosed)
path 就是代表要计算的目标Path。
forceClosed 是否闭合,true会计算闭合状态下的Path,false会按照Path原来情况来计算。

2.函数调用

自定义一个view

public class PathView extends View {
    Path mPath;
    Paint mPaint;
    PathMeasure mPathMeasure;

    public PathView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mPath=new Path();
        mPaint=new Paint();
        mPathMeasure=new PathMeasure();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.translate(250,250); //画布移动
        mPaint.setColor(Color.BLUE); //画笔颜色
        mPaint.setStrokeWidth(5); //画笔粗细
        mPaint.setStyle(Paint.Style.STROKE); //画笔风格

        mPath.moveTo(0,0);
        mPath.lineTo(0,100);
        mPath.lineTo(100,100);
        mPath.lineTo(100,0);

        mPathMeasure.setPath(mPath,true);
        Log.v("showLog",
                "getLength()=="+mPathMeasure.getLength()
                        +"  isClosed()=="+ mPathMeasure.isClosed()); //结果400.0  true

        mPathMeasure.setPath(mPath,false);
        Log.v("showLog",
                "getLength()=="+mPathMeasure.getLength()
                        +"  isClosed()=="+ mPathMeasure.isClosed()); //结果300.0  false

        canvas.drawPath(mPath,mPaint); //绘制路径
    }
}

绘制效果:

image.png

2.1 PathMeasure.getLength()

PathMeasure.getLength() 函数用于测量路径的长度

2.2 PathMeasure.isClosed()

PathMeasure.isClosed() 函数用于返回是否测量闭合状态

2.3 PathMeasure.nextContour()

   		mPath.addRect(-50, -50, 50, 50, Path.Direction.CW);
        canvas.drawPath(mPath, mPaint);

        mPath.addRect(-100, -100, 100, 100, Path.Direction.CW);
        canvas.drawPath(mPath, mPaint);

        mPath.addRect(-120, -120, 120, 120, Path.Direction.CW);
        canvas.drawPath(mPath, mPaint);

        mPathMeasure.setPath(mPath, false);

        do {
            float len = mPathMeasure.getLength();
            Log.v("showLog", "len=" + len);
        } while (mPathMeasure.nextContour());

效果:

image.png

打印结果:

len=400.0
len=800.0
len=960.0

PathMeasure.nextContour()得到的顺序与添加的Path的顺序相同

PathMeasure.getLength()只是得到当前path的长度,不是全部的长度

2.3 getSegment()

使用getSegment函数需要禁用硬件加速 在构造方法中加入
setLayerType(LAYER_TYPE_SOFTWARE,null);

 		mPath.addRect(-50, -50, 50, 50, Path.Direction.CW);
        mPathMeasure.setPath(mPath,false); //计算的path
        mPathMeasure.getSegment(0,150,mDstPath,true); //截取并添加到mDstPath,是添加,不是其他
        canvas.drawPath(mPath, mPaint); //绘制原来的path

        canvas.translate(200,0); //画布移动
        mPaint.setColor(Color.RED);
        canvas.drawPath(mDstPath, mPaint); //绘制添加后的mDstPath
 boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)

startDpath开始截取的点,截取的起始点,是以左上角的点开始的

stopD截取停止的点

dst截取后添加到的path

startWithMoveTo是否保存原状,true保存原样,false则会连接初始点和终点,和原来的不一定相同形状
以上代码的效果: 截图的方向,与原来的path的生成方向有关

image.png

2.4 动态画圆的例子

代码:


public class PathView extends View {
    Path mPath, mDstPath;
    Paint mPaint;
    PathMeasure mPathMeasure;
    float mCurAnimValue;

    public PathView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        mPath = new Path();
        mDstPath = new Path();
        mPaint = new Paint();
        mPathMeasure = new PathMeasure();

        mPaint.setColor(Color.BLUE); //画笔颜色
        mPaint.setStrokeWidth(5); //画笔粗细
        mPaint.setStyle(Paint.Style.STROKE); //画笔风格

        mPath.addCircle(100, 100, 50, Path.Direction.CW); //一个完整的圆
        mPathMeasure.setPath(mPath, true); //要计算的path

        ValueAnimator animator = ValueAnimator.ofFloat(0, 1); //进度 0~1
        animator.setRepeatCount(ValueAnimator.INFINITE); //无限循环
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurAnimValue = (Float) animation.getAnimatedValue(); //得到当前的进度
                invalidate();//重绘,重新执行onDraw()方法
            }
        });
        animator.setDuration(5000);
        animator.start();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(100, 100); //画布移动

        float stop=mPathMeasure.getLength()*mCurAnimValue; //一个进度确定一个截取点
        mDstPath.reset();
        mPathMeasure.getSegment(0,stop,mDstPath,true); //一点点添加

        canvas.drawPath(mDstPath,mPaint); //每次有进度更新,就绘制一小段截取
    }
}

效果:
动态画圆.gif

2.5 getPosTan()

先看看函数的定义:

boolean getPosTan(float distance, float pos[], float tan[]) 

float distance 距离path的其实长度

float pos[] 该点的坐标值。x和y pos[0]=x,pos[1]=y

float tan[] 该点的正切值。x和y pos[0]=x,pos[1]=y tan<a=y/x

2.6 箭头画圆的例子

代码:


public class PathView extends View {
    Path mPath, mDstPath;
    Paint mPaint;
    PathMeasure mPathMeasure;
    float mCurAnimValue;
    Bitmap mArrowBmp;
    float[] mPos;
    float[] mTan;
    int mCenterX,mCenterY;
    float mRadius;

    public PathView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        mPath = new Path();
        mDstPath = new Path();
        mPaint = new Paint();
        mPathMeasure = new PathMeasure();
        mPos=new float[2];
        mTan=new float[2];

        //加载箭头图片
        mArrowBmp= BitmapFactory.decodeResource(getResources(), R.drawable.arrow);

        mPaint.setColor(Color.BLUE); //画笔颜色
        mPaint.setStrokeWidth(5); //画笔粗细
        mPaint.setStyle(Paint.Style.STROKE); //画笔风格

        mPath.addCircle(540, 972, 486, Path.Direction.CW); //一个完整的圆
        mPathMeasure.setPath(mPath, true); //要计算的path

        ValueAnimator animator = ValueAnimator.ofFloat(0, 1); //进度 0~1
        animator.setRepeatCount(ValueAnimator.INFINITE); //无限循环
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurAnimValue = (Float) animation.getAnimatedValue(); //得到当前的进度
                invalidate();//重绘,重新执行onDraw()方法
            }
        });
        animator.setDuration(5000);
        animator.start();
    }

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

        /*
         * 得到h,w的最小的那个值;
         * >> 1 移位 跟 /2 相同;
         * 乘以0.9f,表示占布局的90%
         * */
        mRadius = (Math.min(h, w) >> 1) * 0.9f;

        // 中心坐标
        mCenterX = w / 2;
        mCenterY = h / 2;
        Log.v("showLog",mCenterX+"  "+mCenterY+"  "+mRadius);
        postInvalidate();
        super.onSizeChanged(w, h, oldw, oldh);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float stop=mPathMeasure.getLength()*mCurAnimValue; //一个进度确定一个截取点

        mDstPath.reset();
        mPathMeasure.getSegment(0,stop,mDstPath,true); //一点点添加

        canvas.drawPath(mDstPath,mPaint); //每次有进度更新,就绘制一小段截取

        mPathMeasure.getPosTan(stop,mPos,mTan); //获得每点的正切值和坐标

        /**
         *    Math.atan2(mTan[1],mTan[0])获得tan的弧度值
         *    *180.0/Math.PI将转化为角度值
         * */
        float degrees=(float)(Math.atan2(mTan[1],mTan[0])*180.0/Math.PI);

        Matrix matrix=new Matrix();

        /**
         * 将图片围绕中心点旋转指定角度
         * postRotate(float degrees, float px, float py)
         * degrees是角度  (px,py)是图片中心点
         * */
        matrix.postRotate(degrees,mArrowBmp.getWidth()/2,mArrowBmp.getHeight()/2);

        /**
         * 将图片从默认的(0,0)点移动到路径的最前端
         * */
        matrix.postTranslate(mPos[0]-mArrowBmp.getWidth()/2,mPos[1]-mArrowBmp.getHeight()/2);

        //绘制图片
        canvas.drawBitmap(mArrowBmp,matrix,mPaint);

    }
}

效果:

箭头动态画圆.gif

2.7 getMatrix()

参数类型:

boolean getMatrix(float distance, Matrix matrix, int flags)

使用方法:

  		//计算方位角
        Matrix matrix = new Matrix();

		//获取位置信息
        mPathMeasure.getMatrix(stop,matrix,PathMeasure.POSITION_MATRIX_FLAG);

		//获取切边信息
        mPathMeasure.getMatrix(stop,matrix,PathMeasure.TANGENT_MATRIX_FLAG); 

2.8 支付成功例子

public class TickView extends View {
    Path mPath, mDstPath;
    Paint mPaint;
    PathMeasure mPathMeasure;
    float mCurAnimValue;
    int mCenterX, mCenterY;
    float mRadius;

    public TickView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        mPath = new Path();
        mDstPath = new Path();
        mPaint = new Paint();
        mPathMeasure = new PathMeasure();

        mPaint.setColor(Color.BLUE); //画笔颜色
        mPaint.setStrokeWidth(5); //画笔粗细
        mPaint.setStyle(Paint.Style.STROKE); //画笔风格

        mCenterX = 540;
        mCenterY = 972;
        mRadius = 486 / 2;

        /**
         * 圆
         * */
        mPath.addCircle(mCenterX, mCenterY, mRadius, Path.Direction.CW);

        /**
         * 对勾
         * */
        mPath.moveTo(mCenterX - mRadius / 2, mCenterY);
        mPath.lineTo(mCenterX, mCenterY + mRadius / 2);
        mPath.lineTo(mCenterX + mRadius / 2, mCenterY - mRadius / 3);

        mPathMeasure.setPath(mPath, false); //要计算的path

        ValueAnimator animator = ValueAnimator.ofFloat(0, 2); //进度 0~1 是圆,1~2是对勾
        animator.setRepeatCount(ValueAnimator.RESTART);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurAnimValue = (Float) animation.getAnimatedValue(); //得到当前的进度
                invalidate();//重绘,重新执行onDraw()方法
            }
        });
        animator.setDuration(5000);
        animator.start();
    }

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

        /*
         * 得到h,w的最小的那个值;
         * >> 1 移位 跟 /2 相同;
         * 乘以0.9f,表示占布局的90%
         * */
        mRadius = (Math.min(h, w) >> 1) * 0.9f;

        // 中心坐标
        mCenterX = w / 2;
        mCenterY = h / 2;
        Log.v("showLog", mCenterX + "  " + mCenterY + "  " + mRadius);
        postInvalidate();
        super.onSizeChanged(w, h, oldw, oldh);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (mCurAnimValue < 1) {
            float stop = mPathMeasure.getLength() * mCurAnimValue;
            mPathMeasure.getSegment(0, stop, mDstPath, true);
        } else if (mCurAnimValue == 1) {
            mPathMeasure.getSegment(0, mPathMeasure.getLength(), mDstPath, true);
            mPathMeasure.nextContour();
        } else {
            float stop = mPathMeasure.getLength() * (mCurAnimValue - 1);
            mPathMeasure.getSegment(0, stop, mDstPath, true);
        }

        canvas.drawPath(mDstPath, mPaint);
    }
}

效果:
对勾动画.gif

编程中我们会遇到多少挫折?表放弃,沙漠尽头必是绿洲。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值