Android旋转TextView后如何避免继承父布局宽度,适配动态内容?
嗨,这个TextView旋转后布局适配的问题我太熟了!之前做类似垂直文字需求时也踩过同样的坑,给你两个靠谱的解决方案,保证能解决文字截断的问题,还支持动态更新月份~
问题根源先搞懂
你遇到的核心问题是:Android的布局阶段和绘制阶段是分开的。rotation属性只是在绘制时旋转画布,但布局时TextView还是用原来的120dp宽、20dp高来计算空间。当你把父容器宽度设为20dp时,布局阶段TextView的宽度被强制压缩成20dp,哪怕旋转后内容需要更多空间,也会因为布局尺寸被限制而截断,clipChildren=false也救不了——它只是允许子View超出父容器显示,但布局本身已经把TextView的宽度锁死了。
方案一:自定义TextView(最稳定推荐)
直接自定义一个TextView,重写onMeasure方法,让它在旋转-90/270度时自动交换宽高的测量结果,这样布局阶段就会用旋转后的正确尺寸来分配空间,从根源解决问题。
1. 自定义RotatedTextView代码(Kotlin)
class RotatedTextView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : AppCompatTextView(context, attrs, defStyleAttr) { private var targetRotation = 0f init { // 从XML属性读取旋转角度(可选,也可以代码设置) attrs?.let { val typedArray = context.obtainStyledAttributes(it, R.styleable.RotatedTextView) targetRotation = typedArray.getFloat(R.styleable.RotatedTextView_rotation_angle, 0f) typedArray.recycle() } rotation = targetRotation } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) // 旋转-90或270度时,交换测量后的宽高 if (targetRotation == -90f || targetRotation == 270f) { setMeasuredDimension(measuredHeight, measuredWidth) } } // 支持动态修改旋转角度(如果需要的话) fun setRotationAngle(angle: Float) { targetRotation = angle rotation = angle requestLayout() // 触发重新测量布局 } }
2. 定义XML属性(res/values/attrs.xml)
<resources> <declare-styleable name="RotatedTextView"> <attr name="rotation_angle" format="float" /> </declare-styleable> </resources>
3. 布局中使用
<RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content"> <com.your.package.name.RotatedTextView android:id="@+id/monthTextView" android:layout_width="120dp" android:layout_height="20dp" android:gravity="center" android:text="January" app:rotation_angle="-90" /> </RelativeLayout>
这样设置后,父容器用wrap_content就会自动适配旋转后的尺寸,文字不会被截断,而且你直接调用monthTextView.setText("February")就能动态更新月份,完全不用管布局适配的问题~
方案二:用ViewTreeObserver动态调整父容器尺寸(快速临时方案)
如果不想自定义View,也可以通过监听TextView的测量完成事件,手动修改父容器的布局参数,让它适配旋转后的尺寸:
val monthTextView = findViewById<TextView>(R.id.monthTextView) monthTextView.rotation = -90f // 监听TextView测量完成,调整父容器尺寸 monthTextView.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { override fun onGlobalLayout() { // 记得移除监听,避免重复触发 monthTextView.viewTreeObserver.removeOnGlobalLayoutListener(this) val parent = monthTextView.parent as ViewGroup val parentParams = parent.layoutParams // 旋转后,父容器的宽度应该等于TextView原来的高度,高度等于原来的宽度 parentParams.width = monthTextView.measuredHeight parentParams.height = monthTextView.measuredWidth parent.requestLayout() } })
不过这个方案有个小缺点:如果动态更新文字导致TextView尺寸变化,需要重新触发监听,不如自定义View省心。
最后提醒
别再给父容器硬设20dp宽度啦,不管用哪种方案,父容器用wrap_content就能自动适配旋转后的TextView尺寸,完美匹配你的设计原型~
内容的提问来源于stack exchange,提问作者marius

