前言

这个日历是一个仿MIUI交互的日历,尽可能实现MIUI日历的交互设计,加入了一些自定义属性,如设置默认视图,设置一周的第一天是周日还是周一等。这个日历是在之前我写的那个日历基础上改的,里面的关于绘制的部分和之前是一样的,这篇文章就不再说了,这次主要说一下怎么实现miui日历的滑动效果。

效果图

项目地址

日历实现思路

1、视图切换。周日历显示的位置是不变的,一直都在布局的顶部,可以固定在顶部,NCalendar类主要控制月日历MonthCalendar和NestedScrollingChild,随着滑动位置的变化控制周日历的显示和隐藏。

2、滑动的处理。计算不同选中日期月日历MonthCalendar和NestedScrollingChild的子类所需要滑动的距离,使用View的offsetTopAndBottom(int offset)方法完成滑动。

具体实现

1、初始化NCalendar类

在NCalendar类的构造方法中,首先new了一个月日历MonthCalendar和一个周日历WeekCalendar,并确定这两个日历的高度,月日历的高度可以通过自定义属性设置,默认为300dp,周日历则为月日历的五分之一,然后把月日历MonthCalendar和周日历WeekCalendar通过addView方法添加到NCalendar中,在onLayout中排列各自的位置。代码public NCalendar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);

monthCalendar = new MonthCalendar(context, attrs);

weekCalendar = new WeekCalendar(context, attrs);

weekHeigh = monthHeigh / 5;

monthCalendar.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, monthHeigh));

weekCalendar.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, weekHeigh));

addView(monthCalendar);

addView(weekCalendar);

post(new Runnable() {            @Override

public void run() {

weekCalendar.setVisibility(STATE == MONTH ? INVISIBLE : VISIBLE);

}

});

}

实际项目中,构造方法中还有设置默认视图,初始化动画等,这里只贴出来关键代码。

2、onNestedPreScroll

NCalendar实现了NestedScrollingParent,主要的交互都在这两个方法中完成,onNestedPreScroll和onStopNestedScroll,前一个是嵌套滑动时调用,后一个是停止滑动的回调。我们需要在onNestedPreScroll中完成上滑和下滑时,改变月日历MonthCalendar和NestedScrollingChild的位置,在onStopNestedScroll中完成手指离开后自动滑动。miui日历的上滑操作是先让月日历滑动到选中的选中的那一行,再向上移动NestedScrollingChild,下滑时也是先移动月日历,再移动NestedScrollingChild。NClendar在处理这个滑动时分了四种情况:月日历和NestedScrollingChild同时上滑。

月日历上滑到一定位置后,NestedScrollingChild单独上滑。

月日历和NestedScrollingChild同时下滑。

月日历下滑到一定位置后,NestedScrollingChild单独下滑。

这四种情况判断的条件就是月日历和NestedScrollingChild距离顶部的位置。NestedScrollingChild距顶部距离的判断比较简单,月视图时,距离就是月日历的高度,周视图时就是周日历的高度。月日历距顶部的距离稍微复杂一下,需要先计算出来月日历总共需要滑动的距离monthCalendarOffset,再通过offsetTopAndBottom移动,移动的时候,不断获取月日历的getTop(),当getTop()达到总的偏移量时,就说明月日历已经移动到指定位置,接下来就是NestedScrollingChild单独滑动。上滑下滑都是这个逻辑。代码:@Override    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {        int monthTop = monthCalendar.getTop();        int nestedScrollingChildTop = nestedScrollingChild.getTop();

monthCalendarOffset = getMonthCalendarOffset();        //4种情况

if (dy > 0 && Math.abs(monthTop)

int offset = getOffset(dy, monthCalendarOffset - Math.abs(monthTop));

monthCalendar.offsetTopAndBottom(-offset);

nestedScrollingChild.offsetTopAndBottom(-offset);

consumed[1] = dy;

} else if (dy > 0 && nestedScrollingChildTop > weekHeigh) {            //月日历滑动到位置后,nestedScrollingChild继续上滑,覆盖一部分月日历

int offset = getOffset(dy, nestedScrollingChildTop - weekHeigh);

nestedScrollingChild.offsetTopAndBottom(-offset);

consumed[1] = dy;

} else if (dy

int offset = getOffset(Math.abs(dy), Math.abs(monthTop));

monthCalendar.offsetTopAndBottom(offset);

nestedScrollingChild.offsetTopAndBottom(offset);

consumed[1] = dy;

} else if (dy

int offset = getOffset(Math.abs(dy), monthHeigh - nestedScrollingChildTop);

nestedScrollingChild.offsetTopAndBottom(offset);

consumed[1] = dy;

}        //nestedScrollingChild滑动到周位置后,标记状态,同时周日显示

if (nestedScrollingChildTop == weekHeigh) {

STATE = WEEK;

weekCalendar.setVisibility(VISIBLE);

}        //周状态,下滑显示月日历,把周日历隐掉

if (STATE == WEEK && dy

weekCalendar.setVisibility(INVISIBLE);

}        //彻底滑到月日历,标记状态

if (nestedScrollingChildTop == monthHeigh) {

STATE = MONTH;

}

}

