当前位置: 移动技术网 > 移动技术>移动开发>Android > 荐 【安卓】让你的 AbsListView可以自动滚动,还能循环!

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

2020年07月17日  | 移动技术网移动技术  | 我要评论

制作一个带有循环播放效果的轮盘式自动选中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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网