前言
==
最近看了別人的一篇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
實現過程
實現過程也按照思路來
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地址