根据需求,需要判断NestedScrollingChild的条目已经不能再滑动时才移动NestedScrollingChild本身。在滑动过程中,要标记当前视图的状态 MONTH 或者WEEK。其中计算monthCalendarOffset的方法://月日历需要滑动的距离,

private int getMonthCalendarOffset() {

NMonthView currectMonthView = monthCalendar.getCurrectMonthView();        //该月有几行

int rowNum = currectMonthView.getRowNum();        //现在选中的是第几行

int selectRowIndex = currectMonthView.getSelectRowIndex();        //month需要移动selectRowIndex*h/rowNum ,计算时依每个行高的中点计算

int monthCalendarOffset = selectRowIndex * currectMonthView.getDrawHeight() / rowNum;        return monthCalendarOffset;

}

计算方法是,得到当前月的View NMonthView ,再通过该月的行数(5或6)、被选中的日期在哪一行以及NMonthView 的绘制高度算出月日历需要移动的距离。月日历的绘制高度和月日历的高度不是一个数值,因为当月份有6行时,公历日期绘制在一行的中间位置,下面的农历就没有太多的地方绘制,在最后一行的农历就会和月日历底部非常接而影响美观,为了避免这种情况,日历View绘制的时候,把绘制高度比日历高度小了一点,这里需要计算的移动量是由绘制区域的行高决定。

3、onStopNestedScroll

这个方法处理自动滑动的问题。在滑动过程中如果松手,日历要自动回到对应的位置,对应的位置就是说,滑动的距离小时,还回到原来的位置,滑动的距离大时,回到相反的位置。这里的动画用的是ValueAnimator,在初始化NCalendar时,new了两个ValueAnimator对象,在onStopNestedScroll回调时,分别给他们的起始值和结束值,再通过动画中得到的getAnimatedValue值,计算偏移量,执行offsetTopAndBottom方法,完成动画。@Override

public void onStopNestedScroll(View target) {         //停止滑动的时候,距顶部的距离

int monthCalendarTop = monthCalendar.getTop();        int nestedScrollingChildTop = nestedScrollingChild.getTop();        if (monthCalendarTop == 0 && nestedScrollingChildTop == monthHeigh) {            return;

}        if (monthCalendarTop == -monthCalendarOffset && nestedScrollingChildTop == weekHeigh) {            return;

}        if (STATE == MONTH) {        //nestedScrollingChild移动的超过周高度时才会滑动到周

if (monthHeigh - nestedScrollingChildTop

autoScroll(monthCalendarTop, 0, nestedScrollingChildTop, monthHeigh);

} else {

autoScroll(monthCalendarTop, -monthCalendarOffset, nestedScrollingChildTop, weekHeigh);

}

} else {          //nestedScrollingChild移动的超过周高度时才会滑动到月

if (nestedScrollingChildTop

autoScroll(monthCalendarTop, -monthCalendarOffset, nestedScrollingChildTop, weekHeigh);

} else {

autoScroll(monthCalendarTop, 0, nestedScrollingChildTop, monthHeigh);

}

}

}

autoScroll方法://自动滑动

private void autoScroll(int startMonth, int endMonth, int startChild, int endChild) {

monthValueAnimator.setIntValues(startMonth, endMonth);

monthValueAnimator.setDuration(duration);

monthValueAnimator.start();

nestedScrollingChildValueAnimator.setIntValues(startChild, endChild);

nestedScrollingChildValueAnimator.setDuration(duration);

nestedScrollingChildValueAnimator.start();

}

ValueAnimator动画的回调:@Override

