制作一个带有循环播放效果的轮盘式自动选中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="https://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:tools="https://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
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/addevelopment/896410.html