欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

【安卓】让你的 AbsListView可以自动滚动,还能循环!

程序员文章站 2022-11-05 10:34:00
1. 轮盘式循环 listView2. 自动选中置顶 item3. 选项对齐符合你需要就进来看看吧~...

制作一个带有循环播放效果的轮盘式自动选中AbsListView

一、需求描述

我这边的需求是这样的:服务端会传过来一个数据集合,还有一个需要显示选中的数据。产品希望可以展示出来一个随机抽奖循环的效果,最终停在指定选中的位置。没问题,安排~

二、效果预览

老规矩,无图言 ×。上图!
【安卓】让你的 AbsListView可以自动滚动,还能循环!

三、上代码

自认为代码里注释已经足够多了,所以这里就不在做过多解释了。

1. 主逻辑代码 - AutoScrollAdapter.java
import android.annotation.SuppressLint;
import android.content.Context;
import android.support.annotation.ColorInt;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.baijiayun.groupclassui.R;

import java.util.List;

/**
 * @author lzd
 * 自动旋转至目标位置的 adapter,调用
 * {@link AutoScrollAdapter#initListView(AbsListView, Context, List)} 以使用
 */
public class AutoScrollAdapter extends BaseAdapter implements
        AbsListView.OnScrollListener, View.OnTouchListener {
    private Context context;
    private AbsListView listView;
    private List<String> dataList;
    private OnAutoScrollListener onAutoScrollListener;

    private int maxNum = 5;
    private int nowFirstPosition;

    private int normalTextSize;
    private int selectTextSize;
    private @ColorInt
    int normalTextColor;
    private @ColorInt
    int selectTextColor;

    private int listViewVisibleHeight;
    private int itemHeight;
    private int listViewPaddingTop;

    /**
     * 最小滚动个数 - 可以滚动多圈,但是最少要这么多
     */
    private static final int MIN_SCROLL_NUM = 30;

    /**
     * 目标 FirstPosition {@link AutoScrollAdapter#updateSelect(int)} 中设定
     */
    private int targetFirstPosition = -1;
    /**
     * 前进至 {@link AutoScrollAdapter#targetFirstPosition} 的剩余距离
     */
    private double remainLength = 0;
    /**
     * 偏差补齐模式
     */
    private boolean isMakeUpDeviation = false;

    private AutoScrollAdapter(Context context, List<String> dataList) {
        this.context = context;
        this.dataList = dataList;
    }

    /**
     * 调用此方法以初始化并获取一个 AutoScrollAdapter
     *
     * @param listView 需要适配的 ListView
     * @param dataList 数据集
     */
    @SuppressLint("ClickableViewAccessibility")
    public static AutoScrollAdapter initListView(
            final AbsListView listView, final Context context, final List<String> dataList) {
        AutoScrollAdapter autoScrollAdapter = new AutoScrollAdapter(context, dataList);
        listView.setOnTouchListener(autoScrollAdapter);
        listView.setOnScrollListener(autoScrollAdapter);
        listView.setAdapter(autoScrollAdapter);
        autoScrollAdapter.listViewPaddingTop = listView.getPaddingTop();
        autoScrollAdapter.initParams(listView.getLayoutParams().height
                - listView.getPaddingTop() - listView.getPaddingBottom());
        autoScrollAdapter.nowFirstPosition = Integer.MAX_VALUE / 2;
        listView.setSelection(autoScrollAdapter.nowFirstPosition);
        autoScrollAdapter.listView = listView;
        return autoScrollAdapter;
    }

    private void initParams(int listViewVisibleHeight) {
        this.listViewVisibleHeight = listViewVisibleHeight;
        this.itemHeight = listViewVisibleHeight / maxNum;
        // region 默认字号
        this.normalTextSize = this.itemHeight / 5;
        this.selectTextSize = this.itemHeight / 4;
        // endregion
    }

    /**
     * 设置 最大显示 个数
     */
    public void setMaxNum(int maxNum) {
        this.maxNum = maxNum;
        initParams(listViewVisibleHeight);
        listView.setSelection(nowFirstPosition);
    }

    /**
     * 设置文字颜色
     *
     * @param normalTextColor 普通状态
     * @param selectTextColor 中间选中状态
     */
    public void setTextColor(@ColorInt int normalTextColor, @ColorInt int selectTextColor) {
        this.normalTextColor = normalTextColor;
        this.selectTextColor = selectTextColor;
    }

    /**
     * 前进至 目标
     *
     * @param targetPosition 目标 在 {@link AutoScrollAdapter#dataList} 中的下标
     */
    public void updateSelect(int targetPosition) {
        if (this.remainLength != 0) {
            return;
        }
        // region 计算 当前位置 到 目标选中位置 的 总共要前进的距离
        // 计算规则:"前进至少一轮 且 要大于 MIN_SCROLL_NUM" 的结果值 + "当前位置 到 目标位置一轮内的偏移"
        int targetOffset = targetPosition - maxNum / 2;
        int now2targetOffset = (targetOffset - nowFirstPosition % dataList.size() + dataList.size()) % dataList.size();
        int minRemainDistance = MIN_SCROLL_NUM - now2targetOffset;
        int remainDistance = ((minRemainDistance / dataList.size()) + 1) * dataList.size();

        this.targetFirstPosition = nowFirstPosition + remainDistance + now2targetOffset;
        this.remainLength = (remainDistance + now2targetOffset) * (itemHeight + 1);
        // endregion
        startScrollStep();
    }

    public interface OnAutoScrollListener {
        /**
         * 自动 scroll 结束回调
         */
        void onStateIdle();
    }

    public void setOnAutoScrollListener(OnAutoScrollListener onAutoScrollListener) {
        this.onAutoScrollListener = onAutoScrollListener;
    }

    /**
     * 每帧刷新
     * 注: 这里之所以使用 {@link android.widget.ListView#scrollListBy(int)}
     *     而没有使用 {@link android.widget.ListView#smoothScrollToPositionFromTop(int, int, int)}
     *     是因为 {@link android.widget.ListView#smoothScrollToPositionFromTop(int, int, int)}
     *     方法 有bug,目前( 2020-07-14 )未修复
     * 注: smooth 系列均有bug,具体表现为:偶现,虽然回调了
     *     {@link android.widget.AbsListView.OnScrollListener#onScroll(AbsListView, int, int, int)}
     *     但是并无法正确跳转到指定位置
     */
    private void startScrollStep() {
        // region 每帧刷新前进
        // length 计算规则:1. remainLength > 0 前进;remainLength > 0 后退
        //                2. isMakeUpDeviation 为 true 即为补齐偏差,每帧 1 像素
        //                3. isMakeUpDeviation 为 false,正常前进,每次前进 remainLength 的 20 分之 1,
        //                   不超过 itemHeight 的一半
        final int length = (isMakeUpDeviation ? 1 :
                (int) Math.ceil(Math.min(Math.abs(remainLength) / 20, itemHeight / 2d)))
                * (remainLength > 0 ? 1 : -1);
        listView.scrollListBy(length);
        remainLength -= length;
        // endregion
        listView.postDelayed(() -> {
            if (Math.abs(remainLength) > 1) {
                // 剩余超过 1 像素,继续
                startScrollStep();
            } else {
                // 不足一像素
                if (checkCalcDeviation()) {
                    // 检查需补齐,继续
                    startScrollStep();
                    return;
                }
                // 检查已补齐,修正 selection ,回复数据
                listView.setSelection(targetFirstPosition);
                targetFirstPosition = -1;
                if (onAutoScrollListener != null) {
                    // 结束回调
                    onAutoScrollListener.onStateIdle();
                }
            }
        }, 10);
    }

    /**
     * 计算剩余计算偏差
     * 注:方法 {@link AutoScrollAdapter#updateSelect(int)} 方法中,
     * remainLength 的计算会有一定的偏差,这里需要补齐
     *
     * @return 已无偏差 返回 false
     */
    private boolean checkCalcDeviation() {
        double calcDeviation = 0;
        if (listView.getFirstVisiblePosition() > targetFirstPosition) {
            // 超出,过 item height 回退
            if (listView.getTop() > listView.getChildAt(0).getTop()) {
                calcDeviation = listView.getChildAt(0).getTop() - listView.getTop();
            } else {
                calcDeviation = itemHeight;
            }
        } else if (listView.getFirstVisiblePosition() == targetFirstPosition) {
            // 超出,回退超出部分
            if (listView.getTop() > listView.getChildAt(0).getTop()) {
                calcDeviation = listView.getChildAt(0).getTop() - listView.getTop();
            }
        } else {
            // 未到达,继续前进
            calcDeviation = itemHeight - (listView.getTop() - listView.getChildAt(0).getTop());
        }
        remainLength = calcDeviation;
        isMakeUpDeviation = calcDeviation != 0;
        return isMakeUpDeviation;
    }

    /**
     * 屏蔽点击事件
     */
    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        return event.getAction() == MotionEvent.ACTION_MOVE;
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
    }

    /**
     * 滚动时监听
     */
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        if (view.getChildAt(0) == null) {
            return;
        }
        // 中间选中位置的坐标
        int selectPosition = view.getTop() + listViewPaddingTop +
                ((maxNum / 2) * itemHeight);
        int newTopPosition = 0;
        for (int i = 0; i < visibleItemCount; i++) {
            View item = view.getChildAt(i);
            if (item == null) {
                return;
            }
            // region 通过每个 item 的 top 坐标来计算字号和文字颜色
            int itemPosition = item.getTop();

            // 计算当前 item 距离中间位置的比例,超出一个 item 距离则为 0,直接使用 normal 属性
            // 否则按比例使用 select 属性
            double normal2selRatio = ((double) itemHeight -
                    Math.min(Math.abs(itemPosition - selectPosition), itemHeight)) / itemHeight;
            if (normal2selRatio < 0.5) {
                // 第一次赋值,且 normal2selRatio < 0.5,即为最接近中间的一个
                newTopPosition = firstVisibleItem;
            }

            double textSize = normalTextSize + (selectTextSize - normalTextSize) * normal2selRatio;
            Object viewHolder = item.getTag();
            if (viewHolder instanceof ViewHolder) {
                ((ViewHolder) viewHolder).tvName.setTextSize((float) textSize);
                ((ViewHolder) viewHolder).tvName.setTextColor(
                        normal2selRatio < 0.5 ? normalTextColor : selectTextColor);
                if (newTopPosition != nowFirstPosition) {
                    nowFirstPosition = newTopPosition;
                    // 改变字号和颜色后,requestLayout 一次,否则显示有问题
                    ((ViewHolder) viewHolder).tvName.requestLayout();
                }
            }
            // endregion
        }
    }

    @Override
    public int getCount() {
        return Integer.MAX_VALUE;
    }

    @Override
    public Object getItem(int position) {
        return dataList.get(position % dataList.size());
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder;
        if (convertView == null) {
            convertView = View.inflate(context, R.layout.auto_scroll_item, null);
            viewHolder = new ViewHolder();

            viewHolder.tvName = convertView.findViewById(R.id.auto_scroll_item_name);
            convertView.setTag(viewHolder);

            //region 设置 item 高度
            LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) viewHolder.tvName.getLayoutParams();
            params.height = itemHeight;
            viewHolder.tvName.setLayoutParams(params);
            //endregion
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }
        viewHolder.tvName.setText(dataList.get(position % dataList.size()));
        viewHolder.tvName.setTextSize(normalTextSize);
        viewHolder.tvName.setTextColor(normalTextColor);

        return convertView;
    }

    static class ViewHolder {
        TextView tvName;
    }
}

