Recycleview双列表联动与悬停

预览.png

前言
==

最近看了别人的一篇blog,也是实现recycleview的双列表联动,同时应用了MVP框架。于是就模仿写了一个类似的双列表联动与悬停。在MVP方面,我仿照的是官方的todo-mvp,感觉写得有点不伦不类了,这里就不详述,另外在实现需求方面,和那个大神相比,也做了许多改变,当然有些具体的难点我没想到,参照了他的思路,然后实现出来了。在开发中,也尝试了其他的方法:
1.在点击左边省份时,若右边的省份在下面不可见区域,会出现bug,后面会详述。
2.滑动右边城市区域是,想和左边的省份联动,当时也用了其他方法,但是失败了,后面也会具体讲到。
最后想说一点:看别人的代码要有耐心,刚开始看的时候也是一脸懵逼,后面静下心来慢慢看,看到后面就觉得很简单了。多看,多写,多尝试,多思考。

实现的功能

先直接上一个效果图:

show.gif

然后再来分析实现的功能:
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

实现过程

实现过程也按照思路来

1.加载左边城市的数据:

通过RecycleView加载左边的省份,省份是在一个String-array下定义的,然后获取资源,通过适配器加载出来;

  String [] province=getResources().getStringArray(R.array.province);//获取省份
        final List<String> 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);

2.加载右边城市数据:

通过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;i<province.length;i++)
            {
                CityBean titleBean=new CityBean();
                titleBean.setProvince(province[i]);
                titleBean.setTitle(true);//设置为title
                titleBean.setTag(String.valueOf(i));//设置tag,方便获取position
                list.add(titleBean);

                for (int j=0;j<citylist.get(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;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 (moveCounts<firstItem)
        {
            recyclerView.scrollToPosition(moveCounts);
        }
        else if (moveCounts<lastItem)
        {
            View aimsView=recyclerView.getChildAt(moveCounts-firstItem);
            int 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&&n<recyclerView.getChildCount())
                {
                    int 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);
        }

最后我们调用一个接口回调方法,让左边的省份背景改变。

小bug

由于右边滑动的时候左边省份背景改变了,但是不在可见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<itemPosition&&itemPosition<manager.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来一个star吧。

最后是github地址

https://github.com/Simon986793021/WindLinkRecycleView

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,825评论 4 377
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,887评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,425评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,801评论 0 224
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,252评论 3 299
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,089评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,216评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 31,005评论 0 215
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,747评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,883评论 2 255
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,354评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,694评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,406评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,222评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,242评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,017评论 2 281

推荐阅读更多精彩内容