最近看了别人的一篇blog,也是实现recycleview的双列表联动,同时应用了MVP框架。于是就模仿写了一个类似的双列表联动与悬停。在MVP方面,我仿照的是官方的todo-mvp,感觉写得有点不伦不类了,这里就不详述,另外在实现需求方面,和那个大神相比,也做了许多改变,当然有些具体的难点我没想到,参照了他的思路,然后实现出来了。在开发中,也尝试了其他的方法:
1.在点击左边省份时,若右边的省份在下面不可见区域,会出现bug,后面会详述。
2.滑动右边城市区域是,想和左边的省份联动,当时也用了其他方法,但是失败了,后面也会具体讲到。
最后想说一点:看别人的代码要有耐心,刚开始看的时候也是一脸懵逼,后面静下心来慢慢看,看到后面就觉得很简单了。多看,多写,多尝试,多思考。
先直接上一个效果图:
然后再来分析实现的功能:
1.点击左边省份,省份背景改变,右边顶部显示省份悬停,下面显示省份的城市;
2.滑动右边的城市,顶部省份悬停,左边随着省份的改变而改变;
3.所有控件可点击;
4.仿官方mvpdemo的mvp框架;
5.snackbar的使用;
6.recycleview的花式使用。
下面在来分析下实现的思路:
之前就说了思路是非常重要的,下面来详细说说如果有一个这种需求,改从何下手:
先看效果分析大概:
1:可以把左边和右边的布局各设置为一个Recycleview;
2:点击左边省份,右边需要跟着滑动,是不是可以计算需要滑动的距离,然后通过recycleview的方法进行指定滑动呢。只是一种猜想,实际比这复杂一点;
3:滑动右边的城市,然后左边省份需要跟着滑动,更改背景颜色,试想下,通过判断滑动的position,然后计算滑动到了哪个省份,进行改变呢。
接下来我再来详细说下实现的过程:
1.通过RecycleView加载左边的省份,省份是在一个String-array下定义的,然后获取资源,通过适配器加载出来;
2.通过RecycleView加载右边的数据,这个时候要注意了,因为左边数据和右边数据是对应的,所以我们通过遍历String-array下的省份,然后往不同的省份注入不同的城市,从效果可以看出来,我们右边不只是简单的布局,可以看出来上面有一个title(省份),下面是显示content(城市),我们通过在设置城市数据时候给一个isTitle来区分省份和城市,后面布局也通过isTitle来区分,我们可以看出右边省份是一行只有一个数据,而城市有三个数据。然后通过不同的布局显示出来。到这里,我们的将数据显示出来了。
3.点击省份,背景颜色改变,就是讲点击的item设置成你想要的颜色,没有点击的就是其他颜色了,通过position判断,右边的城市需要滑动,主要通过计算滑动的position。比较麻烦,后面具体讲。
4.滑动右边的数据,前面我们在加载城市数据的时候,我们将城市和省份通过一个tag进行了绑定,当我们滑动的时候,获取这个tag(position),让他与左边的position比较,不相同的话,就把tag赋值给position,有了这个position,我们就可以更改背景颜色了;
5.由于滑动的时候会出现bug,我们将左边选中的省份一直显示在屏幕中间。通过recycleView设置下就可以。
6.所有都可点击,recycleView的点击事件属于RecycleView的基础,不清楚的可以看我的一个demo:
https://github.com/Simon986793021/RecycleView
实现过程也按照思路来
通过RecycleView加载左边的省份,省份是在一个String-array下定义的,然后获取资源,通过适配器加载出来;
String [] province=getResources().getStringArray(R.array.province);//获取省份
final List list= Arrays.asList(province);
/*
适配数据和设置监听事件
*/
adapter=new ProvinceRvAdapter(this, list, new ItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Utils.showSnackBar(recycleview,list.get(position));
mposition=position;
startMove(position,true);
Log.i(">>>>>>","position:"+position);
moveToCenter(position);
}
});
recycleview.setAdapter(adapter);
通过RecycleView加载右边的数据,这个时候要注意了,因为左边数据和右边数据是对应的,所以我们通过遍历String-array下的省份,然后往不同的省份注入不同的城市,从效果可以看出来,我们右边不只是简单的布局,可以看出来上面有一个title(省份),下面是显示content(城市),我们通过在设置城市数据时候给一个isTitle来区分省份和城市,后面布局也通过isTitle来区分,我们可以看出右边省份是一行只有一个数据,而城市有三个数据。然后通过不同的布局显示出来。到这里,我们的将数据显示出来了。
1.先看下bean对象:
public class CityBean {
public String city;
public boolean isTitle;//判断是否为省份,来进行加载数据
public String province;
public String tag;//一个position,同时将城市与省份绑定
public void setTitle(boolean title)
{
isTitle=title;
}
public void setProvince (String province)
{
this.province=province;
}
public String getProvince()
{
return province;
}
public boolean isTitle()
{
return isTitle;
}
public void setCity(String city)
{
this.city=city;
}
public String getCity()
{
return city;
}
public void setTag(String tag)
{
this.tag=tag;
}
public String getTag()
{
return tag;
}
}
2.获取数据源:
通过遍历省份,给省份添加城市数据;
for (int i=0;inew CityBean();
titleBean.setProvince(province[i]);
titleBean.setTitle(true);//设置为title
titleBean.setTag(String.valueOf(i));//设置tag,方便获取position
list.add(titleBean);
for (int j=0;jget(i).length;j++)
{
CityBean cityBean=new CityBean();
cityBean.setCity(citylist.get(i)[j]);
cityBean.setTag(String.valueOf(i));//设置成和省份一样的tag,将省份与城市绑定。
list.add(cityBean);
}
}
3.通过设置的isTitle与否来加载数据:
int itemViewTtpe=CityRvAdapter.this.getItemViewType(position);
switch (itemViewTtpe)
{
case 0://省份
title.setText(list.get(position).getProvince());
break;
case 1://城市
city.setText(list.get(position).getCity());
break;
case 2:
break;
}
具体就不详细讲,代码中也有。
点击省份,背景颜色改变,就是讲点击的item设置成你想要的颜色,没有点击的就是其他颜色了,通过position判断,右边的城市需要滑动,主要通过计算滑动的position。
1.背景的改变:
获取点击的position,传到adapter中,然后进行判断,进行背景改变:
贴出关键代码:
if (position==clickPositon)
{
view.setBackgroundColor(Color.parseColor("#9EABF4"));
textView.setTextColor(Color.parseColor("#ffffff"));
}
else {
view.setBackgroundColor(Color.parseColor("#00FFFFFF"));//设置为透明的,因为白色会覆盖分割线
textView.setTextColor(Color.parseColor("#1e1d1d"));
}
textView.setText(s);
}
2.左边联动右边:
建议先看这篇blog,滑动定位的解决方案:
http://blog.csdn.net/tyzlmjj/article/details/49227601
通过计算需要滑动的距离来进行滑动
我们先计算需要滑动的position:
for (int i=0;i//position 为点击的position
{
Log.i("<<<<<<",i+":"+cityFragment.citylist.get(i).length);
counts+=cityFragment.citylist.get(i).length;//计算需要滑动的城市数目
}
if (isLeft)
{
cityFragment.setCounts(counts+position);//加上title(省份)数目
}
官方提供了两种滑动方案:
1.scrollToPosition(int)
滑动到指定的item
2.scrollBy(int x,int y)
滑动到指定的距离
一开始用的scrollToPosition(int),但是在实际开发终于到了问题
scrollToposition 只能将item显示出来,至于显示在哪里他就不管了,不过有一点可以肯定的,若item从不可见滑动到可见,一般会出现在最底部,而我们需要的是在最顶部,显然是不行的。我们可以通过用scrollToPosition()和scrollBy 结合使用。
我们可以将滑动分为三种情况:
第一种:从上往下滑动(目标item不可见),这种最复杂,需要scrollToPosition()和scrollBy 结合使用,监听scroll接口;
第二种:从上往下滑动(目标item可见),scrollBy就可以解决;
第三种:从下往上滑动,目标可见不可见一样,调用scrollToPosition()都会显示在顶部;
贴出具体代码与我尝试的其他方法(在注释中)
int firstItem=gridLayoutManager.findFirstVisibleItemPosition();//获取屏幕可见的第一个item的position
int lastItem=gridLayoutManager.findLastVisibleItemPosition();//获取屏幕可见的最后一个item的position
if (moveCountselse if (moveCountsint top =aimsView.getTop();
recyclerView.scrollBy(0,top);
}
else {
/*
当往下滑动的position大于可见的最后一个item的时候,调用 recyclerView.scrollToPosition(moveCounts);
只能讲item滑动到屏幕的底部。
*/
/*
第一种方案:先将item移动到底部,然后在调用scrollBy移动到顶部。不可行,不能讲item滑动到顶部,
离上面还有一小段距离;
recyclerView.scrollToPosition(moveCounts);
int top=recyclerView.getHeight();
recyclerView.scrollBy(0,top);
第二种方案:直接计算要滑动的距离。程序崩溃,报空指针。看系统源码可知,当
滑动的距离大于ChildCount(可见的item数目),将返回空。
int top=recyclerView.getChildAt(moveCounts-firstItem).getTop();
recyclerView.scrollBy(0,top);
第三种解决方案:先将目标item滑动到底部,然后进行异步处理。调用滚动监听方法RecyclerViewListener,滑动到顶部。
*/
// int top=recyclerView.getHeight();
// recyclerView.scrollBy(0,top);
// int childcount=recyclerView.getChildCount();
// Log.i("<<<<<<<<<<","childcount"+childcount);
// int top=recyclerView.getChildAt(moveCounts-firstItem).getTop();
// recyclerView.scrollBy(0,top);
recyclerView.scrollToPosition(moveCounts);
move=true;
}
监听回调,从底部滑动到顶部:
class RecyclerViewListener extends RecyclerView.OnScrollListener{
/*
监听回调,滑动结束回调。
*/
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
//在这里进行第二次滚动(最后的100米!)
if (move ){
move = false;
//获取要置顶的项在当前屏幕的位置,moveCount是记录的要置顶项在RecyclerView中的位置
int n = moveCounts - gridLayoutManager.findFirstVisibleItemPosition();
if ( 0 <= n && n < recyclerView.getChildCount()){
//获取要置顶的项顶部离RecyclerView顶部的距离
int top = recyclerView.getChildAt(n).getTop();
//最后的移动
recyclerView.scrollBy(0, top);
}
}
}
/*
监听回调,滑动状态改变回调
*/
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (move&&newState==RecyclerView.SCROLL_STATE_IDLE)
{
move=false;
int n=moveCounts-gridLayoutManager.findFirstVisibleItemPosition();
if (0<=n&&nint top=recyclerView.getChildAt(n).getTop();
recyclerView.scrollBy(0,top);
}
}
}
这样我们就讲左边联动右边完成了。
滑动右边的数据,前面我们在加载城市数据的时候,我们将城市和省份通过一个tag进行了绑定,当我们滑动的时候,获取这个tag(position),让他与左边的position比较,不相同的话,就把tag赋值给position,有了这个position,我们就可以更改背景颜色了;
贴出关键代码:
if (!TextUtils.equals(tag, currentTag)) {
currentTag = tag;
Log.i("zhangcong",currentTag);
Integer integer = Integer.valueOf(currentTag);
mCheckListener.check(integer, false);
}
最后我们调用一个接口回调方法,让左边的省份背景改变。
由于右边滑动的时候左边省份背景改变了,但是不在可见view中,我们将左边选中的省份一直显示在屏幕中间。通过recycleView设置下就可以。
代码:
//将当前选中的item居中
public void moveToCenter(int position) {
//将点击的position转换为当前屏幕上可见的item的位置以便于计算距离顶部的高度,从而进行移动居中
Log.i(">>>>>>>>>",position - manager.findFirstVisibleItemPosition()+"eeeee");
int itemPosition=position-manager.findFirstVisibleItemPosition();
/*
当往上滑动太快,会出现itemPosition为-1的情况。做下判断
*/
if (0.getChildCount())
{
View childAt = recycleview.getChildAt(position - manager.findFirstVisibleItemPosition());
Log.i("<<<<<<",position - manager.findFirstVisibleItemPosition()+"");
int y = (childAt.getTop() - recycleview.getHeight() / 2);
Log.i("<<<<<<",childAt.getTop()+"ssssss");
Log.i("<<<<<<", y+"");
recycleview.smoothScrollBy(0, y);
}
}
在做右边联动左边的时候,当时想着获取title的position,然后将position传过去,进行背景的改变,这种方法是可行的,但是体验非常的不好,因为当你滑动很快的时候,是获取不到中间经过的省份的position,就感觉不连贯,直接跳到了最后你滑动的位置,所以换了一种解决思路(如果你以为是我想出来的你就太年轻了)。
总结的话就没有,前面思路讲的已经够清楚了。最后给大家一点看代码的兴趣,这里面还缺少了许多的城市和省份,大家看懂了这些代码的话可以在里面加上自己的城市,或者加上图片展示功能;然后提交pull request,我这边会帮你们merge的(请在添加代码的地方加上你自己的注释 如: Simon add shanghai city on 20170727)。也非常欢迎大家在底下留言,探讨一些问题,当然有看不明白的也可以提出问题,我会尽力解答的。
最后是github地址
https://github.com/Simon986793021/WindLinkRecycleView
觉得有帮助的话给github来个star吧