2. 相关布局文件 - auto_scroll_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:tools="http://schemas.android.com/tools"
    tools:background="@android:color/black"
    android:background="@android:color/transparent"
    android:gravity="center"
    android:orientation="vertical">

    <TextView
        android:id="@+id/auto_scroll_item_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textAlignment="center"
        android:gravity="center"
        android:text="@string/app_name"
        android:maxLines="1"
        android:ellipsize="end"
        android:textColor="?attr/base_window_main_text_color"
        android:textSize="14sp" />

</LinearLayout>
3. 调用方式示例
ListView lvNames = $.id(R.id.random_select_names_container).view();
String[] names = new String[]{
        "0刘德华",
        "1马云",
        "2猪八戒",
        "3太上老君",
        "4爱迪生",
        "5胖大海",
        "6迪迦",
};

// 初始化 AbsListView
autoScrollAdapter = AutoScrollAdapter.initListView(lvNames, context, Arrays.asList(names));

// 注册监听
autoScrollAdapter.setOnAutoScrollListener(() -> {
    $.id(R.id.random_select_operate_btn).view().setEnabled(true);
    Log.d("lzdTest", "update select OK");
});

// 设置部分参数
autoScrollAdapter.setMaxNum(3);
autoScrollAdapter.setTextColor(normalColor, selectColor);

// 开始选中事件
$.id(R.id.random_select_operate_btn).clicked(v -> {
    int newInd = new Random().nextInt(names.length);
    Log.d("lzdTest", "选中 -> " + names[newInd]);
    autoScrollAdapter.updateSelect(newInd);
    $.id(R.id.random_select_operate_btn).view().setEnabled(false);
});

四、不要脸环节

如果帮到你了,给一个三连如何 (* ̄v ̄) - 点赞,收藏,心情好的话还可以评论下~
转载请标明出处 - https://blog.csdn.net/weixin_41957078

本文地址:https://blog.csdn.net/weixin_41957078/article/details/107348194