Last active
June 30, 2020 06:19
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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