1. 程式人生 > >仿花束直播點贊效果

仿花束直播點贊效果

這是一個利用貝塞爾曲線實現的仿花束直播的點贊效果,實現該效果涉及到:

1、Random隨機數的使用;

2、ObjectAnimator屬性動畫及插值器的使用;

3、貝塞爾曲線的使用;

/**
 * Created by Administrator on 2018/1/30.
 * 點讚的效果
 */

public class LoveLayout extends RelativeLayout {
    //隨機數
    private Random mRandom;
    //圖片資源
    private int[] mImageRes;
    //控制元件的寬高
    private int mWidth, mHeight;
    //獲取圖片的寬高
    private int mDrawableWidth, mDrawableHeight;
    //插值器陣列
    private Interpolator[] mInterpolator;

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

    public LoveLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LoveLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mRandom = new Random();
        mImageRes = new int[]{R.drawable.pl_blue, R.drawable.pl_red, R.drawable.pl_yellow};
        //獲取圖片的寬高
        Drawable drawable = ContextCompat.getDrawable(context, R.drawable.pl_blue);
        mDrawableWidth = drawable.getIntrinsicWidth();
        mDrawableHeight = drawable.getIntrinsicHeight();

        mInterpolator = new Interpolator[]{new AccelerateDecelerateInterpolator(), new AccelerateInterpolator(),
                new DecelerateInterpolator(), new LinearInterpolator()};
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //獲取控制元件的寬高
        mWidth = MeasureSpec.getSize(widthMeasureSpec);
        mHeight = MeasureSpec.getSize(heightMeasureSpec);
    }
}

上面這些這是是初始化資料及獲取一些圖片的寬度和高度,在android屬性動畫的基本使用(http://blog.csdn.net/wangwo1991/article/details/77424239)這篇部落格中有對屬性動畫的差值器有做說明;接下來在觸發動作的時候提供一個addLove(),將效果新增到佈局容器中;

/**
  * 新增一個點讚的view
  */
public void addLove() {
    //新增一個imageview在底部
    final ImageView loveIv = new ImageView(getContext());
    //設定圖片資源(隨機數)
    loveIv.setImageResource(mImageRes[mRandom.nextInt(mImageRes.length - 1)]);
    //新增到底部中心
    RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
        ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    //設定底部居中
    params.addRule(ALIGN_PARENT_BOTTOM);
    params.addRule(CENTER_HORIZONTAL);
    loveIv.setLayoutParams(params);

    addView(loveIv);
    //新增的效果是:有放大和透明的變化
    AnimatorSet animatorSet = getAimator(loveIv);
    animatorSet.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            super.onAnimationEnd(animation);
            //動畫執行完畢後,將其移除
            removeView(loveIv);
        }
    });
    animatorSet.start();
}

這裡就是建立一個ImageView,隨機設定ImageView的背景,並設定其在佈局容器中顯示的位置,同時給ImageView新增透明度和縮放動畫效果;

/**
  * 設定屬性動畫效果
  * @param loveIv
  * @return
  */
public AnimatorSet getAimator(ImageView loveIv) {
    //新增的效果是:有放大和透明的變化
    AnimatorSet allAnimator = new AnimatorSet();

    AnimatorSet innerAnimator = new AnimatorSet();
    //新增屬性動畫
    ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(loveIv, "alpha", 0.3f, 1.0f);
    ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(loveIv, "scaleX", 0.3f, 1.0f);
    ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(loveIv, "scaleY", 0.3f, 1.0f);
    //一起執行動畫
    innerAnimator.playTogether(alphaAnimator, scaleXAnimator, scaleYAnimator);
    innerAnimator.setDuration(350);
    //執行的路徑動畫  按順序執行動畫
    allAnimator.playSequentially(innerAnimator, getBezierAnimator(loveIv));
    return allAnimator;
}

這裡設定了透明度和縮放動畫效果,通過AnimatorSet一起開啟動畫執行,在執行的時候根據貝塞爾曲線的路徑去執行,而不是隨意的去執行;

  • 一階曲線原理:

