自定义View使用与动画总结《Android开发艺术》动画要点精炼


要点提炼|开发艺术之Animation
Android 属性动画:这是一篇全面 & 详细的 属性动画 总结&攻略

自定义View

1.空气质量

在这里插入图片描述
可以看出该自定义View包括:(1)顶层文字Text(2)中间的文字Text(3)中间的数字Text(动画形式)(4)两个有角度的不同颜色的圆,第一个圆默认画至一定角度,第二个圆根据空气质量数值转化成角度。
我们设定空气质量为500时是满格,空气质量的数字是Text是动画效果增长至最终值,而顶层圆也是动画效果到达对应的角度。提供一个方法当获取到天气信息后调用,在updateIndex(int value,String middleText)进行动画效果,

主要遇到的问题是drawText中的baseline选取。

layout中使用:

<com.example.aaa.coolweather.View.CircleIndexView
       android:id="@+id/circleindexview"
       android:layout_width="170dp"
       android:layout_height="170dp"
       app:topText="污染指数"
       />

2.日出日落

在这里插入图片描述
主要思路:绘制一个封闭的半圆,在半圆的左下角与右下角绘制日出时间与日落时间。根据日出日落时间可以计算出总时间。用当前时间-日出时间,可以计算出现在太阳应走过的时间。用应走过的时间/总时间就可以得到当前时间对应的角度。

计算出当前时间对应的最终角度的代码如下,之后会通过动画的形式来将太阳图片移动到对应角度。

mStartTime = startTime;
        mEndTime = endTime;
        mCurrentTime = currentTime;
        mTotalMinute = calculateTime(mStartTime, mEndTime);//计算总时间,单位:分钟
        mNeedMinute = calculateTime(mStartTime, mCurrentTime);//计算当前所给的时间 单位:分钟
        mPercentage = formatTime(mTotalMinute, mNeedMinute);//当前时间的总分钟数占日出日落总分钟数的百分比
        mCurrentAngle =  (180 * mPercentage);
        setAnimation(0, mCurrentAngle, 5000);

太阳对应的x座标位置与y座标位置如下,这里的mCurrentAngle是动画中当前帧的角度,而不是最终角度。可以通过sin函数和cos函数计算出对应的xy座标值,因为该自定义View具有属性radius来代表圆的半径。

注意:(1)要记得减去这个图标自己的宽度/2,否则图片就不在线的正中间了。
(2)同时要注意Math.cos和sin传入的都是弧度制。
(3)marginTop是用于将圆弧距离顶部一定位置,防止图片到上面就显示不全了。

//当太阳在最高点时不会有部分消失
private int marginTop = 30;//离顶部的高度
private int mRadius;  //圆的半径
...
positionX=mWidth/2-(float)(mRadius*Math.cos((mCurrentAngle)*Math.PI/180))-mSunIcon.getWidth()/2;
positionY=marginTop+mRadius-(float)(mRadius*Math.sin((mCurrentAngle)*Math.PI/180))-mSunIcon.getHeight()/2;

在layout中的具体使用,除了宽高以外,还定义了字体大小,字体颜色,圆的颜色以及圆的半径(最重要)

<com.example.aaa.coolweather.View.SunView
    android:id="@+id/sunview"
    android:layout_width="match_parent"
    android:layout_height="150dp"
    app:sun_circle_color="#ffff"
    app:sun_circle_radius="120"
    app:sun_font_size="10px"
    app:sun_font_color="#ffff"
    />

自定义View+动画的整体流程总述

自定义View过程

(1-6是自定义View,7-9是属性动画的使用)

  1. 写一个MyView类,继承自View。
  2. 在values文件夹下创建attrs.xml文件,在 <declare-styleable name="XXXView">标签下声明自定义的属性(会生存R.styleable.name的数组,幷包含R.styleable.name_attrname的索引)。
  3. 在构造函数中使用TypedArray获取在布局中定义的属性,并使用
  4. 重写onMeasure以支持wrap_content属性。定义wrap_content下的宽高值,并当SpecMode(不论宽高)==MeasureSpec.AT_Most,就调用setDimension(定义的宽与高);
  5. 重写onDraw来绘制我们想要绘制的东西
  6. 在layout布局中使用自定义属性,注意开头要使用app,而非android。

