最近在作一个地产项目,其实以前作出了一版,但如今要求重作(连上架的机会都没有),很服气啊~~而如今作的项目呢,比上一版功能要求更多,其中,销控表的界面效果要求跟房产销冠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);
}
});复制代码
再来试试效果: