如何获取含内边距的View相对父布局的位置?布局回调值异常
问题描述
我有一个FrameLayout,其子布局为LinearLayout。我通过设置LinearLayout的底部padding来更新其位置,但在OnLayoutChange回调中,bottom与oldBottom始终相同。我尝试了offsetDescendantRectToMyCoords、getLocationInWindow和getGlobalVisibleRect方法,似乎都未考虑padding。点击FrameLayout时,会给bannerView增加32dp的底部padding,可见视图上移,但日志始终显示bottom差值为0,位置为(0,0)。
布局文件
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" android:id="@+id/frameLayout"> </FrameLayout>
banner_layout.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/banner_layout" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="bottom"> <LinearLayout android:id="@+id/banner_container" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/banner_bgcolor"> <TextView android:id="@+id/banner_text" android:layout_width="0dp" android:layout_height="wrap_content" android:fontFamily="sans-serif-light" android:padding="2dp" android:textSize="15sp" android:textColor="#FF323232" android:textColorLink="#FF828282" android:textIsSelectable="false" android:textStyle="normal" android:layout_weight="1" android:layout_gravity="left" android:text="Banner text here"/> <TextView android:id="@+id/banner_logotext" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="10dp" android:paddingBottom="2dp" android:paddingTop="2dp" android:paddingRight="10dp" android:textSize="15sp" android:textColor="#FF828282" android:textColorLink="#FF828282" android:textIsSelectable="false" android:textStyle="normal" android:layout_gravity="right" android:text="Logo"/> </LinearLayout> </LinearLayout>
MainActivity 代码
class MainActivity : AppCompatActivity() { var bottom = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val frameLayout = findViewById<FrameLayout>(R.id.frameLayout) val bannerView = frameLayout.addBannerView() frameLayout.setOnClickListener { bannerView.post { bottom += 32 bannerView.setPadding(0,0,0, bottom) } } } private fun FrameLayout.addBannerView(): View { val inflater = LayoutInflater.from(context) inflater.inflate(R.layout.banner_layout, this, true) val bannerView = findViewById<View>(R.id.banner_layout) bannerView.addOnLayoutChangeListener { view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom -> val msg = "Difference bottom: ${bottom - oldBottom}\n" + "Position top left: (${bannerView.left}, ${bannerView.top})" Log.e("MainActivity", msg) Toast.makeText(context, msg, Toast.LENGTH_LONG).show() } return bannerView } }
原因分析
- 视图边界与padding的关系:
OnLayoutChangeListener回调中的bottom参数指的是视图自身的布局边界底部坐标。你的bannerView设置了fill_parent的宽高,意味着它的边界始终和父容器FrameLayout完全重合,设置padding只会挤压内部子View的显示空间,不会改变自身的布局边界,所以bottom和oldBottom始终相等,bannerView.left/bannerView.top也始终为0。 - 变量名冲突:你类中的成员变量
bottom和回调参数里的bottom重名,日志中计算的bottom - oldBottom其实是回调参数的边界值差值,而非你设置的padding值变化。
解决方案
1. 修正变量名冲突
将类成员变量bottom重命名为paddingBottom,避免与回调参数混淆:
var paddingBottom = 0
2. 监听内部子View的布局变化
因为bannerView自身边界不变,真正位置变化的是内部的banner_container,给它添加布局变化监听:
private fun FrameLayout.addBannerView(): View { val inflater = LayoutInflater.from(context) inflater.inflate(R.layout.banner_layout, this, true) val bannerView = findViewById<View>(R.id.banner_layout) val bannerContainer = findViewById<View>(R.id.banner_container) bannerContainer.addOnLayoutChangeListener { view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom -> val msg = "容器Bottom变化差值: ${bottom - oldBottom}\n" + "容器位置: (${view.left}, ${view.top})\n" + "bannerView当前底部padding: ${bannerView.paddingBottom}" Log.e("MainActivity", msg) Toast.makeText(context, msg, Toast.LENGTH_LONG).show() } return bannerView }
3. 正确转换dp为像素
你当前直接添加的32是像素值,不是dp,建议转换为dp对应的像素值,保证多设备一致性:
frameLayout.setOnClickListener { bannerView.post { val dp32 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 32f, resources.displayMetrics).toInt() paddingBottom += dp32 bannerView.setPadding(0, 0, 0, paddingBottom) } }
内容的提问来源于stack exchange,提问作者Hudson Miears
相关产品推荐
相关产品推荐