动画过程

  1. 创建ValueAnimator对象或ObjectAnimator对象,设置动画持续时间与开始停止位置
  2. 设置动画从开始到停止的执行逻辑设置插值器与估值器,比如线性与非线性的动画效果等等,线性不需要设置
    注:(1)(2)过程都是在ValueAnimator.ofObject/ofInt/ofFloat中执行的
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
  1. 设置AnimatorUpdateListener监听器,当动画每播放一帧时会调用其中的onAnimationUpdate方法,如果是ValueAnimator就需要在其中手动设置该对象的属性,否则则不需要。最后调用invalidate方法让View重绘,调用onDraw方法。
  2. (如果需要的话)使用AnimatorSet让两个动画同时进行,使用playTogether(Animator A,Animator B)方法。
  3. 如果使用AnimatorSet就要调用其start方法,如果没有使用,就要调用ValueAnimator对象的start方法。
  4. 之后动画会按照要求执行,直到结束。

自定义View过程

  1. 写一个MyView类,继承自View。
  2. 在values文件夹下创建attrs.xml文件,声明自定义的属性。
  3. 在构造函数中使用TypedArray获取在布局中定义的属性,并使用。
  4. 重写onMeasure以支持wrap_content属性
  5. 重写onDraw来绘制我们想要绘制的东西。
  6. 在layout布局中使用自定义属性,注意开头要使用app,而非android。

1.构造函数

只带有Context的构造函数是在代码中使用的,而带有context和AttributeSet(属性集)的是layout文件中使用的。在最后的构造函数中,我们要初始化参数(将layout中的参数加载进来),以及初始化画笔(我们要通过画笔绘制Text与圆)。

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

    public CircleIndexView(Context context, AttributeSet attrs)
    {
        this(context, attrs, -1);
    }

    public CircleIndexView(Context context, AttributeSet attrs, int defStyleAttr)
    {
        super(context, attrs, defStyleAttr);
        initParams(context, attrs);
        initPaint();
    }

2.创建attrs.xml文件,并声明自定义的属性

需要在values文件夹下创建attrs.xml文件声明自定义的属性。在resource标签下,写一个declare-styleable的标签,name一定要写自定义View的类名。具体属性使用<attr name="middleText" format="string"/>,name表示名称,format代表类型。类型包括:string、dimension(尺寸)、color、integer等等。

declare-styleable 不是必要的,但是styleable可以帮我们节省开发工作量,帮我们生成数组的索引常量。每个declare-styleable产生一个R.styleable.name数组,外加每个属性的R.styleable.name_attrname 对应属性的下标,可以通过这个下标取出对应属性存储的值。

<resources>
    <declare-styleable name="CircleIndexView">
        <attr name="middleText" format="string"/>
        <attr name="middleTextSize" format="dimension"/>
        <attr name="middleTextColor" format="color"/>
        <attr name="topText" format="string"/>
        <attr name="topTextSize" format="dimension"/>
        <attr name="topTextColor" format="color"/>
        <attr name="numberTextSize" format="dimension"/>
        <attr name="numberColor" format="color"/>
        <attr name="outCircleColor" format="color"/>
        <attr name="inCircleColor" format="color"/>
        <attr name="duration" format="integer"/>
    </declare-styleable>
</resources>

3.构造函数中使用TypedArray获取在布局中定义的属性,并使用。

TypedArray帮我们实现了获取attrs中的id与解析id的功能,因此我们可以根据这个id在TypedArray中获取想要的属性值。

(1)取出数组名为R.styleable.CircleIndexView的TypeArray,之后读取该TypedArray中的属性。
(2)最后,recycle()方法的作用就是关闭了这个TypedArray。

可以看到下标为R.styleable.CircleIndexView_middleText,因此可知declare-styleable为我们创建了数组,同时可以让我们通过TypedArray对应下标去查询属性。