一階曲線是沒有控制點的,僅有兩個資料點(A 和 B),最終效果一個線段。


一階公式如下:


  • 二階曲線原理

二階曲線由兩個資料點(A 和 C),一個控制點(B)來描述曲線狀態,大致如下:


那麼ac之間的紅線是怎麼生成的呢,讓我們瞭解一下:


在AB線段和BC線段分別去D、E兩點,且滿足條件


連線DE,取點F,使得: ,這樣獲取到的點F就是貝塞爾曲線上的一個點,動態圖如下:


二階公式如下:


  • 三階曲線原理

三階曲線由兩個資料點(A 和 D),兩個控制點(B 和 C)來描述曲線狀態


動態圖如下:


三階公式如下:


這是一階貝塞爾、二階貝塞爾和三階貝塞爾,當然還有四階、五階等,具體網上有很多關於貝塞爾曲線的知識(https://www.cnblogs.com/wjtaigwh/p/6647114.html);這個效果裡面用到的是三階貝塞爾曲線,不過這裡需要自定義路徑屬性動畫;

/**
 * Created by Administrator on 2018/1/30.
 * 自定義的路徑屬性動畫
 */

public class LoveTypeEvaluator implements TypeEvaluator<PointF> {
    private PointF point1, point2;

    public LoveTypeEvaluator(PointF point1, PointF point2) {
        this.point1 = point1;
        this.point2 = point2;
    }

    @Override
    public PointF evaluate(float t, PointF point0, PointF point3) {
        //t 的範圍是0-1的範圍
        //可以開始套公式了
        PointF pointF = new PointF();
        pointF.x = point0.x * (1 - t) * (1 - t) * (1 - t)
                + 3 * point1.x * t * (1 - t) * (1 - t)
                + 3 * point2.x * t * t * (1 - t)
                + point3.x * t * t * t;

        pointF.y = point0.y * (1 - t) * (1 - t) * (1 - t)
                + 3 * point1.y * t * (1 - t) * (1 - t)
                + 3 * point2.y * t * t * (1 - t)
                + point3.y * t * t * t;
        return pointF;
    }
}

根據構造方法傳入的point1和point2使用三階貝塞爾曲線公式進行路徑的繪製;

/**
     * 繪製貝塞爾曲線
     * @param loveIv
     * @return
     */
    private Animator getBezierAnimator(final ImageView loveIv) {
        //確定這四個點
        PointF point0 = new PointF(mWidth / 2 - mDrawableWidth / 2, mHeight - mDrawableHeight);
        //確保p2點的y值一定要大於p2點的y值
        PointF point1 = getPoint(1);
        PointF point2 = getPoint(2);
        PointF point3 = new PointF(mRandom.nextInt(mWidth) - mDrawableWidth, 0);
        LoveTypeEvaluator typeEvaluator = new LoveTypeEvaluator(point1, point2);
        //第一個引數 typeEvaluator  第二個引數就是p0,第三個引數就是p3
        ValueAnimator bezererAnimator = ObjectAnimator.ofObject(typeEvaluator, point0, point3);
        //插值器
        bezererAnimator.setInterpolator(mInterpolator[mRandom.nextInt(mInterpolator.length - 1)]);
        bezererAnimator.setDuration(5000);
        bezererAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                PointF pointF = (PointF) animation.getAnimatedValue();
                loveIv.setX(pointF.x);
                loveIv.setY(pointF.y);
                //設定透明度
                float t = animation.getAnimatedFraction();
                loveIv.setAlpha(1 - t + 0.2f);
            }
        });
        return bezererAnimator;
    }

    /**
     * 獲取p1和p2
     * @param index
     * @return
     */
    private PointF getPoint(int index) {
        return new PointF(mRandom.nextInt(mWidth) - mDrawableWidth, mRandom.nextInt(mHeight / 2) + (index - 1) * (mHeight / 2));
    }

這樣效果就實現了。

原始碼地址:

https://pan.baidu.com/s/1rakMHcS