public void onAnimationUpdate(ValueAnimator animation) {        if (animation == monthValueAnimator) {            int animatedValue = (int) animation.getAnimatedValue();            int top = monthCalendar.getTop();            int i = animatedValue - top;

monthCalendar.offsetTopAndBottom(i);

}        if (animation == nestedScrollingChildValueAnimator) {            int animatedValue = (int) animation.getAnimatedValue();            int top = nestedScrollingChild.getTop();            int i = animatedValue - top;

nestedScrollingChild.offsetTopAndBottom(i);

}

}

到此,交互的部分就结束了。

其他问题

写完 以上这些,这个日历算是基本完工,但是还是有不少bug的,其他的问题,就在写的过程中解决的一些bug。

1、滑动过快的问题

快速滑动nestedScrollingChild时,有时会出现不友好的情况,这有两个地方做了限制。

一个是NestedScrollingParent中的onNestedPreFling方法:@Override

public boolean onNestedPreFling(View target, float velocityX, float velocityY) {        //防止快速滑动

int nestedScrollingChildTop = nestedScrollingChild.getTop();        if (nestedScrollingChildTop > weekHeigh) {            return true;

}        return false;

}

上面的方法主要限制nestedScrollingChild的快速滑动。还有一个地方是月日历的快速移动,是滑动到边界的问题:private int getOffset(int offset, int maxOffset) {        if (offset > maxOffset) {            return maxOffset;

}        return offset;

}

这个方法是获取偏移量的时候,如果得到的数值大于需要的最大值,则返回最大值,防止出现view越界的情况。

2、翻页闪烁的问题 onLayout

这个问题,是当滑动到相应位置后,左右翻页月日历,会出现月日历返回到原来移动之前的位置上,造成闪烁,并且此时位置也乱了。这时就需要在onLayout重新确定月日历和nestedScrollingChild的位置,需要在每次操作之后执行requestLayout()方法:@Override

protected void onLayout(boolean changed, int l, int t, int r, int b) {        //  super.onLayout(changed, l, t, r, b);

if (STATE == MONTH) {

monthCalendarTop = monthCalendar.getTop();

childViewTop = nestedScrollingChild.getTop() == 0 ? monthHeigh : nestedScrollingChild.getTop();

} else {

monthCalendarTop = -getMonthCalendarOffset();

childViewTop = nestedScrollingChild.getTop() == 0 ? weekHeigh : nestedScrollingChild.getTop();

}

monthCalendar.layout(0, monthCalendarTop, r, monthHeigh + monthCalendarTop);

ViewGroup.LayoutParams layoutParams = nestedScrollingChild.getLayoutParams();

nestedScrollingChild.layout(0, childViewTop, r, layoutParams.height + childViewTop);

}

3、滑动到周日历底部空白的问题 onMeasure

onMeasure的问题,当日历滑动到周日历的之后,NestedScrollingChild下方会出现空白,这个空白是由于NestedScrollingChild上移造成的,因为NestedScrollingChild高度一定,上移以后,下面没有东西了自然就会留空。我们可以把NestedScrollingChild的高度变高,这样上滑之后,之前没有显示的那部分就会显示出来,就不会有空白了。观察之后会发现,NestedScrollingChild移动到周日历的位置后,整个View上面是周日历,下面是NestedScrollingChild,所以我们可以把NestedScrollingChild的高度变成整个View的高度和周日历高度之差,这样就可以了:@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

ViewGroup.LayoutParams layoutParams = nestedScrollingChild.getLayoutParams();

layoutParams.height = getMeasuredHeight() - weekHeigh;

}

至此,这个日历算是完成了。

