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