Android(安卓)弧形列表转盘的实现(二),列表自动选中;RecyclerView滑动后自动选中居中的条目,RecyclerView实现WheelView效果;
这篇主要是列表滑动后停止后,自动选中居中的条目,类似于WheelView的效果;通俗的讲就是用RecyclerView实现WheelView的效果;
接上篇:Android 弧形转盘的实现,弧形列表; 弧形列表已经实现了,下面就是自动选中的功能了;
代码已上传:https://github.com/CuiChenbo/ArcSelectList , 欢迎Star
列表滑动后自动选中
先来分析一波:
如果RecyclerView滑动停止后是下面这个情况,应该把索引4这个条目滑动到中间位置
红色的这条线是RecyclerView的竖向的中心线,当列表滑动停止后遍历可见区域的所有View,计算出距离中心线最近的一个View(是该View的中心点距离中心线最近),然后移动该View至中心线位置;
1、RecyclerView滑动停止后获取可见区域的所有View:
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); if (newState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); int fi = linearLayoutManager.findFirstVisibleItemPosition(); int la = linearLayoutManager.findLastVisibleItemPosition(); Log.i("ccb", "onScrollStateChanged:首个item: " + fi + " 末尾item:" + la); } } });
2、计算出距离中心线最近的一个View;
这个可以粗略的获取到中间的View(粗略获取到中心View):
int centerPositionDiffer = (la - fi) / 2; int centerChildViewPosition = fi + centerPositionDiffer; //获取当前所有条目中中间的一个条目索引
但是它未必是最靠近中线的一个,我们这个地方需要这个View的前一个和后一个,从这三个View中计算最靠近中线的一个View(精确获取到中心View):
//获取最中间的Item View int centerPositionDiffer = (la - fi) / 2; int centerChildViewPosition = fi + centerPositionDiffer; //获取当前所有条目中中间的一个条目索引 centerViewItems.clear(); //遍历循环,获取到和中线相差最小的条目索引(精准查找最居中的条目) if (centerChildViewPosition != 0){ for (int i = centerChildViewPosition -1 ; i < centerChildViewPosition+2; i++) { View cView = recyclerView.getLayoutManager().findViewByPosition(i); int viewTop = cView.getTop()+(cView.getHeight()/2); centerViewItems.add(new CenterViewItem(i ,Math.abs(centerToTopDistance - viewTop))); } CenterViewItem centerViewItem = getMinDifferItem(centerViewItems); centerChildViewPosition = centerViewItem.position; }
static class CenterViewItem{ public CenterViewItem(int position, int differ) { this.position = position; //当前Item索引 this.differ = differ; //当前item和居中位置的差值 } public int position; public int differ; }
/** * 计算距离中间最近的一个ItemView * @param itemHeights * @return */ private static CenterViewItem getMinDifferItem(List itemHeights){ CenterViewItem minItem = itemHeights.get(0); //默认第一个是最小差值 for (int i = 0; i < itemHeights.size(); i++) { //遍历获取最小差值 if (itemHeights.get(i).differ <= minItem.differ){ minItem = itemHeights.get(i); } } return minItem; }
3、移动View至中心线:
/** * 移动指定索引到中心处 , 只可以移动可见区域的内容 * @param position */ private void scrollToCenter(int position){ LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); View childView = linearLayoutManager.findViewByPosition(position); Log.i("ccb", "滑动后中间View的索引: " + position); //把当前View移动到居中位置 if (childView == null) return; int childVhalf = childView.getHeight() / 2; int childViewTop = childView.getTop(); int viewCTop = centerToTopDistance; int smoothDistance = childViewTop - viewCTop + childVhalf; Log.i("ccb", "\n居中位置距离顶部距离: " + viewCTop + "\n当前居中控件距离顶部距离: " + childViewTop + "\n当前居中控件的一半高度: " + childVhalf + "\n滑动后再次移动距离: " + smoothDistance); recyclerView.smoothScrollBy(0, smoothDistance,null,500); mAdapter.setSelectPosition(position); TUtils.show(AutoSelectActivity.this , "滑动后选中:" + mDatas.get(position)); }
移动RecyclerView条目后,还会再次触发 onScrollStateChanged的回调,需要做一个标记来判断是用户滑动的还是RecuclerView自己滑动的;
recyclerView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { isTouch = true; return false; } });
这个具体实现等下看完整代码;
OK,这样一个RecyclerView滑动后居中选中的功能就好了;但是还有一个问题,前几条和最后几条无法滑动到中间你位置,这个地方还需要处理一下,我这边做法比较简单粗暴,直接给RecyclerView的数据源设置几条空数据,使用空数据把条目填充起来;
所有条目都可以滑动到中心处
1、先计算RecyclerView一屏最多可以显示几个item,然后再除2就是半个RecyclerView可以显示几个Item;
int childViewHeight = UiUtils.dip2px(AutoSelectActivity.this, 43); //43是当前已知的 Item的高度childViewHalfCount = (recyclerView.getHeight() / childViewHeight + 1) / 2;
2、填充空数据;
private void initData() { if (mDatas == null) mDatas = new ArrayList<>(); for (int i = 0; i < 55; i++) { mDatas.add("CAR_Item" + i); } for (int j = 0; j < childViewHalfCount; j++) { //头部的空布局 mDatas.add(0, ""); } for (int k = 0; k < childViewHalfCount; k++) { //尾部的空布局 mDatas.add(""); } }
3、自动选中居中条目时不可选择空数据的Item;
/** * 移动指定索引到中心处 , 只可以移动可见区域的内容 * @param position */ private void scrollToCenter(int position){ position = position < childViewHalfCount ? childViewHalfCount : position; position = position < mAdapter.getItemCount() - childViewHalfCount -1 ? position : mAdapter.getItemCount() - childViewHalfCount -1; LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); View childView = linearLayoutManager.findViewByPosition(position); Log.i("ccb", "滑动后中间View的索引: " + position); //把当前View移动到居中位置 if (childView == null) return; int childVhalf = childView.getHeight() / 2; int childViewTop = childView.getTop(); int viewCTop = centerToTopDistance; int smoothDistance = childViewTop - viewCTop + childVhalf; Log.i("ccb", "\n居中位置距离顶部距离: " + viewCTop + "\n当前居中控件距离顶部距离: " + childViewTop + "\n当前居中控件的一半高度: " + childVhalf + "\n滑动后再次移动距离: " + smoothDistance); recyclerView.smoothScrollBy(0, smoothDistance,null,500); mAdapter.setSelectPosition(position); TUtils.show(AutoSelectActivity.this , "滑动后选中:" + mDatas.get(position)); }
好了,这样一个RecyclerView实现的WheelView就完成了;
如果需要点击其它条目自动选中时调用scrollToCenter方法就好了, 如果需要自动选中一个不在屏幕内的条目,需要先调用 scrollToPosition方法:
/** * 移动指定索引 * @param position */ private void smoothToPosition(int position){ position = position < childViewHalfCount ? childViewHalfCount : position; position = position < mAdapter.getItemCount() - childViewHalfCount -1 ? position : mAdapter.getItemCount() - childViewHalfCount -1; LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); linearLayoutManager.scrollToPosition(position); }
好了,上图!!!
代码已上传:https://github.com/CuiChenbo/ArcSelectList , 欢迎Star
再配合弧形列表的效果:
下一篇:Android 弧形转盘的实现(三),View跟随RecyclerView做旋转动画;
RecyclerView实现WheelView功能,Activity代码:
/** * 滑动后自动选中居中的条目 类似 WheelView */public class AutoSelectActivity extends AppCompatActivity { private RecyclerView recyclerView; private MAdapter mAdapter; private int centerToTopDistance; //RecyclerView高度的一半 ,也就是控件中间位置到顶部的距离 , private int childViewHalfCount = 0; //当前RecyclerView一半最多可以存在几个Item @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_auto_select); recyclerView = findViewById(R.id.rv); init(); } private void init() { recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this); } centerToTopDistance = recyclerView.getHeight() / 2; int childViewHeight = UiUtils.dip2px(AutoSelectActivity.this, 43); //43是当前已知的 Item的高度 childViewHalfCount = (recyclerView.getHeight() / childViewHeight + 1) / 2; initData(); findView(); } }); recyclerView.postDelayed(new Runnable() { @Override public void run() { scrollToCenter(childViewHalfCount); } }, 100L); } private List mDatas; private void initData() { if (mDatas == null) mDatas = new ArrayList<>(); for (int i = 0; i < 55; i++) { mDatas.add("CAR_Item" + i); } for (int j = 0; j < childViewHalfCount; j++) { //头部的空布局 mDatas.add(0, ""); } for (int k = 0; k < childViewHalfCount; k++) { //尾部的空布局 mDatas.add(""); } } private boolean isTouch = false; private List centerViewItems = new ArrayList<>(); private void findView() { mAdapter = new MAdapter(); recyclerView.setAdapter(mAdapter); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); if (newState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); int fi = linearLayoutManager.findFirstVisibleItemPosition(); int la = linearLayoutManager.findLastVisibleItemPosition();// int fi = linearLayoutManager.findFirstCompletelyVisibleItemPosition();// int la = linearLayoutManager.findLastCompletelyVisibleItemPosition(); Log.i("ccb", "onScrollStateChanged:首个item: " + fi + " 末尾item:" + la); if (isTouch) { isTouch = false; //获取最中间的Item View int centerPositionDiffer = (la - fi) / 2; int centerChildViewPosition = fi + centerPositionDiffer; //获取当前所有条目中中间的一个条目索引 centerViewItems.clear(); //遍历循环,获取到和中线相差最小的条目索引(精准查找最居中的条目) if (centerChildViewPosition != 0){ for (int i = centerChildViewPosition -1 ; i < centerChildViewPosition+2; i++) { View cView = recyclerView.getLayoutManager().findViewByPosition(i); int viewTop = cView.getTop()+(cView.getHeight()/2); centerViewItems.add(new CenterViewItem(i ,Math.abs(centerToTopDistance - viewTop))); } CenterViewItem centerViewItem = getMinDifferItem(centerViewItems); centerChildViewPosition = centerViewItem.position; } scrollToCenter(centerChildViewPosition);// centerChildViewPosition = centerChildViewPosition < childViewHalfCount ? childViewHalfCount : centerChildViewPosition;// centerChildViewPosition = centerChildViewPosition <= mAdapter.getItemCount() - childViewHalfCount -1 ? centerChildViewPosition : mAdapter.getItemCount() - childViewHalfCount -1;// View childView = recyclerView.getLayoutManager().findViewByPosition(centerChildViewPosition);// Log.i("ccb", "滑动后中间View的索引: " + centerChildViewPosition);//// //把当前View移动到居中位置// if (childView == null) return;// int childVhalf = childView.getHeight() / 2;// int childViewTop = childView.getTop();// int viewCTop = centerToTopDistance;// int smoothDistance = childViewTop - viewCTop + childVhalf;// Log.i("ccb", "居中位置距离顶部距离: " + viewCTop + "当前居中控件距离顶部距离: " + childViewTop);// Log.i("ccb", "滑动后再次移动距离: " + smoothDistance);// recyclerView.smoothScrollBy(0, smoothDistance);// Toast.makeText(AutoSelectActivity.this, "滑动后选中:" + mDatas.get(centerChildViewPosition), Toast.LENGTH_SHORT).show();// mAdapter.setSelectPosition(centerChildViewPosition); } } } @Override public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); for (int i = 0; i < recyclerView.getChildCount(); i++) { recyclerView.getChildAt(i).invalidate(); } } }); recyclerView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { isTouch = true; return false; } }); } /** * 移动指定索引到中心处 , 只可以移动可见区域的内容 * @param position */ private void scrollToCenter(int position){ position = position < childViewHalfCount ? childViewHalfCount : position; position = position < mAdapter.getItemCount() - childViewHalfCount -1 ? position : mAdapter.getItemCount() - childViewHalfCount -1; LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); View childView = linearLayoutManager.findViewByPosition(position); Log.i("ccb", "滑动后中间View的索引: " + position); //把当前View移动到居中位置 if (childView == null) return; int childVhalf = childView.getHeight() / 2; int childViewTop = childView.getTop(); int viewCTop = centerToTopDistance; int smoothDistance = childViewTop - viewCTop + childVhalf; Log.i("ccb", "\n居中位置距离顶部距离: " + viewCTop + "\n当前居中控件距离顶部距离: " + childViewTop + "\n当前居中控件的一半高度: " + childVhalf + "\n滑动后再次移动距离: " + smoothDistance); recyclerView.smoothScrollBy(0, smoothDistance,null,500); mAdapter.setSelectPosition(position); TUtils.show(AutoSelectActivity.this , "滑动后选中:" + mDatas.get(position)); } class MAdapter extends RecyclerView.Adapter { @NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return new VH(LayoutInflater.from(AutoSelectActivity.this).inflate(R.layout.item_auto_select, parent, false)); } @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { VH vh = (VH) holder; if (selectPosition == position) { vh.tv.setTextColor(getResources().getColor(R.color.textSelect)); } else { vh.tv.setTextColor(getResources().getColor(R.color.colorText)); } vh.tv.setText(mDatas.get(position)); final int fp = position; vh.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { scrollToCenter(fp); Toast.makeText(AutoSelectActivity.this, "点击" + mDatas.get(fp), Toast.LENGTH_SHORT).show(); } }); } private int selectPosition = -1; public void setSelectPosition(int cposition) { selectPosition = cposition;// notifyItemChanged(cposition); notifyDataSetChanged(); } @Override public int getItemCount() { return mDatas.size(); } class VH extends RecyclerView.ViewHolder { public TextView tv; public VH(@NonNull View itemView) { super(itemView); tv = itemView.findViewById(R.id.tv); } } } /** * 计算距离中间最近的一个ItemView * @param itemHeights * @return */ private static CenterViewItem getMinDifferItem(List itemHeights){ CenterViewItem minItem = itemHeights.get(0); //默认第一个是最小差值 for (int i = 0; i < itemHeights.size(); i++) { //遍历获取最小差值 if (itemHeights.get(i).differ <= minItem.differ){ minItem = itemHeights.get(i); } } return minItem; } public static void main(String[] a){ CenterViewItem i = getMinDifferItem(Arrays.asList( new CenterViewItem(2 , 39) ,new CenterViewItem(3 , 3) ,new CenterViewItem(1 , 9) ,new CenterViewItem(4 , 449))); System.out.println("position:"+i.position+" height:"+i.differ); } static class CenterViewItem{ public CenterViewItem(int position, int differ) { this.position = position; //当前Item索引 this.differ = differ; //当前item和居中位置的差值 } public int position; public int differ; }}
更多相关文章
- android app -- 关于listview的几种用法(复用,不复用,半复用)解决ite
- 关于Android(安卓)draw中的画布的说明
- Android开发:APP引导页启动页小Demo(实例)
- Android(安卓)项目中常用的页面切换TableLayout+Fragment+ViewPa
- Android事件分发机制以及滑动冲突处理
- android ViewPager 实现点击小圆点切换页面 案例
- Android(安卓)RecyclerView 实现快速滑动
- Android基础控件(EditView、SeekBar等)的使用方法
- Android(安卓)自定义Spinner显示条目与下拉框的布局