I wanted to say thank you @aminography, @hohteri, and @molundb because I made a different solution based on yours.
Because all texts came from backend, I made a custom view that reacts when its text size is smaller than the first time it was drawn. In that case, it fetches the other views of the same type to reduce their text size too.
If you have the same scenario, firstly register the attribute to store the id references of the same view type in the attr.xml
file.
<declare-styleable name="SizeAwareTextView">
<attr name="textViewGroup" format="reference"/>
</declare-styleable>
In the arrays.xml
file, register the ids of the type SizeAwareTextView
.
<array name="myScreen">
<item>@id/first_text</item>
<item>@id/second_text</item>
</array>
Then, create the SizeAwareTextView
class and remove the comments :)
class SizeAwareTextView @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null
) : AppCompatTextView(context, attributeSet) {
// It will register the initial text size and the smaller size the custom view could have
private var minTextSize: Float
private var viewRefs: TypedArray? = null
init {
minTextSize = textSize
val typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.SizeAwareTextView)
try {
typedArray.getResourceId(R.styleable.SizeAwareTextView_textViewGroup, 0).let {
if (it > 0) {
viewRefs = resources.obtainTypedArray(it)
}
}
} finally {
typedArray.recycle()
}
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
if (minTextSize != textSize) {
// If the text size is smaller than the minTextSize, update the value and retrieve the other custom views of the same type
// Otherwise, if the textSize is bigger than the minTextSize, it means the text changed, so pass the same view
val sizeAwareTextViewList = if (minTextSize > textSize) {
minTextSize = textSize
getSizeAwareViewList()
} else {
mutableSetOf(this)
}
resizeSizeAwareTextViews(sizeAwareTextViewList)
}
}
private fun getSizeAwareViewList(): MutableSet<SizeAwareTextView> {
val sizeAwareTextViewList = mutableSetOf<SizeAwareTextView>()
viewRefs?.let { typedArray ->
val rootView = getRootView(typedArray, sizeAwareTextViewList)
setSizeAwareViewsFromRootView(typedArray, rootView as View, sizeAwareTextViewList)
typedArray.recycle()
viewRefs = null
}
return sizeAwareTextViewList
}
private fun getRootView(
typedArray: TypedArray,
sizeAwareTextViewList: MutableSet<SizeAwareTextView>
): ViewParent {
var rootView = parent
while (rootView is View) {
// We need the root view to locate the other custom views, but we could have any element inside a recycler view. So, it is necessary to analyze every view inside it because it always retrieves the first element from the root view
verifyRootViewIsRecyclerView(typedArray, rootView as ViewGroup, sizeAwareTextViewList)
if (rootView.parent is View) {
rootView = rootView.parent
} else {
break
}
}
return rootView
}
private fun verifyRootViewIsRecyclerView(
typedArray: TypedArray,
rootView: ViewGroup,
sizeAwareTextViewList: MutableSet<SizeAwareTextView>
) {
rootView.children.forEach {
if (it is RecyclerView) {
findSizeAwareViewsInRecyclerView(typedArray, it, sizeAwareTextViewList)
}
}
}
private fun findSizeAwareViewsInRecyclerView(
typedArray: TypedArray,
rootView: RecyclerView,
sizeAwareTextViewList: MutableSet<SizeAwareTextView>
) {
rootView.children.forEach {
setSizeAwareViewsFromRootView(typedArray, it, sizeAwareTextViewList)
}
}
private fun setSizeAwareViewsFromRootView(
typedArray: TypedArray,
rootView: View,
sizeAwareTextViewList: MutableSet<SizeAwareTextView>
) {
for (resourceElement in 0 until typedArray.length()) {
val resId = typedArray.getResourceId(resourceElement, 0)
rootView.findViewById<SizeAwareTextView>(resId).takeIf { this != it }?.let {
sizeAwareTextViewList.add(it)
}
}
}
private fun resizeSizeAwareTextViews(sizeAwareTextViewList: MutableSet<SizeAwareTextView>) {
sizeAwareTextViewList.forEach { sizeAwareTextView ->
if (minTextSize < sizeAwareTextView.textSize) {
TextViewCompat.setAutoSizeTextTypeUniformWithPresetSizes(
sizeAwareTextView,
intArrayOf(minTextSize.toInt()),
TypedValue.COMPLEX_UNIT_PX
)
}
invalidate()
requestLayout()
}
}
}
When you create the SizeAwareTextView
custom view in your layout, remember to pass myScreen
array to the textViewGroup
attribute.
Are you curious why I did not override the third attribute defStyleAttr
with @JvmOverloads
? We could run into trouble if that attribute is not empty. Check this article out for more!
And that's it. If you have any questions, don't hesitate to ask!