最近新需求有一個熱評列表需要做成輪流從屏幕底部向上彈出的動畫效果,看了效果圖第一時間就想到了彈幕實現,但是思考一番後又否定了這個想法,首先這個熱評列表不像彈幕需要實時獲取播放,第二屏幕上展示的評論條數有限,固定最多就幾條,再有新的彈出就把最上面一條消失掉,不想彈幕似的滿屏都是,第三就是我看了B站的開源彈幕庫,並不支持豎向的彈幕播放,如果要通過彈幕的方式實現這個需求的話,還需要自己對這個庫進行擴展,有點捨近求遠的意思。
思前想後,我想起了ViewGroup可以設置添加和刪除內部View時的動畫,那麼它是不是可以滿足我對功能的所有需求呢?於是網上百度了一下,果然有用這種方式做類似效果的,比如這篇Android UI:上下滾動的評論彈幕
通過對ViewGroup設置LayoutTransition來實現控制View添加和刪除時的動畫效果,界面固定顯示四條評論,整個評論的輪播都是複用四個TextView,當顯示超過四條評論時刪除第一條顯示的評論。但是我發現它這個Demo只有在添加View的時候執行了提前設置的動畫,刪除的時候並沒有執行。總之先Copy下來跑一下看看吧(複製粘貼Levi能力滿級),不跑不知道,一跑嚇一跳,連添加View時的動畫都不能正常執行,當添加第五個View時(也就是第五條評論,複用的第一條評論內容),新增的VIew是直接顯示出來,並沒有執行動畫,在這之前的四條都是可以正常執行的。
我開始懷疑是不是因爲在新增之前執行了刪除操作所以出了問題,然後我就把刪除的代碼注掉又跑了一遍,果然沒問題了,於是我就大膽猜測ViewGroup不能同時執行添加和刪除的動畫,爲了驗證我的猜想,我又去百度了(百度能力滿分),找了一圈發現有一篇博客也是這個問題,並且和我推斷出了相同的結論,因爲當時也沒有收藏書籤,也懶得找了,就不放鏈接了。
除了我遇到的問題,在百度的過程中發現LayoutTransition挺多坑的,具體可以看看啓艦大神的這篇博客自定義控件三部曲之動畫篇(十二)——animateLayoutChanges與LayoutTransition
解決辦法
既然不能同時執行添加和刪除的動畫,那麼我可以監聽添加的動畫,等它執行完成後再進行刪除操作,但是這樣一來,添加和刪除的動畫就不能同步執行了,這樣效果就會大打折扣,怎麼辦呢,曲線救國,再添加動畫開始的時候對對要刪除的VIew執行一個透明度變化的動畫,等添加動畫結束後被刪除的VIew也就變透明瞭,這時再進行一個刪除的操作,就可以神不知鬼不覺的實現我們要的效果了。注意,這裏刪除時也會有刪除的動畫執行,所以我們需要對刪除的動畫進行一個處理,以防出現錯誤的效果。
上代碼
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:orientation="vertical">
<LinearLayout
android:id="@+id/ll_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_gravity="bottom"
android:divider="@drawable/divider"
android:orientation="vertical"
android:padding="15dp"
android:showDividers="middle">
</LinearLayout>
</RelativeLayout>
public class Main2Activity extends AppCompatActivity {
private LinearLayout llContainer;
LayoutTransition transition;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
llContainer = (LinearLayout) findViewById(R.id.ll_container);
transition = new LayoutTransition();
//添加動畫
ObjectAnimator valueAnimator = ObjectAnimator.ofFloat(null, "alpha", 0, 1);
valueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
//當前展示超過四條,執行刪除動畫
if (llContainer.getChildCount() == 4) {
handler.sendEmptyMessage(1);
}
}
@Override
public void onAnimationEnd(Animator animation) {
if (llContainer.getChildCount() == 5)
//動畫執行完畢,刪除view
handler.sendEmptyMessage(2);
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
transition.setAnimator(LayoutTransition.APPEARING, valueAnimator);
//刪除動畫
PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0, 0);
ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(null, new PropertyValuesHolder[]{alpha}).setDuration(transition.getDuration(LayoutTransition.DISAPPEARING));
transition.setAnimator(LayoutTransition.DISAPPEARING, objectAnimator);
llContainer.setLayoutTransition(transition);
}
private String[] texts = new String[]{
"火來我在灰燼中等你",
"我對這個世界沒什麼可說的。我對這個世界沒什麼可說的。我對這個世界沒什麼可說的。",
"俠之大者,爲國爲民。",
"爲往聖而繼絕學"};
Pools.SimplePool<TextView> textViewPool = new Pools.SimplePool<>(texts.length);
private TextView obtainTextView() {
TextView textView = textViewPool.acquire();
if (textView == null) {
textView = new TextView(Main2Activity.this);
textView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
textView.setPadding(dp2px(10), dp2px(5), dp2px(10), dp2px(5));
textView.setTextColor(0xffffffff);
textView.setMaxLines(1);
textView.setEllipsize(TextUtils.TruncateAt.END);
textView.setGravity(Gravity.CENTER);
textView.setTextSize(15);
textView.setTextColor(0xffffffff);
Drawable drawable = getResources().getDrawable(R.mipmap.circle_head);
drawable.setBounds(0, 0, 80, 80);
textView.setCompoundDrawablesRelative(drawable, null, null, null);
textView.setCompoundDrawablePadding(10);
switch (index) {
case 0:
textView.setBackgroundDrawable(ContextCompat.getDrawable(Main2Activity.this, R.drawable.rect_black));
break;
case 1:
textView.setBackgroundDrawable(ContextCompat.getDrawable(Main2Activity.this, R.drawable.rect_blue));
break;
case 2:
textView.setBackgroundDrawable(ContextCompat.getDrawable(Main2Activity.this, R.drawable.rect_green));
break;
case 3:
textView.setBackgroundDrawable(ContextCompat.getDrawable(Main2Activity.this, R.drawable.rect_red));
break;
}
}
textView.setText(texts[index]);
return textView;
}
private int dp2px(float dp) {
DisplayMetrics displayMetrics = new DisplayMetrics();
this.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, displayMetrics);
}
int index = 0;
@SuppressLint("HandlerLeak")
private Handler handler = new Handler() {
@SuppressLint("ResourceAsColor")
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0:
TextView textView = obtainTextView();
llContainer.addView(textView);
sendEmptyMessageDelayed(0, 2000);
index++;
if (index == 4) {
index = 0;
}
break;
case 1:
//給展示的第一個view增加漸變透明動畫
llContainer.getChildAt(0).animate().alpha(0).setDuration(transition.getDuration(LayoutTransition.APPEARING)).start();
break;
case 2:
//刪除頂部view
llContainer.removeViewAt(0);
break;
}
}
};
@Override
protected void onResume() {
super.onResume();
handler.sendEmptyMessage(0);
}
@Override
protected void onPause() {
super.onPause();
handler.removeMessages(0);
}
}
上效果圖