开源通用弹窗目前增加了9种弹窗(7种类别),包括自定义、警告类、更新类、列表类、地区选择类。小萌新个人项目目前用到了警告类和列表类。地区选择弹窗后面项目升级也需要,所以就搞了搞。
Github FanChael/CommonPopupWindow
地区选择弹窗,三级联动效果:
实现思路:
1. 三个均分屏幕宽度的垂直Recycleview列表
2. 省列表采用List<String>参数,市列表根据省的名称获取HashMap<String, List<CityBean>>, 区列表根据市名称获取HashMap<String, List<String>>. 目前暂时就是这样的结构。采用json转对象的话,传递到内部,我的处理还是会处理为对应的结构。
另外实际垂直列表适配器需要的是List<String>。因此就有以下数据列表
3. 滑动定位的问题
3.1 首先滑动到某个pos采用scrollToPositionWithOffset,此时Rv会滑动该条目并且置顶。但是我们需要在中间那个位置
小萌新想的一个方案就是,列表的前后分别增加两个空字符串条目,总共就是四个。此时想想,第一个真的地区实际上是第2个,要保证第2个居中,那么就需要第1个置顶。如果需要第二个真的地区居中(此时第二个是第3个,从0开始哟),那么就是第二个置顶。
那么滑动后,如何计算当前应该是哪个居中?请看如下代码:
/**
* 定位item到指定的貌似中间的位置
*
* @param mRecyclerView
* @return
*/
private int changePos(RecyclerView mRecyclerView) {
LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
View firstVisibItem = mRecyclerView.getChildAt(0);
//int firstItemPosition = layoutManager.findFirstVisibleItemPosition();
int itemCount = layoutManager.getItemCount();
//int recycleViewHeight = recyclerView.getHeight();
int itemHeight = firstVisibItem.getHeight();
//int lastItemPosition = layoutManager.findLastVisibleItemPosition();
//int lastCItemPosition = layoutManager.findLastCompletelyVisibleItemPosition();
int scrollViewHeight = getDistance(mRecyclerView);
///< 滚动后需要停留的位置
///< 列表前后各加了两个空位置,第1个置顶刚好是我们默认需要停留的位置(此时第0个看不见了)
///< 条目高度假设是100, 那么第1个置顶,滚动的距离也scrollViewHeight = 100;
///< 再向上或者向下滑动距离不足150的话,还是第1个置顶,具体看图说话才好
int scrollPos = 1;
if (scrollViewHeight < (itemHeight + itemHeight / 2)) {
scrollPos = 1;
} else if (scrollViewHeight > itemHeight * (itemCount - 4)) {
scrollPos = itemCount - 4;
} else {
scrollPos = (scrollViewHeight + itemHeight / 2) / itemHeight;
}
((LinearLayoutManager) mRecyclerView.getLayoutManager()).scrollToPositionWithOffset(scrollPos, 0);
return scrollPos;
}
/**
* RecyclerView已滑动的距离
*
* @param mRecyclerView
* @return
*/
private int getDistance(RecyclerView mRecyclerView) {
LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
View firstVisibItem = mRecyclerView.getChildAt(0);
int firstItemPosition = layoutManager.findFirstVisibleItemPosition();
//int itemCount = layoutManager.getItemCount();
//int recycleViewHeight = mRecyclerView.getHeight();
int itemHeight = firstVisibItem.getHeight();
int firstItemBottom = layoutManager.getDecoratedBottom(firstVisibItem);
return (firstItemPosition + 1) * itemHeight - firstItemBottom;
}
其中置顶的条目位置计算如下(真正居中的条目则是改置顶条目的下一个条目):
先看个图,每个条目高度是100
1. 此时如果向上滑动了0 - 150的距离,此时居中的那个条目就应该回弹居中。 而上面一个条目则应该scrollToPositionWithOffset置顶,此时滚动的位置就是0。
2. 此时如果滚动额距离是150 - 250的话,此时中间条目已经滑动过半了。此时则最下面的条目应该居中,此时scrollToPositionWithOffset的位置应该是1,正好第二个置顶,第三个居中。
一次类推。。。
此时为了计算这个scrollToPositionWithOffset,统一先将0 - 150, 150 - 250等 加上一个50成为50 - 200(<200) 200 - 300, 300 - 400, 然后再除以100就可以得到整数1, 2, 3。
而针对上面的第2点,我们滚动了150 - 250 ,加了50成了200 - 300之间。除以100就是2, 貌似多了1. 这个就可以减去1. 但是,因为我们为了滚动前后列表增加了两个空条目,此时滚动如果是2,你想,滚2正好就是我们要居中的条目。 如果前后增加,正常逻辑即可。但是就没有那种空位置的视觉效果。你可以自己推演一下应该怎么计算该滚动多少。。
至于列表更新代码也贴下,主要是就是地区列表数据滑动选择、更新以及重置位置的处理,此外也保存了下相关值。方便传递...
/**
* 滚动监听并定位滑动item居中
*/
private class MyOnSrollListenner extends RecyclerView.OnScrollListener {
private int type = 0;
MyOnSrollListenner(int type) {
this.type = type;
}
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (0 == newState) {
int scrollPos = changePos(recyclerView);
if (0 == type) { ///< 滑动省
currentProvince = provinceData.get(scrollPos + 1);
///< 更新市
cityData.clear();
cityData.addAll(cityList.get(currentProvince));
cityAdapter.notifyDataSetChanged();
((LinearLayoutManager) cityRv.getLayoutManager()).scrollToPositionWithOffset(1, 0);
currentCity = cityData.get(2);
///< 更新区
districtData.clear();
districtData.addAll(districtList.get(currentCity));
districtAdapter.notifyDataSetChanged();
((LinearLayoutManager) districtRv.getLayoutManager()).scrollToPositionWithOffset(1, 0);
currentDistrict = districtData.get(2);
} else if (1 == type) { ///< 滑动市
currentCity = cityData.get(scrollPos + 1);
///< 更新区
districtData.clear();
districtData.addAll(districtList.get(currentCity));
districtAdapter.notifyDataSetChanged();
((LinearLayoutManager) districtRv.getLayoutManager()).scrollToPositionWithOffset(1, 0);
currentDistrict = districtData.get(2);
} else if (2 == type) { ///< 滑动区
currentDistrict = districtData.get(scrollPos + 1);
}
}
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
}
重点是逻辑。本身滚动还好。我看了下好多实现貌似像这样的效果,具体是不是这个逻辑不太清楚呀。 我看了下京东的地区选择,后面空了可以搞搞。感觉实现不难,处理下逻辑应该就ojbk了。 不准说脏话,要开心哟!