These are various snippets that can be used to create views that have a fixed ratio.
Note that you can often use a ContraintLayout instead, and apply a ratio on its children:
<androidx.constraintLayout.widget.ConstraintLayout ... >
<TextView
android:text="I will be square"
app:layout_constraintDimensionRatio="H,1:1"
android:layout_width="match_parent"
android:layout_height="0dp"
...
/>
</androidx.constraintLayout.widget.ConstraintLayout>
SquareLayout.kt
package com.pixplicity.ui.view.
import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
import kotlin.math.min
/**
* FrameLayout that forces the height to match the width
*/
class SquareLayout : FrameLayout {
constructor(context: Context) : this(context, null, 0)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
// Return height as width to force square
val width = MeasureSpec.getSize(widthMeasureSpec)
val height = MeasureSpec.getSize(heightMeasureSpec)
val size = min(width, height)
val makeMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY)
super.onMeasure(makeMeasureSpec, makeMeasureSpec)
}
}
RatioLayout.kt
An improved version can take any ratio through the XML attributes.
RatioLayout.kt
package com.pixplicity.example.views
import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
import com.pixplicity.example.R
/**
* Makes the height match the width, using the set ratio
*/
class RatioLayout(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : FrameLayout(
context,
attrs,
defStyleAttr
) {
var ratio = 1f
set(value) {
field = value
requestLayout()
}
constructor(context: Context) : this(context, null, 0)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
init {
context.theme.obtainStyledAttributes(
attrs,
R.styleable.RatioLayout,
0, 0
).apply {
try {
ratio = getFloat(R.styleable.RatioLayout_ratio, 1f)
} finally {
recycle()
}
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
// Return height as width to force square
val width = MeasureSpec.getSize(widthMeasureSpec)
val height = (width.toFloat() / ratio).toInt()
//val size = min(width, height)
val wSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY)
val hSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
super.onMeasure(wSpec, hSpec)
}
}
res/values/attr.xml
<resources>
<declare-styleable name="RatioView">
<attr name="ratio" format="float" />
</declare-styleable>
</resources>
Now you can use it like so:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res/com.pixplicity.example.views">
<com.pixplicityexample.views.RatioLayout
app:ratio="1" />
</LinearLayout>
RatioImageView.kt
ImageView
that takes a predefined width-to-height ratio. The ratio is set programmatically, not in the XML.
package com.pixplicity.ui.view
import android.content.Context
import android.support.v7.widget.AppCompatImageView
import android.util.AttributeSet
import android.view.View
import android.widget.Checkable
import android.widget.ImageView
import kotlin.math.min
/**
* Makes the height match the width, using the set ratio
*/
class RatioImageView : ImageView {
private var ratio = 1f
constructor(context: Context) : this(context, null, 0)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
/**
* w:h
*/
fun setRatio(r: Float) {
ratio = r
requestLayout()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
// Return height as width to force square
val width = MeasureSpec.getSize(widthMeasureSpec)
val height = (width.toFloat() / ratio).toInt()
//val size = min(width, height)
val wSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY)
val hSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
super.onMeasure(wSpec, hSpec)
}
}
SquareImageView.kt
Checkable, square ImageView
.
package com.pixplicity.ui.view
import android.content.Context
import android.support.v7.widget.AppCompatImageView
import android.util.AttributeSet
import android.view.View
import android.widget.Checkable
import android.widget.ImageView
import kotlin.math.min
class SquareImageView : AppCompatImageView, Checkable {
companion object {
private val CHECKED_STATE_SET = intArrayOf(android.R.attr.state_checked)
}
private var checked = false
constructor(context: Context) : this(context, null, 0)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
// Return height as width to force square
val width = MeasureSpec.getSize(widthMeasureSpec)
val height = MeasureSpec.getSize(heightMeasureSpec)
val size = min(width, height)
val makeMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY)
super.onMeasure(makeMeasureSpec, makeMeasureSpec)
}
override fun onCreateDrawableState(extraSpace: Int): IntArray {
val drawableState = super.onCreateDrawableState(extraSpace + 1)
if (isChecked) {
View.mergeDrawableStates(drawableState, CHECKED_STATE_SET)
}
return drawableState
}
override fun isChecked(): Boolean = checked
override fun toggle() {
checked = !checked
}
override fun setChecked(checked: Boolean) {
this.checked = checked
}
}