Skip to content

Instantly share code, notes, and snippets.

@JulienArzul
Last active June 30, 2020 06:19
Show Gist options
  • Save JulienArzul/a696c10172dbf5124633d310a541be86 to your computer and use it in GitHub Desktop.
Save JulienArzul/a696c10172dbf5124633d310a541be86 to your computer and use it in GitHub Desktop.
ConstraintHelper that allows to change a View's aspect ratio depending on the ConstraintLayout's width
package com.julienarzul.android
import android.content.Context
import android.util.AttributeSet
import androidx.constraintlayout.widget.ConstraintHelper
import androidx.constraintlayout.widget.ConstraintLayout
import au.net.abc.triplej.core.R
/**
* A ConstraintHelper class that can apply an aspect ratio to its referenced views.
* It allows to change the aspect ratio according to the width of the ConstraintLayout it's displayed in.
*
* It will use the value set for `baseAspectRatio` as the aspect ratio by default. But once the ConstraintLayout
* is wider than the `widthBreakpoint` set, it will apply the `conditionalAspectRatio` set.
*
* The class also allows to set a `onThresholdChangedListener` so that the user can react to changes in the aspect ratio (in case any other change is needed).
*/
class ConditionalAspectRatioHelper
@JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ConstraintHelper(context, attrs, defStyleAttr) {
private var isThresholdMet: Boolean? = null
private var shouldDispatchThresholdChange: Boolean = false
private val widthBreakpoint: Int
private val baseAspectRatio: String?
private val conditionalAspectRatio: String?
/**
* Listener that will be called every time the aspect ratio of the View is changed.
* The listener specifies if the threshold condition has been met or not.
*/
var onThresholdChangedListener: ((thresholdMet: Boolean) -> Unit)? = null
init {
if (attrs != null) {
val a = this.context.obtainStyledAttributes(attrs, R.styleable.ConditionalAspectRatioHelper)
widthBreakpoint = a.getDimensionPixelOffset(R.styleable.ConditionalAspectRatioHelper_widthBreakpoint, -1)
baseAspectRatio = a.getString(R.styleable.ConditionalAspectRatioHelper_baseAspectRatio)
conditionalAspectRatio = a.getString(R.styleable.ConditionalAspectRatioHelper_conditionalAspectRatio)
a.recycle()
} else {
widthBreakpoint = -1
baseAspectRatio = null
conditionalAspectRatio = null
}
}
override fun updatePreLayout(container: ConstraintLayout) {
super.updatePreLayout(container)
val newIsThresholdMet = isParentMeetingThreshold(container)
// Makes sure that we are applying the layout changes and dispatching the new threshold value only once
// This prevents infinite layout loops
if (isThresholdMet != newIsThresholdMet) {
shouldDispatchThresholdChange = true
isThresholdMet = newIsThresholdMet
val aspectRatio = if (newIsThresholdMet) {
conditionalAspectRatio
} else {
baseAspectRatio
}
mIds.forEach {
val view = container.getViewById(it)
// We only need to change the layout params (as opposed to triggering a requestLayout)
// because this is done right before a layout pass in the ConstraintLayout
(view?.layoutParams as ConstraintLayout.LayoutParams?)?.dimensionRatio = aspectRatio
}
}
}
override fun updatePostLayout(container: ConstraintLayout) {
super.updatePostLayout(container)
// Only dispatches the event if we have a threshold value and a dispatch was planned
if (shouldDispatchThresholdChange) {
isThresholdMet?.let {
onThresholdChangedListener?.invoke(it)
}
shouldDispatchThresholdChange = false
}
}
private fun isParentMeetingThreshold(container: ConstraintLayout): Boolean {
// If widthBreakpoint doesn't have a positive value, the threshold can never be met
val widthBreakpoint = if (widthBreakpoint > 0) widthBreakpoint else return false
return container.width > widthBreakpoint
}
}
<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:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/myImageView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<com.julienarzul.android.ConditionalAspectRatioHelper
android:layout_width="0dp"
android:layout_height="0dp"
app:baseAspectRatio="H,1:1"
app:conditionalAspectRatio="H,16:9"
app:constraint_referenced_ids="myImageView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:widthBreakpoint="412dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment