zoukankan      html  css  js  c++  java
  • Flipboard-BottomSheetlayout 源码分析

     

    BottomSheetLayout

     

    BottomSheet:Google在API 23中已经加入了这样的一个控件。

    BottomSheet介绍:

      BottomSheet是一个可以从底部飞入和飞出的Android控件,通常是用于替换Dialog和menu(如:分享menu)

     

    这里我介绍的并非是Android的一个这样的控件,而是Flipboard开元的这样的一个三方框架源码;

    首先地址:

      https://github.com/Flipboard/bottomsheet 

    建议大家看这篇博客之前先看下这官方的使用说明Usage,以便更好的理解下面介绍的内容.

    效果图github上边基本都有了这里就不在二次粘图了,不是程序员该做的事.....

    现在咱们就基于这个仓库的三方框架进行介绍:

      导入之后的项目结构截图:

      

      大家可以看到这里三个module(不要问我什么是module,信不信我会打死你....)

    简单说下这三个module的作用啊

      bottomsheet:这里主要说的是BottomSheet.xml,首先它是一个view,继承自Framelayout,你只需给他一个view就可以实现了飞入和飞出效果。

      bottomsheet-commons:见下图红线内容区:底部飞入所需要的内容区域的布局Gridview,Listview,Fragment等也就是需要给bottomsheet的内容view。

      

      bottomsheet-sample:不需要多说你懂得,6666,不知道我也是没办法教你了....

    接下来比较吊了

      照图说话boottomsheet module;

     

     

    先说背景色的实现代码:

    private void init() {
            ............
            
            dimView = new View(getContext());
            dimView.setBackgroundColor(Color.BLACK);
            dimView.setAlpha(0);
            dimView.setVisibility(INVISIBLE);
            .................  
    }    
    

      很明显是个view,透明度默认是0,可见性invisible。so 什么是可见的哪。

        private void setSheetTranslation(float sheetTranslation) {
            this.sheetTranslation = sheetTranslation;
            int bottomClip = (int) (getHeight() - Math.ceil(sheetTranslation));
            this.contentClipRect.set(0, 0, getWidth(), bottomClip);
            getSheetView().setTranslationY(getHeight() - sheetTranslation);
            transformView(sheetTranslation);
            if (shouldDimContentView) {
                float dimAlpha = getDimAlpha(sheetTranslation);
                dimView.setAlpha(dimAlpha);//根据当前平移的位置换算当前view透明度
                dimView.setVisibility(dimAlpha > 0 ? VISIBLE : INVISIBLE);//这里看是否透明度大于0,如果是那么久设置为可见
            }
        }
    

      这里说的是当bottomsheet从底部出现之后,设置背景view可见同事修改透明度,当然透明度是根当前平移的位置换算粗来的。紧接着就是判断是否背景view的透明度大于零,如果是那么就直接设置背景view可见。

      这里还有一个问题这个view是如何添加到BottomSheet中的哪?

      先看一种设置方式:

        /**
         * Set the content view of the bottom sheet. This is the view which is shown under the sheet
         * being presented. This is usually the root view of your application.
         *
         * @param contentView The content view of your application.
         */
        public void setContentView(View contentView) {
            super.addView(contentView, -1, generateDefaultLayoutParams());
            super.addView(dimView, -1, generateDefaultLayoutParams());
        }
    

      这里调用地方法setContentView,如果这种方式的需要你出入一个contentView,当然这里不需要你自己去调用,这里并不是说你不可以自己调用。那么如果不是自己调用那应该怎么调用哪???还是看代码:

    1 @Override
    2     public void addView(@NonNull View child) {
    3         if (getChildCount() > 0) {
    4             throw new IllegalArgumentException("You may not declare more then one child of bottom sheet. The sheet view must be added dynamically with showWithSheetView()");
    5         }
    6         setContentView(child);
    7     }

      代码其实很简单就是封装了一层,最终还是调用了method setContentView方法。不到大家发现了个问题了木,这个是方法是重载的方式,辣么为啥这么做类,其实是因为当BottomSheetLayout被add到一个父布局的时候需要调到这块,一般这块是系统调用,因为我们一般使用的时候都在在xml文件中使用。文章开头说道这个布局的父布局是FrameLayout,这样就有个好处,可以使用BottomSheetLayout作为root布局,其实根据官方demo来看,确实也是建议最好这么使用的。顺便说下额外的(使用Framelayout作为root布局的好处:对比RelativeLayout绘制上来说绘制次数:FrameLayout 绘制一次 而RelativeLayout绘制两次,所以FrameLayout的效率较高。至于为什么那就需要查源码了。。。。)

    接下来就是BottomSheet中的内容view了,看代码:

     1 /**
     2      * Convenience for showWithSheetView(sheetView, null, null).
     3      *
     4      * @param sheetView The sheet to be presented.
     5      */
     6     public void showWithSheetView(View sheetView) {
     7         showWithSheetView(sheetView, null);
     8     }
     9 
    10     /**
    11      * Present a sheet view to the user.
    12      * If another sheet is currently presented, it will be dismissed, and the new sheet will be shown after that
    13      *
    14      * @param sheetView The sheet to be presented.
    15      * @param viewTransformer The view transformer to use when presenting the sheet.
    16      */
    17     public void showWithSheetView(final View sheetView, final ViewTransformer viewTransformer) {
    18         if (state != State.HIDDEN) {
    19             Runnable runAfterDismissThis = new Runnable() {
    20                 @Override
    21                 public void run() {
    22                     showWithSheetView(sheetView, viewTransformer);
    23                 }
    24             };
    25             dismissSheet(runAfterDismissThis);
    26             return;
    27         }
    28         setState(State.PREPARING);
    29 
    30         LayoutParams params = (LayoutParams) sheetView.getLayoutParams();
    31         if (params == null) {
    32             params = new LayoutParams(isTablet ? LayoutParams.WRAP_CONTENT : LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL);
    33         }
    34 
    35         if (isTablet && params.width == FrameLayout.LayoutParams.WRAP_CONTENT) {
    36 
    37             // Center by default if they didn't specify anything
    38             if (params.gravity == -1) {
    39                 params.gravity = Gravity.CENTER_HORIZONTAL;
    40             }
    41 
    42             params.width = defaultSheetWidth;
    43 
    44             // Update start and end coordinates for touch reference
    45             int horizontalSpacing = screenWidth - defaultSheetWidth;
    46             sheetStartX = horizontalSpacing / 2;
    47             sheetEndX = screenWidth - sheetStartX;
    48         }
    49 
    50         super.addView(sheetView, -1, params);
    51         initializeSheetValues();
    52         this.viewTransformer = viewTransformer;
    53 
    54         // Don't start animating until the sheet has been drawn once. This ensures that we don't do layout while animating and that
    55         // the drawing cache for the view has been warmed up. tl;dr it reduces lag.
    56         getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    57             @Override
    58             public boolean onPreDraw() {
    59                 getViewTreeObserver().removeOnPreDrawListener(this);
    60                 post(new Runnable() {
    61                     @Override
    62                     public void run() {
    63                         // Make sure sheet view is still here when first draw happens.
    64                         // 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.
    65                         if (getSheetView() != null) {
    66                             peekSheet();
    67                         }
    68                     }
    69                 });
    70                 return true;
    71             }
    72         });
    73 
    74         // sheetView should always be anchored to the bottom of the screen
    75         currentSheetViewHeight = sheetView.getMeasuredHeight();
    76         sheetViewOnLayoutChangeListener = new OnLayoutChangeListener() {
    77             @Override
    78             public void onLayoutChange(View sheetView, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
    79                 int newSheetViewHeight = sheetView.getMeasuredHeight();
    80                 if (state != State.HIDDEN && newSheetViewHeight < currentSheetViewHeight) {
    81                     // The sheet can no longer be in the expanded state if it has shrunk
    82                     if (state == State.EXPANDED) {
    83                         setState(State.PEEKED);
    84                     }
    85                     setSheetTranslation(newSheetViewHeight);
    86                 }
    87                 currentSheetViewHeight = newSheetViewHeight;
    88             }
    89         };
    90         sheetView.addOnLayoutChangeListener(sheetViewOnLayoutChangeListener);
    91     }

      用于显示和隐藏的API方法。只要是显示就必须调用此方法。

      文章判断是否当前状态是隐藏,如果是就显示,否则就隐藏。这样就省去了好多麻烦,如果先显示或者是隐藏调用这一个API就完事。

      Line 30 --33 这里这块代码很关键,也许有时候你填充的一个view设置的参数宽度和高度都是match_parent,但是真实的显示确实宽度是macth_parent,但是高度却不是咱们set的那样,问题就在这里了。其实这个判断很简单,先是获取LayooutParams,了解这块的童鞋知道,这里其实获取的是父布局的LayoutParams,但是如果如果你从XML文件inflate的一个view,可想而知,这里其实是没有父布局的,因为你还没有执行为这个view执行addview的操作,所以这里获取的参数肯定为NULL,辣么判断很显然成立,辣么问题来了,这里从新new一个新的params,宽度match_parent,高度wrap_content,然后紧接着50 line完成了addview,然后传入参数就是这个params对象。this is cool....这里动画类的就不说了,我太菜了,看不懂....

      

    前边的GIF大家看了是不是感觉很炫,有个小点不知道大家注意了木,当BottomSheet出现的时候先出现一部分,之后拖动填充整个屏幕?

      咱们继续看代码咯,look this.

    1     private float getDefaultPeekTranslation() {
    2         return hasFullHeightSheet() ? getHeight() / 3 : getSheetView().getHeight();
    3     }
    1     private boolean hasFullHeightSheet() {
    2         return getSheetView() == null || getSheetView().getHeight() == getHeight();
    3     }

      判断你填充的view高度是不是match_parent,为什么只判断高度,因为宽度之前说到了都是macth_parent,这里只要填充的view的高度等于当前父view的高度,那么就先允许你出现当前高度的三分之一。如果不是那就简单咯,有多高就一下展示出来,就这么简单咯。

     

    对于Module bottomsheet的,基本你可能需要知道的就这些了,想要调成布局大小或者是背景透明度的看完上边的就阔以了,下边的就别看了  

    接下里咱们要说的就是常用的包了。

    bottomsheet-commons

    Usage:BottomSheetFragment.java BottomSheetFragmentDelegate.java

      使用其实很简单,直接创建class 继承这个BootomSheetFragment,然后重写onCreateView方法即可,是不是很简单,调用的时候依然一行code,new 这个新增Fagment对象然后调用期show(FragmentTransaction transaction, @IdRes int bottomSheetLayoutId)方法。

      辣么问题来了,方法参数,表示什么意思,他又是如何显示出来的,可能你会说这不是show方法么,其实不然。这里咱们看下源码干了些什么?

    1     /**
    2      * {@inheritDoc}
    3      */
    4     @Override
    5     public int show(FragmentTransaction transaction, @IdRes int bottomSheetLayoutId) {
    6         return getDelegate().show(transaction, bottomSheetLayoutId);
    7     }
     1     /**
     2      * DialogFragment-like show() method for displaying this the associated sheet fragment
     3      *
     4      * @param manager FragmentManager instance
     5      * @param bottomSheetLayoutId Resource ID of the {@link BottomSheetLayout}
     6      */
     7     public void show(FragmentManager manager, @IdRes int bottomSheetLayoutId) {
     8         dismissed = false;
     9         shownByMe = true;
    10         this.bottomSheetLayoutId = bottomSheetLayoutId;
    11         manager.beginTransaction()
    12                 .add(fragment, String.valueOf(bottomSheetLayoutId))
    13                 .commit();
    14     }

      这就是所有源码中的代码,通过辅助类间接调用,这里很简单就是给FragmentManager添加了一个Fragment对象。额,辣么之前说了一个方法用于显示和隐藏BottomSheet的showWithSheetView方法,这里很显然没调用这个方法。那么跟之前我说的只要显示必须调用showWithSheetView方法不一致了,尼玛,是不是觉得我之吹流弊了,非也,咱们继续看。

      其实就翻看源码而言如果你发现遇到上边的问题的时候,你需要想到问题是,Fragment如何和BtoomSheetLayou联系在一起的,这时候你就可以在类

    BottomSheetFragment搜索看是不否有对BottomSheetLayout的引用,其次是辅助类,BottomSheetFragmentDelegate,这样你就能很快的定位出其引用关系,按照如上
    就定位出在
    BottomSheetFragmentDelegate有对BottomSheetLayout的使用,辣么,咱们自然的就想到了是不是有调用显示的方法,辣么代码就来了。
    
    
     1     /**
     2      * Corresponding onStart() method
     3      */
     4     public void onStart() {
     5         if (bottomSheetLayout != null) {
     6             viewDestroyed = false;
     7             bottomSheetLayout.showWithSheetView(fragment.getView(), sheetFragmentInterface.getViewTransformer());
     8             bottomSheetLayout.addOnSheetDismissedListener(this);
     9         }
    10     }
    
    

      关于其调用来自BottomSheetFragment的onStart方法,代码如下;

    1     @Override
    2     public void onStart() {
    3         super.onStart();
    4         getDelegate().onStart();
    5     }

      到了这里大家发现每次Framgent onStart的时候调用,也就完成了显示。这里大家发现所以对BottomSheetlayout的操作都是有辅助类,BottomSheetFragmentDelegate来玩成与BottomSheetLayout的沟通交流。

      这里我在唠叨几句我自己感觉挺好的地方。在BottomSheetLayout中重载了一个getLayoutInflater方法,看代码

    1     @Override
    2     public LayoutInflater getLayoutInflater(Bundle savedInstanceState) {
    3         return getDelegate().getLayoutInflater(savedInstanceState, super.getLayoutInflater(savedInstanceState));
    4     }

      当然这里也是通过辅助类来完成的操作,这里传入的第二个参数也就是系统自己生成的layoutInflater对象。继续深入下:

     1    /**
     2      * Retrieves the appropriate layout inflater, either the sheet's or the view's super container. Note that you should
     3      * handle the result of this in your getLayoutInflater method.
     4      *
     5      * @param savedInstanceState Instance state, here to match Fragment API but unused.
     6      * @param superInflater The result of the view's inflater, usually the result of super.getLayoutInflater()
     7      * @return the layout inflater to use
     8      */
     9     @CheckResult
    10     public LayoutInflater getLayoutInflater(Bundle savedInstanceState, LayoutInflater superInflater) {
    11         if (!showsBottomSheet) {
    12             return superInflater;
    13         }
    14         bottomSheetLayout = getBottomSheetLayout();
    15         if (bottomSheetLayout != null) {
    16             return LayoutInflater.from(bottomSheetLayout.getContext());
    17         }
    18         return LayoutInflater.from(fragment.getContext());
    19     }

      首先判断是不是使用BottomSheetlayout,如果是就直接使用系统生成的layoutInflater对象,否则就是读取bottomSheet对象是不是为null,如果不是就使用bottomSheetlayout的上下文去生成一个layoutInflater对象,否则就直接使用start这个Fragment的Activity的上下文去生成这个layoutInflater对象。

      这里咱们深入挖掘下这个BottomSheetLayout对象的获取,

     1    /**
     2      * @return this fragment sheet's {@link BottomSheetLayout}.
     3      */
     4     public BottomSheetLayout getBottomSheetLayout() {
     5         if (bottomSheetLayout == null) {
     6             bottomSheetLayout = findBottomSheetLayout();
     7         }
     8 
     9         return bottomSheetLayout;
    10     }

      判断缓存中是否存在这个对象,如果存在就直接return回去这个对象,否则就去查找。

     1     @Nullable
     2     private BottomSheetLayout findBottomSheetLayout() {
     3         Fragment parentFragment = fragment.getParentFragment();
     4         if (parentFragment != null) {
     5             View view = parentFragment.getView();
     6             if (view != null) {
     7                 return (BottomSheetLayout) view.findViewById(bottomSheetLayoutId);
     8             } else {
     9                 return null;
    10             }
    11         }
    12         Activity parentActivity = fragment.getActivity();
    13         if (parentActivity != null) {
    14             return (BottomSheetLayout) parentActivity.findViewById(bottomSheetLayoutId);
    15         }
    16         return null;
    17     }

      首先判断当前Fragment是否有父Fragment如果当前Fragment没有Attached在一个Activity上返回父Fragemnt对象,否则就返回null。如果不返回null说明此事还没有粘附在任何Activity上,那么久获取onCreateView中的View去查找bottonSheetLayouyId,这个bottonSheetLayouyId在show方法调用的时候传入,然后查找到这个BottomSheetLayout对象。如果反回null,说明此事已经Attach在一个Activity上了,辣么,就回去粘附的Activity对象,也就是上下文对象,然后根据BottomSheetId去查找到这个对象,然后返回给调用者。最后是默认值直接就是null。

      基本到这里咱们需要知道有关BottomSheetFragemnt的使用就这些了。

    下边就简单commons控件了,大家按照之前我说的很快就能明白了,没啥技术难度的了。。。

      就暂时先这些了,文章中的不足之处,请多多指出,以便我及时的修改,谢谢。。。

      把想象付诸实践,然后你就成功了一半,人的价值总是在这些成功或失败中得以体现。

      

       

     

     

     

     

     

     

  • 相关阅读:
    数据库建表的时候报 “1215 Cannot add foreign key constraint”
    Maven项目中提示:Eclipse “cannot be resolved to a type” error
    数据表设计的几个简单原则
    使用brew安装软件
    linux如何设置用户权限
    前端页面——Cookie与Session有什么区别
    Git Push 避免用户名和密码方法
    $GLOBALS['HTTP_RAW_POST_DATA'] 和$_POST的区别
    PHP获取POST的原始数据的方法
    PHP底层的运行机制与原理
  • 原文地址:https://www.cnblogs.com/liemng/p/5383761.html
Copyright © 2011-2022 走看看