美文网首页Android项目
Flipboard bottomsheet源码分析

Flipboard bottomsheet源码分析

作者: andev009 | 来源:发表于2017-12-30 16:13 被阅读169次

    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源码分析

        本文链接:https://www.haomeiwen.com/subject/eixngxtx.html