android 日历动画效果,Android仿 MIUI日历相关推荐

  1. android 圆动画效果,Android实现任意绕圆或椭圆旋转的动画——SatelliteAnimator使用介绍...

    话说实习也就快一个月了,虽然没干什么活,但是这几天总算是有一些可以写的东西. 代码中应该还存在很多问题要修改,大神们请赐教,不胜感激. 开始正题. 关于Android实现任意绕圆或椭圆旋转动画,我称之 ...

  2. android下雨动画效果,Android 自定义View(二) 下雨效果

    Rain.gif Android 自定义View(二) 下雨效果 一 实现思路, 雨点用线段表示,通过控制线段的大小和宽度来表示不同的线段. 一个雨点下雨的过程可以表示为一条直线,一次雨点在下雨的过程 ...

  3. android解锁动画效果,Android 5秒学会使用手势解锁功能

    Android手势解锁 本文讲述的是一个手势解锁的库,可以定制显示隐藏宫格点.路径.并且带有小九宫格显示图,和震动!让你学会使用这个简单,高效的库! 先来一波效果效果展示: 手势解锁效果 今天给大家介 ...

  4. android解锁动画效果,android 关于自定义解锁动画和实现全屏的解决方案

    先说说缘由吧,最近项目要研究在亮屏的时候用展示一个开屏动画,我一想这个不难吧,截获亮屏的广播事件 Intent.ACTION_SCREEN_ON然后处理对应的事件就好了,但我发现好像没有正确的api可 ...

  5. android解锁动画效果,Android开发学习——Day24(火焰燃烧和蒙眼解锁界面动画:关键帧动画和补间动画)...

    学习目的 1.学习使用两类动画:关键帧动画和补间动画 2.运用上述两类动画进行实战练习:火焰燃烧和蒙眼睛解锁 学习过程 认识两类动画的意义及其使用的基本操作,进行实战运用. 技术 一.关键帧动画&am ...

  6. android下雨动画效果,Android 自定义View之下雨动画

    开始前先做个热身( ˘•灬•˘ ) 自己实现比较容易,但是到了要出博客整理思路,总结要点的时候就挠头,不知云所以,所以最简单的还是 如果对安卓UI有兴趣的朋友可以加我好友互相探讨, 思路 思路比较简单 ...

  7. android 卷轴动画效果,Android自定义View-卷轴

    0. 今天是母亲节,谨以此文献和此程序给我的母亲,节日快乐. 话不多说,先看效果 image 附上git地址 Github 1. 想好再动笔,整个卷轴有左右卷轴柄,纸和文字组成,默认卷轴关闭,点击是张 ...

  8. Android 吸入动画效果详解(仿mac退出效果)

    转载自:http://m.blog.csdn.net/blog/leehong2005/9127095 [转]Android 吸入动画效果详解 1,背景 吸入(Inhale)效果,最初我是在iOS上面 ...

  9. Android m 自定义下拉菜单,Android实现动画效果的自定义下拉菜单功能

    我们在购物APP里面设置收货地址时,都会有让我们选择省份及城市的下拉菜单项.今天我将使用Android原生的 Spinner 控件来实现一个自定义的下拉菜单功能,并配上一个透明渐变动画效果. 要实现的 ...

最新文章

  1. 把图片做成html,HTML5实践-图片设置成灰度图
  2. 59 MM配置-后勤发票校验-维护税代码缺省值
  3. 机会难得 | 这家上市公司终于招人了
  4. python读取excel(xlrd)
  5. 标签布局Tab与TabHost详细教程
  6. 【嵌入式】牧马人G3 电子竞技鼠标拆解分析
  7. Java 堆排序(大根堆及小根堆)
  8. matlab求稳定时间ts,一阶方程调节时间ts
  9. python产生一个1到10的列表_python-列表生成式(一)
  10. 九个小众实用设计预览网站
  11. 寻找java项目/兼职
  12. Java编程语言学习:Java语言编程的基础知识之Java的变量与数据类型、符号、运算符、数组Array总结之详细攻略
  13. jq简单实现五星好评
  14. 以前写过的ajax基础案例(王欢-huanhuan)
  15. 两年多里自己都干了什么?
  16. 福州大学计算机与科学学院,1:福州大学数学与计算机科学学院
  17. Cocos2d-x制作跳棋第三步:棋子动作实现及AI算法思想
  18. 烽火fr2600怎么web登录_烽火R2600(R2640)路由器配置
  19. 暗影精灵3安装无线网卡驱动(ubuntu16.04)
  20. xlite(eyebeam)contact带的rinstance参数

热门文章

  1. Java小白入门总结
  2. 重心和面积以及坐标的关系
  3. MSP430F5xx / F6xx系列 DCO频率范围选择方法
  4. 硬盘测试工具 CrystalDiskMark和Crystaldiskinfo
  5. 转录组分析的正确姿势你了解了吗?
  6. 一分钟教你如何在没有网络的电脑上安装VsCode插件
  7. 游戏服务器框架之跨服(二)
  8. 二手车电商大战拐点:技术才是角逐的关键
  9. word双引号间距大_Word 2013双引号的BUG
  10. 威联通NAS使用容器创建harbor镜像仓库