本篇文章已受权微信公众号 guolin_blog (郭霖)独家发布php
ConstraintLayout在 1.0 的时候提供了 GuideLine 辅助布局,在 1.1 时提供了 Group 和 Barrier,在 2.0 时候提供了Layer以及放开了限制,开发者能够自定义 Helper 了。java
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
来看一个场景,下面是一个表单,Email 和 Password 左对齐 中间的虚线为 GuideLine,具体字段都和GuideLine左对齐。github
如今若是须要作多语言,翻译为德文后变成了下面的效果微信
这时候就须要Barrier出场了,Barrier是栅栏的意思,能够理解为挡着不让超过。app
改进方法ide
这样 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 能够看做是它引用的 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的方式。
为何须要自定义?
如何自定义?
对一张图片做出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 里面指定就能够了,作到了很好的复用。
再来看看这个 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 的完整代码参考这里
每一个 view 不但能够接受一个ConstraintHelper,还能够同时接受多个ConstraintHelper。
左边的四个 ImageView 和右下的 FloatingActionButton 都有 Flyin 的效果,同时左边的四个ImageView还在绕 Y 轴作 3D 旋转。上方的 Seekbar的背景在作CircularReveal的效果。有了前面编写的CircularRevealHelper以及 FlyInHelper 咱们能够很方便作到这样的效果。
Flow 是 VirtualLayout,Flow 能够像 Chain 那样帮助快速横向/纵向布局constraint_referenced_ids里面的元素。 经过flow_wrapMode能够指定具体的排列方式,有三种模式
下面看下如何实现这个计算器布局:
<?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,减小层级。
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 能够参考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