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. solr5.5 基于内置jetty配置 Ubuntu

    下载地址:http://archive.apache.org/dist/lucene/solr/ 在你的目录下直接解压 tar -zxvf xxxxxx.tgz 现在就可以直接开启solr了bin/s ...

  2. easyui tree loadFilter的使用

      实例化.这里增加了三个属性,可以指定idFiled,textFiled和parentField.所以这里的simpleData可以不严格转换成tree的数据格式. $(function(){ $( ...

  3. JAVA反编工具件安装 JD-eclipse

    想看Android  API源码,但是只有class文件,于是找了个反编译工具Java Decompiler,免费的反编译工具.eclipse插件:JD-eclipse. 安装步骤: 1.确保计算机上 ...

  4. grunt 一个目录下所有的js文件压缩 配置收藏

    module.exports = function(grunt){ // 项目配置 grunt.initConfig({ pkg: grunt.file.readJSON('package.json' ...

  5. Qt写Activex插件 总结

    最近写的插件功能基本完成,也遇到了一些坑,在这里记录一下. 我写的这个插件的js接口是仿造google earth的js接口,尽可能的达到与它的api一致.先从最简单的说起: 1. 导出接口中的flo ...

  6. i2c协议

    i2c协议 http://blog.csdn.net/g_salamander/article/details/8016698 总线设备驱动模型 http://blog.csdn.net/u01395 ...

  7. 真刀真枪压测:基于TCPCopy的仿真压测方案

    郑昀 基于刘勤红和石雍志的实践报告 创建于2015/8/13 最后更新于2015/8/19 关键词:压测.TCPCopy.仿真测试.实时拷贝流量 本文档适用人员:技术人员 提纲: 为什么要做仿真测试 ...

  8. 字体在网页中画ICON图标

    用字体在网页中画ICON图标有三种小技巧: 1.用CSS Sprite在网页中画小图标 实现方法: 首先将小图片整合到一张大的图片上 然后根据具体图标在大图上的位置,给背景定位.background- ...

  9. 重学ps_1

    1,选取 打开图片->点击选取工具->ctrl+c->ctrl+n->ctrl+v 2,去除图片背景 打开你要去除背景的图片->在图层面板中->双击图层把图层改为0 ...

  10. elasticsearch-hadoop使用示例

    在elasticsearch-hadoop的具体使用中碰到了几个问题,有必要记录一下,避免下次遇到时又要重新研究. 利用spark读取es数据源的简单示例 import org.elasticsear ...