最近在作一個地產項目,其實以前作出了一版,但如今要求重作(連上架的機會都沒有),很服氣啊~~而如今作的項目呢,比上一版功能要求更多,其中,銷控表的界面效果要求跟房產銷冠APP的銷控表界面差很少,先來看下房產銷冠APP的銷控表效果吧:android
說說我第一次看到這個界面效果時的感受,就一個詞:amazing~ 是的,公司就我一我的作安卓開發,感受有點壓力山大,可是,不慫,靜下心來分析一下就明朗多了。先說說本文核心技術重點:兩個RecyclerView同步滾動。好,下面進入正文。git
我認爲的佈局實現:將銷控表分爲左右兩部分:左邊是樓層列表,右邊是單元(房間)列表。樓層列表就是一個簡單的LinearLayout+TextView+RecyclerView,單元(房間)列表則有點小複雜(HorizontalScrollView、LinearLayout)+TextView+RecyclerView。爲了各位看客能直觀理解,我特地作了張圖,請看:github
其中黃色區域就是銷控表的部分。bash
那麼,要實現一、2的效果,能夠監聽這兩個列表的滾動,當其中一個列表滾動時,讓另外一個列表滾動相同的距離便可。要實現3的效果就簡單了,由於HorizontalScrollView中嵌套RecyclerView並無滾動衝突,HorizontalScrollView處理水平滑動事件,RecyclerView處理豎直滾動事件,因此暫時不用理(後面仍是要作點簡單處理的)。ide
上面已經分析出了佈局結構,下面直接貼布局代碼:佈局
<?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="統計"
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>複製代碼
再經過列表的數據進行填充(這部分不是重點就不貼出來了),效果就出來了:ui
接下來就是實現同步滾動效果了。this
一個大致的思路就是分別對其中一個列表設置滾動監聽,當這個列表滾動時,讓另外一個列表也一塊兒滾動。
但細節上要考慮到,這種監聽是雙向的,A列表滾動時觸發其滾動回調接口,致使B列表滾動,而此時B列表也已經設置過滾動監聽,它的滾動也會觸發它的滾動回調接口,致使A列表滾動,這樣就造成了一個死循環。因此適當添加或移除滾動監聽是本功能實現的重難點,下面直接貼出代碼,請自行結合代碼及註釋理解。spa
這樣的封裝使咱們不用在其餘地方考慮列表空閒狀態時的處理,會省去不少事。3d
/**
* @建立者 CSDN_LQR
* @描述 實現一個RecyclerView.OnScrollListener的子類,當RecyclerView空閒時取消自身的滾動監聽
*/
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);
}
}
}複製代碼
如下兩段代碼涉及兩個列表滾動同步和添加或移除滾動監聽的時機,具體代碼及註釋我已經寫得很清楚了,請仔細看:
private final RecyclerView.OnScrollListener mLayerOSL = new MyOnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// 當樓層列表滑動時,單元(房間)列表也滑動
mRvRoom.scrollBy(dx, dy);
}
};
/**
* 設置兩個列表的同步滾動
*/
private void setSyncScrollListener() {
mRvLayer.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 && mRvRoom.getScrollState() == RecyclerView.SCROLL_STATE_IDLE) {
// 記錄當前另外一個列表的y座標並對當前列表設置滾動監聽
mLastY = rv.getScrollY();
rv.addOnScrollListener(mLayerOSL);
} else {
// 若當前列表原地擡起手指時,移除當前列表的滾動監聽
if (e.getAction() == MotionEvent.ACTION_UP && rv.getScrollY() == mLastY) {
rv.removeOnScrollListener(mLayerOSL);
}
}
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
});
...
}複製代碼
對於單元(房間)列表滾動監聽的設置,跟前面同樣,我就順便寫一下好了。
private final RecyclerView.OnScrollListener mRoomOSL = new MyOnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// 當單元(房間)列表滑動時,樓層列表也滑動
mRvLayer.scrollBy(dx, dy);
}
};
/**
* 設置兩個列表的同步滾動
*/
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) {
}
});
}複製代碼
好了,到這裏同步滾動效果就實現了,先看看效果。
在上圖中,咱們能夠看到 ,同步滾動效果確實是實現了,但有個問題,只要一水平滾動後,再來滾動左邊的樓層列表時程序就會崩潰,如果滾動右邊的單元(房間)列表則會滾動不一樣步,會形成這種狀況是由於,當水平滾動是時,事件被HorizontalScrollView處理了,致使右邊的單元(房間)列表的滾動監聽沒有被移除。
當咱們去滾動左邊的樓層列表時,會爲其設置滾動監聽,這時這兩個列表都存在滾動監聽,因此就形成了監聽的遞歸調用(死循環),因而內存就妥妥的溢出了。下面是錯誤提示:
因此,解決的方法就是,當HorizontalScrollView處理水平滾動事件時,取消列表的滾動監聽,而ScrollView自己不支持滾動監聽,因此須要從新HorizontalScrollView,向外提供滾動監聽功能。自定義HorizontalScrollView代碼以下:
/**
* @建立者 CSDN_LQR
* @描述 自定義HorizontalScrollView,向外提供滑動監聽功能
*/
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>
...複製代碼
在代碼中監聽HorizontalScrollView滾動,當其滾動時,移除列表控件的移動監聽事件:
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);
}
});複製代碼
再來試試效果: