ConstraintLayout核心用法详解(五):Layer、ImageFilter*与MockView

整理旧电脑上的一些笔记

前言


今天继续整理 ConstraintLayout

ConstraintLayout 的特点:

  • 扁平布局,无嵌套,一个层级就可以绘制复杂布局;
  • 渲染性能高;
  • 集合了线性布局、相对布局、百分比布局的特点和大多数功能;
  • 支持可视化环境下拖拽绘制约束布局;
  • 轻松地为应用中使用的 UI 组件添加动画效果。

1. Layer

LayerConstraintLayout 2.0 加入的新的辅助类,它同 GuidelineGroup 类似都是虚拟辅助。Layer 是用来控制一组视图的移动和旋转,它和 Group 的场景不一样,Group 是用来简单控制视图的可见性(VISIBILITYINVISIBILITY)。

我们可以把 Layer 的效果看成是一个 ViewGroup,但是实际书写代码中并不需要把 view 包裹进去 Layer

下面看具体 XML 代码示例:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".constraintlayout.LayerActivity">

    <ImageView
        android:id="@+id/iv_android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        android:src="@mipmap/ic_launcher"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="android"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/iv_android" />

    <androidx.constraintlayout.helper.widget.Layer
        android:id="@+id/layer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:constraint_referenced_ids="iv_android,tv_android"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/btn_rotation"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="rotation"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

上面 xml 代码中关键属性:

  • constraint_referenced_ids:指定要控制的视图ID,如iv_android、tv_android,逗号分隔

Kotlin 代码示例:

class LayerActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_layer)

        val layer = findViewById<Layer>(R.id.layer)
        val button = findViewById<Button>(R.id.btn_rotation)
        button.setOnClickListener {
            val animator = ValueAnimator.ofFloat(0f, 360f)
            animator.duration = 2000
            animator.addUpdateListener { animator ->
                layer.rotation = animator.animatedValue as Float
                layer.scaleX = 2.0f
                layer.scaleY = 2.0f
                layer.translationX = 100f
                layer.alpha = 0.3f
            }
            animator.start()
        }
    }
}

上面代码因为比较简单,直接使用Kotlin原始方法findViewById,实际推荐使用viewBinding,减少模板代码。

运行效果图:

layer

2.ImageFilterView和ImageFilterButton

ImageFilterViewImageFilterButton 也都是 ConstraintLayout 2.0 新添加的控件。它是 Android 第一次提供原生支持图像圆角、滤镜(饱和度、亮度、对比度和色温)、交叉淡入(crossfade)等特性。

以往产品中遇到需要圆角图形,如头像、歌曲专辑封面等,往往采用自定义View或第三方控件实现,有了ImageFitlerView 就很好的解决了该场景问题。

2.1 ImageFilterView

首先介绍 ImageFilterView 支持的属性:

  • altSrc:设置一个和src属性不同的图,用来实现交叉淡入淡出效果
  • saturation:设置饱和度。其中0=灰度,1=原始图,2=超饱和
  • brightness:设置亮度。0=全黑,1=原始图,2=亮度加倍
  • warmth:设置色温。1=中性,2=暖色,0.5=冷色
  • contrast:设置图像对比度。1=不变,0=灰色,2=高对比度
  • crossfade:设置两张图像间的混合程度。0=完全显示src图,1=完全显示altSrc图像
  • round:设置圆角半径
  • roundPercent:设置圆角半径为短边长的一个比例值
  • overlay:设置替代图(altSrc)是以叠加方式淡入原始图像上还是与原始图像进行交叉淡入淡出。默认=true

XML代码展示:

<androidx.constraintlayout.utils.widget.ImageFilterView
        android:id="@+id/iv_avatar"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:src="@drawable/img_avatar"
        app:altSrc="@drawable/img_duck"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:round="20dp" />

Kotlin代码展示:

binding.sliderSaturation.addOnChangeListener { slider, value, fromUser ->
    if (fromUser) {
        val intValue = value.toInt()
        Toast.makeText(this, "$intValue", Toast.LENGTH_SHORT).show()
        when (intValue) {
            0 -> binding.ivAvatar.saturation = 0f
            1 -> binding.ivAvatar.saturation = 1f
            2 -> binding.ivAvatar.saturation = 2f
        }
    }
}

binding.sliderBrightness.addOnChangeListener { slide, value, fromUser ->
    if (fromUser) {
        val intValue = value.toInt()
        when (intValue) {
            0 -> binding.ivAvatar.brightness = 0f
            1 -> binding.ivAvatar.brightness = 1f
            2 -> binding.ivAvatar.brightness = 2f
        }
    }
}

