引言
這是作者第一個CSDN的文章,寫的不好的地方,請大家多多提提意見哈。今天寫的文章是關(guān)于動畫的,前幾天公司要求我寫一個觀看直播點擊送花與主播收到花朵的效果。
 主播接到花朵的效果
 觀眾送花效果
思路
好了看見了效果之后想必大家都應(yīng)該知道肯定想到要用Animator(4.0增加的動畫類),想必很多人都接觸到了,但是剛接觸Android的兄弟可能就沒怎么使用這個。網(wǎng)上有很多文章對這個類的介紹,所以這里就不注重解釋了。
主要的思路如下: 1.確定起點(主播是隨機起點)與終點位置; 2.為其添加動畫特效,從起點運動到終點,然后銷毀View。
代碼
我就不拿以前的代碼了,我一邊敲一邊說,首先創(chuàng)建個項目(不多說了哈),然后我們創(chuàng)建一個工具類FlowerAnimation.java。我們就對這個類瘋狂擼吧。
固定起點->固定終點特效實現(xiàn)
private static final String TAG = 'FlowerAnimation'; //上下文 private Context mContext; //生成的View添加在的ViewGroup private ViewGroup rootView; private ViewGroup.LayoutParams layoutParams;
//資源文件 private Drawable[] drawables; //插值器 private Interpolator[] interpolators;
上面代碼的注釋很清楚了,大家一看就明白了 ,TGA這是為了測試打印log使用的(AS快捷鍵-logt+enter)。
public FlowerAnimation(Context mContext, ViewGroup rootView) { this.mContext = mContext; this.rootView = rootView; init(); }
private void init() { drawables = new Drawable[8]; drawables[0] = mContext.getResources().getDrawable(R.mipmap.flower_01); drawables[1] = mContext.getResources().getDrawable(R.mipmap.flower_02); drawables[2] = mContext.getResources().getDrawable(R.mipmap.flower_03); drawables[3] = mContext.getResources().getDrawable(R.mipmap.flower_04); drawables[4] = mContext.getResources().getDrawable(R.mipmap.flower_05); drawables[5] = mContext.getResources().getDrawable(R.mipmap.flower_06); drawables[6] = mContext.getResources().getDrawable(R.mipmap.flower_07); drawables[7] = mContext.getResources().getDrawable(R.mipmap.flower_08);
interpolators = new Interpolator[4]; interpolators[0] = new LinearInterpolator();//線性 interpolators[1] = new AccelerateInterpolator();//加速 interpolators[2] = new DecelerateInterpolator();//減速 interpolators[3] = new AccelerateDecelerateInterpolator();//先加速后減速
layoutParams = new ViewGroup.LayoutParams(DensityUtil.dip2px(mContext, 50), DensityUtil.dip2px(mContext, 50)); }
上面就是一些初始化的東西了,大家看看就好。
準(zhǔn)備工作做好了,我們寫最主要的方法,那就是生成動畫了,上代碼
/** /** * 開啟動畫 * * @param view 執(zhí)行動畫的view * @param startP 起點 如果傳null 默認(rèn)從view位置開始 * @param stopP 終點 */ public void startAnim(@NonNull final View view, @Nullable PointF startP, @NonNull PointF stopP) { if (startP == null) { startP = new PointF(view.getX(), view.getY()); } //透明度變化 ObjectAnimator animatorAlpha = ObjectAnimator.ofFloat(view, 'alpha', 0, 1); animatorAlpha.setDuration(200); //位移動畫 ObjectAnimator animatorX = ObjectAnimator.ofFloat(view, 'translationX', startP.x, stopP.x); animatorX.setDuration(1000); ObjectAnimator animatorY = ObjectAnimator.ofFloat(view, 'translationY', startP.y, stopP.y); animatorY.setDuration(1000); //生成動畫集合 AnimatorSet set = new AnimatorSet(); //開啟透明度動畫然后執(zhí)行位移動畫 set.play(animatorAlpha).before(animatorX).with(animatorY); //加入植入器 set.setInterpolator(interpolators[rand.nextInt(interpolators.length)]); //添加動畫監(jiān)聽事件 為了移除view 防止造成oom set.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); rootView.removeView(view); } }); set.start(); }
/** * 開啟動畫 * * @param view 執(zhí)行動畫的view * @param stopP 終點 */ public void startAnim(@NonNull final View view, @NonNull PointF stopP) { startAnim(view, null, stopP); }
/** * 添加花朵 * * @param startPoint */ public void addFlower(@NonNull PointF startPoint, @NonNull PointF stopP) { ImageView flower = new ImageView(mContext); flower.setX(startPoint.x); flower.setY(startPoint.y); Drawable drawable = drawables[rand.nextInt(drawables.length)]; flower.setBackground(drawable); rootView.addView(flower, layoutParams); startAnim(flower, startPoint, stopP); }
好了,我們看到,生成這個動畫需要View(這是必然的)終點也是必然,起點就無所謂了。每一步的注釋都寫的很清楚,ObjectAnimator這個類功能很強大(4.0+)。下面開始寫個界面來看看效果啦。界面代碼我直接上代碼 不講解了!對了有關(guān)于view位置的問題大家直接去看別的文章吧,很多的哈,我這里貼個我認(rèn)為不錯的:
http://blog.csdn.net/jason0539/article/details/42743531
public class MainActivity extends AppCompatActivity implements View.OnClickListener { //終點坐標(biāo)imageView private ImageView endFlowerIv; //開啟動畫按鈕 也是起點坐標(biāo) private Button startFlowerBt; private FlowerAnimation flowerAnimation;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); }
private void initView() { endFlowerIv = (ImageView) findViewById(R.id.main_end_flower_iv); startFlowerBt = (Button) findViewById(R.id.main_start_flower_bt); startFlowerBt.setOnClickListener(this); flowerAnimation = new FlowerAnimation(this, (ViewGroup) findViewById(R.id.activity_main)); }
@Override public void onClick(View v) { flowerAnimation.addFlower(new PointF(v.getX(), v.getY()), new PointF(endFlowerIv.getX(), endFlowerIv.getY())); }
android:id='@+id/activity_main' android:layout_width='match_parent' android:layout_height='match_parent'>
<> android:id='@+id/main_end_flower_iv' android:layout_width='50dp' android:layout_height='50dp' android:layout_gravity='center_horizontal' android:background='@mipmap/flower_01' />
<> android:id='@+id/main_start_flower_bt' android:layout_width='50dp' android:layout_height='50dp' android:layout_gravity='center_horizontal|bottom' android:background='@mipmap/flower_01' />
下面是效果圖
 隨即起點->固定終點特效實現(xiàn)
這個問題想必大家在上面的基礎(chǔ)上就完全可以寫出來 直接把代碼貼上來與效果
/** * 添加花朵 隨即生成起點(rootView范圍) * * @param stopP 終點 */ public void addFlowerByScope(@NonNull PointF stopP) { float x = rand.nextFloat() * rootView.getWidth(); float y = rand.nextFloat() * rootView.getHeight(); addFlower(new PointF(x, y), stopP); }
/** * 添加花朵 隨即生成起點 * * @param stopP 終點 * @param scopeP 范圍 隨即生成的點將會按照此范圍隨即取值 */ public void addFlowerByScope(@NonNull PointF stopP, @NonNull PointF scopeP) { float x = rand.nextFloat() * scopeP.x; float y = rand.nextFloat() * scopeP.y; addFlower(new PointF(x, y), stopP); }
界面的點擊事件換成對應(yīng)的方法
@Override public void onClick(View v) { flowerAnimation.addFlowerByScope(new PointF(endFlowerIv.getX(), endFlowerIv.getY())); }

到此,你會發(fā)現(xiàn)已經(jīng)完成主播接到花朵的效果(就是隨即從各個地方出現(xiàn)花朵飛到花朵出)以上就是主播界面顯示的效果了。代碼比較簡單,下面實現(xiàn)觀眾點擊送花的效果。
思路呢?其實跟上面差不多,觀眾送花的效果類似固定點到固定點的效果(類似哈哈),為什么說類似呢?因為從圖上可以看到,路徑是不同的,很明顯發(fā)現(xiàn) 觀眾送花的效果的路徑是隨即的(亂飄)。這里就引出來了ValueAnimator 這個東西你會發(fā)現(xiàn)他是ObjectAnimator的父類。
ValueAnimator 顧名思義哈 就是針對數(shù)值的動畫,他能幫我完成什么呢? 比如我我想讓一個數(shù)值從0-10 時間是10s,我們來寫寫看我們新建一個ValueAnimActivity.java來實現(xiàn)觀眾的界面,順便在里面測試demo。
public class ValueAnimActivity extends AppCompatActivity implements View.OnClickListener { private TextView countTv; private Button startBt;
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_value); initView(); }
private void initView() { countTv = (TextView) findViewById(R.id.value_count_tv); startBt = (Button) findViewById(R.id.value_start_bt); startBt.setOnClickListener(this); }
@Override public void onClick(View v) { startValueAnim(); }
private void startValueAnim() { //從0-10 時間10s ValueAnimator countAnim = ValueAnimator.ofInt(0, 10) .setDuration(10000); countAnim.setInterpolator(new LinearInterpolator()); countAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { countTv.setText(animation.getAnimatedValue().toString()); } }); countAnim.start(); }
}
android:layout_width='match_parent' android:layout_height='match_parent' android:orientation='vertical'>
<> android:id='@+id/value_count_tv' android:layout_width='wrap_content' android:layout_height='wrap_content' android:layout_gravity='center' android:textSize='30dp' />
<> android:id='@+id/value_start_bt' android:layout_width='wrap_content' android:layout_height='wrap_content' android:layout_gravity='center_horizontal|bottom' android:text='start' />
代碼跟布局都很簡單 我們簡單寫了個ValueAnimator的例子 直接看效果

那怎么實現(xiàn)我們的效果呢?這里就用到一個方法
public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values)
他能讓一個對象進(jìn)行改變,那怎么改變呢?其實隨便是什么?都要跟時間掛鉤(重要),為什么跟時間掛鉤,這還要解釋么?onAnimationUpdate回調(diào)的方法你打印log你會發(fā)現(xiàn)他會調(diào)用N多次,10S回調(diào)了496次,可想而知我們對象改變也會跟時間有關(guān)系,那么我們看看TypeEvaluator(類型評估者)這是一個接口,我們來看看它要我們實現(xiàn)的方法 public T evaluate(float fraction, T startValue, T endValue);
這個方法看到之后,后面2個我肯定知道是什么意思,動畫的起始值,那第一個是什么?(英文翻譯是分?jǐn)?shù))咱們說過肯定跟時間有關(guān)的,那么這個是不是就是時間呢?看了官方解釋之后,這個意思就是當(dāng)前完成動畫的百分比。 還不懂?那好我們還看看官方有沒有默認(rèn)給我們實現(xiàn)的類,一看,有很多,我們直接拿來一個用看看效果,上代碼
private void startValueAnim1() { //從0-10 時間10s ValueAnimator countAnim = ValueAnimator.ofObject(new IntEvaluator() { @Override public Integer evaluate(float fraction, Integer startValue, Integer endValue) { Log.e(TAG, 'evaluate() called with: fraction = [' + fraction + '], startValue = [' + startValue + '], endValue = [' + endValue + ']'); return super.evaluate(fraction, startValue, endValue); } }, 0, 10) .setDuration(10000); countAnim.setInterpolator(new LinearInterpolator()); countAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { countTv.setText(animation.getAnimatedValue().toString()); } }); countAnim.start(); }

下面重點來了,如果要實現(xiàn)這種效果,那就要一個公式(貝塞爾曲線)這個當(dāng)初我也是一頭霧水啊,先不管直接套用公式就行了(先上一個圖片)

這個公式只要理解我們給出4個點,他就算出當(dāng)前的點。這里有4個點,但是我們只有2個點,另外2個點是為了控制曲線的走向,我隨即取就可以咯。好了,我們先不管點,先把TypeEvaluator寫好
/** * 自定義的估值器 */ public static class MyTypeEvaluator implements TypeEvaluator { private PointF pointF1, pointF2;
public MyTypeEvaluator(PointF pointF1, PointF pointF2) { this.pointF1 = pointF1; this.pointF2 = pointF2; }
@Override public PointF evaluate(float fraction, PointF startValue, PointF endValue) { float timeLeft = 1.0f - fraction; PointF pointF = new PointF();//結(jié)果 pointF.x = timeLeft * timeLeft * timeLeft * (startValue.x) + 3 * timeLeft * timeLeft * fraction * (pointF1.x) + 3 * timeLeft * fraction * fraction * (pointF2.x) + fraction * fraction * fraction * (endValue.x);
pointF.y = timeLeft * timeLeft * timeLeft * (startValue.y) + 3 * timeLeft * timeLeft * fraction * (pointF1.y) + 3 * timeLeft * fraction * fraction * (pointF2.y) + fraction * fraction * fraction * (endValue.y); return pointF; } }
只是簡單的套用公式,就不用多講了直接復(fù)制就好,要不然能看的眼花。
下面放上取中間控制點的代碼

注釋也簡單,我覺得很好弄懂,下面上主要代碼





下面是最終效果啦

到這里差不多可以結(jié)束了。想必大家可能會問,如果動畫還沒執(zhí)行完就退出了,那就內(nèi)存泄漏了啊。所以我再來個方法。


好了,跑起來沒有問題了。
源碼地址:https://github.com/CFlingchen/CSDN1

|