前言
==
最近看了別人的一篇blog,也是實現(xiàn)recycleview的雙列表聯(lián)動钧椰,同時應用了MVP框架到千。于是就模仿寫了一個類似的雙列表聯(lián)動與懸停。在MVP方面齐邦,我仿照的是官方的todo-mvp椎侠,感覺寫得有點不倫不類了,這里就不詳述措拇,另外在實現(xiàn)需求方面我纪,和那個大神相比,也做了許多改變丐吓,當然有些具體的難點我沒想到浅悉,參照了他的思路,然后實現(xiàn)出來了券犁。在開發(fā)中术健,也嘗試了其他的方法:
1.在點擊左邊省份時,若右邊的省份在下面不可見區(qū)域粘衬,會出現(xiàn)bug荞估,后面會詳述。
2.滑動右邊城市區(qū)域是稚新,想和左邊的省份聯(lián)動勘伺,當時也用了其他方法,但是失敗了褂删,后面也會具體講到飞醉。
最后想說一點:看別人的代碼要有耐心,剛開始看的時候也是一臉懵逼屯阀,后面靜下心來慢慢看缅帘,看到后面就覺得很簡單了轴术。多看,多寫钦无,多嘗試逗栽,多思考。
實現(xiàn)的功能
先直接上一個效果圖:
然后再來分析實現(xiàn)的功能:
1.點擊左邊省份铃诬,省份背景改變祭陷,右邊頂部顯示省份懸停,下面顯示省份的城市趣席;
2.滑動右邊的城市兵志,頂部省份懸停,左邊隨著省份的改變而改變宣肚;
3.所有控件可點擊想罕;
4.仿官方mvpdemo的mvp框架;
5.snackbar的使用霉涨;
6.recycleview的花式使用按价。
下面在來分析下實現(xiàn)的思路:
思路
之前就說了思路是非常重要的,下面來詳細說說如果有一個這種需求笙瑟,改從何下手:
先看效果分析大概:
1:可以把左邊和右邊的布局各設置為一個Recycleview楼镐;
2:點擊左邊省份,右邊需要跟著滑動往枷,是不是可以計算需要滑動的距離框产,然后通過recycleview的方法進行指定滑動呢。只是一種猜想错洁,實際比這復雜一點秉宿;
3:滑動右邊的城市,然后左邊省份需要跟著滑動屯碴,更改背景顏色描睦,試想下,通過判斷滑動的position导而,然后計算滑動到了哪個省份忱叭,進行改變呢。
接下來我再來詳細說下實現(xiàn)的過程:
1.通過RecycleView加載左邊的省份今艺,省份是在一個String-array下定義的窑多,然后獲取資源,通過適配器加載出來洼滚;
2.通過RecycleView加載右邊的數(shù)據(jù),這個時候要注意了技潘,因為左邊數(shù)據(jù)和右邊數(shù)據(jù)是對應的遥巴,所以我們通過遍歷String-array下的省份千康,然后往不同的省份注入不同的城市,從效果可以看出來铲掐,我們右邊不只是簡單的布局拾弃,可以看出來上面有一個title(省份),下面是顯示content(城市)摆霉,我們通過在設置城市數(shù)據(jù)時候給一個isTitle來區(qū)分省份和城市豪椿,后面布局也通過isTitle來區(qū)分,我們可以看出右邊省份是一行只有一個數(shù)據(jù)携栋,而城市有三個數(shù)據(jù)搭盾。然后通過不同的布局顯示出來。到這里婉支,我們的將數(shù)據(jù)顯示出來了鸯隅。
3.點擊省份,背景顏色改變向挖,就是講點擊的item設置成你想要的顏色蝌以,沒有點擊的就是其他顏色了,通過position判斷何之,右邊的城市需要滑動跟畅,主要通過計算滑動的position。比較麻煩溶推,后面具體講徊件。
4.滑動右邊的數(shù)據(jù),前面我們在加載城市數(shù)據(jù)的時候悼潭,我們將城市和省份通過一個tag進行了綁定庇忌,當我們滑動的時候,獲取這個tag(position),讓他與左邊的position比較舰褪,不相同的話皆疹,就把tag賦值給position,有了這個position占拍,我們就可以更改背景顏色了略就;
5.由于滑動的時候會出現(xiàn)bug,我們將左邊選中的省份一直顯示在屏幕中間晃酒。通過recycleView設置下就可以表牢。
6.所有都可點擊,recycleView的點擊事件屬于RecycleView的基礎贝次,不清楚的可以看我的一個demo:
https://github.com/Simon986793021/RecycleView
實現(xiàn)過程
實現(xiàn)過程也按照思路來
1.加載左邊城市的數(shù)據(jù):
通過RecycleView加載左邊的省份崔兴,省份是在一個String-array下定義的,然后獲取資源,通過適配器加載出來敲茄;
String [] province=getResources().getStringArray(R.array.province);//獲取省份
final List<String> list= Arrays.asList(province);
/*
適配數(shù)據(jù)和設置監(jiān)聽事件
*/
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.加載右邊城市數(shù)據(jù):
通過RecycleView加載右邊的數(shù)據(jù)位谋,這個時候要注意了,因為左邊數(shù)據(jù)和右邊數(shù)據(jù)是對應的堰燎,所以我們通過遍歷String-array下的省份掏父,然后往不同的省份注入不同的城市,從效果可以看出來秆剪,我們右邊不只是簡單的布局赊淑,可以看出來上面有一個title(省份),下面是顯示content(城市)仅讽,我們通過在設置城市數(shù)據(jù)時候給一個isTitle來區(qū)分省份和城市陶缺,后面布局也通過isTitle來區(qū)分,我們可以看出右邊省份是一行只有一個數(shù)據(jù)何什,而城市有三個數(shù)據(jù)组哩。然后通過不同的布局顯示出來。到這里处渣,我們的將數(shù)據(jù)顯示出來了伶贰。
1.先看下bean對象:
public class CityBean {
public String city;
public boolean isTitle;//判斷是否為省份,來進行加載數(shù)據(jù)
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.獲取數(shù)據(jù)源:
通過遍歷省份黍衙,給省份添加城市數(shù)據(jù);
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與否來加載數(shù)據(jù):
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;
}
具體就不詳細講柑贞,代碼中也有方椎。
左邊聯(lián)動右邊(省份聯(lián)動城市)
點擊省份,背景顏色改變钧嘶,就是講點擊的item設置成你想要的顏色棠众,沒有點擊的就是其他顏色了,通過position判斷有决,右邊的城市需要滑動闸拿,主要通過計算滑動的position。
1.背景的改變:
獲取點擊的position书幕,傳到adapter中新荤,然后進行判斷,進行背景改變:
貼出關(guān)鍵代碼:
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.左邊聯(lián)動右邊:
建議先看這篇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;//計算需要滑動的城市數(shù)目
}
if (isLeft)
{
cityFragment.setCounts(counts+position);//加上title(省份)數(shù)目
}
官方提供了兩種滑動方案:
1.scrollToPosition(int)
滑動到指定的item
2.scrollBy(int x,int y)
滑動到指定的距離
一開始用的scrollToPosition(int)篱瞎,但是在實際開發(fā)終于到了問題
scrollToposition 只能將item顯示出來,至于顯示在哪里他就不管了智袭,不過有一點可以肯定的奔缠,若item從不可見滑動到可見,一般會出現(xiàn)在最底部吼野,而我們需要的是在最頂部,顯然是不行的两波。我們可以通過用scrollToPosition()和scrollBy 結(jié)合使用瞳步。
我們可以將滑動分為三種情況:
第一種:從上往下滑動(目標item不可見),這種最復雜腰奋,需要scrollToPosition()和scrollBy 結(jié)合使用单起,監(jiān)聽scroll接口;
第二種:從上往下滑動(目標item可見)劣坊,scrollBy就可以解決嘀倒;
第三種:從下往上滑動,目標可見不可見一樣局冰,調(diào)用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的時候测蘑,調(diào)用 recyclerView.scrollToPosition(moveCounts);
只能講item滑動到屏幕的底部。
*/
/*
第一種方案:先將item移動到底部康二,然后在調(diào)用scrollBy移動到頂部碳胳。不可行,不能講item滑動到頂部沫勿,
離上面還有一小段距離挨约;
recyclerView.scrollToPosition(moveCounts);
int top=recyclerView.getHeight();
recyclerView.scrollBy(0,top);
第二種方案:直接計算要滑動的距離。程序崩潰产雹,報空指針诫惭。看系統(tǒng)源碼可知蔓挖,當
滑動的距離大于ChildCount(可見的item數(shù)目)夕土,將返回空。
int top=recyclerView.getChildAt(moveCounts-firstItem).getTop();
recyclerView.scrollBy(0,top);
第三種解決方案:先將目標item滑動到底部时甚,然后進行異步處理隘弊。調(diào)用滾動監(jiān)聽方法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;
}
監(jiān)聽回調(diào)梨熙,從底部滑動到頂部:
class RecyclerViewListener extends RecyclerView.OnScrollListener{
/*
監(jiān)聽回調(diào),滑動結(jié)束回調(diào)刀诬。
*/
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
//在這里進行第二次滾動(最后的100米Q噬取)
if (move ){
move = false;
//獲取要置頂?shù)捻椩诋斍捌聊坏奈恢眯安疲琺oveCount是記錄的要置頂項在RecyclerView中的位置
int n = moveCounts - gridLayoutManager.findFirstVisibleItemPosition();
if ( 0 <= n && n < recyclerView.getChildCount()){
//獲取要置頂?shù)捻楉敳侩xRecyclerView頂部的距離
int top = recyclerView.getChildAt(n).getTop();
//最后的移動
recyclerView.scrollBy(0, top);
}
}
}
/*
監(jiān)聽回調(diào),滑動狀態(tài)改變回調(diào)
*/
@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);
}
}
}
這樣我們就講左邊聯(lián)動右邊完成了质欲。
右邊聯(lián)動左邊
滑動右邊的數(shù)據(jù)树埠,前面我們在加載城市數(shù)據(jù)的時候,我們將城市和省份通過一個tag進行了綁定嘶伟,當我們滑動的時候怎憋,獲取這個tag(position),讓他與左邊的position比較,不相同的話九昧,就把tag賦值給position绊袋,有了這個position,我們就可以更改背景顏色了铸鹰;
貼出關(guān)鍵代碼:
if (!TextUtils.equals(tag, currentTag)) {
currentTag = tag;
Log.i("zhangcong",currentTag);
Integer integer = Integer.valueOf(currentTag);
mCheckListener.check(integer, false);
}
最后我們調(diào)用一個接口回調(diào)方法癌别,讓左邊的省份背景改變。
小bug
由于右邊滑動的時候左邊省份背景改變了蹋笼,但是不在可見view中展姐,我們將左邊選中的省份一直顯示在屏幕中間。通過recycleView設置下就可以剖毯。
代碼:
//將當前選中的item居中
public void moveToCenter(int position) {
//將點擊的position轉(zhuǎn)換為當前屏幕上可見的item的位置以便于計算距離頂部的高度圾笨,從而進行移動居中
Log.i(">>>>>>>>>",position - manager.findFirstVisibleItemPosition()+"eeeee");
int itemPosition=position-manager.findFirstVisibleItemPosition();
/*
當往上滑動太快,會出現(xiàn)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);
}
}
遇到的問題
在做右邊聯(lián)動左邊的時候墅拭,當時想著獲取title的position,然后將position傳過去涣狗,進行背景的改變谍婉,這種方法是可行的,但是體驗非常的不好镀钓,因為當你滑動很快的時候穗熬,是獲取不到中間經(jīng)過的省份的position,就感覺不連貫丁溅,直接跳到了最后你滑動的位置唤蔗,所以換了一種解決思路(如果你以為是我想出來的你就太年輕了)。
總結(jié)
總結(jié)的話就沒有窟赏,前面思路講的已經(jīng)夠清楚了妓柜。最后給大家一點看代碼的興趣,這里面還缺少了許多的城市和省份涯穷,大家看懂了這些代碼的話可以在里面加上自己的城市棍掐,或者加上圖片展示功能;然后提交pull request拷况,我這邊會幫你們merge的(請在添加代碼的地方加上你自己的注釋 如: Simon add shanghai city on 20170727)作煌。也非常歡迎大家在底下留言掘殴,探討一些問題,當然有看不明白的也可以提出問題粟誓,我會盡力解答的奏寨。大家覺得有幫助的話麻煩給我的github來一個star吧。
最后是github地址