Flipboard bottomsheet源码分析

Flipboard的bottomsheet源码地址https://github.com/Flipboard/bottomsheet。

bottomsheet中最重要的是BottomSheetLayout,其实它是个自定义的ViewGroup,继承于FrameLayout。查看文档和bottomsheet-sample里的例子,调用入口都是showWithSheetView方法,如PickerActivity中使用

bottomSheetLayout.showWithSheetView(intentPickerSheet);

intentPickerSheet就是从底部弹出的View,在Module bottomsheet-commons中,Flipboard给了几个封装好的View,适用于相关场景,比如选择图片,选择Menu等。本文重点是分析BottomSheetLayout

在从入口方法showWithSheetView分析前,先说明两个问题,一个是BottomSheetLayout的状态,另一个是BottomSheetLayout的布局结构。
一、BottomSheetLayout状态说明
BottomSheetLayout根据弹窗的状态,分为四个状态:

public enum State {
        HIDDEN,\\隐藏
        PREPARING,\\准备中,唯一出现时机就是调用showWithSheetView开始时
        PEEKED,\\弹出
        EXPANDED\\见下面的说明
    }

(在BottomSheetLayout中,弹出的View定义为sheetView,BottomSheetLayout会定义一个默认的弹出高度peekKeyline,如果弹出的sheetView高度大于peekKeyline,并且显示的高度大于peekKeyline,则State为EXPANDED。)
peekKeyline的定义如下:

peekKeyline = point.y - (screenWidth / (16.0f / 9.0f));\\point.y为屏幕的高度

二、BottomSheetLayout布局结构

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中调用setContentView,然后inflate布局文件,BottomSheetLayout是个ViewGroup,inflate的时候调用

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
}

下面进入正题,开始分析showWithSheetView(与isTablet相关的代码可以不用管,我们主要是针对手机平台,而不是平板电脑)。
三、showWithSheetView流程分析
代码太长,有些没贴,最好对照源码看。
showWithSheetView有几个重载方法,最终走到

 public void showWithSheetView(final View sheetView, final ViewTransformer viewTransformer) ,

如果用户没有提供自定义的ViewTransformer,BottomSheetLayout就使用默认的BaseViewTransformer,功能很简单,就是根据弹出高度计算dimView的透明度,代码不贴了,看看就知道了。
进入showWithSheetView方法,首先走到

if(state!=State.HIDDEN){
    Runnable runAfterDismissThis=new Runnable(){
        @Override
        public void run(){
            showWithSheetView(sheetView,viewTransformer);
        }
    };
    dismissSheet(runAfterDismissThis);
    return;
}

在调用showWithSheetView前,BottomSheetLayout可能不是HIDDEN状态,这段代码就是处理这种情况,看名字runAfterDismissThis就是知道,先调用dismissSheet隐藏sheetView,在dismissSheet动画结束时,再调用runAfterDismissThis,相关代码在dismissSheet里监听动画结束的地方onAnimationEnd

setState(State.HIDDEN);\\改变状态为HIDDEN
setSheetLayerTypeIfEnabled(LAYER_TYPE_NONE);\\关闭硬件加速
removeView(sheetView);\\隐藏动画结束sheetView被remove了
if (runAfterDismiss != null) {
        runAfterDismiss.run();
        runAfterDismiss = null;
}

然后是设置BottomSheetLayout状态是PREPARING,接着一段是设置sheetView的LayoutParams,然后调用addView把sheetView加入到BottomSheetLayout,之前说BottomSheetLayout布局结构时说过,当然最开始sheetView位置设置到了BottomSheetLayout的底部不可见,通过initializeSheetValues方法中的

 getSheetView().setTranslationY(getHeight());

位置都设置好了,开始动画,之所以post方式开始动画,是要sheetView已经画过在执行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上画属性动画,设置BottomSheetLayout的状态为PEEKED。当然这里有个属性动画怎么画的问题,wrap了一层,可以参考代码中的SHEET_TRANSLATION,这里不分析了。
总结一下,showWithSheetView方法主要功能就是add sheetView到BottomSheetLayout后开始弹出动画。

以后再分析BottomSheetLayout比较有亮点的功能,可拖动sheetView。

你可能感兴趣的:(Flipboard bottomsheet源码分析)