热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

androidlayout界面开发,Android开发之CoordinatorLayout使用详解一

官网描述为:CoordinatorLayout是一个增强版的FrameLayout(继承自ViewGroup)用途:1、作为应用的顶层视图。2、作为一个

官网描述为:CoordinatorLayout是一个增强版的FrameLayout(继承自ViewGroup)

用途:

1、作为应用的顶层视图。

2、作为一个可以指定子View之间相互作用的容器,通过给CoordinatorLayout的子View指定CoordinatorLayout.Behavior 来定义子view之间的相互作用。(你可以想象成:CoordinatorLayout相当于在两个View之间充当中介,这样子的好处就是两个view之间的耦合度降低了,只需要跟coordinatorLayout打交到即可,而CoordinatorLayout.Behavior 相当于两个view之间的协议,即通过怎样的规则来约束双方的行为。)

设计概念:

CoordinatorLayout:CoordinatorLayout 作为最顶层视图,将负责管理所有的子view,使其内部的子View彼此间产生一种联系。这个联系通过Behavior来实现(包括了滑动状态的处理以及View状态的处理)。

AppBarLayout:AppBarLayout 继承自限性布局,作为增强版的线性布局,他增加了对滑动手势的处理。

Behavior:Behavior 是google新提出的,能够让你以非侵入式的方式去处理目标View和其他View的交互行为。Behavior需要设置在触发事件(比如滚动)的view上,且这个View必须是CoordinatorLayout的第一层级下的子view,否则没有效果,因为Behavior的初始化是在CoordinatorLayout的LayoutParams中通过反射完成的。

Behavior实例化方式:1、通过app:layout_behavior声明 ;2、在你的自定义View类上添加@DefaultBehavior(MyBehavior.class);

Behavior只是个接口,其调用是由NestedScrollingParent与NestedScrollingChild接口负责调用。

接下来我们通过阅读部分源码进行学习:

首先,我们从两个view是如何通过coordinatorlayout产生关联来入手;看代码

LayoutParams(Context context, AttributeSet attrs) {

super(context, attrs);

final TypedArray a = context.obtainStyledAttributes(attrs,

R.styleable.CoordinatorLayout_Layout);

this.gravity = a.getInteger(

R.styleable.CoordinatorLayout_Layout_android_layout_gravity,

Gravity.NO_GRAVITY);

mAnchorId = a.getResourceId(R.styleable.CoordinatorLayout_Layout_layout_anchor,

View.NO_ID);

this.anchorGravity = a.getInteger(

R.styleable.CoordinatorLayout_Layout_layout_anchorGravity,

Gravity.NO_GRAVITY);

this.keyline = a.getInteger(R.styleable.CoordinatorLayout_Layout_layout_keyline,

-1);

insetEdge = a.getInt(R.styleable.CoordinatorLayout_Layout_layout_insetEdge, 0);

dodgeInsetEdges = a.getInt(

R.styleable.CoordinatorLayout_Layout_layout_dodgeInsetEdges, 0);

mBehaviorResolved = a.hasValue(

R.styleable.CoordinatorLayout_Layout_layout_behavior);

if (mBehaviorResolved) {

mBehavior = parseBehavior(context, attrs, a.getString(

R.styleable.CoordinatorLayout_Layout_layout_behavior));

}

a.recycle();

if (mBehavior != null) {

// If we have a Behavior, dispatch that it has been attached

mBehavior.onAttachedToLayoutParams(this);

}

}

mBehaviorResolved = a.hasValue(

R.styleable.CoordinatorLayout_Layout_layout_behavior);

if (mBehaviorResolved) {

mBehavior = parseBehavior(context, attrs, a.getString(

R.styleable.CoordinatorLayout_Layout_layout_behavior));

}

这几句我们可以看到。

mBehaviorResolved 是个boolean 变量,如果

R.styleable.CoordinatorLayout_Layout_layout_behavior CoordinatorLayout的

layout_behavior这个字段设置有值,

1、mBehaviorResolved = true -》调用parseBehavior方法,将所需参数传入通过java的反射技术返回一个Behavior实例。

static Behavior parseBehavior(Context context, AttributeSet attrs, String name) {

if (TextUtils.isEmpty(name)) {

return null;

}

final String fullName;

if (name.startsWith(".")) {

// Relative to the app package. Prepend the app package name.

fullName = context.getPackageName() + name;

} else if (name.indexOf('.') >= 0) {

// Fully qualified package name.

fullName = name;

} else {

// Assume stock behavior in this package (if we have one)

fullName = !TextUtils.isEmpty(WIDGET_PACKAGE_NAME)

? (WIDGET_PACKAGE_NAME + '.' + name)

: name;

}

try {

Map constructors = sConstructors.get();

if (constructors == null) {

constructors &#61; new HashMap<>();

sConstructors.set(constructors);

}

Constructor c &#61; constructors.get(fullName);

if (c &#61;&#61; null) {

final Class clazz &#61; (Class) Class.forName(fullName, true,

context.getClassLoader());

c &#61; clazz.getConstructor(CONSTRUCTOR_PARAMS);

c.setAccessible(true);

constructors.put(fullName, c);

}

return c.newInstance(context, attrs);

} catch (Exception e) {

throw new RuntimeException("Could not inflate Behavior subclass " &#43; fullName, e);

}

}