另外注意一点TypedValue.applyDimension()可以将非标准尺寸(dp,sp等等)转成Android中的标准尺寸(px像素)。

 /**
     * 初始化View参数
     * 获取布局(xml)中的属性以及使用布局(xml)中的属性,
     * 
     * 
     * @param context
     * @param attrs
     */
    private void initParams(Context context, AttributeSet attrs)
    {
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CircleIndexView);
        middleText = ta.getString(R.styleable.CircleIndexView_middleText);
       setMiddleText(middleText);
     	...
     	//这里其实是一般在layot里没有去写这个属性,因此这里直接将默认值转为px以便使用
 topTextSize = ta.getDimension(R.styleable.CircleIndexView_topTextSize,
                TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 15, context.getResources().getDisplayMetrics())); 
        ta.recycle();
    }

初始化画笔就比较简单了,初始化画笔的颜色,粗细等等

4.重写onMeasure以支持wrap_content属性

常规操作:
1. 首先通过当前的MeasureSpec来获取宽高测量模式和大小
2. 设定WrapContent模式下的宽高
3. 当测量模式为AT_MOST且布局参数为wrap_content时,直接调用setMeasuredDimension(mWidth, mHeight);,去设置宽高。

完整代码:

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

        // 获取宽-测量规则的模式和大小
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        // 获取高-测量规则的模式和大小
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        // 设置wrap_content的默认宽 / 高值
        // 默认宽/高的设定并无固定依据,根据需要灵活设置
        // 类似TextView,ImageView等针对wrap_content均在onMeasure()对设置默认宽 / 高值有特殊处理,具体读者可以自行查看
        int mWidth = 400;
        int mHeight = 400;

        // 当布局参数设置为wrap_content时,设置默认值
        if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT && getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
            setMeasuredDimension(mWidth, mHeight);
            // 宽 / 高任意一个布局参数为= wrap_content时,都设置默认值
        } else if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT) {
            setMeasuredDimension(mWidth, heightSize);
        } else if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
            setMeasuredDimension(widthSize, mHeight);
        }
    }

而本自定义View中为了好看,默认wrap_content和match_parent都为宽高200dp,除非为固定大小,我们才更改size为固定的大小,注意这里不管是200dp还是固定大小,都要将dp转成px。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        setMeasuredDimension(measureWidth(widthMeasureSpec),
                measureHeight(heightMeasureSpec));
    }

5.重写onDraw来绘制我们想绘制的东西

(1)绘制圆(canvas.drawArc)
RectF的四个参数分别是左上右下,可以通过创建RectF来让canvas通过canvas.drawArc(mRectF,startAngle,sweepAngle,false,outPaint);来画圆(其中传入参数是圆的左上右下四个点的位置,开始的角度,停止的角度),稍微注意一下内圆圈的角度是会变的,所以这里使用getInSweepAngle()方法来获取。

(2)绘制文字(canvas.drawText)
canvas.drawText(text, x, y, paint)方法,其中x是这个字符串的左边在屏幕的位置(也就是最左边那条竖线的座标),如果设置了paint.setTextAlign(Paint.Align.CENTER);那就是字符串的中心;y是指定这个字符串baseline在屏幕上的位置,而不是字符居中的位置

baseline问题
在这里插入图片描述
FontMetrics可以理解为字体度量,可以获取到top,ascent,descent与bottom距离base的距离。而值向下增加,值向上减少,也就是说top、ascent的值都是负的

fontMetrics=middleTextPaint.getFontMetrics();

在设计baseline时,我们想让Text内容居中在某个位置(比如说整个View的正中心)。那么我们就要让baseline的位置为正中心高度middle+(base距离正中心高度的距离)。

我们可以推出这个距离设base距离正中心高度为x,因为是正中心(top距离middle和bottom距离middle相等),因此top距离base的距离-x = bottom距离base的距离+x,可以求得x=(dtop-dbottom)/2,而已知top的值是负的,因此x就等于(-top-bottom)/2,将该值加上正中心高度middle即可。

