android 日历动画效果,Android仿 MIUI日历
前言
這個日歷是一個仿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日历的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: erdas裁剪影像_erdas切割图像步
- 下一篇: PMS安装及配置