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,当然这里不需要你自己去调用,这里并不是说你不可以自己调用。那么如果不是自己调用那应该怎么调用哪???还是看代码:

 @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);
}

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

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

 /**
* Convenience for showWithSheetView(sheetView, null, null).
*
* @param sheetView The sheet to be presented.
*/
public void showWithSheetView(View sheetView) {
showWithSheetView(sheetView, null);
} /**
* Present a sheet view to the user.
* If another sheet is currently presented, it will be dismissed, and the new sheet will be shown after that
*
* @param sheetView The sheet to be presented.
* @param viewTransformer The view transformer to use when presenting the sheet.
*/
public void showWithSheetView(final View sheetView, final ViewTransformer viewTransformer) {
if (state != State.HIDDEN) {
Runnable runAfterDismissThis = new Runnable() {
@Override
public void run() {
showWithSheetView(sheetView, viewTransformer);
}
};
dismissSheet(runAfterDismissThis);
return;
}
setState(State.PREPARING); LayoutParams params = (LayoutParams) sheetView.getLayoutParams();
if (params == null) {
params = new LayoutParams(isTablet ? LayoutParams.WRAP_CONTENT : LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL);
} if (isTablet && params.width == FrameLayout.LayoutParams.WRAP_CONTENT) { // Center by default if they didn't specify anything
if (params.gravity == -1) {
params.gravity = Gravity.CENTER_HORIZONTAL;
} params.width = defaultSheetWidth; // Update start and end coordinates for touch reference
int horizontalSpacing = screenWidth - defaultSheetWidth;
sheetStartX = horizontalSpacing / 2;
sheetEndX = screenWidth - sheetStartX;
} super.addView(sheetView, -1, params);
initializeSheetValues();
this.viewTransformer = viewTransformer; // Don't start animating until the sheet has been drawn once. This ensures that we don't do layout while animating and that
// the drawing cache for the view has been warmed up. tl;dr it reduces lag.
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;
}
}); // sheetView should always be anchored to the bottom of the screen
currentSheetViewHeight = sheetView.getMeasuredHeight();
sheetViewOnLayoutChangeListener = new OnLayoutChangeListener() {
@Override
public void onLayoutChange(View sheetView, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
int newSheetViewHeight = sheetView.getMeasuredHeight();
if (state != State.HIDDEN && newSheetViewHeight < currentSheetViewHeight) {
// The sheet can no longer be in the expanded state if it has shrunk
if (state == State.EXPANDED) {
setState(State.PEEKED);
}
setSheetTranslation(newSheetViewHeight);
}
currentSheetViewHeight = newSheetViewHeight;
}
};
sheetView.addOnLayoutChangeListener(sheetViewOnLayoutChangeListener);
}

  用于显示和隐藏的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.

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

  判断你填充的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方法么,其实不然。这里咱们看下源码干了些什么?

     /**
* {@inheritDoc}
*/
@Override
public int show(FragmentTransaction transaction, @IdRes int bottomSheetLayoutId) {
return getDelegate().show(transaction, bottomSheetLayoutId);
}
     /**
* DialogFragment-like show() method for displaying this the associated sheet fragment
*
* @param manager FragmentManager instance
* @param bottomSheetLayoutId Resource ID of the {@link BottomSheetLayout}
*/
public void show(FragmentManager manager, @IdRes int bottomSheetLayoutId) {
dismissed = false;
shownByMe = true;
this.bottomSheetLayoutId = bottomSheetLayoutId;
manager.beginTransaction()
.add(fragment, String.valueOf(bottomSheetLayoutId))
.commit();
}

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

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

BottomSheetFragment搜索看是不否有对BottomSheetLayout的引用,其次是辅助类,BottomSheetFragmentDelegate,这样你就能很快的定位出其引用关系,按照如上
就定位出在BottomSheetFragmentDelegate中有对BottomSheetLayout的使用,辣么,咱们自然的就想到了是不是有调用显示的方法,辣么代码就来了。

     /**
* Corresponding onStart() method
*/
public void onStart() {
if (bottomSheetLayout != null) {
viewDestroyed = false;
bottomSheetLayout.showWithSheetView(fragment.getView(), sheetFragmentInterface.getViewTransformer());
bottomSheetLayout.addOnSheetDismissedListener(this);
}
}

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

     @Override
public void onStart() {
super.onStart();
getDelegate().onStart();
}

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

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

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

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

    /**
* Retrieves the appropriate layout inflater, either the sheet's or the view's super container. Note that you should
* handle the result of this in your getLayoutInflater method.
*
* @param savedInstanceState Instance state, here to match Fragment API but unused.
* @param superInflater The result of the view's inflater, usually the result of super.getLayoutInflater()
* @return the layout inflater to use
*/
@CheckResult
public LayoutInflater getLayoutInflater(Bundle savedInstanceState, LayoutInflater superInflater) {
if (!showsBottomSheet) {
return superInflater;
}
bottomSheetLayout = getBottomSheetLayout();
if (bottomSheetLayout != null) {
return LayoutInflater.from(bottomSheetLayout.getContext());
}
return LayoutInflater.from(fragment.getContext());
}

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

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

    /**
* @return this fragment sheet's {@link BottomSheetLayout}.
*/
public BottomSheetLayout getBottomSheetLayout() {
if (bottomSheetLayout == null) {
bottomSheetLayout = findBottomSheetLayout();
} return bottomSheetLayout;
}

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

     @Nullable