baseline=(getCircleHeight()-fontMetrics.bottom-fontMetrics.top)/2;
 protected void onDraw(Canvas canvas){
        //200dp:middleTextSize:60,middleNumberSize:100,bottomTextSize:45
        super.onDraw(canvas);
        mCenter = getCircleWidth()/2;
        //mRadius = getCircleWidth() / 2 - 50;
        mRadius = getCircleWidth()/2-50;
        //左,上,右,下
        mRectF =new RectF(mCenter-mRadius,mCenter-mRadius+getTopTextSize(),
                mCenter+mRadius,mCenter+mRadius+getTopTextSize());
        //外圆圈
        canvas.drawArc(mRectF,startAngle,sweepAngle,false,outPaint);
        //内圆圈
        canvas.drawArc(mRectF,startAngle,getInSweepAngle(),false,inPaint);

        //中心文字(etc. 良)
        middleTextPaint.setColor(getMiddleTextColor());
        middleTextPaint.setTextSize(getMiddleTextSize());
        Paint.FontMetrics fontMetrics=middleTextPaint.getFontMetrics();
        float baseline=(getCircleHeight()-getNumberTextSize()-getMiddleTextSize()-20-fontMetrics.bottom-fontMetrics.top)/2+getTopTextSize();
       //想让他在居中的位置(getCircleHeight()/2-getNumberTextSize()/2-getMiddleTextSize()/2-20)
        canvas.drawText(getMiddleText(), getCircleWidth() / 2, baseline , middleTextPaint);

        //中心数字(etc)
        middleTextPaint.setColor(getNumberColor());
        middleTextPaint.setTextSize(getNumberTextSize());
        fontMetrics=middleTextPaint.getFontMetrics();
        baseline=(getCircleHeight()-fontMetrics.bottom-fontMetrics.top)/2+getTopTextSize();
        canvas.drawText(getIndexValue()+"",getCircleWidth()/2,
                baseline,middleTextPaint);


        //顶部文字(etc. 空气污染指数)
        middleTextPaint.setColor(getTopTextColor());
        middleTextPaint.setTextSize(getTopTextSize());
        fontMetrics=middleTextPaint.getFontMetrics();
        baseline=(getCircleHeight()-2*mRadius-fontMetrics.bottom-fontMetrics.top)/2;
        //canvas.drawText(getBottomText(), getCircleWidth() / 2, getCircleHeight() - 50, middleTextPaint);
        //让这个与圆的最顶端间隔20px
        canvas.drawText(getTopText(), getCircleWidth() / 2,baseline ,middleTextPaint);
}

6.layout文件中使用

注意我们自己定义的属性,要使用app:attrname。同时要在前面定义 :xmlns:app=“http://schemas.android.com/apk/res-auto”。

动画

空气质量的使用

这是空气质量动画暴露给外界使用的函数,当获取到天气信息后,会通过updateIndex去更新当前空气质量的数值与空气质量的文字,其中使用了动画。
注意调用invalidate会导致View重绘,最终会调用onDraw。因此在invalidate之前要通过set方法设置当前的角度与当前的空气质量值。

/**
     *
     * @param value 空气质量数值
     * @param middleText 空气质量(良)
     */
    public void updateIndex(int value,String middleText){
        setMiddleText(middleText);
        invalidate();
        //当前角度
        float inSweepAngle = sweepAngle * value / 500;
        //角度由0f到当前角度变化
        ValueAnimator angleAnim = ValueAnimator.ofFloat(0f, inSweepAngle);
        //动画持续时间
        angleAnim.setDuration(getDuration());
        //数值由0到value变化
        ValueAnimator valueAnim = ValueAnimator.ofInt(0,value);
        valueAnim.setDuration(getDuration());
        //注册监听器
        angleAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
        {
            @Override
            //当angleAnim变化回调/
            public void onAnimationUpdate(ValueAnimator valueAnimator)
            {
                float currentValue = (float) valueAnimator.getAnimatedValue();
                //将当前的角度值赋给inSweepAngle
                setInSweepAngle(currentValue);
                //通知view改变,调用这个函数后,会返回到onDraw();
                invalidate();
            }
        });
        valueAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
        {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator)
            {
                int currentValue = (int) valueAnimator.getAnimatedValue();
                setIndexValue(currentValue);
                invalidate();
            }
        });
        //让两个动画同时进行。
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.setInterpolator(new DecelerateInterpolator());
        animatorSet.setStartDelay(150);
        animatorSet.playTogether(angleAnim, valueAnim);
        animatorSet.start();

    }

