Android ConstraintLayout使用进阶

前言

曾经Android有五大布局,LinearLayout、FrameLayout、TableLayout、RelativeLayout和AbsoluteLayout,那会我们比较常用的布局就两三个,写xml的时候根据界面灵活选择布局,但是往往会面临布局嵌套过深的问题,阅读也不方便。随着Android生态的发展,Google后来推出了新的布局——ConstraintLayout(约束布局)。
我很快去学习并将其用在项目中,刚开始的时候觉得比较抽象难懂,各种不适应;一段时间过后,这玩意儿真香!
本文不讲ConstraintLayout基本使用(网上资料很多),而是关于使用ConstraintLayout的进阶。

导入依赖:(2.x版本)

implementation 'androidx.constraintlayout:constraintlayout:2.0.2'

进阶1

在开发中可能需要实现如下效果:
在这里插入图片描述
长文本
文本外层有背景,短文本的时候宽度自适应,长文本超过屏幕的时候,背景贴右边,文字显示…,这样的UI需求很常见,我们来一步步拆解。
1、文本背景需要占满屏幕,并且文本显示…

<TextView
      android:layout_width="0dp"
      android:ellipsize="end"
      android:maxLines="1"
      android:singleLine="true"
      android:background="@drawable/xxx"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      />

2、这时候TextView会水平居中,我们需要添加

app:layout_constraintHorizontal_bias="0"

layout_constraintHorizontal_bias表示水平偏移,即“当组件左侧和右侧 ( 或者 开始 和 结束 ) 两边被约束后, 两个联系之间的比例”,取值为0-1,具体看ConstraintLayout 偏移 ( Bias ) 计算方式详解,我们只需要将水平偏移量设置为0,控件就会被约束在左侧了。
3、最后一步,短文本的时候宽度自适应,长文本的时候占满屏幕,需要添加

app:layout_constraintWidth_max="wrap"

layout_constraintWidth_max表示指定视图的最大宽度,取值为“wrap”,它和“wrap_content”不同,虽然都是适应内容,但仍然允许视图比约束要求的视图更小。
最终代码:

<TextView
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:background="@drawable/xxx"
      android:ellipsize="end"
      android:maxLines="1"
      android:singleLine="true"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintHorizontal_bias="0"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintWidth_max="wrap"
      tools:text="这是一个测试文案"
      />

进阶2

再来看个效果图:
在这里插入图片描述
在这里插入图片描述
还是文本适配的问题,短昵称的时候自适应,长昵称的时候,性别图标跟随文本长度移动,但是图标必须在“聊天”按钮左侧,文本显示…
我们再来一步步拆解(仅针对昵称Textview):
一、重复上面的步骤1和步骤2,代码如下(注意layout_width=“wrap_content”,上面的是0dp)

<TextView
        android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="我是昵称"
        android:singleLine="true"
        android:ellipsize="end"
        android:maxLines="1"
        app:layout_constraintHorizontal_bias="0"
        app:layout_constraintStart_toEndOf="@id/iv_head"
        app:layout_constraintEnd_toStartOf="@id/iv_gender"
/>

二、这时候我们会发现布局是居中的,而且昵称TextView都需要收尾元素相连,我们可以使用layout_constraintHorizontal_chainStyle改变整条链的约束状态,它有三个值,分别是spread、spread_inside和packed,其中packed表示将所有 Views 打包到一起不分配多余的间隙(当然不包括通过 margin 设置多个 Views 之间的间隙),然后将整个组件组在可用的剩余位置居中(可以查看Chains链布局),同时由于layout_constraintHorizontal_bias="0"的作用,布局将会向左侧偏移。

app:layout_constraintHorizontal_chainStyle="packed"

三、最后,当我们输入文本时,发现文本并没有约束到“聊天”按钮左侧,因为layout_width=“wrap_content”,添加的约束是不起作用的,所以需要强制约束

 app:layout_constrainedWidth="true"

代码动态改变约束

初始约束:
在这里插入图片描述
修改后的约束:
在这里插入图片描述
如上图,初始状态,中间按钮约束在按钮1右侧,某个条件下需要将中间按钮约束在按钮2左侧,这种时候,我们就需要在代码动态设置约束了。
具体代码:

constraintLayout?.let {
			//初始化一个ConstraintSet
            val set = ConstraintSet()
            //将原布局复制一份
            set.clone(it)
            //分别将“中间按钮”START方向和BOTTOM方向的约束清除
            set.clear(“中间按钮”, ConstraintSet.START)
            set.clear(“中间按钮”, ConstraintSet.BOTTOM)
            //重新建立新的约束
            //“中间按钮”的END约束“按钮2”控件的START
            //相当于 app:layout_constraintEnd_toStartOf="@id/按钮2"
            set.connect(
                “中间按钮”,
                ConstraintSet.END,
                “按钮2,
                ConstraintSet.START,
                resources.getDimensionPixelSize(R.dimen.dp_9)
            )
            //以及底部方向的约束
            ...
            //最后将更新的约束应用到布局
            set.applyTo(it)
        }

MotionLayout

接下来是今天重头戏——MotionLayout。
MotionLayout继承自ConstraintLayout,能够通过约束关系构建丰富的view动画,动画状态分为start与end两个状态,它还能作为支持库,兼容到api 14。
来看下效果图,这是我司App某个页面的动画效果,就是用MotionLayout实现。

在这里插入图片描述

我们可以写个简单的demo实现上面一部分动画效果,如下图

在这里插入图片描述

首先我们需要在资源文件夹 res 下新建一个名为 xml 的资源文件夹,然后在 文件夹内新建一个根节点是 MotionScene 的 xml 文件,文件名为 test_motion_scene.xml,如下:

