仿房產(chǎn)銷冠 APP 銷控表界面-多 RecyclerView 同步滾動

支持原作者 | CSDN_LQR
地址 | http://www.reibang.com/p/ed7fce51e5b5

最近在做一個地產(chǎn)項目,其實之前做出了一版忿薇,但現(xiàn)在要求重做(連上架的機(jī)會都沒有),很服氣啊~~而現(xiàn)在做的項目呢峡碉,比上一版功能要求更多,其中窄刘,銷控表的界面效果要求跟房產(chǎn)銷冠 APP 的銷控表界面差不多阱驾,先來看下房產(chǎn)銷冠 APP 的銷控表效果吧:

說說我第一次看到這個界面效果時的感覺就谜,就一個詞:amazing~ 是的,公司就我一個人做安卓開發(fā)里覆,感覺有點壓力山大丧荐,但是,不慫喧枷,靜下心來分析一下就明朗多了篮奄。先說說本文核心技術(shù)重點:兩個 RecyclerView 同步滾動。好割去,下面進(jìn)入正文窟却。

分析

1、布局分析
我認(rèn)為的布局實現(xiàn):將銷控表分為左右兩部分:左邊是樓層列表呻逆,右邊是單元(房間)列表夸赫。樓層列表就是一個簡單的LinearLayout+TextView+RecyclerView,單元(房間)列表則有點小復(fù)雜(HorizontalScrollView咖城、LinearLayout)+TextView+RecyclerView茬腿。為了各位看客能直觀理解,我特意做了張圖宜雀,請看:

其中黃色區(qū)域就是銷控表的部分切平。

2、效果分析
當(dāng)左邊的樓層列表上下滑動時辐董,右邊的單元(房間)列表也跟著一起滑動悴品,單元(房間)列表上的單元編號不動。

當(dāng)右邊的單元(房間)列表上下滑動時简烘,左邊的樓層列表也跟著一起滑動苔严,單元(房間)列表上的單元編號不動。

當(dāng)右邊的單元(房間)列表左右滑動時孤澎,單元(房間)列表上的單元編號一起左右滑動届氢,左邊的樓層列表不動。

那么覆旭,要實現(xiàn)1退子、2的效果岖妄,可以監(jiān)聽這兩個列表的滾動,當(dāng)其中一個列表滾動時寂祥,讓另一個列表滾動相同的距離即可衣吠。要實現(xiàn)3的效果就簡單了,因為HorizontalScrollView 中嵌套 RecyclerView 并沒有滾動沖突壤靶,HorizontalScrollView 處理水平滑動事件缚俏,RecyclerView 處理豎直滾動事件,所以暫時不用理(后面還是要做點簡單處理的)贮乳。

實現(xiàn)

1忧换、布局
上面已經(jīng)分析出了布局結(jié)構(gòu),下面直接貼布局代碼:

<?xml version="1.0" encoding="utf-8"?><LinearLayout   xmlns:android="http://schemas.android.com/apk/res/android"   android:layout_width="match_parent"   android:layout_height="match_parent"   android:background="#f5f5f5"   android:orientation="vertical">   <android.support.design.widget.AppBarLayout       android:layout_width="match_parent"       android:layout_height="wrap_content">       <android.support.v7.widget.Toolbar           android:id="@+id/toolbar"           android:layout_width="match_parent"           android:layout_height="wrap_content">           <TextView               android:layout_width="wrap_content"               android:layout_height="wrap_content"               android:layout_gravity="center"               android:text="銷控表"               android:textColor="#000"               android:textSize="16sp"/>           <TextView               android:layout_width="wrap_content"               android:layout_height="wrap_content"               android:layout_gravity="center_vertical|right"               android:layout_marginRight="10dp"               android:text="統(tǒng)計"               android:textColor="#000"               android:textSize="12sp"/>       </android.support.v7.widget.Toolbar>   </android.support.design.widget.AppBarLayout>   <TextView       android:layout_width="match_parent"       android:layout_height="wrap_content"       android:background="#fff"       android:gravity="center"       android:padding="10dp"       android:text="CSDN_LQR的私人后宮-項目1期-1棟"       android:textColor="#333"       android:textSize="10sp"/>   <LinearLayout       android:layout_width="match_parent"       android:layout_height="match_parent"       android:layout_marginTop="10px"       android:orientation="horizontal">       <!--樓層-->       <LinearLayout           android:layout_width="60dp"           android:layout_height="wrap_content"           android:orientation="vertical">           <TextView               android:layout_width="match_parent"               android:layout_height="50dp"               android:background="#fff"               android:gravity="center"               android:padding="10dp"               android:text="樓層
單元"               android:textSize="12sp"/>           <android.support.v7.widget.RecyclerView               android:id="@+id/rv_layer"               android:layout_width="match_parent"               android:layout_height="match_parent"               android:layout_marginTop="1dp"/>       </LinearLayout>       <!--單元(房間)-->       <HorizontalScrollView           android:layout_width="match_parent"           android:layout_height="match_parent"           android:layout_marginLeft="4dp"           android:fillViewport="true"           android:scrollbars="none">           <LinearLayout               android:layout_width="match_parent"               android:layout_height="wrap_content"               android:orientation="vertical">               <TextView                   android:layout_width="match_parent"                   android:layout_height="50dp"                   android:background="#fff"                   android:gravity="center"                   android:padding="10dp"                   android:text="3"                   android:textSize="12sp"/>               <android.support.v7.widget.RecyclerView                   android:id="@+id/rv_room"                   android:layout_width="match_parent"                   android:layout_height="match_parent"                   android:layout_marginTop="1dp"/>           </LinearLayout>       </HorizontalScrollView>   </LinearLayout></LinearLayout>