通过这一段我们可以知道&#xff0c;最后是通过调用Behavior的参数为(context,attrs)的构造函数进行实例化。

实例化出Behavior之后我们会调用behavior的onAttachedToLayoutParams方法 将LayoutParams的实例对象传进去mBehavior.onAttachedToLayoutParams(this);

mBehavior.onAttachedToLayoutParams是一个当LayoutParams被实例化后的回调方法。

通过这里&#xff0c;我们的

CoordinatorLayout就能够跟用layout_behavior标识的子View产生联系。

当子View发生变化时&#xff0c;CoordinatorLayout又是如何处理的的&#xff0c;请看下面代码&#xff1a;

final void onChildViewsChanged(&#64;DispatchChangeEvent final int type) {

final int layoutDirection &#61; ViewCompat.getLayoutDirection(this);

final int childCount &#61; mDependencySortedChildren.size();

final Rect inset &#61; acquireTempRect();

final Rect drawRect &#61; acquireTempRect();

final Rect lastDrawRect &#61; acquireTempRect();

for (int i &#61; 0; i

final View child &#61; mDependencySortedChildren.get(i);

final LayoutParams lp &#61; (LayoutParams) child.getLayoutParams();

if (type &#61;&#61; EVENT_PRE_DRAW && child.getVisibility() &#61;&#61; View.GONE) {

// Do not try to update GONE child views in pre draw updates.

continue;

}

// Check child views before for anchor

for (int j &#61; 0; j

final View checkChild &#61; mDependencySortedChildren.get(j);

if (lp.mAnchorDirectChild &#61;&#61; checkChild) {

offsetChildToAnchor(child, layoutDirection);

}

}

// Get the current draw rect of the view

getChildRect(child, true, drawRect);

// Accumulate inset sizes

if (lp.insetEdge !&#61; Gravity.NO_GRAVITY && !drawRect.isEmpty()) {

final int absInsetEdge &#61; GravityCompat.getAbsoluteGravity(

lp.insetEdge, layoutDirection);

switch (absInsetEdge & Gravity.VERTICAL_GRAVITY_MASK) {

case Gravity.TOP:

inset.top &#61; Math.max(inset.top, drawRect.bottom);

break;

case Gravity.BOTTOM:

inset.bottom &#61; Math.max(inset.bottom, getHeight() - drawRect.top);

break;

}

switch (absInsetEdge & Gravity.HORIZONTAL_GRAVITY_MASK) {

case Gravity.LEFT:

inset.left &#61; Math.max(inset.left, drawRect.right);

break;

case Gravity.RIGHT:

inset.right &#61; Math.max(inset.right, getWidth() - drawRect.left);

break;

}

}

// Dodge inset edges if necessary

if (lp.dodgeInsetEdges !&#61; Gravity.NO_GRAVITY && child.getVisibility() &#61;&#61; View.VISIBLE) {

offsetChildByInset(child, inset, layoutDirection);

}

if (type &#61;&#61; EVENT_PRE_DRAW) {

// Did it change? if not continue

getLastChildRect(child, lastDrawRect);

if (lastDrawRect.equals(drawRect)) {

continue;

}

recordLastChildRect(child, drawRect);

}

// Update any behavior-dependent views for the change

for (int j &#61; i &#43; 1; j

final View checkChild &#61; mDependencySortedChildren.get(j);

final LayoutParams checkLp &#61; (LayoutParams) checkChild.getLayoutParams();

final Behavior b &#61; checkLp.getBehavior();

if (b !&#61; null && b.layoutDependsOn(this, checkChild, child)) {

if (type &#61;&#61; EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) {

// If this is from a pre-draw and we have already been changed

// from a nested scroll, skip the dispatch and reset the flag

checkLp.resetChangedAfterNestedScroll();

continue;

}

final boolean handled;

switch (type) {

case EVENT_VIEW_REMOVED:

// EVENT_VIEW_REMOVED means that we need to dispatch

// onDependentViewRemoved() instead

b.onDependentViewRemoved(this, checkChild, child);

handled &#61; true;

break;

default:

// Otherwise we dispatch onDependentViewChanged()

handled &#61; b.onDependentViewChanged(this, checkChild, child);

break;

}

if (type &#61;&#61; EVENT_NESTED_SCROLL) {

// If this is from a nested scroll, set the flag so that we may skip

// any resulting onPreDraw dispatch (if needed)

checkLp.setChangedAfterNestedScroll(handled);

}

}

}

}

