縱享絲滑滑動切換的周月日曆,水滴效果,豐富自定義日曆樣式,仿小米日曆(ViewDragHelper實現)

本文已受權微信公衆號:鴻洋(hongyangAndroid)在微信公衆號平臺原創首發git

老規矩先貼效果圖github

樣式.gif
水滴效果.gif
p1.jpg
p2.jpg

github地址,以爲有幫助的能夠給個 star 唄bash

github.com/idic779/mon…微信

添加依賴ide

compile 'com.github.idic779:monthweekmaterialcalendarview:1.7'佈局

具體如何使用看這裏ui

這個庫能夠作什麼?

  • 能夠控制是否容許左右滑動,上下滑動,切換年月this

  • 流暢的上下週月模式切換spa

  • 自定義日曆樣式.net

  • 基於material-calendarview 這個庫實現,能夠根據需求定製效果

    以前開發任務中有涉及到年月日日曆的切換效果,因爲是須要聯動,想到的方向大概有3種,要麼經過處理viewtouch事件,要麼是經過自定義behavior去實現,要麼是經過ViewDragHelper這個神器去實現,網上比較多的是經過自定義behavior去實現,本文使用的是第三種方法,實現的是一個可高度定製自由切換的周月日曆視圖,提供一種思路去實現頁面聯動效果。 ##準備
    因爲重點實現的是年月切換的效果,原本想着說能夠本身寫一個日曆組件而後再加上ViewDragHelper,應該能夠實現周月聯動的效果吧?後面想了想,重點在切換那就乾脆直接找個開源庫穩定性好點的日曆組件,因此用github.com/prolificint…快4000start的庫吧, ViewDragHelper,做爲一個神器能夠作不少的事情,官方的DrawerLayoutBottomSheetBehavior用他來實現,爲何用它?對於拖動某個View,若是是本身去重寫touch事件的,計算滑動距離再去移動View會須要處理比較多繁瑣的代碼去實現。若是咱們用ViewDragHelper的話能很輕易的實現這樣的效果。 簡單的介紹下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,咱們能夠在這裏設置捕獲的條件
  • clampViewPositionHorizontal ()``clampViewPositionVertical(): 分別對child水平和豎直方向移動的邊界進行控制,例如限制周月移動的距離能夠在這裏作處理
  • onViewPositionChanged() : 當child的位置發生移動時候會回調這個方法
  • onViewReleased():手指釋放時候的回調
  • getViewHorizontalDragRange()``getViewVerticalDragRange():返回child橫向或者縱向移動的範圍,大於0才能捕獲。

更多的能夠參考鴻洋的Android ViewDragHelper徹底解析 自定義ViewGroup神器

如何實現

既然選擇ViewDragHelper要實現周月聯動呢,咱們來理一理要實現的效果,在月視圖的時候,可以把下面的recyclerView上移拖到到周視圖的高度,上移過程若是超過必定距離就默認滾動到周視圖。 在周視圖的的時候又能把recyclerView下移拖動到月視圖的高度位置,下移過程若是超過必定距離就默認滾動到月視圖。

總體分析

整個頁面是由頂部的周名字的View、周模式的MaterialCalendarView、月模式的MaterialCalendarView和最下面的recyclerView組成 須要注意的是MaterialCalendarView 這個庫原來是有周名字還有頂部顯示日期的, 須要注意的是這裏稍微作了下修改把這些給隱藏掉了,具體能夠看MaterialCalendarView.setTopbarVisible()。而且作了下修改增長了得到單行的高度方法MaterialCalendarView.getItemHeight() ,即爲周模式時顯示的高度。

具體實現

  • 拖動前處理 整個頁面只有recyclerView ,月模式下若是向上拖動時候若是recyclerView不是滾動到了頂部的話那麼就不容許拖動,相關代碼
@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得到初始的一些數據值,包括周模式的高度,月模式的高度,最大移動的距離,單行的高度
@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繪製到對應的位置上面

  • 最大移動的距離defaultStopHeight在選中日期時候就會經過 getCurrentItemPosition()計算出它點擊所在的行數再調用setStopItemPosition()就能夠獲得要中止下來的高度,

  • 接下來講下最關鍵的地方 既然是周月聯動咱們發如今拖動recyclerView視圖的時候咱們會不停回調onViewPositionChanged()這個方法,咱們在這個方法裏面就能夠根據recyclerView移動的距離來移動對應的月視圖,

//滑動處理
       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()作相關狀態的改變,若是滑動的距離超過必定的值就把當前視圖置爲月模式仍是周模式
    @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) {
                  //若是滑動距離超過當前選中項和最大滑動距離之間的距離
                  if (moveY > weekdistance && moveY < maxDistance) {
                      //變爲周模式
                      setMode(Mode.WEEK);
                  } else if (moveY <= weekdistance) {
                      //變爲月模式
                      setMode(Mode.MONTH);
                  }
              } else {
                  //周模式下距離頂部選中日期的距離小於最大滑動距離-10的話就讓它變爲月模式
                  if (moveY > maxOffset - 10) {
                      //變爲周模式
                      setMode(Mode.WEEK);
                  } else if (moveY <= maxOffset - 10) {
                      //變爲月模式
                      setMode(Mode.MONTH);
                  }
              }
          }
    複製代碼

須要注意的是在onInterceptTouchEvent()若是是月模式而且能夠拖動的時候, 底部的recyclerView是不容許滑動的

if (currentMode == Mode.MONTH&& canDrag) {
               setRecyclerViewCanScroll(false);
}
複製代碼

還能夠怎麼用

接下來講下你能夠怎麼去定製?若是你想替換項目中的月和周視圖的話,不想用Material-calendarview ,很簡單,只須要你本身的周月視圖必須有一個方法得到單行日曆的高度(例如個人庫中的MaterialCalendarView.getItemHeight() ),而後把這個月視圖和周視圖,分別在MonthWeekMaterialCalendarView裏面按照順序放到對應位置便可。而後再setListener()裏面設置相關的回調處理,例如日期選中或者月份切換的回調等。

好的大工告成。

相關文章
相關標籤/搜索