本文已授權(quán)微信公眾號:鴻洋(hongyangAndroid)在微信公眾號平臺原創(chuàng)首發(fā)
老規(guī)矩先貼效果圖
github地址,覺得有幫助的可以給個 star 唄
https://github.com/idic779/monthweekmaterialcalendarview
添加依賴
compile 'com.github.idic779:monthweekmaterialcalendarview:1.7'
具體如何使用看這里
這個庫可以做什么谴返?
可以控制是否允許左右滑動煞肾,上下滑動,切換年月
流暢的上下周月模式切換
自定義日歷樣式
基于material-calendarview 這個庫實(shí)現(xiàn)嗓袱,可以根據(jù)需求定制效果
之前開發(fā)任務(wù)中有涉及到年月日日歷的切換效果籍救,由于是需要聯(lián)動,想到的方向大概有3種渠抹,要么通過處理view
的touch
事件蝙昙,要么是通過自定義behavior
去實(shí)現(xiàn)闪萄,要么是通過ViewDragHelper
這個神器去實(shí)現(xiàn),網(wǎng)上比較多的是通過自定義behavior
去實(shí)現(xiàn)奇颠,本文使用的是第三種方法败去,實(shí)現(xiàn)的是一個可高度定制自由切換的周月日歷視圖,提供一種思路去實(shí)現(xiàn)頁面聯(lián)動效果。
準(zhǔn)備
由于重點(diǎn)實(shí)現(xiàn)的是年月切換的效果烈拒,本來想著說可以自己寫一個日歷組件然后再加上ViewDragHelper
,應(yīng)該可以實(shí)現(xiàn)周月聯(lián)動的效果吧为迈?后面想了想,重點(diǎn)在切換那就干脆直接找個開源庫穩(wěn)定性好點(diǎn)的日歷組件缺菌,所以用https://github.com/prolificinteractive/material-calendarview快4000start的庫吧葫辐,
ViewDragHelper
,作為一個神器可以做很多的事情伴郁,官方的DrawerLayout
耿战,BottomSheetBehavior
用他來實(shí)現(xiàn),為什么用它焊傅?對于拖動某個View
,如果是自己去重寫touch
事件的剂陡,計算滑動距離再去移動View
會需要處理比較多繁瑣的代碼去實(shí)現(xiàn)。如果我們用ViewDragHelper
的話能很輕易的實(shí)現(xiàn)這樣的效果狐胎。
簡單的介紹下ViewDragHelper
ViewDragHelper helper= ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(View child, int pointerId) {
return true;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx)
{
return left;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy)
{
return top;
}
@Override
public int getViewHorizontalDragRange(View child) {
return super.getViewHorizontalDragRange(child);
}
@Override
public int getViewVerticalDragRange(View child) {
return super.getViewVerticalDragRange(child);
}
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
}
});
-
tryCaptureView()
:如果返回true鸭栖,則說明可以捕獲該view,我們可以在這里設(shè)置捕獲的條件 -
clampViewPositionHorizontal ()``clampViewPositionVertical()
:
分別對child
水平和豎直方向移動的邊界進(jìn)行控制握巢,例如限制周月移動的距離可以在這里做處理 -
onViewPositionChanged()
: 當(dāng)child
的位置發(fā)生移動時候會回調(diào)這個方法 -
onViewReleased()
:手指釋放時候的回調(diào) -
getViewHorizontalDragRange()``getViewVerticalDragRange()
:返回child
橫向或者縱向移動的范圍晕鹊,大于0才能捕獲。
更多的可以參考鴻洋的Android ViewDragHelper完全解析 自定義ViewGroup神器
如何實(shí)現(xiàn)
既然選擇ViewDragHelper
要實(shí)現(xiàn)周月聯(lián)動呢暴浦,我們來理一理要實(shí)現(xiàn)的效果溅话,在月視圖的時候,能夠把下面的recyclerView
上移拖到到周視圖的高度歌焦,上移過程如果超過一定距離就默認(rèn)滾動到周視圖飞几。
在周視圖的的時候又能把recyclerView
下移拖動到月視圖的高度位置,下移過程如果超過一定距離就默認(rèn)滾動到月視圖独撇。
整體分析
整個頁面是由頂部的周名字的View
屑墨、周模式的MaterialCalendarView
、月模式的MaterialCalendarView
和最下面的recyclerView
組成
需要注意的是MaterialCalendarView
這個庫原來是有周名字還有頂部顯示日期的纷铣,
需要注意的是這里稍微做了下修改把這些給隱藏掉了卵史,具體可以看MaterialCalendarView.setTopbarVisible()
。并且做了下修改增加了獲得單行的高度方法MaterialCalendarView.getItemHeight()
关炼,即為周模式時顯示的高度程腹。
具體實(shí)現(xiàn)
- 拖動前處理
整個頁面只有recyclerView
,月模式下如果向上拖動時候如果recyclerView
不是滾動到了頂部的話那么就不允許拖動,相關(guān)代碼
@Override
public boolean tryCaptureView(View child, int pointerId) {
return !mDragHelper.continueSettling(true)
&&child == mRecyclerView && !animatStart
&& isAtTop(mRecyclerView) &&
!ViewCompat.canScrollVertically(mRecyclerView, -1);
}
- 限制
recyclerView
移動的高度在周模式和月模式之間
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
//決定豎直方向上能移動的距離為 finalWeekModeHeight到finalMonthModeHeight
int topBound = finalWeekModeHeight;
int bottomBound = finalMonthModeHeight;
int newTop = Math.min(Math.max(top, topBound), bottomBound);
return newTop;
}
- 在
onMeasure
獲得初始的一些數(shù)據(jù)值儒拂,包括周模式的高度,月模式的高度寸潦,最大移動的距離,單行的高度
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
calendarItemHight = mCalendarViewMonth.getItemHeight();
calendarWeekHight = calendarItemHight;
if (defaultStopHeight == 0) {
defaultStopHeight = getCurrentItemPosition(CalendarDay.today()) * calendarItemHight;
}
calendarMonthHight = mCalendarViewMonth.getMeasuredHeight();
weekViewHight = mTopWeekView.getMeasuredHeight();
finalMonthModeHeight = weekViewHight + calendarMonthHight;
finalWeekModeHeight = calendarItemHight + weekViewHight;
maxOffset = calendarMonthHight - calendarItemHight;
}
然后在
onlayout()
把布局里的View
繪制到對應(yīng)的位置上面最大移動的距離defaultStopHeight在選中日期時候就會通過
getCurrentItemPosition()
計算出它點(diǎn)擊所在的行數(shù)再調(diào)用setStopItemPosition()
就可以得到要停止下來的高度社痛,接下來說下最關(guān)鍵的地方
既然是周月聯(lián)動我們發(fā)現(xiàn)在拖動recyclerView
視圖的時候我們會不图回調(diào)onViewPositionChanged()
這個方法,我們在這個方法里面就可以根據(jù)recyclerView
移動的距離來移動對應(yīng)的月視圖蒜哀,
//滑動處理
private void HandlerOffset(View changedView, int left, int top, int dx, int dy) {
//獲取日歷相對手指移動的相對距離 dy向上移動小于0
transY = transY + dy;
if (transY > 0) {
transY = 0;
}
if (transY < -calendarMonthHight - calendarItemHight) {
transY = -calendarMonthHight - calendarItemHight;
}
float abstransY = Math.abs(transY);
if (dy < 0) {
//如果上滑動斩箫,并且滑向動的絕對值距離在超過calendarHight-defaultStopHeight
// 并且小于可以滑動的距離calendarHight-calendarItemHight之間的話
if (abstransY >= (calendarMonthHight - defaultStopHeight) && abstransY < calendarMonthHight - calendarItemHight) {
if (!animatStart) {
mCalendarViewMonth.setTranslationY(getOffset((int) mCalendarViewMonth.getTranslationY() + dy, calendarItemHight - defaultStopHeight));
}
}
}
if (dy > 0) {
if (abstransY < maxOffset
&& currentMode.equals(Mode.WEEK)) {
mCalendarViewWeek.setVisibility(INVISIBLE);
}
if (abstransY < maxOffset) {
mCalendarViewMonth.setTranslationY(getOffset((int) mCalendarViewMonth.getTranslationY() + dy, 0));
}
}
}
月視圖的移動我們是通過setTranslationY
來移動的,為了防止滑動時候過快通過getOffset()
限制一下它滑動的最大距離撵儿。
- 在松開手指的時候我們在
onViewReleased()
做相關(guān)狀態(tài)的改變乘客,如果滑動的距離超過一定的值就把當(dāng)前視圖置為月模式還是周模式@Override public void onViewReleased(View releasedChild, float xvel, float yvel) { int moveY = finalMonthModeHeight - mRecyclerView.getTop(); //周模式距離滑動為一行的高度,超過就滑動到周位置 int weekdistance = calendarItemHight; //最大滑動距離 int maxDistance = calendarMonthHight; if (currentMode == Mode.MONTH) { //如果滑動距離超過當(dāng)前選中項(xiàng)和最大滑動距離之間的距離 if (moveY > weekdistance && moveY < maxDistance) { //變?yōu)橹苣J? setMode(Mode.WEEK); } else if (moveY <= weekdistance) { //變?yōu)樵履J? setMode(Mode.MONTH); } } else { //周模式下距離頂部選中日期的距離小于最大滑動距離-10的話就讓它變?yōu)樵履J? if (moveY > maxOffset - 10) { //變?yōu)橹苣J? setMode(Mode.WEEK); } else if (moveY <= maxOffset - 10) { //變?yōu)樵履J? setMode(Mode.MONTH); } } }
需要注意的是在onInterceptTouchEvent()
如果是月模式并且可以拖動的時候淀歇,
底部的recyclerView
是不允許滑動的
if (currentMode == Mode.MONTH&& canDrag) {
setRecyclerViewCanScroll(false);
}
還可以怎么用
接下來說下你可以怎么去定制易核?如果你想替換項(xiàng)目中的月和周視圖的話,不想用Material-calendarview 浪默,很簡單牡直,只需要你自己的周月視圖必須有一個方法獲得單行日歷的高度(例如我的庫中的MaterialCalendarView.getItemHeight() ),然后把這個月視圖和周視圖纳决,分別在MonthWeekMaterialCalendarView
里面按照順序放到對應(yīng)位置即可碰逸。然后再setListener()
里面設(shè)置相關(guān)的回調(diào)處理,例如日期選中或者月份切換的回調(diào)等阔加。
好的大工告成饵史。