releaseTempRect(inset);

releaseTempRect(drawRect);

releaseTempRect(lastDrawRect);

}

我们可以看到文档说明&#xff0c;大概意思是当子view发生变化会调用该方法。该方法会遍历所有的子view&#xff0c;

然后调用如下代码&#xff0c;layoutDependsOn()这个方法是做什么的呢&#xff1f;我们接下来看下该方法。

if (b !&#61; null && b.layoutDependsOn(this, checkChild, child)) {

if (type &#61;&#61; EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) {

// If this is from a pre-draw and we have already been changed

// from a nested scroll, skip the dispatch and reset the flag

checkLp.resetChangedAfterNestedScroll();

continue;

}

final boolean handled;

switch (type) {

case EVENT_VIEW_REMOVED:

// EVENT_VIEW_REMOVED means that we need to dispatch

// onDependentViewRemoved() instead

b.onDependentViewRemoved(this, checkChild, child);

handled &#61; true;

break;

default:

// Otherwise we dispatch onDependentViewChanged()

handled &#61; b.onDependentViewChanged(this, checkChild, child);

break;

}

if (type &#61;&#61; EVENT_NESTED_SCROLL) {

// If this is from a nested scroll, set the flag so that we may skip

// any resulting onPreDraw dispatch (if needed)

checkLp.setChangedAfterNestedScroll(handled);

}

}

/**

* Determine whether the supplied child view has another specific sibling view as a

* layout dependency.

*

*

This method will be called at least once in response to a layout request. If it * returns true for a given child and dependency view pair, the parent CoordinatorLayout * will:

*

*

Always lay out this child after the dependent child is laid out, regardless * of child order.

*

Call {&#64;link #onDependentViewChanged} when the dependency view&#39;s layout or * position changes.

*

*

* &#64;param parent the parent view of the given child

* &#64;param child the child view to test

* &#64;param dependency the proposed dependency of child

* &#64;return true if child&#39;s layout depends on the proposed dependency&#39;s layout,

* false otherwise

*

* &#64;see #onDependentViewChanged(CoordinatorLayout, android.view.View, android.view.View)

*/

public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {

return false;

}

这个方法&#xff0c;大概意思是如果我们返回true&#xff0c;说明当前发生变化的子view发生变化时。也就是该方法决定我们用

layout_behavior标识的view是否应该做出相应的变化。默认返回false&#xff0c;该方法需要我们在创建自己的Behavior时重写。

当返回true的话&#xff0c;可以看到会调用

b.onDependentViewRemoved(this, checkChild, child);

handled &#61; b.onDependentViewChanged(this, checkChild, child);

为了更好理解这两句代码&#xff0c;我们举个例子&#xff0c;假设有ViewA和ViewB &#xff0c;当ViewB发生移动时&#xff0c;ViewA要向反方向移动。

1、当ViewB被移除时会调用

b.onDependentViewRemoved(this, checkChild, child);

2、当ViewB发生变化时&#xff0c;会调用

handled &#61; b.onDependentViewChanged(this, checkChild, child);

那么我们就可以在

b.onDependentViewChanged里面写我们的功能代码了。

通过以上的分析&#xff0c;希望能帮到大家对CoordinatorLayout的协作调用过程有一些些的帮助。