再通過列表的數(shù)據(jù)進(jìn)行填充(這部分不是重點就不貼出來了)向拆,效果就出來了:

接下來就是實現(xiàn)同步滾動效果了亚茬。

2、多RecyclerView同步滾動實現(xiàn)
一個大體的思路就是分別對其中一個列表設(shè)置滾動監(jiān)聽浓恳,當(dāng)這個列表滾動時刹缝,讓另一個列表也一起滾動。
但細(xì)節(jié)上要考慮到颈将,這種監(jiān)聽是雙向的梢夯,A 列表滾動時觸發(fā)其滾動回調(diào)接口,導(dǎo)致B列表滾動晴圾,而此時 B 列表也已經(jīng)設(shè)置過滾動監(jiān)聽颂砸,它的滾動也會觸發(fā)它的滾動回調(diào)接口,導(dǎo)致A列表滾動死姚,這樣就形成了一個死循環(huán)人乓。所以適當(dāng)添加或移除滾動監(jiān)聽是本功能實現(xiàn)的重難點,下面直接貼出代碼都毒,請自行結(jié)合代碼及注釋理解色罚。

1)封裝一個可以自行取消監(jiān)聽的滾動回調(diào)接口


這樣的封裝使我們不用在其他地方考慮列表空閑狀態(tài)時的處理,會省去很多事账劲。

/*** @創(chuàng)建者 CSDN_LQR* @描述 實現(xiàn)一個RecyclerView.OnScrollListener的子類戳护,當(dāng)RecyclerView空閑時取消自身的滾動監(jiān)聽*/public class MyOnScrollListener extends RecyclerView.OnScrollListener { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); if (newState == recyclerView.SCROLL_STATE_IDLE) { recyclerView.removeOnScrollListener(this); } }}

2)為樓層列表控件設(shè)置滾動監(jiān)聽
以下兩段代碼涉及兩個列表滾動同步和添加或移除滾動監(jiān)聽的時機(jī),具體代碼及注釋我已經(jīng)寫得很清楚了涤垫,請仔細(xì)看:

private final RecyclerView.OnScrollListener mLayerOSL = new MyOnScrollListener() {   @Override   public void onScrolled(RecyclerView recyclerView, int dx, int dy) {       super.onScrolled(recyclerView, dx, dy);       // 當(dāng)樓層列表滑動時姑尺,單元(房間)列表也滑動       mRvRoom.scrollBy(dx, dy);   }};/*** 設(shè)置兩個列表的同步滾動*/private void setSyncScrollListener() {   mRvLayer.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {       private int mLastY;       @Override       public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {           // 當(dāng)列表是空閑狀態(tài)時           if (rv.getScrollState() == RecyclerView.SCROLL_STATE_IDLE) {               onTouchEvent(rv, e);           }           return false;       }       @Override       public void onTouchEvent(RecyclerView rv, MotionEvent e) {           // 若是手指按下的動作竟终,且另一個列表處于空閑狀態(tài)           if (e.getAction() == MotionEvent.ACTION_DOWN && mRvRoom.getScrollState() == RecyclerView.SCROLL_STATE_IDLE) {               // 記錄當(dāng)前另一個列表的y坐標(biāo)并對當(dāng)前列表設(shè)置滾動監(jiān)聽               mLastY = rv.getScrollY();               rv.addOnScrollListener(mLayerOSL);           } else {               // 若當(dāng)前列表原地抬起手指時蝠猬,移除當(dāng)前列表的滾動監(jiān)聽               if (e.getAction() == MotionEvent.ACTION_UP && rv.getScrollY() == mLastY) {                   rv.removeOnScrollListener(mLayerOSL);               }           }       }       @Override       public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {       }   });   ...}

3)為單元(房間)列表設(shè)置滾動監(jiān)聽
對于單元(房間)列表滾動監(jiān)聽的設(shè)置,跟前面一樣统捶,我就順便寫一下好了榆芦。

private final RecyclerView.OnScrollListener mRoomOSL = new MyOnScrollListener() {   @Override   public void onScrolled(RecyclerView recyclerView, int dx, int dy) {       super.onScrolled(recyclerView, dx, dy);       // 當(dāng)單元(房間)列表滑動時柄粹,樓層列表也滑動       mRvLayer.scrollBy(dx, dy);   }};/*** 設(shè)置兩個列表的同步滾動*/private void setSyncScrollListener() {   ...   mRvRoom.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {       private int mLastY;       @Override       public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {           if (rv.getScrollState() == RecyclerView.SCROLL_STATE_IDLE) {               onTouchEvent(rv, e);           }           return false;       }       @Override       public void onTouchEvent(RecyclerView rv, MotionEvent e) {           if (e.getAction() == MotionEvent.ACTION_DOWN && mRvLayer.getScrollState() == RecyclerView.SCROLL_STATE_IDLE) {               mLastY = rv.getScrollY();               rv.addOnScrollListener(mRoomOSL);           } else {               if (e.getAction() == MotionEvent.ACTION_UP && rv.getScrollY() == mLastY) {                   rv.removeOnScrollListener(mRoomOSL);               }           }       }       @Override       public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {       }   });}

好了,到這里同步滾動效果就實現(xiàn)了匆绣,先看看效果驻右。

3、處理水平滾動列表事件
在上圖中崎淳,我們可以看到 堪夭,同步滾動效果確實是實現(xiàn)了,但有個問題拣凹,只要一水平滾動后森爽,再來滾動左邊的樓層列表時程序就會崩潰,若是滾動右邊的單元(房間)列表則會滾動不同步嚣镜,會造成這種情況是因為爬迟,當(dāng)水平滾動是時,事件被 HorizontalScrollView 處理了菊匿,導(dǎo)致右邊的單元(房間)列表的滾動監(jiān)聽沒有被移除付呕。

當(dāng)我們?nèi)L動左邊的樓層列表時,會為其設(shè)置滾動監(jiān)聽跌捆,這時這兩個列表都存在滾動監(jiān)聽徽职,所以就造成了監(jiān)聽的遞歸調(diào)用(死循環(huán)),于是內(nèi)存就妥妥的溢出了佩厚。下面是錯誤提示:

所以活箕,解決的方法就是,當(dāng) HorizontalScrollView 處理水平滾動事件時可款,取消列表的滾動監(jiān)聽育韩,而 ScrollView 本身不支持滾動監(jiān)聽,所以需要重新HorizontalScrollView闺鲸,向外提供滾動監(jiān)聽功能筋讨。自定義HorizontalScrollView 代碼如下:

/*** @創(chuàng)建者 CSDN_LQR* @描述 自定義HorizontalScrollView,向外提供滑動監(jiān)聽功能*/public class ObservableHorizontalScrollView extends HorizontalScrollView {   private ScrollViewListener scrollViewListener = null;   public ObservableHorizontalScrollView(Context context) {       super(context);   }   public ObservableHorizontalScrollView(Context context, AttributeSet attrs,                                         int defStyle) {       super(context, attrs, defStyle);   }   public ObservableHorizontalScrollView(Context context, AttributeSet attrs) {       super(context, attrs);   }   public void setScrollViewListener(ScrollViewListener scrollViewListener) {       this.scrollViewListener = scrollViewListener;   }   @Override   protected void onScrollChanged(int x, int y, int oldx, int oldy) {       super.onScrollChanged(x, y, oldx, oldy);       if (scrollViewListener != null) {           scrollViewListener.onScrollChanged(this, x, y, oldx, oldy);       }   }   public interface ScrollViewListener {       void onScrollChanged(ObservableHorizontalScrollView scrollView, int x, int y, int oldx, int oldy);   }}

接著就是替換代碼中的 HorizontalScrollView 控件

<!--單元(房間)--><com.lqr.topsales.ObservableHorizontalScrollView   android:layout_width="match_parent"   android:layout_height="match_parent"   android:layout_marginLeft="4dp"   android:fillViewport="true"   android:scrollbars="none">   <LinearLayout       android:layout_width="match_parent"       android:layout_height="wrap_content"       android:orientation="vertical">       <TextView           android:layout_width="match_parent"           android:layout_height="50dp"           android:background="#fff"           android:gravity="center"           android:padding="10dp"           android:text="3"           android:textSize="12sp"/>       <android.support.v7.widget.RecyclerView           android:id="@+id/rv_room"           android:layout_width="match_parent"           android:layout_height="match_parent"           android:layout_marginTop="1dp"/>   </LinearLayout></com.lqr.topsales.ObservableHorizontalScrollView>...

在代碼中監(jiān)聽 HorizontalScrollView 滾動摸恍,當(dāng)其滾動時悉罕,移除列表控件的移動監(jiān)聽事件:

mSvRoom.setScrollViewListener(new ObservableHorizontalScrollView.ScrollViewListener() {   @Override   public void onScrollChanged(ObservableHorizontalScrollView scrollView, int x, int y, int oldx, int oldy) {       mRvLayer.removeOnScrollListener(mLayerOSL);       mRvRoom.removeOnScrollListener(mRoomOSL);   }});

再來試試效果:

demo 地址

TopsalesSellControlTableDemo
https://github.com/GitLqr/TopsalesSellControlTableDemo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市立镶,隨后出現(xiàn)的幾起案子壁袄,更是在濱河造成了極大的恐慌,老刑警劉巖媚媒,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嗜逻,死亡現(xiàn)場離奇詭異,居然都是意外死亡缭召,警方通過查閱死者的電腦和手機(jī)栈顷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門逆日,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人萄凤,你說我怎么就攤上這事室抽。” “怎么了靡努?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵坪圾,是天一觀的道長。 經(jīng)常有香客問我惑朦,道長神年,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任行嗤,我火速辦了婚禮已日,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘栅屏。我一直安慰自己飘千,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布栈雳。 她就那樣靜靜地躺著护奈,像睡著了一般。 火紅的嫁衣襯著肌膚如雪哥纫。 梳的紋絲不亂的頭發(fā)上霉旗,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天,我揣著相機(jī)與錄音蛀骇,去河邊找鬼厌秒。 笑死,一個胖子當(dāng)著我的面吹牛擅憔,可吹牛的內(nèi)容都是我干的鸵闪。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼暑诸,長吁一口氣:“原來是場噩夢啊……” “哼蚌讼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起个榕,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤篡石,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后西采,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體凰萨,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了沟蔑。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片湿诊。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡狱杰,死狀恐怖瘦材,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情仿畸,我是刑警寧澤食棕,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站错沽,受9級特大地震影響簿晓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜千埃,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一憔儿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧放可,春花似錦谒臼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至冯挎,卻和暖如春底哥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背房官。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工趾徽, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人翰守。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓附较,卻偏偏與公主長得像,于是被迫代替她去往敵國和親潦俺。 傳聞我的和親對象是個殘疾皇子拒课,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,756評論 25 707
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 46,727評論 22 665
  • 又是呼呼的朔風(fēng),想來這已經(jīng)是第六個年頭了事示。 六年里早像,代王嘉每夜三更之后都會在這座三里小城的代城墻頭久久矗...
    一一語閱讀 368評論 0 0
  • 本文參加#青春不一YOUNG#征稿活動,本人承諾肖爵,文章內(nèi)容為原創(chuàng)卢鹦,且未在其他平臺發(fā)表過。 年少的資格 文/胖喵一九...
    九九九兮閱讀 503評論 8 11
  • 彼時白衣年少 舊鄉(xiāng)紅顏妙嬌 浣紗絳 牽柳梢 邀月做媒鵲做橋 往事如沙聚成島 回憶似汐迭作潮 一人酒醉昨宵 十年夢醒...
    寄文奴閱讀 206評論 0 0