Android中ListView子项margin失效解决办法

本文介绍了在Android ListView中实现子项间距的具体方法。通过在子项布局外添加额外的LinearLayout,并在该层设置margin值,成功实现了预期的视觉效果。

转载请标明出处:http://blog.youkuaiyun.com/ckchenwei/article/details/51549956

ListView是Android开发中的常用控件,在其子项布局文件中,如果在子项顶级布局中设置margin值,如下所示:

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="3dp"
android:background="@drawable/rect_gray"
>


 
其中在四个方向都设置了margin值,但实际效果图如下: 

从图中可以看出并没有实际产生margin值效果。
谷歌一下,在stackoverflow上找到了 答案,要想让ListView中两个item之间产生margin间隙,只需在item布局最外层再加上一个LinearLayout布局,然后再第二层中设置margin属性才能生效,代码如下:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="100dp"
    >

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="3dp"
android:background="@drawable/rect_gray"
    >


 
 

效果如下所示:


产生了预期的margin效果。

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"> <androidx.appcompat.widget.LinearLayoutCompat android:layout_width="match_parent" android:orientation="vertical" android:layout_height="wrap_content"> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" app:title="" app:navigationIcon="@drawable/back"> </androidx.appcompat.widget.Toolbar> <ImageView android:layout_width="match_parent" android:layout_height="180dp" android:src="@mipmap/picture_8" android:scaleType="centerCrop"/> <androidx.appcompat.widget.LinearLayoutCompat android:layout_width="match_parent" android:layout_margin="10dp" android:orientation="vertical" android:layout_height="wrap_content"> </androidx.appcompat.widget.LinearLayoutCompat> <TextView android:layout_marginTop="5dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="泰山,又名岱山、岱宗、岱岳、东岳、泰岳,为五岳之一,有“五岳之首”、“五岳独尊”、“天下第一山”之称,被中外学者称为“中国的奥林匹斯山”,位于山东省中部,隶属于泰安市,绵亘于泰安、济南、淄博三市之间,总面积25000公顷,主峰玉皇顶海拔约1545米"/> </androidx.appcompat.widget.LinearLayoutCompat> </RelativeLayout>
06-24
private fun createViewGroupSkeleton(group: ViewGroup, config: SkeletonConfig): FrameLayout { val context = group.context val skeletonContainer = FrameLayout(context) // 获取父容器信息用于日志输出 val parentName = group.javaClass.simpleName val parentId = if (group.id == View.NO_ID) "NO_ID" else { try { group.resources.getResourceEntryName(group.id) } catch (e: Exception) { "UNKNOWN" } } Log.d("SkeletonFactory", "▶️ Entering createViewGroupSkeleton") Log.d("SkeletonFactory", " Parent ViewGroup: $parentName, ID: $parentId, Hash=${Integer.toHexString(group.hashCode())}") Log.d("SkeletonFactory", " Size before measure: [w=${group.width}, h=${group.height}], Visibility: ${if (group.visibility == View.VISIBLE) "VISIBLE" else "NOT VISIBLE"}") val parentWidth = (group.parent as? View)?.measuredWidth ?: group.measuredWidth val parentHeight = (group.parent as? View)?.measuredHeight ?: group.measuredHeight // ✅ 确保父容器已完成测量和布局 if (group.width == 0 || group.height == 0) { val widthSpec = when (group.layoutParams?.width) { ViewGroup.LayoutParams.MATCH_PARENT -> { val availableWidth = (group.parent as? View)?.measuredWidth ?: group.measuredWidth View.MeasureSpec.makeMeasureSpec(availableWidth, View.MeasureSpec.EXACTLY) } ViewGroup.LayoutParams.WRAP_CONTENT -> { View.MeasureSpec.makeMeasureSpec(group.measuredWidth, View.MeasureSpec.AT_MOST) } else -> { View.MeasureSpec.makeMeasureSpec(group.layoutParams.width, View.MeasureSpec.EXACTLY) } } val heightSpec = when (group.layoutParams?.height) { ViewGroup.LayoutParams.MATCH_PARENT -> { val availableHeight = (group.parent as? View)?.measuredHeight ?: group.measuredHeight View.MeasureSpec.makeMeasureSpec(availableHeight, View.MeasureSpec.EXACTLY) } ViewGroup.LayoutParams.WRAP_CONTENT -> { View.MeasureSpec.makeMeasureSpec(group.measuredHeight, View.MeasureSpec.AT_MOST) } else -> { View.MeasureSpec.makeMeasureSpec(group.layoutParams.height, View.MeasureSpec.EXACTLY) } } group.measure(widthSpec, heightSpec) group.layout(0, 0, group.measuredWidth, group.measuredHeight) // ✅ 强制 layout Log.d("SkeletonFactory", " ⚠️ Measured & Laid out manually. New size: [w=${group.measuredWidth}, h=${group.measuredHeight}]") } // ✅ 获取父容器全局位置(必须在 layout 后) val parentLocation = IntArray(2) group.getLocationInWindow(parentLocation) val parentX = parentLocation[0] val parentY = parentLocation[1] Log.d("SkeletonFactory", " Parent Global Position (in Window): x=$parentX, y=$parentY") // 遍历所有子 view for (i in 0 until group.childCount) { val child = group.getChildAt(i) Log.d("SkeletonFactory", " ┌──────────────────────────────") Log.d("SkeletonFactory", " │ Child Index: $i") Log.d("SkeletonFactory", " │ Child Type: ${child.javaClass.simpleName}") Log.d("SkeletonFactory", " │ Child Hash: ${Integer.toHexString(child.hashCode())}") val childId = if (child.id == View.NO_ID) "NO_ID" else { try { child.resources.getResourceEntryName(child.id) } catch (e: Exception) { "UNKNOWN" } } Log.d("SkeletonFactory", " │ Child ID: $childId") // ✅ 跳过占位符 if (child is Space ) { Log.d("SkeletonFactory", " │ 💤 Skipping Space/Blank view.") Log.d("SkeletonFactory", " └──────────────────────────────") continue } // ✅ 强制测量 + 布局 child if (child.measuredWidth == 0 || child.measuredHeight == 0 || !child.isLaidOut) { val widthSpec = when (val lpWidth = child.layoutParams?.width ?: -1) { -1 -> View.MeasureSpec.makeMeasureSpec(group.measuredWidth, View.MeasureSpec.EXACTLY) -2 -> View.MeasureSpec.makeMeasureSpec(group.measuredWidth, View.MeasureSpec.AT_MOST) else -> View.MeasureSpec.makeMeasureSpec(lpWidth, View.MeasureSpec.EXACTLY) } val heightSpec = when (val lpHeight = child.layoutParams?.height ?: -1) { -1 -> View.MeasureSpec.makeMeasureSpec(group.measuredHeight, View.MeasureSpec.EXACTLY) -2 -> View.MeasureSpec.makeMeasureSpec(group.measuredHeight, View.MeasureSpec.AT_MOST) else -> View.MeasureSpec.makeMeasureSpec(lpHeight, View.MeasureSpec.EXACTLY) } child.measure(widthSpec, heightSpec) // ✅ 关键:执行 layout 设置 left/top/right/bottom child.layout( (child.left.takeIf { it != 0 } ?: 0), (child.top.takeIf { it != 0 } ?: 0), (child.left.takeIf { it != 0 } ?: 0) + child.measuredWidth, (child.top.takeIf { it != 0 } ?: 0) + child.measuredHeight ) Log.d("SkeletonFactory", " │ 🔧 Measured & Laid out child -> Size: [w=${child.measuredWidth}, h=${child.measuredHeight}]") } Log.d("SkeletonFactory", " │ Measured Size: [w=${child.measuredWidth}, h=${child.measuredHeight}]") // ✅ 方法一:推荐!使用 child.left/.top 相对于父容器(更稳定) val leftInParent = child.left val topInParent = child.top // ✅ 方法二(备用):若必须用 getLocationInWindow,需验证有效性 // val childLocation = IntArray(2) // child.getLocationInWindow(childLocation) // val isValid = childLocation[0] < 1e7 && childLocation[1] < 1e7 // 排除 0x3FFFFFFF 类似值 // val leftInParent = if (isValid) childLocation[0] - parentX else child.left // val topInParent = if (isValid) childLocation[1] - parentY else child.top Log.d("SkeletonFactory", " │ Local Position (in Parent): x=$leftInParent, y=$topInParent") // 创建骨架视图 val childSkeleton = createAutoSkeleton(child, config).view // 设置布局参数并加入容器 val lp = FrameLayout.LayoutParams(child.measuredWidth, child.measuredHeight) lp.leftMargin = leftInParent lp.topMargin = topInParent skeletonContainer.addView(childSkeleton, lp) Log.d("SkeletonFactory", " │ ✅ Added skeleton with margin: [l=$leftInParent, t=$topInParent]") Log.d("SkeletonFactory", " └──────────────────────────────") } Log.d("SkeletonFactory", "✅ Finished building skeleton. Total children processed: ${skeletonContainer.childCount}") return skeletonContainer }和private fun createViewGroupSkeleton(group: ViewGroup, config: SkeletonConfig): FrameLayout { val context = group.context val skeletonContainer = FrameLayout(context) // 测量整个 group 以确保 layout 已完成 if (group.width == 0 || group.height == 0) { group.measure( View.MeasureSpec.makeMeasureSpec(group.layoutParams.width, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(group.layoutParams.height, View.MeasureSpec.EXACTLY) ) group.layout(group.left, group.top, group.right, group.bottom) } // 获取父容器在窗口中的位置(用于坐标转换) val parentLocation = IntArray(2) group.getLocationInWindow(parentLocation) val parentX = parentLocation[0] val parentY = parentLocation[1] for (i in 0 until group.childCount) { val child = group.getChildAt(i) if (child is Space) continue // 确保测量完成 if (child.measuredWidth == 0 || child.measuredHeight == 0) { val widthSpec = if (child.layoutParams?.width ?: -1 >= 0) View.MeasureSpec.makeMeasureSpec((child.layoutParams?.width ?: 0), View.MeasureSpec.EXACTLY) else View.MeasureSpec.makeMeasureSpec(group.measuredWidth, View.MeasureSpec.AT_MOST) val heightSpec = if (child.layoutParams?.height ?: -1 >= 0) View.MeasureSpec.makeMeasureSpec((child.layoutParams?.height ?: 0), View.MeasureSpec.EXACTLY) else View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) child.measure(widthSpec, heightSpec) } // 获取 child 在屏幕中的全局坐标 val childLocation = IntArray(2) child.getLocationInWindow(childLocation) val parentLocation = IntArray(2) group.getLocationInWindow(parentLocation) val leftInParent = childLocation[0] - parentLocation[0] val topInParent = childLocation[1] - parentLocation[1] // 创建该 child 的骨架 View(如果是 ViewGroup,则 createViewGroupSkeleton 会递归处理) val childSkeletonView = createAutoSkeleton(child, config).view // 设置 LayoutParams 并用 margin 定位 val lp = FrameLayout.LayoutParams(child.measuredWidth, child.measuredHeight) lp.leftMargin = leftInParent lp.topMargin = topInParent skeletonContainer.addView(childSkeletonView, lp) } return skeletonContainer }这两个代码差在哪里↳ FrameLayout@1eaae96 ID=NO_ID, Size=[w:1220, h:0], PosWin=(x:0, y:104), PosParent=(x:0, y:0), Vis=VISIBLE, Tag=null 2025-09-23 13:57:33.591 22633-22633 ViewDump com.example.test3 D LayoutParams: 2025-09-23 13:57:33.591 22633-22633 ViewDump com.example.test3 D Padding: l=0, t=0, r=0, b=0 2025-09-23 13:57:33.591 22633-22633 ViewDump com.example.test3 D Child Count: 2 2025-09-23 13:57:33.591 22633-22633 ViewDump com.example.test3 D ↳ LinearLayout@223a17 ID=NO_ID, Size=[w:1220, h:0], PosWin=(x:0, y:104), PosParent=(x:0, y:0), Vis=VISIBLE, Tag=null 2025-09-23 13:57:33.591 22633-22633 ViewDump com.example.test3 D LayoutParams: 2025-09-23 13:57:33.591 22633-22633 ViewDump com.example.test3 D Padding: l=0, t=0, r=0, b=0 2025-09-23 13:57:33.591 22633-22633 ViewDump com.example.test3 D Child Count: 3 2025-09-23 13:57:33.591 22633-22633 ViewDump com.example.test3 D ↳ View@10fee04 ID=NO_ID, Size=[w:1220, h:0], PosWin=(x:0, y:104), PosParent=(x:0, y:0), Vis=VISIBLE, Tag=null 2025-09-23 13:57:33.591 22633-22633 ViewDump com.example.test3 D LayoutParams: 2025-09-23 13:57:33.591 22633-22633 ViewDump com.example.test3 D ↳ View@965bfed ID=NO_ID, Size=[w:976, h:0], PosWin=(x:0, y:104), PosParent=(x:0, y:0), Vis=VISIBLE, Tag=null 2025-09-23 13:57:33.591 22633-22633 ViewDump com.example.test3 D LayoutParams: 2025-09-23 13:57:33.591 22633-22633 ViewDump com.example.test3 D ↳ View@f1b1e22 ID=NO_ID, Size=[w:732, h:0], PosWin=(x:0, y:104), PosParent=(x:0, y:0), Vis=VISIBLE, Tag=null 2025-09-23 13:57:33.591 22633-22633 ViewDump com.example.test3 D LayoutParams: 2025-09-23 13:57:33.591 22633-22633 ViewDump com.example.test3 D ↳ LinearLayout@20660b3 ID=NO_ID, Size=[w:1220, h:0], PosWin=(x:0, y:104), PosParent=(x:0, y:0), Vis=VISIBLE, Tag=null 2025-09-23 13:57:33.591 22633-22633 ViewDump com.example.test3 D LayoutParams: 2025-09-23 13:57:33.591 22633-22633 ViewDump com.example.test3 D Padding: l=0, t=0, r=0, b=0 2025-09-23 13:57:33.591 22633-22633 ViewDump com.example.test3 D Child Count: 3 2025-09-23 13:57:33.591 22633-22633 ViewDump com.example.test3 D ↳ View@b9fe670 ID=NO_ID, Size=[w:1220, h:0], PosWin=(x:0, y:104), PosParent=(x:0, y:0), Vis=VISIBLE, Tag=null 2025-09-23 13:57:33.591 22633-22633 ViewDump com.example.test3 D LayoutParams: 2025-09-23 13:57:33.591 22633-22633 ViewDump com.example.test3 D ↳ View@5eb6de9 ID=NO_ID, Size=[w:976, h:0], PosWin=(x:0, y:104), PosParent=(x:0, y:0), Vis=VISIBLE, Tag=null 2025-09-23 13:57:33.591 22633-22633 ViewDump com.example.test3 D LayoutParams: 2025-09-23 13:57:33.591 22633-22633 ViewDump com.example.test3 D ↳ View@bf0fa6e ID=NO_ID, Size=[w:732, h:0], PosWin=(x:0, y:104), PosParent=(x:0, y:0), Vis=VISIBLE, Tag=null 2025-09-23 13:57:33.591 22633-22633 ViewDump com.example.test3 D LayoutParams这是第一个得日志输出,FrameLayout@304e458 ID=NO_ID, Size=[w:1220, h:0], PosWin=(x:0, y:452), PosParent=(x:0, y:348), Vis=VISIBLE, Tag=null 2025-09-23 13:58:02.793 22730-22730 ViewDump com.example.test3 D LayoutParams: 2025-09-23 13:58:02.793 22730-22730 ViewDump com.example.test3 D Padding: l=0, t=0, r=0, b=0 2025-09-23 13:58:02.793 22730-22730 ViewDump com.example.test3 D Child Count: 2 2025-09-23 13:58:02.793 22730-22730 ViewDump com.example.test3 D ↳ LinearLayout@ab839b1 ID=NO_ID, Size=[w:16777215, h:79], PosWin=(x:0, y:452), PosParent=(x:0, y:0), Vis=VISIBLE, Tag=null 2025-09-23 13:58:02.793 22730-22730 ViewDump com.example.test3 D LayoutParams: 2025-09-23 13:58:02.793 22730-22730 ViewDump com.example.test3 D Padding: l=0, t=0, r=0, b=0 2025-09-23 13:58:02.793 22730-22730 ViewDump com.example.test3 D Child Count: 3 2025-09-23 13:58:02.793 22730-22730 ViewDump com.example.test3 D ↳ View@1eaae96 ID=NO_ID, Size=[w:16777215, h:19], PosWin=(x:0, y:456), PosParent=(x:0, y:4), Vis=VISIBLE, Tag=null 2025-09-23 13:58:02.793 22730-22730 ViewDump com.example.test3 D LayoutParams: 2025-09-23 13:58:02.793 22730-22730 ViewDump com.example.test3 D ↳ View@223a17 ID=NO_ID, Size=[w:13421772, h:19], PosWin=(x:0, y:482), PosParent=(x:0, y:30), Vis=VISIBLE, Tag=null 2025-09-23 13:58:02.793 22730-22730 ViewDump com.example.test3 D LayoutParams: 2025-09-23 13:58:02.793 22730-22730 ViewDump com.example.test3 D ↳ View@10fee04 ID=NO_ID, Size=[w:10066329, h:19], PosWin=(x:0, y:508), PosParent=(x:0, y:56), Vis=VISIBLE, Tag=null 2025-09-23 13:58:02.793 22730-22730 ViewDump com.example.test3 D LayoutParams: 2025-09-23 13:58:02.794 22730-22730 ViewDump com.example.test3 D ↳ LinearLayout@965bfed ID=NO_ID, Size=[w:16777215, h:79], PosWin=(x:0, y:531), PosParent=(x:0, y:79), Vis=VISIBLE, Tag=null 2025-09-23 13:58:02.794 22730-22730 ViewDump com.example.test3 D LayoutParams: 2025-09-23 13:58:02.794 22730-22730 ViewDump com.example.test3 D Padding: l=0, t=0, r=0, b=0 2025-09-23 13:58:02.794 22730-22730 ViewDump com.example.test3 D Child Count: 3 2025-09-23 13:58:02.794 22730-22730 ViewDump com.example.test3 D ↳ View@f1b1e22 ID=NO_ID, Size=[w:16777215, h:19], PosWin=(x:0, y:535), PosParent=(x:0, y:4), Vis=VISIBLE, Tag=null 2025-09-23 13:58:02.794 22730-22730 ViewDump com.example.test3 D LayoutParams: 2025-09-23 13:58:02.794 22730-22730 ViewDump com.example.test3 D ↳ View@20660b3 ID=NO_ID, Size=[w:13421772, h:19], PosWin=(x:0, y:561), PosParent=(x:0, y:30), Vis=VISIBLE, Tag=null 2025-09-23 13:58:02.794 22730-22730 ViewDump com.example.test3 D LayoutParams: 2025-09-23 13:58:02.794 22730-22730 ViewDump com.example.test3 D ↳ View@b9fe670 ID=NO_ID, Size=[w:10066329, h:19], PosWin=(x:0, y:587), PosParent=(x:0, y:56), Vis=VISIBLE, Tag=null 2025-09-23 13:58:02.794 22730-22730 ViewDump com.example.test3 D LayoutParams:这是第二个代码得日志输出,第一种代码修改以后无法应对一个荣光其动态add进去的场景,第二种也有限,因为测试中如果原来容器是空,动态add的可以被识别到正确显示,但是如果容器内本身有一个元素就无法应对了,这是为什么呢
最新发布
09-24
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值