Android 循环滚动的列表-类似弹幕效果


前言

很简单

一、第一种

示例:

java 复制代码
 
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.LinearLayout;

 
 
public class FlipperLinearLayout  extends LinearLayout {

    private int MAX_SHOW_ITEM_SIZE = 5;

    private IAdapter mIAdapter;

    private int mCount;

    //最后一个item动画
    private Animation mLastOneAnimation;

    //其它item动画
    private Animation mCommonAnimation;

    //数据下标
    private int mCurrentIndex;

    /**
     * 这里动画时间是4秒,所以间隔得大于动画时间
     */
    private static final int DEFAULT_INTERVAL = 4000;

    private int mFlipInterval = DEFAULT_INTERVAL;

    private boolean mAutoStart = false;

    private boolean mRunning = false;
    private boolean mStarted = false;
    private boolean mVisible = false;
    private boolean mUserPresent = true;

    public ViewFlipper(Context context) {
        super(context);
        init(context);
    }

    public ViewFlipper(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public ViewFlipper(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            if (Intent.ACTION_SCREEN_OFF.equals(action)) {
                mUserPresent = false;
                updateRunning();
            } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
                mUserPresent = true;
                updateRunning(false);
            }
        }
    };

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

        // Listen for broadcasts related to user-presence
        final IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        filter.addAction(Intent.ACTION_USER_PRESENT);

        // OK, this is gross but needed. This class is supported by the
        // remote views machanism and as a part of that the remote views
        // can be inflated by a context for another user without the app
        // having interact users permission - just for loading resources.
        // For exmaple, when adding widgets from a user profile to the
        // home screen. Therefore, we register the receiver as the current
        // user not the one the context is for.
        getContext().registerReceiver(mReceiver, filter);

        if (mAutoStart) {
            // Automatically start when requested
            startFlipping();
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mVisible = false;

        getContext().unregisterReceiver(mReceiver);
        updateRunning();
    }

    @Override
    protected void onWindowVisibilityChanged(int visibility) {
        super.onWindowVisibilityChanged(visibility);
        mVisible = visibility == VISIBLE;
        updateRunning(mVisible);
//        updateRunning(false);
    }

    private boolean isSetting = false;

    public void setMaxItem(int max) {
//        if (max < 1) {
//            max = 4;
//        }
        if (isSetting) {
            return;
        }
        isSetting = true;
        Log.e("item", "setMaxItem: " + max);
        this.MAX_SHOW_ITEM_SIZE = max;
        requestLayout();
    }

    private void init(Context context) {
        this.setOrientation(LinearLayout.VERTICAL);
    }

    public void setIAdapter(IAdapter iAdapter) {
        this.mIAdapter = iAdapter;
        initShowItems();
    }

    public void startFlipping() {
        if (mStarted) return;
        mStarted = true;
        updateRunning();
    }

    public void stopFlipping() {
        if (!mStarted) return;
        mStarted = false;
        updateRunning();
    }

    private void updateRunning() {
        updateRunning(true);
    }

    /**
     * Returns true if the child views are flipping.
     */
    public boolean isFlipping() {
        return mStarted;
    }

    /**
     * Set if this view automatically calls {@link #startFlipping()} when it
     * becomes attached to a window.
     */
    public void setAutoStart(boolean autoStart) {
        mAutoStart = autoStart;
    }

    /**
     * Returns true if this view automatically calls {@link #startFlipping()}
     * when it becomes attached to a window.
     */
    public boolean isAutoStart() {
        return mAutoStart;
    }

    @Override
    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
        super.onInitializeAccessibilityEvent(event);
        event.setClassName(ViewFlipper.class.getName());
    }

    @Override
    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
        super.onInitializeAccessibilityNodeInfo(info);
        info.setClassName(ViewFlipper.class.getName());
    }

    /**
     * 初始化childViews
     */
    private void initShowItems() {
        if (mIAdapter != null) {
            mCount = mIAdapter.getCount();
            for (int i = 0; i < mCount; i++) {
                if (i == MAX_SHOW_ITEM_SIZE) {
                    break;
                }
                View convertView = getChildAt(i);
                View item = mIAdapter.getItemView(convertView, i);
                addView(item, i);
            }
        }
    }

    /**
     * Internal method to start or stop dispatching flip {@link Message} based
     * on {@link #mRunning} and {@link #mVisible} state.
     *
     * @param flipNow Determines whether or not to execute the animation now, in
     *                addition to queuing future flips. If omitted, defaults to
     *                true.
     */
    private void updateRunning(boolean flipNow) {
        boolean running = mVisible && mStarted && mUserPresent;
        System.out.println(" updateRunning running:" + running + " mVisible " + mVisible + " userPresent " + mUserPresent);
        if (running != mRunning) {
            if (running && (mCount > MAX_SHOW_ITEM_SIZE)) {
                showItems(mCurrentIndex++, flipNow);
                Message msg = mHandler.obtainMessage(FLIP_MSG);
                mHandler.sendMessageDelayed(msg, mFlipInterval);
            } else {
                mHandler.removeMessages(FLIP_MSG);
            }
            mRunning = running;
        }
    }


    private void showItems(final int position, boolean animate) {
        if (animate && (mLastOneAnimation == null || mCommonAnimation == null)) {
            mLastOneAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.lastone_anim);
            mCommonAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.common_anim);
        }
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            child.clearAnimation();
            int index = position + i;
            child = mIAdapter.getItemView(child, (index >= mIAdapter.getCount()) ? (index - mIAdapter.getCount()) : index);
            if (animate) {
                if (i == childCount - 1) {
                    child.setAnimation(mLastOneAnimation);
                } else {
                    child.setAnimation(mCommonAnimation);
                }
            }
            child.setVisibility(View.VISIBLE);
        }
        if (animate) {
            mCommonAnimation.startNow();
            mLastOneAnimation.startNow();
        }

        //保证传入的position小于getCount
        if (mCurrentIndex >= mIAdapter.getCount()) {
            mCurrentIndex = 0;
        }
    }

    private final int FLIP_MSG = 1;

    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == FLIP_MSG) {
                if (mRunning) {
                    showItems(mCurrentIndex++, true);
                    msg = obtainMessage(FLIP_MSG);
                    sendMessageDelayed(msg, mFlipInterval);
                }
            }
        }
    };

    public interface IAdapter {

        /**
         * @param convertView
         * @param position
         * @return
         */
        public View getItemView(View convertView, int position);

        /**
         * @return 数据count
         */
        public int getCount();

    }

}

使用方式:

java 复制代码
    private List<String> marqueeListData = new ArrayList<>();

public void ini(){
   for (int i = 0; i < 20; i++) {
            marqueeListData.add("暂无数据" + i);
        }
        intergralRv.post(() -> {
            Log.e("TAG", "postitem intergralRv: " + UtilBox.dip2px(60f, getActivity()) + "_---" + intergralRv.getHeight());
            intergralRv.setMaxItem(intergralRv.getHeight() / UtilBox.dip2px(60f, getActivity()) + 2);
            intergralRv.setIAdapter(this);
            flipperIs = true;
            intergralRv.startFlipping();
        });
}

二、第二种

示例:

java 复制代码
 
import android.animation.Animator;
import android.animation.LayoutTransition;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.core.util.Pools;

import com.ylj.water.utils.UtilBox;

import java.util.ArrayList;
import java.util.List;

public class AutoLinLayout extends LinearLayout {
    //最多显示的个数
    private int maxNum = 1;
    //view高度
    private int itemH = 3;

    public AutoLinLayout(Context context) {
        super(context);
        init(context);
        initAnim();
    }

    public AutoLinLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
        initAnim();
    }

    public AutoLinLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
        initAnim();
    }

    private void init(Context context) {
        this.setOrientation(LinearLayout.VERTICAL);
    }

    LayoutTransition transition;

    private void initAnim() {
        transition = new LayoutTransition();
        //添加动画
        ObjectAnimator valueAnimator = ObjectAnimator.ofFloat(null, "alpha", 0, 1);
        valueAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                //当前展示超过四条,执行删除动画
                if (AutoLinLayout.this.getChildCount() == (maxNum - 1)) {
                    handler.sendEmptyMessage(1);
                }
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                if (AutoLinLayout.this.getChildCount() >= maxNum) {
                    //动画执行完毕,删除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);
        this.setLayoutTransition(transition);

    }

    private boolean isRun = false;

    public void startAuto() {
        if (isRun) {
            return;
        }
        if (texts.size() < 1) {
            return;
        }
        isRun = true;
        handler.sendEmptyMessage(0);
    }

    public void startAuto(List<String> data) {
        if (isRun) {
            return;
        }
        if (data.size() < 1) return;
        texts = data;
        textViewPool = new Pools.SimplePool<>(texts.size());

        handler.sendEmptyMessage(0);
    }

    public void setItemHeight(int h) {
        this.itemH = h;
    }

    public void startAuto(List<String> data, int max) {
        if (isRun) {
            return;
        }
        if (data.size() < 1) return;
        texts = data;
        textViewPool = new Pools.SimplePool<>(texts.size());
//        maxNum = h / UtilBox.dip2px(itemH, getContext());
        maxNum = max;
        if (maxNum > 0) {
            for (int i = 0; i < (maxNum - 2); i++) {
                TextView textView = obtainTextView();
                AutoLinLayout.this.addView(textView);
                index++;
            }
            isRun = true;

        }
        handler.sendEmptyMessage(0);
    }

    public void stopAuto() {
        if (!isRun) {
            return;
        }
        isRun = false;
        handler.removeMessages(0);
    }

    private List<String> texts = new ArrayList<>();

    Pools.SimplePool<TextView> textViewPool;

    private TextView obtainTextView() {
        if (null == textViewPool) return null;
        TextView textView = textViewPool.acquire();
        if (textView == null) {
            textView = new TextView(getContext());
            textView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, UtilBox.dip2px(itemH, getContext())));
            textView.setPadding(UtilBox.dip2px(10, getContext()), UtilBox.dip2px(5, getContext()), UtilBox.dip2px(10, getContext()), UtilBox.dip2px(5, getContext()));
            textView.setTextColor(0xffffffff);
            textView.setMaxLines(1);
            textView.setEllipsize(TextUtils.TruncateAt.END);
            textView.setGravity(Gravity.CENTER);
            textView.setTextSize(15);
            textView.setTextColor(0xff000000);
            textView.setCompoundDrawablePadding(10);
        }
        textView.setTag(index);
        textView.setText(texts.get(index));
        return textView;
    }

    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:
                    if (texts.size() < 1) return;
                    if (!isRun) return;
                    TextView textView = obtainTextView();
                    AutoLinLayout.this.addView(textView);
                    sendEmptyMessageDelayed(0, 2000);
                    index++;
                    if (index == texts.size()) {
                        index = 0;
                    }
                    break;
                case 1:
                    if (texts.size() < 1) return;
                    if (!isRun) return;
                    //给展示的第一个view增加渐变透明动画
                    AutoLinLayout.this.getChildAt(0).animate().translationY(-30).alpha(0).setDuration(transition.getDuration(LayoutTransition.APPEARING)).start();

                    break;
                case 2:
                    if (texts.size() < 1) return;
                    if (!isRun) return;
                    //删除顶部view
                    AutoLinLayout.this.removeViewAt(0);
                    break;
            }
        }
    };
}

