You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何获取含内边距的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>
<?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
    }
}

原因分析

  1. 视图边界与padding的关系:OnLayoutChangeListener回调中的bottom参数指的是视图自身的布局边界底部坐标。你的bannerView设置了fill_parent的宽高,意味着它的边界始终和父容器FrameLayout完全重合,设置padding只会挤压内部子View的显示空间,不会改变自身的布局边界,所以bottom和oldBottom始终相等,bannerView.left/bannerView.top也始终为0。
  2. 变量名冲突:你类中的成员变量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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 22:51:52