Mobile Development 9 min read

Using Flow, Layer, ConstraintHelper, and ImageFilterView in Android ConstraintLayout

This article explains how to use Android ConstraintLayout helper widgets such as Flow, Layer, ConstraintHelper, and ImageFilterView, covering their properties, wrap modes, XML configuration, Kotlin animation code, and runtime adjustments with SeekBar to create dynamic UI layouts.

New Oriental Technology
New Oriental Technology
New Oriental Technology
Using Flow, Layer, ConstraintHelper, and ImageFilterView in Android ConstraintLayout

Flow is a virtual layout helper that arranges referenced views in a chain horizontally or vertically without adding them to the layout hierarchy. It supports three wrap modes: none , chain , and aligned .

<androidx.constraintlayout.helper.widget.Flow
    android:layout_width="match_parent"
    android:layout_height="0dp"
    app:constraint_referenced_ids="tvZi,tvChou,tvYin,tvMao,tvChen,tvSi,tvWu,tvWei"
    app:flow_horizontalGap="8dp"
    app:flow_verticalGap="10dp"
    app:flow_wrapMode="aligned" />

Layer is another helper that creates a virtual layer to apply the same transformation (rotate, translate, scale) to multiple views simultaneously. It does not perform layout but groups views for collective animation.

<androidx.constraintlayout.helper.widget.Layer
    android:id="@+id/layer"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:background="@drawable/corner_white"
    app:constraint_referenced_ids="tvZi,tvChou,tvYin,tvMao"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

The following Kotlin code demonstrates a simple translation animation applied to the Layer helper.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_layer)
    layerDemo()
}

/** Execute layer animation */
private fun layerDemo() {
    val layer = findViewById<Layer>(R.id.layer)
    val valueAnimator = ValueAnimator.ofFloat(0f, 60f, -30f, 0f, -20f, 0f, -8f, 0f)
    valueAnimator.addUpdateListener(AnimatorUpdateListener { animation ->
        if (animation.animatedValue == null) return@AnimatorUpdateListener
        val animatedValue = animation.animatedValue.toString().toFloat()
        layer.translationX = animatedValue
    })
    valueAnimator.duration = 800
    valueAnimator.start()
}

Both Flow and Layer inherit from ConstraintHelper , which holds references to the specified views and provides callbacks before and after layout.

Using ConstraintHelper , a custom CircularAnimationHelper can be created to apply circular reveal animations to a group of views.

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

    // Called after onLayout; run the animation here
    override fun updatePostLayout(container: ConstraintLayout) {
        super.updatePostLayout(container)
        val views = getViews(container)
        views.forEach {
            val createCircularReveal = ViewAnimationUtils.createCircularReveal(
                it, it.width / 2, it.height / 2, 0f,
                hypot((it.height / 2).toDouble(), (it.width / 2).toDouble()).toFloat()
            )
            createCircularReveal.duration = 1000 + Random.nextLong(500) * Random.nextInt(5)
            createCircularReveal.start()
        }
    }
}

ImageFilterView, a subclass of AppCompatImageView , can display, combine, and filter images. It offers attributes such as altSrc , saturation , brightness , warmth , contrast , crossfade , round , roundPercent , and overlay to control visual effects.

Attribute

Meaning

altSrc

Alternative image for fade‑in/out transitions

saturation

Image saturation (0 = grayscale, 1 = original, 2 = oversaturated)

brightness

Image brightness (0 = black, 1 = original, 2 = double brightness)

warmth

Apparent color temperature (1 = neutral, 2 = warm, 0.5 = cool)

contrast

Contrast level (1 = unchanged, 0 = gray, 2 = high contrast)

crossfade

Blend between src and altSrc (0 = src, 1 = altSrc)

round

Corner radius in dimension units

roundPercent

Corner radius as a percentage (0‑1, 1 = circle)

overlay

Whether the alternate image overlays (true) or cross‑fades (false)

The following XML places two ImageFilterView instances and a SeekBar that drives their properties at runtime.

// activity_image_filter.xml
<androidx.constraintlayout.utils.widget.ImageFilterView
    android:id="@+id/iv_1"
    android:layout_width="120dp"
    android:layout_height="120dp"
    app:srcCompat="@mipmap/cat" />

<androidx.constraintlayout.utils.widget.ImageFilterView
    android:id="@+id/iv_7"
    android:layout_width="120dp"
    android:layout_height="120dp"
    app:altSrc="@drawable/tiger"
    app:srcCompat="@drawable/cat" />

<androidx.appcompat.widget.AppCompatSeekBar
    android:id="@+id/seekbar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="16dp"
    android:max="100"
    android:progress="0"
    app:layout_constraintBottom_toBottomOf="parent" />

The activity code listens to SeekBar changes and updates the ImageFilterView attributes accordingly.

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

        val iv1: ImageFilterView = findViewById(R.id.iv_1)
        // ... other ImageFilterView references omitted ...
        val seekBar: SeekBar = findViewById(R.id.seekbar)
        seekBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
            override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
                if (fromUser) {
                    val realProgress = (progress / 100.0).toFloat()
                    iv1.saturation = realProgress * 10
                    iv2.brightness = 1 - realProgress
                    iv3.warmth = realProgress * 20
                    iv4.contrast = realProgress * 2
                    iv5.round = realProgress * 100
                    iv6.roundPercent = realProgress
                    iv7.crossfade = realProgress
                }
            }
            override fun onStartTrackingTouch(seekBar: SeekBar?) {}
            override fun onStopTrackingTouch(seekBar: SeekBar?) {}
        })
    }
}

These helpers enable developers to build complex, responsive, and animated UI layouts in Android with concise XML and Kotlin code.

androidKotlinConstraintLayoutFlowImageFilterViewlayer
New Oriental Technology
Written by

New Oriental Technology

Practical internet development experience, tech sharing, knowledge consolidation, and forward-thinking insights.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.