private BottomSheetLayout findBottomSheetLayout() {
Fragment parentFragment = fragment.getParentFragment();
if (parentFragment != null) {
View view = parentFragment.getView();
if (view != null) {
return (BottomSheetLayout) view.findViewById(bottomSheetLayoutId);
} else {
return null;
}
}
Activity parentActivity = fragment.getActivity();
if (parentActivity != null) {
return (BottomSheetLayout) parentActivity.findViewById(bottomSheetLayoutId);
}
return null;
}

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

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

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

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

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

  

   

Flipboard-BottomSheetlayout 源码分析的更多相关文章

  1. ABP源码分析一:整体项目结构及目录

    ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...

  2. HashMap与TreeMap源码分析

    1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...

  3. nginx源码分析之网络初始化

    nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...

  4. zookeeper源码分析之五服务端(集群leader)处理请求流程

    leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...

  5. zookeeper源码分析之四服务端(单机)处理请求流程

    上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...

  6. zookeeper源码分析之三客户端发送请求流程

    znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...

  7. java使用websocket,并且获取HttpSession,源码分析

    转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/6238826.html 一:本文使用范围 此文不仅仅局限于spring boot,普通的sprin ...

  8. ABP源码分析二:ABP中配置的注册和初始化

    一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...

  9. ABP源码分析三:ABP Module

    Abp是一种基于模块化设计的思想构建的.开发人员可以将自定义的功能以模块(module)的形式集成到ABP中.具体的功能都可以设计成一个单独的Module.Abp底层框架提供便捷的方法集成每个Modu ...

  10. ABP源码分析四:Configuration

    核心模块的配置 Configuration是ABP中设计比较巧妙的地方.其通过AbpStartupConfiguration,Castle的依赖注入,Dictionary对象和扩展方法很巧妙的实现了配 ...

随机推荐

  1. 【转】XAMPP中配置多个网站

    XAMPP虚拟主机配置,多域名绑定访问本地站点 XAMPP有时候你需要一些顶级域名访问方式来访问你本地的项目也就是虚拟主机配置,这时候就需要配置虚拟主机,给你的目录绑定一个域名,实现多域名绑定访问. ...

  2. Windows平台下为Python添加MongoDB支持PyMongo

    到Python官网下载pymongo-2.6.3.win-amd64-py2.7.exe 安装pymongo-2.6.3.win-amd64-py2.7.exe 参照官方的用例进行测试 打开命令提示符 ...

  3. 【Oracle】Oracle 序列步长问题

    问题: 数据库中客户表的ID 变化为 21\31\41 有序数字,而不是1\2\3 依次增长 [问题原因]: SEQ_CUSTOMNOTEEN 设置了缓存20,每次取20个数,然后一个一个给你,如果中 ...

  4. uva10375 Choose and Divide(唯一分解定理)

    uva10375 Choose and Divide(唯一分解定理) 题意: 已知C(m,n)=m! / (n!*(m-n!)),输入整数p,q,r,s(p>=q,r>=s,p,q,r,s ...

  5. FP error code老是忘记的看这里:只给出最常用的几个。

    把常见的几个记牢,不要在比赛时纠结. 错误2:输入文件未找到. 错误106:数据读入的格式错误,往往是读入语句出错. 错误200:被零除. 错误201:范围检查错误,数组越界. 错误202:栈溢出. ...

  6. Mysql5.7修改默认密码

    由于 Mysql5.7的默认密码是随机生成的,所以需要修改成我们自己常用的密码 1.修改 my.ini,在 [mysqld] 小节下添加一行:skip-grant-tables=1 这一行配置让 my ...

  7. 关于BeanUiles.copyPropertis()的用法

    最近的项目遇到BeanUiles.copyPropertis(),大大的简化了代码量.用hibernate从数据库中映射的实体类,与pojo对象进行转换,传统做法 Object obj = baseD ...

  8. PHP 使用 mcrypt 扩展中的 mcrypt_encrypt() 和 mcrypt_decrypt() 对数据进行加密和解密

    <?php /* 使用 mcrypt 扩展中的 mcrypt_encrypt() 和 mcrypt_decrypt() 对数据进行加密和解密 */ // 加密 $algorithm = MCRY ...

  9. ios-UserDefaults

    //单例设计模式 /* 1.单例是一种设计模式 是开发人员在开发过程中总结出来的简单方法 2. 如果某个对象在整个工程中有且只有一个(唯一的)就必须使用单例设计模式创建该对象 3.单例设计模式创建的对 ...

  10. MyBatis学习总结(六)——调用存储过程(转载)

    本文转载自:http://www.cnblogs.com/jpf-java/p/6013518.html 一.提出需求 查询得到男性或女性的数量, 如果传入的是0就女性否则是男性 二.准备数据库表和存 ...