Last active
September 30, 2017 14:01
-
-
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.
This file contains 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
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