这篇主要是列表滑动后停止后,自动选中居中的条目,类似于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;
}
}