纵享丝滑滑动切换的周月日历,水滴效果,可高度定制,仿小米日历

老规矩先贴效果图

水滴效果.gif
普通切换.gif

github地址,觉得有帮助的可以给个 star 呗

github.com/idic779/mon…

添加依赖

compile ‘com.github.idic779:monthweekmaterialcalendarview:1.5’

具体如何使用看这里

这个库可以做什么?


可以控制是否允许左右滑动,上下滑动,切换年月

  • 流畅的上下周月模式切换

  • 自定义日历样式

  • 基于material-calendarview 这个库实现,可以根据需求定制效果

    之前开发任务中有涉及到年月日日历的切换效果,由于是需要联动,想到的方向大概有3种,要么通过处理viewtouch事件,要么是通过自定义behavior去实现,要么是通过ViewDragHelper这个神器去实现,网上比较多的是通过自定义bahavior去实现,本文使用的是第三种方法,实现的是一个可高度定制自由切换的周月日历视图,提供一种思路去实现页面联动效果。

    准备

    由于重点实现的是年月切换的效果,本来想着说可以自己写一个日历组件然后再加上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)
        {
            <span class="hljs-built_in">return</span> top;
        }
        @Override
        public int getViewHorizontalDragRange(View child) {
            <span class="hljs-built_in">return</span> super.getViewHorizontalDragRange(child);
        }
    
        @Override
        public int getViewVerticalDragRange(View child) {
            <span class="hljs-built_in">return</span> 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, <span class="hljs-built_in">float</span> xvel, <span class="hljs-built_in">float</span> yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);
        }
    });
    


    • tryCaptureView():如果返回true,则说明可以捕获该view,我们可以在这里设置捕获的条件
    • clampViewPositionHorizontal ()clampViewPositionVertical()</code>:
      分别对<code>child</code>水平和竖直方向移动的边界进行控制,例如限制周月移动的距离可以在这里做处理</li>
      <li><code>onViewPositionChanged()</code> : 当<code>child</code>的位置发生移动时候会回调这个方法</li>
      <li><code>onViewReleased()</code>:手指释放时候的回调</li>
      <li><code>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;
               }
    
    
           <span class="hljs-built_in">float</span> abstransY = Math.abs(transY);
           <span class="hljs-keyword">if</span> (dy &lt; 0) {
               //如果上滑动,并且滑向动的绝对值距离在超过calendarHight-defaultStopHeight
               // 并且小于可以滑动的距离calendarHight-calendarItemHight之间的话
               <span class="hljs-keyword">if</span> (abstransY &gt;= (calendarMonthHight - defaultStopHeight) &amp;&amp; abstransY &lt; calendarMonthHight - calendarItemHight) {
                   <span class="hljs-keyword">if</span> (!animatStart) {
                       mCalendarViewMonth.setTranslationY(getOffset((int) mCalendarViewMonth.getTranslationY() + dy, calendarItemHight - defaultStopHeight));
                   }
               }
           }
           <span class="hljs-keyword">if</span> (dy &gt; 0) {
               <span class="hljs-keyword">if</span> (abstransY &lt; maxOffset
                       &amp;&amp; currentMode.equals(Mode.WEEK)) {
                   mCalendarViewWeek.setVisibility(INVISIBLE);
               }
               <span class="hljs-keyword">if</span> (abstransY &lt; 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()里面设置相关的回调处理,例如日期选中或者月份切换的回调等。

    好的大工告成。

    转自:https://juejin.im/post/5a631efd6fb9a01ca8720f80

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值