接上篇博客一款安卓日历(一),这篇文章主要介绍日历滑动切换的细节。
项目地址:https://github.com/yannecer/NCalendar
月视图和周视图滑动切换,是自定义的MWCalendar
继承LinearLayout
,实现了NestedScrollingParent
接口,消费了RecyclerView
的滑动距离,实现整体上滑,在滑动过程中根据条件判断是否显示周视图WeekCalendar
,用OverScroller
实现滚动。
关于NestedScrollingParent
和RecyclerView
的嵌套滑动,网上已经有比较详细的资料,这里不再复制粘贴,只讲一下实现细节,我主要参考了Hongyang
的照片文章Android NestedScrolling机制完全解析 带你玩转嵌套滑动。
MWCalendar
MWCalendar
是一个继承LinearLayout
,并实现NestedScrollingParent
的容器,里面包含了一个月视图MonthCalendar
和一个RecyclerView
,WeekCalendar
是在xml
加载完后以参数的方式传进去的,目的是为了使MWCalendar
和 WeekCalendar
同在一个RelativeLayout
中方便处理切换,RecyclerView
向上滑动时,让NestedScrollingParent
消耗掉RecyclerView
的上滑距离,当向上滑动到只剩一个行高的时候,NestedScrollingParent
停止滑动,由RecyclerView
继续滑动。这里主要说onStartNestedScroll()
,onStopNestedScroll()
,onNestedPreScroll()
和onMeasure()
这四个方法。
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
boolean hiddenMonthCalendar = dy > 0 && getScrollY() < rowHeigh * 5;
boolean showMonthCalendar = dy < 0 && getScrollY() >= 0 && !ViewCompat.canScrollVertically(target, -1);
if (hiddenMonthCalendar || showMonthCalendar) {
scrollBy(0, dy);
consumed[1] = dy;
}
}
这个方法在滑动时调用,用来判断月视图和周视图的切换,dy>0
向上滑,且滑动距离getScrollY()<rowHeigh * 5(月视图共6个行高)
时,需要月视图向上滑,滑动距离为rowHeigh * 5
。
@Override
public void onStopNestedScroll(View target) {
int scrollY = getScrollY();
if (scrollY == 0 || scrollY == rowHeigh * 5) {
return;
}
if (STATE == OPEN) {
if (scrollY > 100) {
startScroll(scrollY,rowHeigh * 5 - scrollY,300);
} else {
startScroll(scrollY, -scrollY, 300);
}
}
if (STATE == CLOSE) {
if (scrollY < rowHeigh * 5 - 100) {
startScroll(scrollY, -scrollY, 300);
} else {
startScroll(scrollY, rowHeigh * 5 - scrollY, 300);
}
}
}
-----------
private void startScroll(int startY,int dy,int duration) {
mScroller.startScroll(0, startY, 0, dy, duration);
invalidate();
}
这个方法是停止滑动手松开的时候调用,在这个方法中根据状态判断是上滚还是下滚,再根据已滑动的距离getScrollY()
和总的滑动距离得到需要利用mScroller
滚动的距离。
@Override
public void computeScroll() {
int scrollY = getScrollY();
if (scrollY == 0) {
STATE = OPEN;
weekCalendar.setVisibility(INVISIBLE);
} else if (scrollY == 5 * rowHeigh) {
STATE = CLOSE;
weekCalendar.setVisibility(VISIBLE);
} else {
int weekRow = monthCalendar.getCurrentMothView().getWeekRow();
weekCalendar.setVisibility(scrollY >= weekRow * rowHeigh ? VISIBLE : INVISIBLE);
}
if (mScroller.computeScrollOffset()) {
scrollTo(0, mScroller.getCurrY());
invalidate();
}
}
computeScroll()
方法,在执行mScroller.startScroll()
后调用,实时调用并绘制界面,所以可以在这个方法中判断周视图和月视图哪个应该显示。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
rowHeigh = monthCalendar.getRowHeigh();
ViewGroup.LayoutParams params = recyclerView.getLayoutParams();
params.height = getMeasuredHeight() - rowHeigh;
}
在NestedScrollingParent
中如果不重写onMeasure()
方法,会出现上滑的过程中,下面的部分是空白的,原因是整体View
测量的时候,测量的结果适应屏幕的大小,在上滑的过程中并没有重新测量,上滑的时候整体View
一起整体上滑,View
的高度还是那么高,向上滑出一部分,下面的就变成空白了。所以我们需要做的就是在View
测量的时候,给RecyclerView
加高,这样上滑的时候也是正常显示,这个加高不是随便加的,必须是停止滑动的时候正好能显示RecyclerView
的全部数据,这样就可以计算得到,加高的部分是正常的高度减去上面一行的高度,强行把RecyclerView
加高已补充上滑出去的那部分视图。
这就是处理滑动切换视图的主要方法和逻辑,完成项目参见:https://github.com/yannecer/NCalendar