211 lines
8.2 KiB
Kotlin
211 lines
8.2 KiB
Kotlin
package com.keylesspalace.tusky.view
|
|
|
|
import android.content.Context
|
|
import android.graphics.Canvas
|
|
import android.util.AttributeSet
|
|
import android.view.View
|
|
import android.view.ViewGroup
|
|
import android.widget.LinearLayout
|
|
import com.keylesspalace.tusky.R
|
|
import kotlin.math.roundToInt
|
|
|
|
/**
|
|
* Lays out a set of [MediaPreviewImageView]s keeping their aspect ratios into account.
|
|
*/
|
|
class MediaPreviewLayout(context: Context, attrs: AttributeSet? = null) :
|
|
ViewGroup(context, attrs) {
|
|
|
|
private val spacing = context.resources.getDimensionPixelOffset(R.dimen.preview_image_spacing)
|
|
|
|
/**
|
|
* An ordered list of aspect ratios used for layout. An image view for each aspect ratio passed
|
|
* will be attached. Supports up to 4, additional ones will be ignored.
|
|
*/
|
|
var aspectRatios: List<Double> = emptyList()
|
|
set(value) {
|
|
field = value
|
|
attachImageViews()
|
|
}
|
|
|
|
private val imageViewCache = Array(4) { MediaPreviewImageView(context) }
|
|
|
|
private var measuredOrientation = LinearLayout.VERTICAL
|
|
|
|
private fun attachImageViews() {
|
|
removeAllViews()
|
|
for (i in 0 until aspectRatios.size.coerceAtMost(imageViewCache.size)) {
|
|
addView(imageViewCache[i])
|
|
}
|
|
}
|
|
|
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
val width = MeasureSpec.getSize(widthMeasureSpec)
|
|
val halfWidth = width / 2 - spacing / 2
|
|
var totalHeight = 0
|
|
|
|
when (childCount) {
|
|
1 -> {
|
|
val aspect = aspectRatios[0]
|
|
totalHeight += getChildAt(0).measureToAspect(width, aspect)
|
|
}
|
|
2 -> {
|
|
val aspect1 = aspectRatios[0]
|
|
val aspect2 = aspectRatios[1]
|
|
|
|
if ((aspect1 + aspect2) / 2 > 1.2) {
|
|
// stack vertically
|
|
measuredOrientation = LinearLayout.VERTICAL
|
|
totalHeight += getChildAt(0).measureToAspect(width, aspect1.coerceAtLeast(1.8))
|
|
totalHeight += spacing
|
|
totalHeight += getChildAt(1).measureToAspect(width, aspect2.coerceAtLeast(1.8))
|
|
} else {
|
|
// stack horizontally
|
|
measuredOrientation = LinearLayout.HORIZONTAL
|
|
val height = rowHeight(halfWidth, aspect1, aspect2)
|
|
totalHeight += height
|
|
getChildAt(0).measureExactly(halfWidth, height)
|
|
getChildAt(1).measureExactly(halfWidth, height)
|
|
}
|
|
}
|
|
3 -> {
|
|
val aspect1 = aspectRatios[0]
|
|
val aspect2 = aspectRatios[1]
|
|
val aspect3 = aspectRatios[2]
|
|
if (aspect1 >= 1) {
|
|
// | 1 |
|
|
// -------------
|
|
// | 2 | 3 |
|
|
measuredOrientation = LinearLayout.VERTICAL
|
|
totalHeight += getChildAt(0).measureToAspect(width, aspect1.coerceAtLeast(1.8))
|
|
totalHeight += spacing
|
|
val bottomHeight = rowHeight(halfWidth, aspect2, aspect3)
|
|
totalHeight += bottomHeight
|
|
getChildAt(1).measureExactly(halfWidth, bottomHeight)
|
|
getChildAt(2).measureExactly(halfWidth, bottomHeight)
|
|
} else {
|
|
// | | 2 |
|
|
// | 1 |-----|
|
|
// | | 3 |
|
|
measuredOrientation = LinearLayout.HORIZONTAL
|
|
val colHeight = getChildAt(0).measureToAspect(halfWidth, aspect1)
|
|
totalHeight += colHeight
|
|
val halfHeight = colHeight / 2 - spacing / 2
|
|
getChildAt(1).measureExactly(halfWidth, halfHeight)
|
|
getChildAt(2).measureExactly(halfWidth, halfHeight)
|
|
}
|
|
}
|
|
4 -> {
|
|
val aspect1 = aspectRatios[0]
|
|
val aspect2 = aspectRatios[1]
|
|
val aspect3 = aspectRatios[2]
|
|
val aspect4 = aspectRatios[3]
|
|
val topHeight = rowHeight(halfWidth, aspect1, aspect2)
|
|
totalHeight += topHeight
|
|
getChildAt(0).measureExactly(halfWidth, topHeight)
|
|
getChildAt(1).measureExactly(halfWidth, topHeight)
|
|
totalHeight += spacing
|
|
val bottomHeight = rowHeight(halfWidth, aspect3, aspect4)
|
|
totalHeight += bottomHeight
|
|
getChildAt(2).measureExactly(halfWidth, bottomHeight)
|
|
getChildAt(3).measureExactly(halfWidth, bottomHeight)
|
|
}
|
|
}
|
|
|
|
super.onMeasure(
|
|
widthMeasureSpec,
|
|
MeasureSpec.makeMeasureSpec(totalHeight, MeasureSpec.EXACTLY)
|
|
)
|
|
}
|
|
|
|
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
|
|
val width = r - l
|
|
val height = b - t
|
|
val halfWidth = width / 2 - spacing / 2
|
|
when (childCount) {
|
|
1 -> {
|
|
getChildAt(0).layout(0, 0, width, height)
|
|
}
|
|
2 -> {
|
|
if (measuredOrientation == LinearLayout.VERTICAL) {
|
|
val y = imageViewCache[0].measuredHeight
|
|
getChildAt(0).layout(0, 0, width, y)
|
|
getChildAt(1).layout(
|
|
0,
|
|
y + spacing,
|
|
width,
|
|
y + spacing + getChildAt(1).measuredHeight
|
|
)
|
|
} else {
|
|
getChildAt(0).layout(0, 0, halfWidth, height)
|
|
getChildAt(1).layout(halfWidth + spacing, 0, width, height)
|
|
}
|
|
}
|
|
3 -> {
|
|
if (measuredOrientation == LinearLayout.VERTICAL) {
|
|
val y = getChildAt(0).measuredHeight
|
|
getChildAt(0).layout(0, 0, width, y)
|
|
getChildAt(1).layout(0, y + spacing, halfWidth, height)
|
|
getChildAt(2).layout(halfWidth + spacing, y + spacing, width, height)
|
|
} else {
|
|
val colHeight = getChildAt(0).measuredHeight
|
|
getChildAt(0).layout(0, 0, halfWidth, colHeight)
|
|
val halfHeight = colHeight / 2 - spacing / 2
|
|
getChildAt(1).layout(halfWidth + spacing, 0, width, halfHeight)
|
|
getChildAt(2).layout(
|
|
halfWidth + spacing,
|
|
halfHeight + spacing,
|
|
width,
|
|
colHeight
|
|
)
|
|
}
|
|
}
|
|
4 -> {
|
|
val topHeight = (getChildAt(0).measuredHeight + getChildAt(1).measuredHeight) / 2
|
|
getChildAt(0).layout(0, 0, halfWidth, topHeight)
|
|
getChildAt(1).layout(halfWidth + spacing, 0, width, topHeight)
|
|
val bottomHeight =
|
|
(imageViewCache[2].measuredHeight + imageViewCache[3].measuredHeight) / 2
|
|
getChildAt(2).layout(
|
|
0,
|
|
topHeight + spacing,
|
|
halfWidth,
|
|
topHeight + spacing + bottomHeight
|
|
)
|
|
getChildAt(3).layout(
|
|
halfWidth + spacing,
|
|
topHeight + spacing,
|
|
width,
|
|
topHeight + spacing + bottomHeight
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
inline fun forEachIndexed(action: (Int, MediaPreviewImageView) -> Unit) {
|
|
for (index in 0 until childCount) {
|
|
action(index, getChildAt(index) as MediaPreviewImageView)
|
|
}
|
|
}
|
|
|
|
override fun onDraw(canvas: Canvas?) {
|
|
super.onDraw(canvas)
|
|
}
|
|
}
|
|
|
|
private fun rowHeight(halfWidth: Int, aspect1: Double, aspect2: Double): Int {
|
|
return ((halfWidth / aspect1 + halfWidth / aspect2) / 2).roundToInt()
|
|
}
|
|
|
|
private fun View.measureToAspect(width: Int, aspect: Double): Int {
|
|
val height = (width / aspect).roundToInt()
|
|
measureExactly(width, height)
|
|
return height
|
|
}
|
|
|
|
private fun View.measureExactly(width: Int, height: Int) {
|
|
measure(
|
|
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
|
|
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
|
|
)
|
|
}
|