使用方式:

java 复制代码
public void init(){

       llContainer.setItemHeight(30);
       llContainer.post(() -> {
            llContainer.startAuto(marqueeListData,llContainer.getHeight()/UtilBox.dip2px(30, getActivity()));
           flipperIs = true;
      });

}
    @Override
    public void onResume() {
        super.onResume();
        if (flipperIs) {
            llContainer.startAuto();
        }

    }

    @Override
    public void onPause() {
        super.onPause();
        llContainer.stopAuto();
    }

lastone_anim和common_anim资源文件

XML 复制代码
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/anim/slide_in_left.xml
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
**     http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true">
    <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
        android:duration="2000" />
    <translate android:fromYDelta="0" android:toYDelta="-100%"
        android:duration="2000"
        android:startOffset="2000"/>
</set>
XML 复制代码
<?xml version="1.0" encoding="utf-8"?><!--
/* //device/apps/common/res/anim/slide_in_left.xml
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
**     http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true">
    <alpha
        android:fromAlpha="1.0"
        android:toAlpha="1.0"
        android:duration="2000" />
    <translate
        android:fromYDelta="0"
        android:toYDelta="-100%"
        android:duration="2000"
        android:startOffset="2000" />
</set>

总结

实现的很是简单,由于时间的原因,很多地方也不够完善,

仅供参考。

相关推荐
帅次13 分钟前
Android CoordinatorLayout:打造高效交互界面的利器
android·gradle·android studio·rxjava·android jetpack·androidx·appcompat
枯骨成佛1 小时前
Android中Crash Debug技巧
android
kim56596 小时前
android studio 更改gradle版本方法(备忘)
android·ide·gradle·android studio
咸芝麻鱼6 小时前
Android Studio | 最新版本配置要求高,JDK运行环境不适配,导致无法启动App
android·ide·android studio
无所谓จุ๊บ6 小时前
Android Studio使用c++编写
android·c++
csucoderlee7 小时前
Android Studio的新界面New UI,怎么切换回老界面
android·ui·android studio
kim56597 小时前
各版本android studio下载地址
android·ide·android studio
饮啦冰美式7 小时前
Android Studio 将项目打包成apk文件
android·ide·android studio
夜色。7 小时前
Unity6 + Android Studio 开发环境搭建【备忘】
android·unity·android studio
ROCKY_8179 小时前
AndroidStudio-滚动视图ScrollView
android