Flipboard的bottomsheet源碼地址https://github.com/Flipboard/bottomsheet康嘉。
bottomsheet中最重要的是BottomSheetLayout段磨,其實它是個自定義的ViewGroup际歼,繼承于FrameLayout术羔。查看文檔和bottomsheet-sample里的例子,調(diào)用入口都是showWithSheetView方法寞焙,如PickerActivity中使用
bottomSheetLayout.showWithSheetView(intentPickerSheet);
intentPickerSheet就是從底部彈出的View,在Module bottomsheet-commons中储狭,F(xiàn)lipboard給了幾個封裝好的View,適用于相關(guān)場景捣郊,比如選擇圖片辽狈,選擇Menu等。本文重點是分析BottomSheetLayout模她。
在從入口方法showWithSheetView分析前,先說明兩個問題懂牧,一個是BottomSheetLayout的狀態(tài)侈净,另一個是BottomSheetLayout的布局結(jié)構(gòu)。
一僧凤、BottomSheetLayout狀態(tài)說明
BottomSheetLayout根據(jù)彈窗的狀態(tài)畜侦,分為四個狀態(tài):
public enum State {
HIDDEN,\\隱藏
PREPARING,\\準(zhǔn)備中,唯一出現(xiàn)時機(jī)就是調(diào)用showWithSheetView開始時
PEEKED,\\彈出
EXPANDED\\見下面的說明
}
(在BottomSheetLayout中躯保,彈出的View定義為sheetView,BottomSheetLayout會定義一個默認(rèn)的彈出高度peekKeyline旋膳,如果彈出的sheetView高度大于peekKeyline,并且顯示的高度大于peekKeyline途事,則State為EXPANDED验懊。)
peekKeyline的定義如下:
peekKeyline = point.y - (screenWidth / (16.0f / 9.0f));\\point.y為屏幕的高度
二擅羞、BottomSheetLayout布局結(jié)構(gòu)
1.BottomSheetLayout是根布局,布局文件中BottomSheetLayout包含的View為第一個childView
2.dimView是BottomSheetLayout的第二個childView义图,dimView就是那個黑色半透明背景减俏,代碼定義就是 dimView = new View(getContext());定義透明而已
3.sheetView在最上面,是BottomSheetLayout的第三個childView,sheetView就是彈出的View碱工,上面說了
那么問題來了娃承,dimView和sheetView是怎么加入到BottomSheetLayout的?
從源頭看怕篷,在Activity的onCreate中調(diào)用setContentView历筝,然后inflate布局文件,BottomSheetLayout是個ViewGroup,inflate的時候調(diào)用
viewGroup.addView(view, params);
BottomSheetLayout重寫了addView方法廊谓,
@Override
public void addView(@NonNull View child) {
if (getChildCount() > 0) {
throw new IllegalArgumentException("You may not declare more
then one child of bottom sheet. The sheet view must be added
dynamically with showWithSheetView()");
}
setContentView(child);
}
public void setContentView(View contentView) {
super.addView(contentView, -1, generateDefaultLayoutParams());\\第一個childView
super.addView(dimView, -1, generateDefaultLayoutParams());\\第二個childView
}
dimView被add了梳猪,那sheetView是什么時候add呢?答案在showWithSheetView里蹂析。showWithSheetView里有這樣一句
super.addView(sheetView, -1, params);
這樣之后的代碼中獲得sheetView的方法就容易看懂了
public View getSheetView() {
return getChildCount() > 2 ? getChildAt(2) : null;\\直接取第三個childView
}
下面進(jìn)入正題舔示,開始分析showWithSheetView(與isTablet相關(guān)的代碼可以不用管,我們主要是針對手機(jī)平臺电抚,而不是平板電腦)惕稻。
三、showWithSheetView流程分析
代碼太長蝙叛,有些沒貼俺祠,最好對照源碼看。
showWithSheetView有幾個重載方法借帘,最終走到
public void showWithSheetView(final View sheetView, final ViewTransformer viewTransformer) 蜘渣,
如果用戶沒有提供自定義的ViewTransformer,BottomSheetLayout就使用默認(rèn)的BaseViewTransformer肺然,功能很簡單蔫缸,就是根據(jù)彈出高度計算dimView的透明度,代碼不貼了际起,看看就知道了拾碌。
進(jìn)入showWithSheetView方法,首先走到
if(state!=State.HIDDEN){
Runnable runAfterDismissThis=new Runnable(){
@Override
public void run(){
showWithSheetView(sheetView,viewTransformer);
}
};
dismissSheet(runAfterDismissThis);
return;
}
在調(diào)用showWithSheetView前街望,BottomSheetLayout可能不是HIDDEN狀態(tài)校翔,這段代碼就是處理這種情況,看名字runAfterDismissThis就是知道,先調(diào)用dismissSheet隱藏sheetView,在dismissSheet動畫結(jié)束時灾前,再調(diào)用runAfterDismissThis防症,相關(guān)代碼在dismissSheet里監(jiān)聽動畫結(jié)束的地方onAnimationEnd
setState(State.HIDDEN);\\改變狀態(tài)為HIDDEN
setSheetLayerTypeIfEnabled(LAYER_TYPE_NONE);\\關(guān)閉硬件加速
removeView(sheetView);\\隱藏動畫結(jié)束sheetView被remove了
if (runAfterDismiss != null) {
runAfterDismiss.run();
runAfterDismiss = null;
}
然后是設(shè)置BottomSheetLayout狀態(tài)是PREPARING,接著一段是設(shè)置sheetView的LayoutParams,然后調(diào)用addView把sheetView加入到BottomSheetLayout,之前說BottomSheetLayout布局結(jié)構(gòu)時說過蔫敲,當(dāng)然最開始sheetView位置設(shè)置到了BottomSheetLayout的底部不可見饲嗽,通過initializeSheetValues方法中的
getSheetView().setTranslationY(getHeight());
位置都設(shè)置好了,開始動畫燕偶,之所以post方式開始動畫喝噪,是要sheetView已經(jīng)畫過在執(zhí)行peekSheet。
getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(this);
post(new Runnable() {
@Override
public void run() {
// Make sure sheet view is still here when first draw happens.
// In the case of a large lag it could be that the view is dismissed before it is drawn resulting in sheet view being null here.
if (getSheetView() != null) {
peekSheet();
}
}
});
return true;
}
上面一段指么,主要是走到peekSheet方法酝惧,開始動畫。最下面的sheetViewOnLayoutChangeListener先不用管伯诬。
peekSheet代碼如下:
public void peekSheet() {
cancelCurrentAnimation();
setSheetLayerTypeIfEnabled(LAYER_TYPE_HARDWARE);
ObjectAnimator anim = ObjectAnimator.ofFloat(this, SHEET_TRANSLATION, getPeekSheetTranslation());
anim.setDuration(ANIMATION_DURATION);
anim.setInterpolator(animationInterpolator);
anim.addListener(new CancelDetectionAnimationListener() {
@Override
public void onAnimationEnd(@NonNull Animator animation) {
if (!canceled) {
currentAnimator = null;
}
}
});
anim.start();
currentAnimator = anim;
setState(State.PEEKED);
}
peekSheet的功能就是啟用硬件加速在sheetView上畫屬性動畫晚唇,設(shè)置BottomSheetLayout的狀態(tài)為PEEKED。當(dāng)然這里有個屬性動畫怎么畫的問題盗似,wrap了一層哩陕,可以參考代碼中的SHEET_TRANSLATION,這里不分析了赫舒。
總結(jié)一下悍及,showWithSheetView方法主要功能就是add sheetView到BottomSheetLayout后開始彈出動畫。
以后再分析BottomSheetLayout比較有亮點的功能接癌,可拖動sheetView心赶。