android 日历动画效果,Android仿 MIUI日历
前言
這個(gè)日歷是一個(gè)仿MIUI交互的日歷,盡可能實(shí)現(xiàn)MIUI日歷的交互設(shè)計(jì),加入了一些自定義屬性,如設(shè)置默認(rèn)視圖,設(shè)置一周的第一天是周日還是周一等。這個(gè)日歷是在之前我寫的那個(gè)日歷基礎(chǔ)上改的,里面的關(guān)于繪制的部分和之前是一樣的,這篇文章就不再說了,這次主要說一下怎么實(shí)現(xiàn)miui日歷的滑動(dòng)效果。
效果圖
項(xiàng)目地址
日歷實(shí)現(xiàn)思路
1、視圖切換。周日歷顯示的位置是不變的,一直都在布局的頂部,可以固定在頂部,NCalendar類主要控制月日歷MonthCalendar和NestedScrollingChild,隨著滑動(dòng)位置的變化控制周日歷的顯示和隱藏。
2、滑動(dòng)的處理。計(jì)算不同選中日期月日歷MonthCalendar和NestedScrollingChild的子類所需要滑動(dòng)的距離,使用View的offsetTopAndBottom(int offset)方法完成滑動(dòng)。
具體實(shí)現(xiàn)
1、初始化NCalendar類
在NCalendar類的構(gòu)造方法中,首先new了一個(gè)月日歷MonthCalendar和一個(gè)周日歷WeekCalendar,并確定這兩個(gè)日歷的高度,月日歷的高度可以通過自定義屬性設(shè)置,默認(rèn)為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);
}
});
}
實(shí)際項(xiàng)目中,構(gòu)造方法中還有設(shè)置默認(rèn)視圖,初始化動(dòng)畫等,這里只貼出來關(guān)鍵代碼。
2、onNestedPreScroll
NCalendar實(shí)現(xiàn)了NestedScrollingParent,主要的交互都在這兩個(gè)方法中完成,onNestedPreScroll和onStopNestedScroll,前一個(gè)是嵌套滑動(dòng)時(shí)調(diào)用,后一個(gè)是停止滑動(dòng)的回調(diào)。我們需要在onNestedPreScroll中完成上滑和下滑時(shí),改變?cè)氯諝vMonthCalendar和NestedScrollingChild的位置,在onStopNestedScroll中完成手指離開后自動(dòng)滑動(dòng)。miui日歷的上滑操作是先讓月日歷滑動(dòng)到選中的選中的那一行,再向上移動(dòng)NestedScrollingChild,下滑時(shí)也是先移動(dòng)月日歷,再移動(dòng)NestedScrollingChild。NClendar在處理這個(gè)滑動(dòng)時(shí)分了四種情況:月日歷和NestedScrollingChild同時(shí)上滑。
月日歷上滑到一定位置后,NestedScrollingChild單獨(dú)上滑。
月日歷和NestedScrollingChild同時(shí)下滑。
月日歷下滑到一定位置后,NestedScrollingChild單獨(dú)下滑。
這四種情況判斷的條件就是月日歷和NestedScrollingChild距離頂部的位置。NestedScrollingChild距頂部距離的判斷比較簡(jiǎn)單,月視圖時(shí),距離就是月日歷的高度,周視圖時(shí)就是周日歷的高度。月日歷距頂部的距離稍微復(fù)雜一下,需要先計(jì)算出來月日歷總共需要滑動(dòng)的距離monthCalendarOffset,再通過offsetTopAndBottom移動(dòng),移動(dòng)的時(shí)候,不斷獲取月日歷的getTop(),當(dāng)getTop()達(dá)到總的偏移量時(shí),就說明月日歷已經(jīng)移動(dòng)到指定位置,接下來就是NestedScrollingChild單獨(dú)滑動(dòng)。上滑下滑都是這個(gè)邏輯。代碼:@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)?{????????????//月日歷滑動(dòng)到位置后,nestedScrollingChild繼續(xù)上滑,覆蓋一部分月日歷
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滑動(dòng)到周位置后,標(biāo)記狀態(tài),同時(shí)周日顯示
if?(nestedScrollingChildTop?==?weekHeigh)?{
STATE?=?WEEK;
weekCalendar.setVisibility(VISIBLE);
}????????//周狀態(tài),下滑顯示月日歷,把周日歷隱掉
if?(STATE?==?WEEK?&&?dy?
weekCalendar.setVisibility(INVISIBLE);
}????????//徹底滑到月日歷,標(biāo)記狀態(tài)
if?(nestedScrollingChildTop?==?monthHeigh)?{
STATE?=?MONTH;
}
}
根據(jù)需求,需要判斷NestedScrollingChild的條目已經(jīng)不能再滑動(dòng)時(shí)才移動(dòng)NestedScrollingChild本身。在滑動(dòng)過程中,要標(biāo)記當(dāng)前視圖的狀態(tài)?MONTH?或者WEEK。其中計(jì)算monthCalendarOffset的方法://月日歷需要滑動(dòng)的距離,
private?int?getMonthCalendarOffset()?{
NMonthView?currectMonthView?=?monthCalendar.getCurrectMonthView();????????//該月有幾行
int?rowNum?=?currectMonthView.getRowNum();????????//現(xiàn)在選中的是第幾行
int?selectRowIndex?=?currectMonthView.getSelectRowIndex();????????//month需要移動(dòng)selectRowIndex*h/rowNum?,計(jì)算時(shí)依每個(gè)行高的中點(diǎn)計(jì)算
int?monthCalendarOffset?=?selectRowIndex?*?currectMonthView.getDrawHeight()?/?rowNum;????????return?monthCalendarOffset;
}
計(jì)算方法是,得到當(dāng)前月的View?NMonthView?,再通過該月的行數(shù)(5或6)、被選中的日期在哪一行以及NMonthView?的繪制高度算出月日歷需要移動(dòng)的距離。月日歷的繪制高度和月日歷的高度不是一個(gè)數(shù)值,因?yàn)楫?dāng)月份有6行時(shí),公歷日期繪制在一行的中間位置,下面的農(nóng)歷就沒有太多的地方繪制,在最后一行的農(nóng)歷就會(huì)和月日歷底部非常接而影響美觀,為了避免這種情況,日歷View繪制的時(shí)候,把繪制高度比日歷高度小了一點(diǎn),這里需要計(jì)算的移動(dòng)量是由繪制區(qū)域的行高決定。
3、onStopNestedScroll
這個(gè)方法處理自動(dòng)滑動(dòng)的問題。在滑動(dòng)過程中如果松手,日歷要自動(dòng)回到對(duì)應(yīng)的位置,對(duì)應(yīng)的位置就是說,滑動(dòng)的距離小時(shí),還回到原來的位置,滑動(dòng)的距離大時(shí),回到相反的位置。這里的動(dòng)畫用的是ValueAnimator,在初始化NCalendar時(shí),new了兩個(gè)ValueAnimator對(duì)象,在onStopNestedScroll回調(diào)時(shí),分別給他們的起始值和結(jié)束值,再通過動(dòng)畫中得到的getAnimatedValue值,計(jì)算偏移量,執(zhí)行offsetTopAndBottom方法,完成動(dòng)畫。@Override
public?void?onStopNestedScroll(View?target)?{?????????//停止滑動(dòng)的時(shí)候,距頂部的距離
int?monthCalendarTop?=?monthCalendar.getTop();????????int?nestedScrollingChildTop?=?nestedScrollingChild.getTop();????????if?(monthCalendarTop?==?0?&&?nestedScrollingChildTop?==?monthHeigh)?{????????????return;
}????????if?(monthCalendarTop?==?-monthCalendarOffset?&&?nestedScrollingChildTop?==?weekHeigh)?{????????????return;
}????????if?(STATE?==?MONTH)?{????????//nestedScrollingChild移動(dòng)的超過周高度時(shí)才會(huì)滑動(dòng)到周
if?(monthHeigh?-?nestedScrollingChildTop?
autoScroll(monthCalendarTop,?0,?nestedScrollingChildTop,?monthHeigh);
}?else?{
autoScroll(monthCalendarTop,?-monthCalendarOffset,?nestedScrollingChildTop,?weekHeigh);
}
}?else?{??????????//nestedScrollingChild移動(dòng)的超過周高度時(shí)才會(huì)滑動(dòng)到月
if?(nestedScrollingChildTop?
autoScroll(monthCalendarTop,?-monthCalendarOffset,?nestedScrollingChildTop,?weekHeigh);
}?else?{
autoScroll(monthCalendarTop,?0,?nestedScrollingChildTop,?monthHeigh);
}
}
}
autoScroll方法://自動(dòng)滑動(dòng)
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動(dòng)畫的回調(diào):@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);
}
}
到此,交互的部分就結(jié)束了。
其他問題
寫完 以上這些,這個(gè)日歷算是基本完工,但是還是有不少bug的,其他的問題,就在寫的過程中解決的一些bug。
1、滑動(dòng)過快的問題
快速滑動(dòng)nestedScrollingChild時(shí),有時(shí)會(huì)出現(xiàn)不友好的情況,這有兩個(gè)地方做了限制。
一個(gè)是NestedScrollingParent中的onNestedPreFling方法:@Override
public?boolean?onNestedPreFling(View?target,?float?velocityX,?float?velocityY)?{????????//防止快速滑動(dòng)
int?nestedScrollingChildTop?=?nestedScrollingChild.getTop();????????if?(nestedScrollingChildTop?>?weekHeigh)?{????????????return?true;
}????????return?false;
}
上面的方法主要限制nestedScrollingChild的快速滑動(dòng)。還有一個(gè)地方是月日歷的快速移動(dòng),是滑動(dòng)到邊界的問題:private?int?getOffset(int?offset,?int?maxOffset)?{????????if?(offset?>?maxOffset)?{????????????return?maxOffset;
}????????return?offset;
}
這個(gè)方法是獲取偏移量的時(shí)候,如果得到的數(shù)值大于需要的最大值,則返回最大值,防止出現(xiàn)view越界的情況。
2、翻頁(yè)閃爍的問題?onLayout
這個(gè)問題,是當(dāng)滑動(dòng)到相應(yīng)位置后,左右翻頁(yè)月日歷,會(huì)出現(xiàn)月日歷返回到原來移動(dòng)之前的位置上,造成閃爍,并且此時(shí)位置也亂了。這時(shí)就需要在onLayout重新確定月日歷和nestedScrollingChild的位置,需要在每次操作之后執(zhí)行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、滑動(dòng)到周日歷底部空白的問題?onMeasure
onMeasure的問題,當(dāng)日歷滑動(dòng)到周日歷的之后,NestedScrollingChild下方會(huì)出現(xiàn)空白,這個(gè)空白是由于NestedScrollingChild上移造成的,因?yàn)镹estedScrollingChild高度一定,上移以后,下面沒有東西了自然就會(huì)留空。我們可以把NestedScrollingChild的高度變高,這樣上滑之后,之前沒有顯示的那部分就會(huì)顯示出來,就不會(huì)有空白了。觀察之后會(huì)發(fā)現(xiàn),NestedScrollingChild移動(dòng)到周日歷的位置后,整個(gè)View上面是周日歷,下面是NestedScrollingChild,所以我們可以把NestedScrollingChild的高度變成整個(gè)View的高度和周日歷高度之差,這樣就可以了:@Override
protected?void?onMeasure(int?widthMeasureSpec,?int?heightMeasureSpec)?{????????super.onMeasure(widthMeasureSpec,?heightMeasureSpec);
ViewGroup.LayoutParams?layoutParams?=?nestedScrollingChild.getLayoutParams();
layoutParams.height?=?getMeasuredHeight()?-?weekHeigh;
}
至此,這個(gè)日歷算是完成了。
總結(jié)
以上是生活随笔為你收集整理的android 日历动画效果,Android仿 MIUI日历的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: erdas裁剪影像_erdas切割图像步
- 下一篇: 解决某些Android手机人民币符号¥只