<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:motion="http://schemas.android.com/apk/res-auto">
    
</MotionScene>

activity的xml根布局改为MotionLayout,使用app:layoutDescription与之关联

在这里插入图片描述
再编写视图,定义视图具体的view和对应id

<androidx.constraintlayout.motion.widget.MotionLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    app:layoutDescription="@xml/test_motion_scene"
    ...
    >

    <ImageView
        android:id="@+id/iv_head"
        ...
        />
    <TextView
        android:id="@+id/tv1"
        ...
        />

然后切换到test_motion_scene.xml,我们需要明确动画布局的两个状态,start和end。
在MotionScene标签下定义Transition标签,指定动画的start和end状态

<Transition
        motion:constraintSetEnd="@+id/end"
        motion:constraintSetStart="@id/start"
        motion:duration="500">
</Transition>

之后,在Transition同级下再定义ConstrainSet标签,它表示用于指定所有视图在动画序列中某一点上的位置和属性,你可以把它理解成一个集合,集合了所有参与动画的view相关位置和属性,如下:

<?xml version="1.0" encoding="utf-8"?>
<MotionScene 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:motion="http://schemas.android.com/apk/res-auto">
    <Transition>
    ...
    </Transition>

    <ConstraintSet android:id="@+id/start">
    </ConstraintSet>

    <ConstraintSet android:id="@+id/end">
    </ConstraintSet>
</MotionScene>

大体的框架搭建好了,最后就是填充约束view状态的代码了。这时候我们需要明确动画的start状态和end状态,即
(start状态)↓
在这里插入图片描述
(end状态)↓
在这里插入图片描述

前面提到,ConstraintSet是存放一些view 约束和属性的的集合,而具体描述View约束和属性是通过Constraint 标签。我们声明Constraint标签,它支持一组标准 ConstraintLayout 属性,用于添加每个view start状态的约束。

<ConstraintSet android:id="@+id/start">
	<Constraint
			<!-- "android:id"表示activity的xml对应的view id 
            android:id="@id/iv_head"
            android:layout_width="90dp"
            android:layout_height="90dp"
            motion:layout_constraintTop_toTopOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintEnd_toEndOf="parent"/>
    <Constraint
            android:id="@id/iv1"
            .../>
    <Constraint
            android:id="@id/iv2"
            .../>
    ...
</ConstraintSet>

接下来以同样的方式添加end状态的view约束

<ConstraintSet android:id="@+id/end">
	...
</ConstraintSet>

最后,我们需要让它动起来,在Transition标签写添加一个OnClick标签,run,就能让动画动起来

<Transition
        motion:constraintSetEnd="@+id/end"
        motion:constraintSetStart="@id/start"
        motion:duration="1000">
        <!-- 点击-->
        <OnClick
            motion:clickAction="toggle"
            motion:targetId="@id/search_go_btn"/>
</Transition>

OnClick:表示由用户点击触发

属性:

motion:targetId="@id/target_view" (目标View的id)
如果不指定次属性,就是点击整个屏幕触发如果写了这个属性,就是点击对应id的View 触发转场动画

motion:clickAction=“action” 点击后要进行的行为 ,此属性可以设置以下几个值:

transitionToStart
过渡到 元素 motion::constraintSetStart 属性指定的状态,有过度动画效果。

transitionToEnd
过渡到 元素motion:constraintSetEnd 属性指定的状态,有过度动画效果。

jumpToStart
直接跳转到 元素 motion::constraintSetStart 属性指定的状态,没有动画效果。

jumpToEnd
直接跳转到 元素 motion:constraintSetEnd 属性指定的状态。

toggle
默认值就是这个,在 元素motion:constraintSetStart和 motion:constraintSetEnd 指定的布局之间切换,如果处于start状态就过度到end状态,如果处于end状态就过度到start状态,有过度动画。

除了OnClick之外,还有OnSwipe,它是根据用户滑动行为调整动画的进度,具体可查看文末资料。

改变动画运动过程(关键帧KeyFrameSet)

上面讲解了动画的start与end状态,但是如果我们想在动画运动过程去改变一些属性,比如设置view的透明度、旋转,又或者是改变动画运动过程的轨迹等,这时候可以用到关键帧。

KeyFrameSet是Transition的子元素,与OnClick、OnSwipe同级。KeyFrameSet中可以包含KeyPositionKeyAttributeKeyCycleKeyTimeCycleKeyTrigger,它们都可以用来改变动画过程。
此外还有与KeyFrameSet同级的KeyPositionKeyAttribute,具体大家根据需要自行了解即可。

最后再提一下MotionLayout一些常用的java api:
setDebugMode(int debugMode) ——对应xml"app:motionDebug",表示运动进行时是否显示运动路径,用来调试动画;
loadLayoutDescription() ——对应xml"app:layoutDescription",通过代码加载MotionScene;
transitionToStart() ——表示切换到动画start状态;
transitionToEnd() ——表示切换到动画end状态;
它们都默认有过渡效果,如果不需要过渡效果,可以通过
"setProgress(float pos)"处理过渡进度,取值0-1;
transitionToState(int id) ——表示切换到动画某个状态,可以是start也可以是end,参数id指的是ConstraintSet标签定义的id;
setTransitionListener(MotionLayout.TransitionListener listener) ——监听MotionLayout动画执行过程,接口有四个方法,onTransitionStartedonTransitionChangeonTransitionCompletedonTransitionTrigger

OK,最最后,ConstraintLayout能有效提升日常的开发效率,通过这篇文章的介绍,此刻你学废了嘛~

参考
MotionLayout官网文档
ConstraintLayout / MotionLayout GitHub示例
MotionLayout 使用说明书(入门级详解)
ConstraintLayout使用小技巧

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值