自定义view(五) 属性动画的使用及自定义 ,仿直播中花束点赞效果

1 视图动画只能是view使用,对view做出相应的变换,但是属性动画作用于Object,任何对象都可以。

2 视图动画只是改变了视觉效果,但是并没有改变其物理属性,比如button坐标在(100,100,300,300),通过位移进行移动,但是他的可点击区域仍然在(100,100,300,300).

3 最后一点是视图动画提供了四种变化方式。至于其具体的实现由系统计算好了。因此它的形式比较单一。比如(100,100)移动到(300,230)只能以直线的形式移动过去,我们没法控制它的移动轨迹,而属性动画可以自己控制这些东西,因此动画有更多的表现形式。

  • 实现过程

我们可以简单归纳属性动画的实现分为以下几个过程

1 设置目的动画的初始值,终值,以及持续时间。

2 设置动画的插值器(TimeInterpolator, 定义动画的变换率,就是控制时间流逝机制),设置动画的估值器(TypeEvaluator)主要是在动画过程中根据初始和最终值确定任意时刻的状态。比如位移动画,估值器就需要确认任意时刻object的坐标。对于TimeInterpolator和TypeEvaluator系统提供了一些可以直接使用的类,简单一些的动画够用。

3 根据上面的逻辑进行不停的计算,每次计算的结果都进行绘制,直到时间结束。

  • API简介

在属性动画中主要的类是Animator, 它是一个抽象类,直接子类有AnimatorSet和ValueAnimator, 间接子类有ObjectAnimator和TimeAnimator,我们常用的是AnimatorSet和valueAnimator,整个属性动画的流程如下所示:

在更新过程中,就是时间插值器TimeInterpolator计算时间, 而TypeEvaluator计算当前时间Ojbect的状态。我们先看一个简单的属性动画的例子。

public class AnimatorDemoActivity extends Activity {

    @BindView(R.id.bttest)
    Button bt;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_animator_demo);
        ButterKnife.bind(this);
    }

    @OnClick(R.id.bttest)
    public void ButtonLarge(){
        //设置一个属性动画,移动一个button距离左边的距离,设置从0到1000.
        ObjectAnimator va =ObjectAnimator.ofFloat(bt,"translationX",1000);
        //这是一个插值器,线性的,表示匀速进行
        va.setInterpolator(new MyLinearInterpolator());
        //动画时间是2s
        va.setDuration(2000);
        va.start();
    }

}

//以下是mYLinearInterpolator的代码
public class MyLinearInterpolator extends BaseInterpolator {
    @Override
    public float getInterpolation(float v) {
        Log.e("Tag","huai *************the vis======"+v);
        return v;
    }

在这里我们设置的插值器是一个模仿的线性插值器。我们主要是看看这个时间因子v的变化情况。打印如下:

截取了其中2段作为展示,可以看出是在2s内匀速前进。可以理解为2s内匀速的移动1000pix,在这里之所以展示匀速插值器的案例,是因为我们有时间做的动画不需要允许进行。无规则有时候反而更加美观。而我们可以按照我们想要的规则定制这个时间因子。

以上代码是通过ObjectAnimation移动,属性动画形式为:ObjectAnimator.ofFloat(targetObject, "propName", value...),就是针对Object的某一属性做动画,在上面的例子中我们移动的属性是translationX,在这里原则上可以移动任意属性 ,比如Object的x,y的坐标,等等,但是想要移动某个属性必须注意以下条件:

1 移动的属性,propName必须在TargetObejct的类中包含setter,geter函数。如果没有setter函数,那么可以用valueAnimation代替

2 value...如果只有一个值当做终点值,两个值依次为开始,结束值。

3 执行完有可能需要主动调用invalidate。

下面我们就看一个valueAnimator的例子。

 private void valueMove(){
        ValueAnimator va = ValueAnimator.ofInt(bt.getWidth(),1500);
        va.setDuration(3000);
        //设置一个加速的时间插值器
        va.setInterpolator(new AccelerateInterpolator());
        va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                //获取移动过程中,移动的当前值然后将值赋予button的宽
                int width = (int) valueAnimator.getAnimatedValue();
                bt.getLayoutParams().width = width;
                bt.requestLayout();
               
            }
        });
        va.start();
    }

在上面的代码我们都可以看到时间插值器,我们选择了一个加速的效果,我们可以看看他的原码中处理时间因子的代码:

 public float getInterpolation(float input) {
        if (mFactor == 1.0f) {
            return input * input;
        } else {
            //mDoubleFactor默认值是2,就是对时间因子input求二次方,x的2次方,在
              [0,1)的范围内斜率逐渐增大,因此它是逐渐加速的过程
            return (float)Math.pow(input, mDoubleFactor);
        }
    }

之所以在这里分析两个插值器的源码,是因为我们自己写动画的过程中,一般都是需要重写插值器和估值器。可以根据需求来实现自己所需要的插值器估值器。接下来我们要讲估值器,估值器的作用就是需要做动画的属性在某个时间点的状态。

  • 仿花束点赞效果

先上效果图

其实这个动画的过程挺简单,就是计算在某一个时刻,这个心形imageview的位置。所以我们需要移动的是他的位置。就是坐标,可以用一个point来表示的坐标。因此我们可以写明白他的估值器就是要确定任意时刻图片的坐标。 首先我们确认的是图片移动的轨迹是按照三阶贝塞尔曲线来实现的。所以我们可以重写TypeEvaluator如下:

public class BezierEvaluator implements TypeEvaluator<PointF> {

    //这个是贝塞尔曲线中的两个控制点,通过构造函数来传递
    PointF control1,control2;

    public BezierEvaluator(PointF controlPoint1, PointF controlPoint2){
        control1 = controlPoint1;
        control2 = controlPoint2;
    }

    @Override
    public PointF evaluate(float t, PointF point0, PointF point3) {
        //t就是插值器返回的那个时间因子,范围[0,1]
        //这里就是对一个三阶贝塞尔曲线的实例应用,完全是按照贝塞尔的
        //point0,是贝塞尔的初始点,point3是贝塞尔的结束点
        PointF point = new PointF();
        point.x = point0.x * (float)Math.pow((1-t),3)
                + 3 * control1.x * t * (float)Math.pow((1-t),2)
                + 3 * control2.x * (float)Math.pow(t,2)* (1 - t)
                + point3.x * (float)Math.pow(t,3);

        point.y = point0.y * (float)Math.pow((1-t),3)
                + 3 * control1.y * t * (float)Math.pow((1-t),2)
                + 3 * control2.y * (float)Math.pow(t,2) * (1 - t)
                + point3.y * (float)Math.pow(t,3);

        return point;
    }
}

我们重写估值器,主要是需要自己实现evaluate()这个函数,在这个函数里面根据时间因子,确定移动的位移坐标的位置。这里直接用的三阶贝塞尔曲线,如果对贝塞尔曲线不是很清楚,可以看贝塞尔曲线的绘制这篇博客。我们写好了估值器。接下来就可以实现自己的动画了。 

public class LovelyView extends RelativeLayout {

        int mWidth,mHeight;
        int drawableHeight,drawableWidth;
        Drawable love;
        Interpolator[] mInterpolators;
        LayoutParams lp;
        Random mRandom;

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


        public LovelyView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }

        public LovelyView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init();
        }

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            mWidth = getMeasuredWidth();
            mHeight = getMeasuredHeight();
        }

        private void init(){

            initDrawableConfig();
            mRandom = new Random();
            lp = new LayoutParams(drawableWidth,drawableHeight);
            lp.addRule(CENTER_HORIZONTAL,TRUE);
            lp.addRule(ALIGN_PARENT_BOTTOM,TRUE);
        }

        private void initDrawableConfig(){
            love = getResources().getDrawable(R.mipmap.love);
            drawableHeight = love.getIntrinsicHeight();
            drawableWidth = love.getIntrinsicWidth();
        }


        public void addView(){
            //初始化需要添加的imageview
            final  ImageView  desView = new ImageView(getContext());
            desView.setImageDrawable(love);
            desView.setLayoutParams(lp);
            addView(desView);

            AnimatorSet sets = getStartAnimationSet(desView);
            ValueAnimator va = getBezierAnimator(desView);
            AnimatorSet de = new AnimatorSet();

            de.play(sets).before(va);
            de.start();

        }
        /**
         * 获取初始化心形图案的动画
         * @return 动画集合
         */
        private AnimatorSet getStartAnimationSet(ImageView desView){
            AnimatorSet sets = new AnimatorSet();

            ObjectAnimator alphaAnimation = ObjectAnimator.ofFloat(desView,"alpha",0.3f,1f);

            ObjectAnimator scalexAnimation = ObjectAnimator.ofFloat(desView,"scaleX",0.1f,1f);
            ObjectAnimator scaleyAnimation = ObjectAnimator.ofFloat(desView,"scaleY",0.1f,1f);
            sets.playTogether(alphaAnimation,scaleyAnimation,scalexAnimation);
            sets.setDuration(500);
            sets.setTarget(desView);
            return sets;
        }

        //获取移动iamgeview的动画
        private ValueAnimator getBezierAnimator(final ImageView desView){
            int x = (mWidth - drawableWidth)/2;
            int y = (mHeight - drawableHeight);
            PointF p0 = new PointF(x,y);
            PointF p1 = getPoint(1);
            PointF p2 = getPoint(2);
            PointF destinationPoint  = new PointF(mRandom.nextInt(mWidth), 0);
            BezierEvaluator  evaluator = new BezierEvaluator(p1,p2);
            ValueAnimator animator = ValueAnimator.ofObject(evaluator, p0,
                    destinationPoint);
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    // 不断改变ImageView的坐标
                    PointF pointF = (PointF) animation.getAnimatedValue();
                    desView.setX(pointF.x);
                    desView.setY(pointF.y);
                    desView.setAlpha((1 - (animation.getAnimatedFraction())));
                    desView.invalidate();
                }
            });

            animator.setTarget(desView);
            animator.setDuration(2000);
            animator.setInterpolator(new AccelerateDecelerateInterpolator());
            return animator;
        }

        //获取控制点
        private PointF getPoint(int i) {

            return new PointF(mRandom.nextInt(mWidth) - drawableWidth / 2,
                    mRandom.nextInt(mHeight / 2) + (i - 1) * mHeight / 2);
        }
}
  • 总结

其实动画可以看成是定义插值器,估值器的过程,把整个过程一个个拆开就可以分解成最简单的单元.

           自定义Interpolator需要重写getInterpolation(float input),控制时间参数的变化.

           自定义TypeEvaluator需要重写evaluate()方法,计算对象的属性值并将其封装成一个新对象返回.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值