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的可以被识别到正确显示,但是如果容器内本身有一个元素就无法应对了,这是为什么呢
最新发布