日出日落动画的使用

setTimes是暴露给外部使用的方法,用于更新当前的时间。

public void setTimes(String startTime, String endTime, String currentTime)
    {
        mStartTime = startTime;
        mEndTime = endTime;
        mCurrentTime = currentTime;

        mTotalMinute = calculateTime(mStartTime, mEndTime);//计算总时间,单位:分钟
        mNeedMinute = calculateTime(mStartTime, mCurrentTime);//计算当前所给的时间 单位:分钟
        mPercentage = formatTime(mTotalMinute, mNeedMinute);//当前时间的总分钟数占日出日落总分钟数的百分比

        mCurrentAngle =  (180 * mPercentage);

        setAnimation(0, mCurrentAngle, 5000);

    }
 private void setAnimation(float startAngle,float currentAngle,int duration){
        ValueAnimator sunAnimator= ValueAnimator.ofFloat(startAngle,currentAngle);
        sunAnimator.setDuration(duration);
        sunAnimator.setTarget(currentAngle);
        sunAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCurrentAngle=(float)animation.getAnimatedValue();
                invalidateView();
            }
        });
        //千万别忘了这个
        sunAnimator.start();
    }
 private void invalidateView(){
        //绘制太阳的x座标与y座标
        //记得要减去太阳图标的大小。(让太阳始终在半圆上)
        positionX=mWidth/2-(float)(mRadius*Math.cos((mCurrentAngle)*Math.PI/180))-mSunIcon.getWidth()/2;
        positionY=marginTop+mRadius-(float)(mRadius*Math.sin((mCurrentAngle)*Math.PI/180))-mSunIcon.getHeight()/2;
        invalidate();
    }

动画总结

View动画

主要包括translate平移动画、scale缩放动画、rotate旋转动画、alpha透明度动画等四种。

View动画的View移动只是视觉效果,并不能真正的改变view的位置。
在这里插入图片描述
xml的使用:

首先创建xml文件,地址为res/anim/xxx.xml,可以看到动画可以仅是一个动画,也可以是一堆动画的组合。其中<set>标签代表AnimatorSet,<translate>代表平移动画,<scale>代表缩放动画,<rotate>代表旋转动画,<alpha>代表透明动画(改变View的透明度)

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:shareInterpolator="true"
    android:fillAfter="true">
    
    <translate
        android:fromXDelta="float"
        android:toXDelta="float"
        android:fromYDelta="float"
        android:toYDelta="float"/>
     <scale
        android:fromXScale="float"
        android:toXScale="float"
        android:fromYScale="float"
        android:toYScale="float"
        android:pivotX="float"
        android:pivotY="float"/>    
     <rotate
        android:fromDegrees="float"
        android:toDegrees="float"
        android:pivotY="float"
        android:pivotX="float"/>
    <alpha 
        android:fromAlpha="float"
        android:toAlpha="float"/>

</set>

帧动画

帧动画也是View动画的一种,它会按照顺序播放一组预先定义好的图片。对应类AnimationDrawable。

通过xml定义:
该xml文件创建在res/drawable/ 下,其中根节点<animation-list>,属性android:oneshot表示是否执行一次;子节点<item> 下可设置轮播的图片资源id和持续时间。例如:

<?xml version="1.0" encoding="utf-8"?>
<animation-list  xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item android:drawable="@drawable/xxx1" android:duration="500"/>
    <item android:drawable="@drawable/xxx2" android:duration="500"/>
    <item android:drawable="@drawable/xxx3" android:duration="500"/>
    <item android:drawable="@drawable/xxx4" android:duration="500"/>
</animation-list>

在xml声明好之后,将它作为View的背景并通过AnimationDrawable来播放。

mView.setBackgroundResource(R.drawable.XXX);
AnimationDrawable animationDrawable = (AnimationDrawable)mView.getBackground();
animationDrawable.start();

属性动画