binding.sliderWarmth.addOnChangeListener { slide, value, fromUser ->
    if (fromUser) {
        val floatValue = value.toFloat()
        when (floatValue) {
            0.5f -> binding.ivAvatar.warmth = 0.5f
            1.0f -> binding.ivAvatar.warmth = 1f
            2.0f -> binding.ivAvatar.warmth = 2f
        }
    }
}

binding.sliderContrast.addOnChangeListener { slide, value, fromUser ->
    if (fromUser) {
        val intValue = value.toInt()
        when (intValue) {
            0 -> binding.ivAvatar.contrast = 0f
            1 -> binding.ivAvatar.contrast = 1f
            2 -> binding.ivAvatar.contrast = 2f
        }
    }
}

binding.seekbarCrossfade.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
    override fun onProgressChanged(seekbar: SeekBar?, progress: Int, fromUser: Boolean) {
        if (fromUser) {
            val percentage = progress / 100.0f
            binding.ivAvatar.crossfade = percentage
        }
    }

    override fun onStartTrackingTouch(seekbar: SeekBar?) {

    }

    override fun onStopTrackingTouch(seekbar: SeekBar?) {

    }

})

整体的代码示例还是很简单的,我们结合下面的效果图来加深理解即可。

ImageFilterView

2.2 ImageFilterButton

ImageFilterButtonAppCompatImageButton 的子类,可以用来显示、组合和过滤图像。它与上面提到的 ImageFilterView拥有相同的属性,所不一样的就是 ImageFilterButton 兼具 Button 的特性。

直接XML代码展示:

<androidx.constraintlayout.utils.widget.ImageFilterButton
    android:id="@+id/btn_share"
    android:layout_width="80dp"
    android:layout_height="80dp"
    android:layout_marginTop="20dp"
    android:background="#E91E63"
    android:src="@drawable/icon_share"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@id/seekbar_crossfade"
    app:round="40dp" />

<androidx.constraintlayout.utils.widget.ImageFilterButton
    android:id="@+id/btn_favorite"
    android:layout_width="80dp"
    android:layout_height="80dp"
    android:layout_marginTop="20dp"
    android:background="#E91E63"
    android:src="@drawable/icon_favorite"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@id/btn_share"
    app:round="40dp" />

效果图:

ImageFilterButton

ImageFilterButton 还是比较好理解的,它支持的饱和度、对比度等特性参考上面 ImageFilterView 示例,这里也就不再赘述了。

3. MockView

讲解 MockView 前先来看一张图。

MockView

一些产品稿中会使用上图中带对角线的矩形来占位,方便模拟一些视图,而 MockView 也正是这样的用途。

A view that is useful for prototyping layouts.

MockView 在布局中作为占位视图,模拟真实数据或复杂视图的布局效果。实际开发中,存在下面的场景:

  1. 真实数据还没有,比如用户头像;
  2. 某些View是动态加载的,比如RecyclerView加载的item;
  3. 某些View是有不同的状态的,比如loading、空数据提示

我们当然可以在 Java(Kotlin) 中用代码模拟假数据来实现UI效果,但当我们使用 ConstraintLayout 作为根布局时候,更推荐 MockView 来模拟这些数据存在。

下面是 MockView 的一些属性:

  • mock_label:显示字体
  • mock_labelColor:字体颜色
  • mock_labelBackgroundColor:字体背景色
  • mock_diagonalsColor:对角线颜色
  • mock_showDiagonals:是否显示对角线

XML代码示例:

<androidx.constraintlayout.utils.widget.MockView
    android:id="@+id/background"
    android:layout_width="0dp"
    android:layout_height="100dp"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:mock_label="LOGO或风景图"
    app:mock_labelBackgroundColor="#9C27B0"
    app:mock_labelColor="@color/white"
    app:mock_showDiagonals="true" />

<androidx.constraintlayout.utils.widget.MockView
    android:id="@+id/login"
    android:layout_width="200dp"
    android:layout_height="40dp"
    android:layout_marginTop="30dp"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@id/background"
    app:mock_diagonalsColor="#2196F3"
    app:mock_label="登录"
    app:mock_showDiagonals="true" />

<androidx.constraintlayout.utils.widget.MockView
    android:id="@+id/register"
    android:layout_width="200dp"
    android:layout_height="40dp"
    android:layout_marginTop="30dp"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@id/login"
    app:mock_diagonalsColor="#2196F3"
    app:mock_label="注册"
    app:mock_showDiagonals="true" />

预览图:

MockView_2

MockView 作为辅助设计的控件,它不能设置事件而且正式发布项目前建议要移除 MockView,保持布局干净。

4.参考资料

posted @ 2026-09-05 18:44  换元不配限  阅读(9)  评论(0)    收藏  举报