ConstraintLayout 2.0 新特性详解及实战

本篇文章已受权微信公众号 guolin_blog (郭霖)独家发布php

ConstraintHelper

ConstraintLayout在 1.0 的时候提供了 GuideLine 辅助布局,在 1.1 时提供了 Group 和 Barrier,在 2.0 时候提供了Layer以及放开了限制,开发者能够自定义 Helper 了。java

Group (Added in 1.1)

Group能够用来控制一组view的可见性android

<android.support.constraint.Group android:id="@+id/group" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="visible" app:constraint_referenced_ids="button4,button9" />
复制代码

能够经过控制 group 的 hide/show 来直接控制一组 view(button4,button9) 的可见性。git

Barrier (Added in 1.1)

来看一个场景,下面是一个表单,Email 和 Password 左对齐 中间的虚线为 GuideLine,具体字段都和GuideLine左对齐。github

如今若是须要作多语言,翻译为德文后变成了下面的效果微信

这时候就须要Barrier出场了,Barrier是栅栏的意思,能够理解为挡着不让超过。app

改进方法ide

  • 把中间的虚线GuideLine换成Barrier
  • 把①和②加入到Barrier的referenced_ids中
  • 指定barrierDirection为right(右侧不超过)
  • 把③和④左边对齐到Barrier的右边

这样 Email 和 Password就不会超出Barrier,大体代码以下(有删减,完整代码参考这里)函数

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout>
    <TextView android:id="@+id/tv_email" app:layout_constraintBottom_toTopOf="@+id/tv_password" app:layout_constraintStart_toStartOf="@+id/tv_password" app:layout_constraintTop_toTopOf="parent" tools:text="E-mail Addresse" />

    <EditText android:id="@+id/et_email" android:text="me@gmail.com" app:layout_constraintBaseline_toBaselineOf="@+id/tv_email" app:layout_constraintStart_toEndOf="@+id/barrier" />

    <TextView android:id="@+id/tv_password" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="@+id/tv_email" />

    <EditText android:id="@+id/et_password" android:inputType="textPassword" android:text="2321321" app:layout_constraintBaseline_toBaselineOf="@+id/tv_password" app:layout_constraintStart_toEndOf="@+id/barrier" />

    <android.support.constraint.Barrier android:id="@+id/barrier" app:barrierDirection="right" app:constraint_referenced_ids="tv_email,tv_password" />

</android.support.constraint.ConstraintLayout>
复制代码

Layer (Added in 2.0)

Layer 能够看做是它引用的 view 的边界(能够理解为包含这些 view 的一个 ViewGroup,可是Layer并非ViewGroup,Layer并不会增长 view 的层级)。另外Layer支持对里面的 view 一块儿作变换。布局

考虑这么一个场景,若是一个页面里面有部分 view 须要加个背景,使用Layer引用这几个 view,而后给Layer设置背景就能够了。若是不用Layer,只能另外加个 ViewGroup 包住这几个 View 了,这样会增长 view 的层级,不利于性能。

看一个示例(完整代码):

图中Layer包住了中间的 6 个按钮,绿色边线白色填充是经过 Layer设置背景完成的。另外对Layer里面的全部按钮一块儿作动画,出来的效果就是这样子

ConstraintLayout2.0 除了提供几个默认实现的ConstraintHelper外,还提供开发者自定义ConstraintHelper的方式。

自定义 Helper

为何须要自定义?

  • 保持 view 的层级不变,不像 ViewGroup 会增长 view 的层级
  • 封装一些特定的行为,方便复用
  • 一个 View 能够被多个 Helper引用,能够很方便组合出一些复杂的效果出来

如何自定义?

  • Helper持有 view 的引用,因此能够获取 view (getViews)而后操做 view
  • 提供了 onLayout 先后的 callback(updatePreLayout/updatePreLayout)
  • Helper 继承了 view,因此Helper自己也是 view

CircularRevealHelper

对一张图片做出CircularReveal的效果 ViewAnimationUtils给咱们提供了createCircularReveal这个函数

public static Animator createCircularReveal(View view, int centerX, int centerY, float startRadius, float endRadius) 复制代码

