Skip to content

Instantly share code, notes, and snippets.

@ntoskrnl
Last active September 30, 2017 14:01
Show Gist options
  • Save ntoskrnl/f83e5d6fa774624810f3aa0765027db2 to your computer and use it in GitHub Desktop.
Save ntoskrnl/f83e5d6fa774624810f3aa0765027db2 to your computer and use it in GitHub Desktop.
A work around for when you need to display a dynamically-sized view with width="wrap_content" and some other views to the right.
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
/**
* A work around for when you need to display a dynamically-sized view with width="wrap_content" and some other views to the right.
*
* How it works:
* 1. Finds the first child view with width="wrap_content" (target view)
* 2. Checks if all views after it can fit into container
* 3. If some views cannot fit, it sets a fixed width a target view,
* so that other all other views can fit in the container.
*
* Typical use case is when you have a text view with with="wrap_content"
* and want to show one ore more views right next to it.
* Normally, when the text is too long, text view will push other views out of the container.
*
* NB: When using this layout, you should call [@code:HorizontalWrapContentViewContainer.notifyTargetViewChanged()]
* whenever the content of target view changes.
*/
class HorizontalWrapContentViewContainer : LinearLayout {
private var targetView: View? = null
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
if (orientation == LinearLayout.HORIZONTAL) {
val wrapContentViewIndex = findWrapContentViewIndex()
if (wrapContentViewIndex >= 0) {
val wrapContentView = getChildAt(wrapContentViewIndex)
val delta = getSizeCorrection(wrapContentView, wrapContentViewIndex)
if (delta < 0) {
val lp = wrapContentView.layoutParams
lp.width = wrapContentView.measuredWidth + delta
wrapContentView.layoutParams = lp
}
targetView = wrapContentView
}
}
}
override fun removeView(view: View?) {
super.removeView(view)
if (view == targetView) {
targetView = null
}
}
fun notifyTargetViewChanged() {
targetView?.let {
targetView = null
val lp = it.layoutParams
lp.width = ViewGroup.LayoutParams.WRAP_CONTENT
it.layoutParams = lp
}
}
private fun getSizeCorrection(wrapContentView: View, index: Int): Int {
return when (orientation) {
LinearLayout.HORIZONTAL -> {
val currentRight = wrapContentView.left +
getHorizontalSize(wrapContentView) +
getChildrenSizeFrom(index + 1)
measuredWidth - currentRight - paddingRight
}
else -> 0
}
}
private fun findWrapContentViewIndex(): Int {
for (index in 0..childCount - 1) {
val child = getChildAt(index)
val size = child.layoutParams.width
if (child.visibility == View.VISIBLE && size == ViewGroup.LayoutParams.WRAP_CONTENT) {
return index
}
}
return -1
}
private fun getChildrenSizeFrom(startIndex: Int): Int {
return (startIndex..childCount - 1).map { getChildAt(it) }
.filter { it.visibility == View.VISIBLE }
.sumBy { getHorizontalSize(it) }
}
private fun getHorizontalSize(view: View): Int {
val lp = view.layoutParams as MarginLayoutParams
return lp.leftMargin + lp.rightMargin + view.measuredWidth
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment