android 飘心动画(直播点赞)效果(二)---贝塞尔曲线的实现

上篇文章 android 飘心动画(直播点赞)效果 只有代码,没有相关的说明。因为我自己也没有看懂,所以参照网上另一篇关于贝塞尔曲线实现 飘心动画的效果,目的就是 便于理解上篇文章代码的思路,然后写个关于飘心动画的自己的理解。

下面是我参照的文章:一步一步教你实现Periscope点赞效果,—文章出自简书。 我也是是依葫芦画瓢,所以就定义为转载的文章,只是文章里面加了些自己理解的东西。效果图如下:

这里写图片描述


1.定义飘心的布局

这个我相信大家很容易想到使用RelativeLayout,对,没错,那么我们先定义一个Layout吧,继承自RelativeLayout,并且重载构造函数,并定义一些变量.

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
import com.myapplication2.app.R;

import java.util.Random;

/**
 * http://www.jianshu.com/p/03fdcfd3ae9c
 * https://github.com/AlanCheen/PeriscopeLayout/blob/master/library/src/main/java/me/yifeiyuan/library/PeriscopeLayout.java
 *
 * 参考实现的自定义飘心动画的布局
 * time:2016年8月31日10:10:34
 * @see android.widget.RelativeLayout
 */
public class PeriscopeLayout extends RelativeLayout{
    
    

    private int dHeight; //爱心的高度
    private int dWidth; //爱心的宽度
    private int mHeight; //自定义布局的高度
    private int mWidth;  //自定义布局的宽度

    private LayoutParams layoutParams;
    private Random random = new Random();  //用于获取随机心的随机数
    private Drawable[] drawables;  //存放初始化图片的数组

    /**
     * 是在java代码创建视图的时候被调用,如果是从xml填充的视图,就不会调用这个
     */
    public PeriscopeLayout(Context context) {
        super(context);
        init();
    }

    /**
     * 这个是在xml创建但是没有指定style的时候被调用
     */
    public PeriscopeLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    /**
     * 这个是在xml创建但是 有指定style的时候被调用
     */
    public PeriscopeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }


    /**
     * 初始化布局和随机心型图片
     */
    private void init(){

        //初始化显示的图片,暂时使用3 种图片
        drawables = new Drawable[3];

        //getResources().getDrawable 过期的替代方法 ContextCompat.getDrawable(getContext(),R.drawable.heart3);
//        Drawable red = getResources().getDrawable(R.drawable.heart3);
        Drawable red = ContextCompat.getDrawable(getContext(),R.drawable.heart3);
        Drawable yellow = ContextCompat.getDrawable(getContext(),R.drawable.heart8);
        Drawable blue = ContextCompat.getDrawable(getContext(),R.drawable.heart6);

        drawables[0] = red;
        drawables[1] = yellow;
        drawables[2] = blue;

        //获取图的宽高 用于后面的计算
        //注意 我这里3张图片的大小都是一样的,所以我只取了一个
        dHeight = red.getIntrinsicHeight();
        dWidth = red.getIntrinsicWidth();

        //定义心型图片出现的位置,底部 水平居中
        layoutParams = new LayoutParams(dWidth,dHeight);
        layoutParams.addRule(CENTER_HORIZONTAL,TRUE);
        layoutParams.addRule(ALIGN_PARENT_BOTTOM,TRUE);


    }

    /**
     * http://blog.csdn.net/pi9nc/article/details/18764863
     * 自定义布局 onMeasure 的作用
     * 获取控件的实际高度
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //注意!!  获取本身的宽高 需要在测量之后才有宽高
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
    }
}

1.1 处理心性图片的位置

//定义心型图片出现的位置,底部 水平居中
layoutParams = new LayoutParams(dWidth,dHeight);
layoutParams.addRule(CENTER_HORIZONTAL,TRUE);
layoutParams.addRule(ALIGN_PARENT_BOTTOM,TRUE);

1.2 用一个数组存放默认的心型的图片

//getResources().getDrawable 过期的替代方法 ContextCompat.getDrawable(getContext(),R.drawable.heart3);

Drawable red = ContextCompat.getDrawable(getContext(),R.drawable.heart3);
……
…..
drawables[0] = red;
drawables[1] = yellow;
drawables[2] = blue;

1.3 直接通过随机数的方法就可以获取随机的图片

drawables[random.nextInt(3)]//表示0-2的随机数,注意,3是取不到的,是个开区间

1.4 onMeasure方法的说明

这里写图片描述

getWidth(): View在设定好布局后整个view 的宽度。
getMeasuredWidth(): 对View上的內容进行测量后得到到的View內容占据的宽度。

具体的说明如下:
http://blog.csdn.net/pi9nc/article/details/18764863

2.实现缩放和透明度变化的动画

直接通过 ObjectAnimator 即属性动画实现缩放和透明度变化的动画效果
在PeriscopeLayout.java 文件里面添加对应的放就可

这里写图片描述

2.1 属性动画 的实现

/**
     * 通过属性动画 实现爱心图片的缩放和透明度变化的动画效果
     * target 就是爱心图片的view
     */
    private AnimatorSet getEnterAnimtor(final View target){


        ObjectAnimator scaleX = ObjectAnimator.ofFloat(target, View.SCALE_X, 0.2f, 1f);
        ObjectAnimator scaleY = ObjectAnimator.ofFloat(target, View.SCALE_Y, 0.2f, 1f);

        AnimatorSet enter = new AnimatorSet();
        enter.setDuration(500);
        enter.setInterpolator(new LinearInterpolator());//线性变化
        enter.playTogether(scaleX,scaleY);
        enter.setTarget(target);

        return enter;
    }

 /**
     * 动画结束后,remove
     */
    private class AnimEndListener extends AnimatorListenerAdapter {
    
    
        private View target;

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

        @Override
        public void onAnimationEnd(Animator animation) {
            super.onAnimationEnd(animation);
            //因为不停的add 导致子view数量只增不减,所以在view动画结束后remove掉
            removeView((target));
        }
    }

2.2 提供外部方法的实现

/**
     * 提供外部实现点击效果,只有缩放和变淡的效果
     */
    public void addFavorWithoutBiz(){
        ImageView imageView = new ImageView(getContext());
        //随机心型颜色
        imageView.setImageDrawable(drawables[random.nextInt(3)]);
        imageView.setLayoutParams(layoutParams);

        addView(imageView);

        Animator set = getEnterAnimtor(imageView);
        set.addListener(new AnimEndListener(imageView));
        set.start();
    }

2.3 无贝塞尔曲线的效果

这里写图片描述


3.实现贝塞尔曲线效果

百度百科关于贝塞尔曲线的解释

三次方公式
P0、P1、P2、P3四个点在平面或在三维空间中定义了三次方贝兹曲线。曲线起始于P0走向P1,并从P2的方向来到P3。一般不会经过P1或P2;这两个点只是在那里提供方向资讯。P0和P1之间的间距,决定了曲线在转而趋进P3之前,走向P2方向的“长度有多长”。
曲线的参数形式为:

这里写图片描述

现代的成象系统,如PostScript、Asymptote和Metafont,运用了以贝兹样条组成的三次贝兹曲线,用来描绘曲线轮廓。

这里写图片描述


公式中需要四个P,P0,是我们的起点,P3是终点,P1,P2是途径的两个点
而t则是我们的一个因子,取值范围是0-1


3.1 自定义 BezierEvaluator 实现 TypeEvaluator

import android.animation.TypeEvaluator;
import android.graphics.PointF;

/**
 * 我们自定义一个BezierEvaluator 实现 TypeEvaluator
 * 由于我们view的移动需要控制x y 所以就传入PointF 作为参数,是不是感觉完全契合
 */
public class BezierEvaluator implements TypeEvaluator<PointF> {
    
    

    private PointF pointF1;
    private PointF pointF2;

    public BezierEvaluator(PointF pointF1,PointF pointF2){
        this.pointF1 = pointF1;
        this.pointF2 = pointF2;
    }

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

        float timeLeft = 1.0f - time;
        PointF point = new PointF();//结果

        point.x = timeLeft * timeLeft * timeLeft * (startValue.x)
                + 3 * timeLeft * timeLeft * time * (pointF1.x)
                + 3 * timeLeft * time * time * (pointF2.x)
                + time * time * time * (endValue.x);

        point.y = timeLeft * timeLeft * timeLeft * (startValue.y)
                + 3 * timeLeft * timeLeft * time * (pointF1.y)
                + 3 * timeLeft * time * time * (pointF2.y)
                + time * time * time * (endValue.y);
        return point;
    }
}

3.2 定义贝塞尔曲线的动画实现

/**
     * 贝塞尔曲线的动画实现
     */

    private ValueAnimator getBezierValueAnimator(View target) {
        //初始化一个BezierEvaluator
        BezierEvaluator evaluator = new BezierEvaluator(getPointF(2), getPointF(1));

        //第一个PointF传入的是初始点的位置
        ValueAnimator animator = ValueAnimator.ofObject(evaluator, new PointF((mWidth - dWidth) / 2, mHeight - dHeight-20), new PointF(random.nextInt(getWidth()), 0));//随机
        animator.addUpdateListener(new BezierListenr(target));
        animator.setTarget(target);
        animator.setDuration(3000);
        return animator;
    }

    /**
     * 获取中间的两个点
     */
    private PointF getPointF(int scale) {

        PointF pointF = new PointF();
        pointF.x = random.nextInt((mWidth - 50));//减去50 是为了控制 x轴活动范围,看效果 随意~~
        //再Y轴上 为了确保第二个点 在第一个点之上,我把Y分成了上下两半 这样动画效果好一些  也可以用其他方法
        pointF.y = random.nextInt((mHeight - 150)) / scale;
        return pointF;
    }

    /**
     * 只有在回调里使用了计算的值,才能真正做到曲线运动
     */
    private class BezierListenr implements ValueAnimator.AnimatorUpdateListener {
    
    
        private View target;

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

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            //这里获取到贝塞尔曲线计算出来的的x y值 赋值给view 这样就能让爱心随着曲线走啦
            PointF pointF = (PointF) animation.getAnimatedValue();
            target.setX(pointF.x);
            target.setY(pointF.y);

            // alpha动画
            target.setAlpha(1 - animation.getAnimatedFraction());
        }
    }

注意:如果发现初始位置不对,存在抖动现象
getBezierValueAnimator 函数中
ValueAnimator animator = ValueAnimator.ofObject(evaluator, new PointF((mWidth - dWidth) / 2, mHeight - dHeight-20), new PointF(random.nextInt(getWidth()), 0));//随机

第一个pointF 的点来控制位置

这里写图片描述

3.3 增加调用方法

public void addFavor() {
        ImageView imageView = new ImageView(getContext());
        imageView.setImageDrawable(drawables[random.nextInt(3)]);
        imageView.setLayoutParams(layoutParams);

        addView(imageView);

        Animator set = getAnimator(imageView);
        set.addListener(new AnimEndListener(imageView));
        set.start();
    }

3.4 效果展示
最终效果就是一开始展示的效果

4.具体使用

这里写图片描述

xml 代码

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
        android:background="@color/grey"
        android:alpha="0.5">

    <com.myapplication2.app.newsdemo.view.bizHeartview.PeriscopeLayout
            android:id="@+id/heart_layout"
            android:layout_alignParentRight="true"
            android:layout_width="100dp"
            android:layout_height="match_parent"/>

    <TextView
            android:id="@+id/member_send_good"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_gravity="center"
            android:layout_alignParentBottom="true"
            android:layout_alignParentRight="true"
            android:layout_marginRight="30dp"
            android:layout_marginBottom="10dp"
            android:background="@drawable/live_like_icon"
            />

</RelativeLayout>

5.参考资料

http://www.jianshu.com/p/03fdcfd3ae9c
https://github.com/AlanCheen/PeriscopeLayout

6.总结

发现有些东西只看一遍,没什么效果。只有自己动手才知道里面的困难,虽然是一步步仿照过来的,但是也学到了一些东西,例如自定义view相关的知识点,以及属性动画,插补器的使用,因为遇到不懂得地方需要自己去查资料,而查资料的过程就是学习的过程。

猜你喜欢

转载自blog.csdn.net/android_freshman/article/details/52385278