Skip to content

Commit 9d482ac

Browse files
authored
Merge pull request #1081 from wangwang-code/patch-8-31-3
V3 作者资料页与工作环境卡片内容过长时自动独占一行
2 parents d7038b2 + efb234a commit 9d482ac

1 file changed

Lines changed: 97 additions & 70 deletions

File tree

app/src/main/java/ceui/lisa/activities/UserV3InfoFragment.kt

Lines changed: 97 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import android.view.View
77
import android.view.ViewGroup
88
import android.widget.LinearLayout
99
import androidx.core.view.isVisible
10+
import androidx.core.view.doOnPreDraw
1011
import androidx.databinding.DataBindingUtil
1112
import androidx.fragment.app.Fragment
1213
import androidx.fragment.app.activityViewModels
@@ -282,13 +283,80 @@ class UserV3InfoFragment : Fragment() {
282283
chips.add(Triple("Premium", if (user.is_premium == true) "★ Premium User" else "Standard", false))
283284
chips.add(Triple("Pixiv URL", "https://www.pixiv.net/users/${user.id}", true))
284285

285-
val grid = binding.profileGrid
286+
renderChipGrid(
287+
binding.profileGrid,
288+
chips.size,
289+
createChip = { createProfileChip(chips[it]) },
290+
isFullWidth = { chips[it].first == "Pixiv URL" }
291+
)
292+
}
293+
294+
private fun createProfileChip(data: Triple<String, String, Boolean>): View {
295+
val bd = ItemV3ProfileChipBinding.inflate(LayoutInflater.from(requireContext()))
296+
bd.chipLabel.text = data.first
297+
bd.chipValue.text = data.second
298+
if (data.second == "★ Premium User") {
299+
bd.chipValue.setTextColor(0xFFFFC233.toInt())
300+
}
301+
if (data.second.startsWith("http://") || data.second.startsWith("https://")) {
302+
// 不用 autoLinkMask/LinkMovementMethod:LinkMovementMethod 继承
303+
// ScrollingMovementMethod,横拖 URL 会跟外层 ViewPager2 抢手势,整页
304+
// 跟着滑(issue #917)。改成整块点击打开 + 复用 textColorLink 的链接色,
305+
// 跟 addSocialChip 的 tap-to-open 一致。
306+
bd.chipValue.setTextColor(bd.chipValue.linkTextColors)
307+
bd.root.setOnClickListener { openUrl(data.second) }
308+
}
309+
return bd.root
310+
}
311+
312+
/**
313+
* 子卡片先按「半行宽度」量自己的期望宽度,两个都放得下才并列,否则独占一行,而不是强行
314+
* 并列截断。判断依赖 grid 的真实宽度:首次绑定时父卡片刚从 gone 切 visible,grid 还没量过,
315+
* 要等这一次 measure/layout 跑完才有数,所以放到 preDraw 里摆(layout 已完成、还没画)。
316+
* 不能拿屏幕宽度估算——平板双栏 / 分屏下 widthPixels 是整屏宽,估出来比实际宽一倍,长文本
317+
* 照样被并列截断,等于没修。
318+
*
319+
* 也不能用 doOnLayout:OnLayoutChangeListener 是在 grid 自己的 layout() 里回调的,回调里
320+
* addView 打上的 FORCE_LAYOUT 标记会在 layout() 收尾时被清掉,ViewRootImpl 判定「无需第二趟」,
321+
* 首帧 grid 是空的,要等别的东西触发下一次 layout 才出现。preDraw 里 addView 走的是正常的
322+
* requestLayout → 下一帧重排,OneShotPreDrawListener 随 view detach 自动摘除。
323+
*/
324+
private fun renderChipGrid(
325+
grid: LinearLayout,
326+
chipCount: Int,
327+
createChip: (Int) -> View,
328+
isFullWidth: (Int) -> Boolean = { false },
329+
) {
330+
grid.doOnPreDraw { layoutChipGrid(grid, chipCount, createChip, isFullWidth) }
331+
}
332+
333+
private fun layoutChipGrid(
334+
grid: LinearLayout,
335+
chipCount: Int,
336+
createChip: (Int) -> View,
337+
isFullWidth: (Int) -> Boolean,
338+
) {
286339
grid.removeAllViews()
287340
val density = resources.displayMetrics.density
288341
val rowGap = (10 * density).toInt()
289342
val chipGap = (5 * density).toInt()
343+
344+
// 并列时 chip1 有 marginEnd、chip2 有 marginStart,一行让出两份 chipGap。
345+
val halfSlotWidth = ((grid.width - chipGap * 2) / 2).coerceAtLeast(0)
346+
347+
fun fitsHalfSlot(chip: View): Boolean {
348+
val bd = ItemV3ProfileChipBinding.bind(chip)
349+
val labelWidth = bd.chipLabel.paint.measureText(
350+
bd.chipLabel.text.toString().uppercase()
351+
)
352+
val valueWidth = bd.chipValue.paint.measureText(bd.chipValue.text.toString())
353+
val desiredWidth = maxOf(labelWidth, valueWidth).toInt() +
354+
bd.root.paddingLeft + bd.root.paddingRight
355+
return desiredWidth <= halfSlotWidth
356+
}
357+
290358
var i = 0
291-
while (i < chips.size) {
359+
while (i < chipCount) {
292360
val row = LinearLayout(requireContext()).apply {
293361
orientation = LinearLayout.HORIZONTAL
294362
layoutParams = LinearLayout.LayoutParams(
@@ -297,50 +365,45 @@ class UserV3InfoFragment : Fragment() {
297365
).apply { bottomMargin = rowGap }
298366
}
299367

300-
val chip1 = createProfileChip(chips[i])
301-
val isLastSingle = i + 1 >= chips.size
302-
val isFullWidth = chips[i].first == "Pixiv URL"
368+
val chip1 = createChip(i)
369+
val isLastSingle = i + 1 >= chipCount
370+
val fullWidth = isFullWidth(i)
371+
val chip1Fits = !fullWidth && !isLastSingle && fitsHalfSlot(chip1)
372+
val chip2 = if (chip1Fits) createChip(i + 1) else null
373+
val canPair = chip1Fits && chip2 != null && fitsHalfSlot(chip2)
303374

304-
if (isFullWidth || isLastSingle) {
305-
chip1.layoutParams =
306-
LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
307-
row.addView(chip1)
308-
} else {
375+
if (canPair) {
309376
chip1.layoutParams =
310377
LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f).apply {
311378
marginEnd = chipGap
312379
}
313380
row.addView(chip1)
314381

315-
val chip2 = createProfileChip(chips[i + 1])
316-
chip2.layoutParams =
382+
val chip2View = checkNotNull(chip2)
383+
chip2View.layoutParams =
317384
LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f).apply {
318385
marginStart = chipGap
319386
}
320-
row.addView(chip2)
387+
row.addView(chip2View)
321388
i++
389+
} else {
390+
// 独占一行时允许文本换行,长文本不再被单行省略
391+
makeChipWrappable(chip1)
392+
chip1.layoutParams =
393+
LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
394+
row.addView(chip1)
322395
}
323396
grid.addView(row)
324397
i++
325398
}
326399
}
327400

328-
private fun createProfileChip(data: Triple<String, String, Boolean>): View {
329-
val bd = ItemV3ProfileChipBinding.inflate(LayoutInflater.from(requireContext()))
330-
bd.chipLabel.text = data.first
331-
bd.chipValue.text = data.second
332-
if (data.second == "★ Premium User") {
333-
bd.chipValue.setTextColor(0xFFFFC233.toInt())
334-
}
335-
if (data.second.startsWith("http://") || data.second.startsWith("https://")) {
336-
// 不用 autoLinkMask/LinkMovementMethod:LinkMovementMethod 继承
337-
// ScrollingMovementMethod,横拖 URL 会跟外层 ViewPager2 抢手势,整页
338-
// 跟着滑(issue #917)。改成整块点击打开 + 复用 textColorLink 的链接色,
339-
// 跟 addSocialChip 的 tap-to-open 一致。
340-
bd.chipValue.setTextColor(bd.chipValue.linkTextColors)
341-
bd.root.setOnClickListener { openUrl(data.second) }
342-
}
343-
return bd.root
401+
@android.annotation.SuppressLint("InlinedApi")
402+
private fun makeChipWrappable(chip: View) {
403+
val bd = ItemV3ProfileChipBinding.bind(chip)
404+
bd.chipValue.maxLines = Int.MAX_VALUE
405+
bd.chipValue.ellipsize = null
406+
bd.chipValue.breakStrategy = android.graphics.text.LineBreaker.BREAK_STRATEGY_HIGH_QUALITY
344407
}
345408

346409
private fun setupWorkspaceCard(workspace: WorkspaceBean?) {
@@ -363,47 +426,11 @@ class UserV3InfoFragment : Fragment() {
363426
if (items.isEmpty()) return
364427

365428
binding.workspaceCard.visibility = View.VISIBLE
366-
val grid = binding.workspaceGrid
367-
grid.removeAllViews()
368-
val density = resources.displayMetrics.density
369-
val rowGap = (10 * density).toInt()
370-
val chipGap = (5 * density).toInt()
371-
372-
var i = 0
373-
while (i < items.size) {
374-
val row = LinearLayout(requireContext()).apply {
375-
orientation = LinearLayout.HORIZONTAL
376-
layoutParams = LinearLayout.LayoutParams(
377-
LinearLayout.LayoutParams.MATCH_PARENT,
378-
LinearLayout.LayoutParams.WRAP_CONTENT
379-
).apply { bottomMargin = rowGap }
380-
}
381-
382-
val isLastSingle = i + 1 >= items.size
383-
val chip1 = createWorkspaceChip(items[i])
384-
385-
if (isLastSingle) {
386-
chip1.layoutParams =
387-
LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
388-
row.addView(chip1)
389-
} else {
390-
chip1.layoutParams =
391-
LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f).apply {
392-
marginEnd = chipGap
393-
}
394-
row.addView(chip1)
395-
396-
val chip2 = createWorkspaceChip(items[i + 1])
397-
chip2.layoutParams =
398-
LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f).apply {
399-
marginStart = chipGap
400-
}
401-
row.addView(chip2)
402-
i++
403-
}
404-
grid.addView(row)
405-
i++
406-
}
429+
renderChipGrid(
430+
binding.workspaceGrid,
431+
items.size,
432+
createChip = { createWorkspaceChip(items[it]) }
433+
)
407434

408435
binding.workspaceHeaderToggle.setOnClickListener {
409436
val isVisible = binding.workspaceGrid.visibility == View.VISIBLE

0 commit comments

Comments
 (0)