Android动效实现——底部向上弹出滑动式弹幕

 最近新需求有一个热评列表需要做成轮流从屏幕底部向上弹出的动画效果,看了效果图第一时间就想到了弹幕实现,但是思考一番后又否定了这个想法,首先这个热评列表不像弹幕需要实时获取播放,第二屏幕上展示的评论条数有限,固定最多就几条,再有新的弹出就把最上面一条消失掉,不想弹幕似的满屏都是,第三就是我看了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);
    }
}

上效果图

发布了57 篇原创文章 · 获赞 26 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/Ever69/article/details/105105243
今日推荐