借助这个函数只须要计算出中心点(centerX,centerY)和 endRadius(半径)就能够很方便实现CircularReveal的效果

class CircularRevealHelper @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ConstraintHelper(context, attrs, defStyleAttr) {

    override fun updatePostLayout(container: ConstraintLayout) {
        super.updatePostLayout(container)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            val views = getViews(container)
            for (view in views) {
                val anim = ViewAnimationUtils.createCircularReveal(view, view.width / 2,
                        view.height / 2, 0f,
                        Math.hypot((view.height / 2).toDouble(), (view.width / 2).toDouble()).toFloat())
                anim.duration = 3000
                anim.start()
            }
        }
    }
}
复制代码

updatePostLayout会在 onLayout 以后调用,在这里作动画就能够。

有了CircularRevealHelper以后能够直接在 xml 里面使用,在CircularRevealHelper的constraint_referenced_ids里面指定须要作动画 view。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent">


    <ImageView android:id="@+id/img_mario" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:srcCompat="@drawable/mario" />

    <cn.feng.constraintLayout2.helps.CircularRevealHelper android:id="@+id/helper" android:layout_width="wrap_content" android:layout_height="wrap_content" app:constraint_referenced_ids="img_mario" tools:ignore="MissingConstraints" />

</android.support.constraint.ConstraintLayout>
复制代码

后面若是要对 view 作CircularReveal直接在 xml 里面指定就能够了,作到了很好的复用。

FlyinHelper

再来看看这个 Flyin 的飞入效果,view 从四周飞入到各自的位置。

这个动画的关键在于计算出每一个 view 该从什么方向飞入。

红色边框的位置能够借助前面介绍的的Layer找到(固然也能够不借助Layer,本身算,稍显复杂),从而计算出红色框框部分的中间点位置, 再和图中每一个 view 的中间点比较(图中每一个白点的位置)从而得出每一个 view 该从哪一个方向飞入。

计算每一个view 的初始位置代码以下,借助上面的图形应该很好理解。

for (view in views) {

            val viewCenterX = (view.left + view.right) / 2
            val viewCenterY = (view.top + view.bottom) / 2


            val startTranslationX = if (viewCenterX < centerPoint.x) -2000f else 2000f
            val startTranslationY = if (viewCenterY < centerPoint.y) -2000f else 2000f


            view.translationX = (1 - animatedFraction) * startTranslationX
            view.translationY = (1 - animatedFraction) * startTranslationY
        }
复制代码

FlyinHelper 的完整代码参考这里

ComposeMultipleHelper

每一个 view 不但能够接受一个ConstraintHelper,还能够同时接受多个ConstraintHelper。

左边的四个 ImageView 和右下的 FloatingActionButton 都有 Flyin 的效果,同时左边的四个ImageView还在绕 Y 轴作 3D 旋转。上方的 Seekbar的背景在作CircularReveal的效果。有了前面编写的CircularRevealHelper以及 FlyInHelper 咱们能够很方便作到这样的效果。

代码参考这里

Flow (VirtualLayout)

Flow 是 VirtualLayout,Flow 能够像 Chain 那样帮助快速横向/纵向布局constraint_referenced_ids里面的元素。 经过flow_wrapMode能够指定具体的排列方式,有三种模式

  • wrap none : 简单地把constraint_referenced_ids里面的元素组成chain,即便空间不够
  • wrap chain : 根据空间的大小和元素的大小组成一条或者多条 chain
  • wrap aligned : wrap chain相似,可是会对齐

