这是 https://blog.csdn.net/handsonn/article/details/52850815 的第二篇
简单的recyclerview 运用,实现的效果如下,类似一个简易的课表:
activity_main.xml 布局如下:
ScheduleRoom 就是 toolbar 下面的布局,是一个简单的组合控件
那么,需要解决几个问题:
1、首先,如何让 RecyclerView 任意方向滚动呢,要感谢这里作为参考(太牛了,大神啊~~~):
Building a RecyclerView LayoutManager
2、其次,如何获取 ScrollView 的滚动距离?
这个比较简单,继承 ScrollView,抛出一个回调即可,代码如下:
public class ObserverScrollView extends ScrollView {
private ScrollViewListener scrollViewListener;
public ObserverScrollView(Context context) {
super(context);
}
public ObserverScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ObserverScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
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);
}
}
@Override
protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
if (scrollViewListener != null) {
scrollViewListener.onOverScrolled(this, scrollX, scrollY, clampedX, clampedY);
}
}
public interface ScrollViewListener {
void onScrollChanged(ObserverScrollView scrollView, int newX, int newY, int oldX, int oldY);
void onOverScrolled(ObserverScrollView scrollView, int scrollX, int scrollY, boolean clampedX, boolean clampedY);
}
}
3、接着,如何让 ScrollView 和 RecyclerView 、RecyclerView 与 RecyclerView 之间互相联动 ?
什么意思呢?
a、当 ScrollView 滚动时,通过捕获 ScrollView 滚动让 RecyclerView 也同时滚动
b、同理,当 RecyclerView 滚动时,通过捕获 RecyclerView 滚动让 ScrollView 也同时滚动
那么,问题来了,ScrollView 和 RecyclerView 都是要监听的,当 ScrollView 滚动时,RecyclerView就会跟着一起联动,这个时候又会触发 RecyclerView 的滚动监听,里面又会让 ScrollView 跟着联动,就这样互相联动,相当于造成了无限递归了,如下:
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
topRecyclerView.scrollBy(dx, 0);
leftScrollView.scrollBy(0, dy);
}
});
topRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
mRecyclerView.scrollBy(dx, 0);
}
});
leftScrollView.setScrollViewListener(new ObserverScrollView.ScrollViewListener() {
@Override
public void onScrollChanged(ObserverScrollView scrollView, int newX, int newY, int oldX, int oldY) {
mRecyclerView.scrollBy(0, newY - oldY);
}
@Override
public void onOverScrolled(ObserverScrollView scrollView, int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
}
});
那么,肯定是需要一些条件的,这个条件可以让我们完成这些事:
a、当拖动 recyclerview 滚动时候,scrollView 跟着 recyclerview 滚动,但是 ScrollView 里面滚动监听的 recyclerview 不会跟着联动;
b、同理,当拖动 ScrollView 滚动时候,recyclerview 跟着 ScrollView 滚动,但是 recyclerview 里面滚动监听的 ScrollView 不会跟着联动;
(1)一种方法,通过判断手指 touch 在哪个view,添加标记位来记录,不过这样有个坏处,onTouch 是异步的,所以肯定还有 a、b的问题;
(2)另一种,通过查看 RecyclerView 里面的代码,可以发现,系统已经提供了这些状态码:
可以看到 RecyclerView 默认 mScrollState 是 SCROLL_STATE_IDLE,也就是 recyclerview 不自己主动滚动的时候就是这个状态,
当手指拖动 recyclerview 滚动的时候 mScrollState 是 1,手指离开 recyclerview 但是 recyclerview 还在继续滚动的时候,mScrollState 是 2 ,那么就可以根据这些状态来判断了。
添加条件之后如下:
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (topRecyclerView.getScrollState() == 0) {
topRecyclerView.scrollBy(dx, 0);
}
if (mRecyclerView.getScrollState() != 0) {
leftScrollView.scrollBy(0, dy);
}
}
});
topRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (mRecyclerView.getScrollState() == 0) {
mRecyclerView.scrollBy(dx, 0);
}
}
});
leftScrollView.setScrollViewListener(new ObserverScrollView.ScrollViewListener() {
@Override
public void onScrollChanged(ObserverScrollView scrollView, int newX, int newY, int oldX, int oldY) {
if (mRecyclerView.getScrollState() == 0) {
mRecyclerView.scrollBy(0, newY - oldY);
}
}
@Override
public void onOverScrolled(ObserverScrollView scrollView, int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
}
});
4、然后,ScrollView 和 RecyclerView 联动滚动错位问题 ,即 拖动 ScrollView 滚动的时候,RecyclerView 跟着滚动,这个时候会有明显的错位问题,两个控件无法相同时间内滚动相同距离,有快有慢,这个时候需要将 继承的 ScrollView 修改成 NestedScrollView,修改后的 ObserverScrollView 如下:
public class NestedObserverScrollView extends NestedScrollView {
private ScrollViewListener scrollViewListener;
public NestedObserverScrollView(@NonNull Context context) {
super(context);
}
public NestedObserverScrollView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public NestedObserverScrollView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setNestedScrollViewListener(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);
}
}
@Override
protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
if (scrollViewListener != null) {
scrollViewListener.onOverScrolled(this, scrollX, scrollY, clampedX, clampedY);
}
}
public interface ScrollViewListener {
void onScrollChanged(NestedObserverScrollView scrollView, int newX, int newY, int oldX, int oldY);
void onOverScrolled(NestedObserverScrollView scrollView, int scrollX, int scrollY, boolean clampedX, boolean clampedY);
}
}
5、接下来就是选择时间段的问题了,嗯,是的,计算就好了,当然有一点需要注意的是,计算 dp2px 的时候,
例如:
public static int dp2px(Context context, double dp) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dp * scale + 0.5f);
}
num * dp2px() 和 dp2px(dp * num) 和 是不一样的,如果数字 num 较大的话,误差就大了,后者是正确的做法,因为不同设备的 density 不一样,而且 还加了 0.5f,就有 0.5 * num 的误差
关于为什么转换的时候要加 0.5f 呢,很明显是为了 四舍五入,通过看系统一些做法也是这么做的,比如这里:
里面的 applyDimension 就是 先计算 value*density,后面的已经不属于这里讨论的了
6、记录状态,可以定义一个实体类,如下:
附上demo地址: https://github.com/laymanZ/ScheduleRoom
好了,一共就这么多,很简单,作为一个记录,写的不好的地方欢迎指正,共同学习。