1. 程式人生 > >RecycleView實現MVP框架下的雙列表聯動與懸停

RecycleView實現MVP框架下的雙列表聯動與懸停

前言

最近看了別人的一篇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:

實現過程

實現過程也按照思路來

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.左邊聯動右邊:

通過計算需要滑動的距離來進行滑動

我們先計算需要滑動的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地址

覺得有幫助的話給github來個star吧

相關推薦

RecycleView實現MVP框架列表聯動懸停

前言 最近看了別人的一篇blog,也是實現recycleview的雙列表聯動,同時應用了MVP框架。於是就模仿寫了一個類似的雙列表聯動與懸停。在MVP方面,我仿照的是官方的todo-mvp,感覺寫得有點不倫不類了,這裡就不詳述,另外在實現需求方面,和那個大神相

easyui中實現多個列表聯動

先看下原始碼 <script type="text/javascript" charset="UTF-8"> $(function() { var provinceId = $('#provinceId').combobox({ url : 'p

文本框實現列表聯動

function on() ole -1 nbsp val edit end col   昨天做了一個文本框過濾下拉列表選項的東西,技術不復雜,記錄下來一遍提高再使用效率。    $(‘#searchOption‘).keyup(function(){

JAVA EE 專案常用知識 之AJAX技術實現select列表聯動的兩種用法(讓你真正理解ajax)

ajax 下拉列表聯動的用法。 ajax的定義: AJAX 是一種用於建立快速動態網頁的技術。 通過在後臺與伺服器進行少量資料交換,AJAX 可以使網頁實現非同步更新。這意味著可以在不重新載入整個網頁的情況下,對網頁的某部分進行更新。 ajax效果的一個例子: 區域為

java web 列表聯動實現

很多時候在介面佈局時我們會用到下拉列表,單獨的一個列表與資料庫的互動很簡單 <select> <option value="">--請選擇--</option> <c:forEach items="${system

AJAX筆記-實現城市列表聯動

    學習一門新技術不外乎掌握四個點(what、how、where、why)! 一、什麼是AJAX?(what) 首先要了解什麼是同步?什麼是非同步的問題。        同步就是當瀏覽器提交請求到伺服器時,伺服器必須要響應瀏覽器,這樣瀏覽器才能繼續傳送請求。簡單來說就

idea中實現struts2框架國際化

一、輸出國際化資訊 1.需要更改idea設定 File–>settings–>Editer–>File Encodings 在介面的右下角勾選Transparent native

Ajax提高篇(7)Ajax實現簡單的拉框聯動顯示資料

頁面中的兩個下拉列表框:<tr> <td style="width: 130px"> 所在學院:</td> <td styl

Ajax+SSM實現四級聯列表

function catalogABC(){ var object = $("#catalog").val(); $("#second").empty(); if(object !=null){ $.ajax({ url : "

原生js實現對select列表的內容過濾

function selectOnchange(){ // alert("下拉內容改變了") // targetChangeFunction(obj) } <!-

PopupView實現兩級列表

主要實現思想是定義兩個textView作為下拉列表的點選入口,將PopupView作為下拉的列表,在點選的時候開啟PopupView。 首先在佈局檔案中定義兩個TextView,如下所示: <LinearLayout android:id="@+id/start_l

列表聯動總結(AJAX列表聯動簡單示例)

最近發現CSDN上有很多人在詢問 有關聯動的下拉列表的問題,所以在此做以總結,希望還有新的實現方法,這裡介紹三中實現方法,包括一個簡單的AJAX版的聯動.           下拉列表聯動有很多方法可以實現:           1.JavaScript實現,JS實現

extjs列表 聯動

原文地址:http://stargazer0926.blog.163.com/blog/static/882362562010816112357187/ 概述:  RadioGroup內含多個Radio,每個Radio.name必須相同,選中事件RadioGroup不提供

SSH 框架圖片上傳顯示

這幾天在弄畢設,有幾個需求是要上傳圖片的,看了很多教程,各有各的說法,關鍵點、細節很多說不明白,自己倒騰了幾天,弄出來了,給大家分享一下。上傳圖片的JSP<%@ page language="java" import="java.util.*" pageEncoding

使用JavaScript實現列表聯動

在設計使用者註冊頁面時經常有籍貫等資訊要填寫,填寫這些資訊一般都是先選擇國家->省->市->鎮。 此篇主要講述,當第一個下拉列表中的內容選定之後第二個下拉列表中的內容也隨之改變(例如:當你第一個下拉列表選擇江西時,第二個下拉列表會顯示南昌贛州等城市;當你第

省市區三級聯動選單實現及三級聯動列表框預設值的設定

以下是HTML程式碼<html> <body> <label for="select_city">城市</label> <div data-role="controlgroup" id="select_p_c_a" data-ajax="false"&g

Android實現三級聯動拉框 列表spinner

布局 idt ets county wid 分享圖片 n) 技術分享 parent 主要實現辦法:動態加載各級下拉值的適配器 在監聽本級下拉框,當本級下拉框的選中值改變時,隨之修改下級的適配器的綁定值 XML布局:

Java基於POI實現excel任意多級聯動列表——支援從資料庫查詢出多級資料後直接生成【附原始碼】

 Excel相關知識點 (1)名稱管理器——Name Manager 【CoderBaby】首先需要建立多個名稱(包含key及value),作為下拉列表的資料來源,供後續通過名稱引用。可通過選單:“公式”---“名稱管理器&rdquo

列表,點擊選擇實現跳轉鏈接 onchange="window.location=..."

this option 用戶 onchange ... tex 註意 下拉 win <select onchange="window.location=this.value;">  <option value="a.html">用戶管理</op

(轉)基於MVC4+EasyUI的Web開發框架經驗總結(6)--在頁面中應用列表的處理

ica new web開發 don ext images 如果 bob 獲取 http://www.cnblogs.com/wuhuacong/p/3840321.html 在很多Web界面中,我們都可以看到很多下拉列表的元素,有些是固定的,有些是動態的;有些是字典內容,