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相關的知識點,以及屬性動畫,插補器的使用,因爲遇到不懂得地方需要自己去查資料,而查資料的過程就是學習的過程。

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