下面看下如何实现这个计算器布局:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".activity.MainActivity">


    <android.support.constraint.helper.Flow android:id="@+id/flow" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#FFC107" android:padding="20dp" app:constraint_referenced_ids="tv_num_7,tv_num_8,tv_num_9,tv_num_4,tv_num_5,tv_num_6,tv_num_1,tv_num_2,tv_num_3,tv_num_0,tv_operator_div,tv_dot,tv_operator_times" app:flow_horizontalGap="10dp" app:flow_maxElementsWrap="3" app:flow_verticalGap="10dp" app:flow_wrapMode="aligned" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" />

    <TextView android:id="@+id/tv_num_7" style="@style/text_view_style" android:text="7" />

    <TextView android:id="@+id/tv_num_8" style="@style/text_view_style" android:text="8" />

    <TextView android:id="@+id/tv_num_9" style="@style/text_view_style" android:text="9" />


    <TextView android:id="@+id/tv_num_4" style="@style/text_view_style" android:text="4" />

    <TextView android:id="@+id/tv_num_5" style="@style/text_view_style" android:text="5" />

    <TextView android:id="@+id/tv_num_6" style="@style/text_view_style" android:text="6" />


    <TextView android:id="@+id/tv_num_1" style="@style/text_view_style" android:text="1" />

    <TextView android:id="@+id/tv_num_2" style="@style/text_view_style" android:text="2" />

    <TextView android:id="@+id/tv_num_3" style="@style/text_view_style" android:text="3" />

    <TextView android:id="@+id/tv_num_0" style="@style/text_view_style" android:text="0" />

    <TextView android:id="@+id/tv_operator_div" style="@style/text_view_style" android:text="/" tools:layout_editor_absoluteX="156dp" tools:layout_editor_absoluteY="501dp" />

    <TextView android:id="@+id/tv_operator_times" style="@style/text_view_style" android:text="*" />

    <TextView android:id="@+id/tv_dot" style="@style/text_view_style" android:text="." tools:layout_editor_absoluteX="278dp" tools:layout_editor_absoluteY="501dp" />

    <TextView android:id="@+id/KE" android:layout_width="0dp" android:layout_height="0dp" android:background="#00BCD4" android:gravity="center" android:text="Compute" android:textColor="@android:color/white" android:textSize="24sp" app:layout_constraintBottom_toBottomOf="@+id/tv_operator_times" app:layout_constraintEnd_toEndOf="@+id/tv_dot" app:layout_constraintHorizontal_bias="1.0" app:layout_constraintStart_toStartOf="@+id/tv_operator_div" app:layout_constraintTop_toTopOf="@+id/tv_operator_times" />

    <TextView android:id="@+id/KR" android:layout_width="0dp" android:layout_height="0dp" android:background="#03A9F4" android:gravity="right|center_vertical" android:paddingEnd="16dp" android:text="0" android:textColor="@android:color/white" android:textSize="58sp" app:layout_constraintBottom_toTopOf="@+id/flow" app:layout_constraintEnd_toEndOf="@+id/flow" app:layout_constraintStart_toStartOf="@+id/flow" app:layout_constraintTop_toTopOf="parent" />


</android.support.constraint.ConstraintLayout>
复制代码

借助 flow 很快能够布局出来,这里flow_wrapMode使用的是aligned,id 为KE的TextView能够对齐到 Flow 里面的 view,id 为KR的TextView能够对齐到 Flow,另外 Flow 也是ConstraintHelper,因此Flow 也是个 View,能够设置背景,padding等元素。 那么这样布局有什么优点? 这样的布局 view 都在一个层级,不使用 ViewGroup,减小层级。

流式 APIs

1.1 以前须要这样修改属性

val set = ConstraintSet()
        set.clone(constraintLayout)
        set.setTranslationZ(R.id.image, 32f)
        set.setMargin(R.id.image, ConstraintSet.START, 43)
        set.applyTo(constraintLayout)
复制代码

2.0 提供了ConstraintProperties 可使用流式 API 修改属性

val properties = ConstraintProperties(findViewById(R.id.image))
        properties.translationZ(32f)
                .margin(ConstraintSet.START, 43)
                .apply()
复制代码

MotionLayout

关于 MotionLayout 能够参考ConstraintLayout开发者 Nicolas Roard 写的系列文章,

Introduction to MotionLayout (part I)

Introduction to MotionLayout (part II)

Introduction to MotionLayout (part III)

Defining motion paths in MotionLayout


完整代码参考 Github,喜欢的话 star 哦


参考资料

ConstraintLayout Deep Dive (Android Dev Summit '18)

ConstraintLayout 2.0 by Nicolas Roard and John Hoford, Google EN

What's New in ConstraintLayout (Google I/O'19)

相关文章
相关标签/搜索