与View动画不同之处主要在于:(1)View动画通过不断图形变换,而属性动画通过动态改变对象属性;(2)作用对象分别是View和任何对象;(3)如果是通过xml使用的话,存放位置不同:res/anim和res/animator(4)View动画没有真正改变View的位置,而属性动画真正改变了View的位置。

为第四点作补充:
补间动画只是改变了View的视觉效果,而不会真正去改变View的属性
如,将屏幕左上角的按钮 通过补间动画 移动到屏幕的右下角
点击当前按钮位置(屏幕右下角)是没有效果的,因为实际上按钮还是停留在屏幕左上角,补间动画只是将这个按钮绘制到屏幕右下角,改变了视觉效果而已。

在这里插入图片描述

ValueAnimator与ObjectAnimator

ValueAnimator

ValueAnimator是ObjectAnimator的父类,它继承自Animator。ValueAnimaotor同样提供了ofInt、ofFloat、ofObject等静态方法,传入的参数是动画过程的开始值、(中间值可以没有)、结束值来构造动画对象。

可以将ValueAnimator看成一个值变化器,通过不断控制值的变化,再不断手动赋给对象的属性,从而实现动画效果。

在这里插入图片描述

ObjectAnimator

直接对对象(自动)的属性值进行改变操作,从而实现动画效果,和ValueAnimator相比是直接更改属性的值。使用ObjectAnimator必须要实现set方法

自动赋值的思路如下:(1)如果没有传递初值会通过get值来获取;(2)在动画执行的过程中通过set更改属性的值
在这里插入图片描述

插值器与估值器

时间插值器:TimeInterpolator。**根据时间流逝的百分比来计算当前的属性值改变的百分比。**包括:线性插值器(LinearInterpolator 匀速动画)、加速减速插值器(AccelerateDecelerateInterpolator 动画两头慢中间快)、减速插值器(DecelerateInterpolator 动画持续减速);

类型估值器(TypeEvaluator):根据当前属性改变的百分比来计算改变后的属性值。包括针对整形、浮点型、颜色的估值器。

以下图为例,View的x属性需要在40ms内从0到40,结合时间插值器和类型估值器来完成。
在这里插入图片描述
以线性插值器为例,由于动画默认是10ms一帧,那么当运行到第二帧是t=20ms,因此时间流逝了0.5,因此属性值也应该改变0.5。时间插值器计算出0.5并返回,具体属性改变多少由类型估值器计算。估值器通过估值小数*(属性终点-属性起点)来计算出改变后的属性值。

当然插值器与估值器都可以自定义,尤其是在当前类型的估值器没有的情况下就必须自定义类型估值器。

监听器

属性动画提供了AnimatorLinstenr用于在动画开始结束取消重复播放时进行回调,

public static interface AnimatorListener {
    void onAnimationStart(Animator animation); //动画开始
    void onAnimationEnd(Animator animation); //动画结束
    void onAnimationCancel(Animator animation); //动画取消
    void onAnimationRepeat(Animator animation); //动画重复播放
}

AnimatorUpdateListener :监听整个动画过程。每播放一帧onAnimationUpdate()就会被调用一次,如下:

public interface AnimatorUpdateListener {
  void onAnimationUpdate(ValueAnimator var1);//在属性动画的属性值变化是回调。
}

工作流程

(1)创建ValueAnimator或ObjectAnimator,并设置动画持续时间与开始停止位置
(2)设置动画从开始到停止的执行逻辑设置插值器与估值器,比如线性与非线性的动画效果等等,线性不需要设置)
注:(1)(2)过程都是在ValueAnimator.ofObject/ofInt/ofFloat中执行的

ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);

(3)设置AnimatorUpdateListener监听器,当动画每播放一帧时会调用其中的onAnimationUpdate方法,如果是ValueAnimator就需要在其中手动设置该对象的属性,否则则不需要。最后调用invalidate方法让View重绘,调用onDraw方法。

在这里插入代码片

(4)(如果需要的话)使用AnimatorSet让两个动画同时进行,使用playTogether(Animator A,Animator B)方法。
(5)如果使用AnimatorSet就要调用其start方法,如果没有使用,就要调用ValueAnimator对象的start方法。
(6)之后动画会按照要求执行,直到结束。
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章