推荐阅读
  • 深入解析 Android 中 EditText 的 getLayoutParams 方法及其代码应用实例 ... [详细]
  • SQLite数据库CRUD操作实例分析与应用
    本文通过分析和实例演示了SQLite数据库中的CRUD(创建、读取、更新和删除)操作,详细介绍了如何在Java环境中使用Person实体类进行数据库操作。文章首先阐述了SQLite数据库的基本概念及其在移动应用开发中的重要性,然后通过具体的代码示例,逐步展示了如何实现对Person实体类的增删改查功能。此外,还讨论了常见错误及其解决方法,为开发者提供了实用的参考和指导。 ... [详细]
  • 开发日志:201521044091 《Java编程基础》第11周学习心得与总结
    开发日志:201521044091 《Java编程基础》第11周学习心得与总结 ... [详细]
  • Java中不同类型的常量池(字符串常量池、Class常量池和运行时常量池)的对比与关联分析
    在研究Java虚拟机的过程中,笔者发现存在多种类型的常量池,包括字符串常量池、Class常量池和运行时常量池。通过查阅CSDN、博客园等相关资料,对这些常量池的特性、用途及其相互关系进行了详细探讨。本文将深入分析这三种常量池的差异与联系,帮助读者更好地理解Java虚拟机的内部机制。 ... [详细]
  • 在Android应用开发中,实现与MySQL数据库的连接是一项重要的技术任务。本文详细介绍了Android连接MySQL数据库的操作流程和技术要点。首先,Android平台提供了SQLiteOpenHelper类作为数据库辅助工具,用于创建或打开数据库。开发者可以通过继承并扩展该类,实现对数据库的初始化和版本管理。此外,文章还探讨了使用第三方库如Retrofit或Volley进行网络请求,以及如何通过JSON格式交换数据,确保与MySQL服务器的高效通信。 ... [详细]
  • 本指南从零开始介绍Scala编程语言的基础知识,重点讲解了Scala解释器REPL(读取-求值-打印-循环)的使用方法。REPL是Scala开发中的重要工具,能够帮助初学者快速理解和实践Scala的基本语法和特性。通过详细的示例和练习,读者将能够熟练掌握Scala的基础概念和编程技巧。 ... [详细]
  • 本文详细介绍了使用 Python 进行 MySQL 和 Redis 数据库操作的实战技巧。首先,针对 MySQL 数据库,通过 `pymysql` 模块展示了如何连接和操作数据库,包括建立连接、执行查询和更新等常见操作。接着,文章深入探讨了 Redis 的基本命令和高级功能,如键值存储、列表操作和事务处理。此外,还提供了多个实际案例,帮助读者更好地理解和应用这些技术。 ... [详细]
  • C# .NET 4.1 版本大型信息化系统集成平台中的主从表事务处理标准示例
    在C# .NET 4.1版本的大型信息化系统集成平台中,本文详细介绍了主从表事务处理的标准示例。通过确保所有操作要么全部成功,要么全部失败,实现主表和关联子表的同步插入。主表插入时会返回当前生成的主键,该主键随后用于子表插入时的关联。以下是一个示例代码片段,展示了如何在一个数据库事务中同时添加角色和相关用户。 ... [详细]
  • 本文探讨了 Java 中 Pair 类的历史与现状。虽然 Java 标准库中没有内置的 Pair 类,但社区和第三方库提供了多种实现方式,如 Apache Commons 的 Pair 类和 JavaFX 的 javafx.util.Pair 类。这些实现为需要处理成对数据的开发者提供了便利。此外,文章还讨论了为何标准库未包含 Pair 类的原因,以及在现代 Java 开发中使用 Pair 类的最佳实践。 ... [详细]
  • 在Android 4.4系统中,通过使用 `Intent` 对象并设置动作 `ACTION_GET_CONTENT` 或 `ACTION_OPEN_DOCUMENT`,可以从相册中选择图片并获取其路径。具体实现时,需要为 `Intent` 添加相应的类别,并处理返回的 Uri 以提取图片的文件路径。此方法适用于需要从用户相册中选择图片的应用场景,能够确保兼容性和用户体验。 ... [详细]
  • 在前文探讨了Spring如何为特定的bean选择合适的通知器后,本文将进一步深入分析Spring AOP框架中代理对象的生成机制。具体而言,我们将详细解析如何通过代理技术将通知器(Advisor)中包含的通知(Advice)应用到目标bean上,以实现切面编程的核心功能。 ... [详细]
  • 深入解析C#中app.config文件的配置与修改方法
    在C#开发过程中,经常需要对系统的配置文件进行读写操作,如系统初始化参数的修改或运行时参数的更新。本文将详细介绍如何在C#中正确配置和修改app.config文件,包括其结构、常见用法以及最佳实践。此外,还将探讨exe.config文件的生成机制及其在不同环境下的应用,帮助开发者更好地管理和维护应用程序的配置信息。 ... [详细]
  • 2012年9月12日优酷土豆校园招聘笔试题目解析与备考指南
    2012年9月12日,优酷土豆校园招聘笔试题目解析与备考指南。在选择题部分,有一道题目涉及中国人的血型分布情况,具体为A型30%、B型20%、O型40%、AB型10%。若需确保在随机选取的样本中,至少有一人为B型血的概率不低于90%,则需要选取的最少人数是多少?该问题不仅考察了概率统计的基本知识,还要求考生具备一定的逻辑推理能力。 ... [详细]
  • 本文详细探讨了MySQL数据库实例化参数的优化方法及其在实例查询中的应用。通过具体的源代码示例,介绍了如何高效地配置和查询MySQL实例,为开发者提供了有价值的参考和实践指导。 ... [详细]
  • 在分析Socket服务器程序接收中文数据时出现的乱码问题时,我们发现客户端使用C#编写的数据在返回时能够正常显示。本文详细探讨了该问题的成因,并提出了一种有效的解决方案。通过调整字符编码设置和优化数据传输格式,确保了中文数据在传输过程中的完整性与正确性。具体实现代码包括对Socket读取事件的处理,确保数据以正确的编码格式进行解析和显示。 ... [详细]
author-avatar
22222wxr
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有