【安卓】让你的 AbsListView可以自动滚动,还能循环!
程序员文章站
2022-05-16 09:48:13
1. 轮盘式循环 listView2. 自动选中置顶 item3. 选项对齐符合你需要就进来看看吧~...
制作一个带有循环播放效果的轮盘式自动选中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
下一篇: ios 关于成员变量与属性的区别