优化细节
This commit is contained in:
@@ -1,7 +1,10 @@
|
|||||||
package com.bbitcn.f8.pad.ui.screen.mainFunc
|
package com.bbitcn.f8.pad.ui.screen.mainFunc
|
||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import androidx.compose.foundation.Canvas
|
import android.graphics.Paint
|
||||||
|
import android.graphics.RectF
|
||||||
|
import android.text.TextPaint
|
||||||
|
import android.text.TextUtils
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
@@ -13,9 +16,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.widthIn
|
|
||||||
import androidx.compose.foundation.layout.wrapContentWidth
|
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.DatePickerDefaults
|
import androidx.compose.material3.DatePickerDefaults
|
||||||
import androidx.compose.material3.DatePickerDialog
|
import androidx.compose.material3.DatePickerDialog
|
||||||
@@ -31,7 +31,6 @@ import androidx.compose.material3.rememberDateRangePickerState
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@@ -40,18 +39,16 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.draw.drawWithCache
|
||||||
|
import androidx.compose.ui.geometry.CornerRadius
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
|
||||||
|
import androidx.compose.ui.graphics.nativeCanvas
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import androidx.compose.ui.text.drawText
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.text.rememberTextMeasurer
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.Constraints
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.bbitcn.f8.pad.M
|
import com.bbitcn.f8.pad.M
|
||||||
import com.bbitcn.f8.pad.base.MainFuncFrame
|
import com.bbitcn.f8.pad.base.MainFuncFrame
|
||||||
@@ -362,10 +359,7 @@ fun InfoList(
|
|||||||
},
|
},
|
||||||
columns = columns,
|
columns = columns,
|
||||||
item = { data, index ->
|
item = { data, index ->
|
||||||
val isSelected by remember(data.czSysid) {
|
InfoItem(isSelect = data.czSysid == selectedId.value, data = data) {
|
||||||
derivedStateOf { data.czSysid == selectedId.value }
|
|
||||||
}
|
|
||||||
InfoItem(isSelect = isSelected, data = data) {
|
|
||||||
selectedId.value = data.czSysid
|
selectedId.value = data.czSysid
|
||||||
onClick(data)
|
onClick(data)
|
||||||
}
|
}
|
||||||
@@ -397,55 +391,12 @@ fun InfoItem(
|
|||||||
)
|
)
|
||||||
.clickable(onClick = onClick)
|
.clickable(onClick = onClick)
|
||||||
) {
|
) {
|
||||||
Row(
|
PurchaseCardHeader(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
isSelect = isSelect,
|
||||||
modifier = M
|
name = data.nhName,
|
||||||
.fillMaxWidth()
|
billCode = data.billCode.toString(),
|
||||||
.height(32.dp)
|
billState = data.billState
|
||||||
.background(color = if (isSelect) MyColors.BlueGreen else MyColors.White)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = data.nhName,
|
|
||||||
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
|
|
||||||
modifier = M
|
|
||||||
.padding(horizontal = 10.dp)
|
|
||||||
.widthIn(max = 50.dp),
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis, // 超出部分显示省略号
|
|
||||||
color = if (isSelect) MyColors.White else MyColors.Black,
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
)
|
||||||
Spacer(modifier = M.weight(1f))
|
|
||||||
Text(
|
|
||||||
text = data.billCode.toString(),
|
|
||||||
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
|
|
||||||
modifier = M.padding(start = 4.dp),
|
|
||||||
color = if (isSelect) MyColors.White else MyColors.Black,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
Box(
|
|
||||||
modifier = M
|
|
||||||
.padding(start = 6.dp)
|
|
||||||
.height(32.dp)
|
|
||||||
.wrapContentWidth()
|
|
||||||
.background(
|
|
||||||
color = MyColors.Orange,
|
|
||||||
shape = RoundedCornerShape(bottomStart = 7.dp)
|
|
||||||
),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = data.billState,
|
|
||||||
modifier = M.padding(horizontal = 6.dp),
|
|
||||||
color = MyColors.White,
|
|
||||||
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PurchaseSummaryTable(
|
PurchaseSummaryTable(
|
||||||
visibleItems = visibleItems,
|
visibleItems = visibleItems,
|
||||||
hiddenCount = hiddenCount
|
hiddenCount = hiddenCount
|
||||||
@@ -457,17 +408,149 @@ fun InfoItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private data class PurchaseCardHeaderText(
|
||||||
|
val name: String,
|
||||||
|
val billCode: String,
|
||||||
|
val billState: String,
|
||||||
|
val stateWidth: Float
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun PurchaseCardHeader(
|
||||||
|
isSelect: Boolean,
|
||||||
|
name: String,
|
||||||
|
billCode: String,
|
||||||
|
billState: String
|
||||||
|
) {
|
||||||
|
val fontSize = MaterialTheme.typography.bodyMedium.fontSize
|
||||||
|
val backgroundColor = if (isSelect) MyColors.BlueGreen else MyColors.White
|
||||||
|
val textColor = if (isSelect) MyColors.White else MyColors.Black
|
||||||
|
val statusColor = MyColors.Orange
|
||||||
|
val statusTextColor = MyColors.White
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = M
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(32.dp)
|
||||||
|
.drawWithCache {
|
||||||
|
val boldPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||||
|
color = textColor.toArgb()
|
||||||
|
textSize = fontSize.toPx()
|
||||||
|
textAlign = Paint.Align.LEFT
|
||||||
|
isFakeBoldText = true
|
||||||
|
isSubpixelText = true
|
||||||
|
}
|
||||||
|
val billPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||||
|
color = textColor.toArgb()
|
||||||
|
textSize = fontSize.toPx()
|
||||||
|
textAlign = Paint.Align.RIGHT
|
||||||
|
isFakeBoldText = true
|
||||||
|
isSubpixelText = true
|
||||||
|
}
|
||||||
|
val statusTextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||||
|
color = statusTextColor.toArgb()
|
||||||
|
textSize = fontSize.toPx()
|
||||||
|
textAlign = Paint.Align.CENTER
|
||||||
|
isSubpixelText = true
|
||||||
|
}
|
||||||
|
val statusFillPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||||
|
color = statusColor.toArgb()
|
||||||
|
style = Paint.Style.FILL
|
||||||
|
}
|
||||||
|
val horizontalPadding = 10.dp.toPx()
|
||||||
|
val statusHorizontalPadding = 6.dp.toPx()
|
||||||
|
val nameMaxWidth = 50.dp.toPx()
|
||||||
|
val gap = 6.dp.toPx()
|
||||||
|
val stateMaxWidth = (size.width * 0.34f).coerceAtLeast(48.dp.toPx())
|
||||||
|
val stateText = TextUtils.ellipsize(
|
||||||
|
billState,
|
||||||
|
statusTextPaint,
|
||||||
|
(stateMaxWidth - statusHorizontalPadding * 2).coerceAtLeast(1f),
|
||||||
|
TextUtils.TruncateAt.END
|
||||||
|
).toString()
|
||||||
|
val stateWidth = (
|
||||||
|
statusTextPaint.measureText(stateText) + statusHorizontalPadding * 2
|
||||||
|
).coerceAtMost(stateMaxWidth)
|
||||||
|
val nameText = TextUtils.ellipsize(
|
||||||
|
name,
|
||||||
|
boldPaint,
|
||||||
|
nameMaxWidth,
|
||||||
|
TextUtils.TruncateAt.END
|
||||||
|
).toString()
|
||||||
|
val billRight = (size.width - stateWidth - gap).coerceAtLeast(horizontalPadding)
|
||||||
|
val billMaxWidth = (billRight - horizontalPadding - nameMaxWidth - gap).coerceAtLeast(1f)
|
||||||
|
val billText = TextUtils.ellipsize(
|
||||||
|
billCode,
|
||||||
|
billPaint,
|
||||||
|
billMaxWidth,
|
||||||
|
TextUtils.TruncateAt.END
|
||||||
|
).toString()
|
||||||
|
val clippedText = PurchaseCardHeaderText(
|
||||||
|
name = nameText,
|
||||||
|
billCode = billText,
|
||||||
|
billState = stateText,
|
||||||
|
stateWidth = stateWidth
|
||||||
|
)
|
||||||
|
val cornerRadius = 7.dp.toPx()
|
||||||
|
|
||||||
|
onDrawBehind {
|
||||||
|
drawRect(color = backgroundColor, topLeft = Offset.Zero)
|
||||||
|
|
||||||
|
val baseline = (size.height - boldPaint.fontMetrics.ascent - boldPaint.fontMetrics.descent) / 2f
|
||||||
|
drawIntoCanvas { canvas ->
|
||||||
|
val nativeCanvas = canvas.nativeCanvas
|
||||||
|
nativeCanvas.drawText(clippedText.name, horizontalPadding, baseline, boldPaint)
|
||||||
|
nativeCanvas.drawText(clippedText.billCode, billRight, baseline, billPaint)
|
||||||
|
|
||||||
|
val left = size.width - clippedText.stateWidth
|
||||||
|
val path = android.graphics.Path().apply {
|
||||||
|
moveTo(left, 0f)
|
||||||
|
lineTo(size.width, 0f)
|
||||||
|
lineTo(size.width, size.height)
|
||||||
|
lineTo(left + cornerRadius, size.height)
|
||||||
|
arcTo(
|
||||||
|
RectF(
|
||||||
|
left,
|
||||||
|
size.height - cornerRadius * 2,
|
||||||
|
left + cornerRadius * 2,
|
||||||
|
size.height
|
||||||
|
),
|
||||||
|
90f,
|
||||||
|
90f
|
||||||
|
)
|
||||||
|
lineTo(left, 0f)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
nativeCanvas.drawPath(path, statusFillPaint)
|
||||||
|
nativeCanvas.drawText(
|
||||||
|
clippedText.billState,
|
||||||
|
left + clippedText.stateWidth / 2f,
|
||||||
|
baseline,
|
||||||
|
statusTextPaint
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class PurchaseSummaryTableText(
|
||||||
|
val headers: List<String>,
|
||||||
|
val rows: List<List<String>>,
|
||||||
|
val moreText: String?
|
||||||
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun PurchaseSummaryTable(
|
private fun PurchaseSummaryTable(
|
||||||
visibleItems: List<PurchaseDataResponse.Data.ChengZhongItemSum>,
|
visibleItems: List<PurchaseDataResponse.Data.ChengZhongItemSum>,
|
||||||
hiddenCount: Int
|
hiddenCount: Int
|
||||||
) {
|
) {
|
||||||
val textMeasurer = rememberTextMeasurer()
|
|
||||||
val bodyFontSize = MaterialTheme.typography.bodyMedium.fontSize
|
val bodyFontSize = MaterialTheme.typography.bodyMedium.fontSize
|
||||||
val labelFontSize = MaterialTheme.typography.labelSmall.fontSize
|
val labelFontSize = MaterialTheme.typography.labelSmall.fontSize
|
||||||
val headerBackground = MyColors.White
|
val headerBackground = MyColors.White
|
||||||
val headerTextColor = MyColors.Black
|
val headerTextColor = MyColors.Black
|
||||||
val rowBackground = MyColors.White
|
val rowBackground = MyColors.White
|
||||||
|
val rowTextColor = MyColors.Black
|
||||||
val moreTextColor = MyColors.Gray
|
val moreTextColor = MyColors.Gray
|
||||||
val headers = remember {
|
val headers = remember {
|
||||||
listOf("茧别", "毛重", "皮重", "扣重", "净重", "单价")
|
listOf("茧别", "毛重", "皮重", "扣重", "净重", "单价")
|
||||||
@@ -484,55 +567,97 @@ private fun PurchaseSummaryTable(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val headerStyle = TextStyle(
|
|
||||||
color = headerTextColor,
|
|
||||||
fontSize = bodyFontSize,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
textAlign = TextAlign.Center
|
|
||||||
)
|
|
||||||
val rowStyle = TextStyle(
|
|
||||||
color = MyColors.Black,
|
|
||||||
fontSize = bodyFontSize,
|
|
||||||
textAlign = TextAlign.Center
|
|
||||||
)
|
|
||||||
val moreStyle = TextStyle(
|
|
||||||
color = moreTextColor,
|
|
||||||
fontSize = labelFontSize
|
|
||||||
)
|
|
||||||
|
|
||||||
Canvas(
|
Box(
|
||||||
modifier = M
|
modifier = M
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(88.dp)
|
.height(88.dp)
|
||||||
) {
|
.drawWithCache {
|
||||||
|
val headerPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||||
|
color = headerTextColor.toArgb()
|
||||||
|
textSize = bodyFontSize.toPx()
|
||||||
|
textAlign = Paint.Align.CENTER
|
||||||
|
isFakeBoldText = true
|
||||||
|
isSubpixelText = true
|
||||||
|
}
|
||||||
|
val rowPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||||
|
color = rowTextColor.toArgb()
|
||||||
|
textSize = bodyFontSize.toPx()
|
||||||
|
textAlign = Paint.Align.CENTER
|
||||||
|
isSubpixelText = true
|
||||||
|
}
|
||||||
|
val morePaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||||
|
color = moreTextColor.toArgb()
|
||||||
|
textSize = labelFontSize.toPx()
|
||||||
|
textAlign = Paint.Align.LEFT
|
||||||
|
isSubpixelText = true
|
||||||
|
}
|
||||||
val headerHeight = 28.dp.toPx()
|
val headerHeight = 28.dp.toPx()
|
||||||
val rowHeight = 24.dp.toPx()
|
val rowHeight = 24.dp.toPx()
|
||||||
val cellWidth = size.width / headers.size
|
val cellWidth = size.width / headers.size
|
||||||
val cellConstraints = Constraints.fixedWidth(cellWidth.toInt().coerceAtLeast(1))
|
val cellTextWidthPx = (cellWidth - 8.dp.toPx()).coerceAtLeast(1f)
|
||||||
|
val moreTextWidthPx = (size.width - 16.dp.toPx()).coerceAtLeast(1f)
|
||||||
|
val clippedText = PurchaseSummaryTableText(
|
||||||
|
headers = headers.map { text ->
|
||||||
|
TextUtils.ellipsize(
|
||||||
|
text,
|
||||||
|
headerPaint,
|
||||||
|
cellTextWidthPx,
|
||||||
|
TextUtils.TruncateAt.END
|
||||||
|
).toString()
|
||||||
|
},
|
||||||
|
rows = rows.map { row ->
|
||||||
|
row.map { text ->
|
||||||
|
TextUtils.ellipsize(
|
||||||
|
text,
|
||||||
|
rowPaint,
|
||||||
|
cellTextWidthPx,
|
||||||
|
TextUtils.TruncateAt.END
|
||||||
|
).toString()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
moreText = if (hiddenCount > 0) {
|
||||||
|
TextUtils.ellipsize(
|
||||||
|
"+$hiddenCount",
|
||||||
|
morePaint,
|
||||||
|
moreTextWidthPx,
|
||||||
|
TextUtils.TruncateAt.END
|
||||||
|
).toString()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
fun drawCell(text: String, column: Int, top: Float, height: Float, style: TextStyle) {
|
onDrawBehind {
|
||||||
val result = textMeasurer.measure(
|
fun drawNativeText(
|
||||||
text = AnnotatedString(text),
|
text: String,
|
||||||
style = style,
|
x: Float,
|
||||||
overflow = TextOverflow.Ellipsis,
|
top: Float,
|
||||||
maxLines = 1,
|
height: Float,
|
||||||
constraints = cellConstraints
|
paint: TextPaint
|
||||||
)
|
) {
|
||||||
drawText(
|
val baseline = top + (height - paint.fontMetrics.ascent - paint.fontMetrics.descent) / 2f
|
||||||
textLayoutResult = result,
|
drawIntoCanvas { canvas ->
|
||||||
topLeft = Offset(
|
canvas.nativeCanvas.drawText(text, x, baseline, paint)
|
||||||
x = column * cellWidth,
|
}
|
||||||
y = top + (height - result.size.height) / 2f
|
}
|
||||||
)
|
|
||||||
|
fun drawCell(text: String, column: Int, top: Float, height: Float, paint: TextPaint) {
|
||||||
|
drawNativeText(
|
||||||
|
text = text,
|
||||||
|
x = column * cellWidth + cellWidth / 2f,
|
||||||
|
top = top,
|
||||||
|
height = height,
|
||||||
|
paint = paint
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
drawRect(color = headerBackground, topLeft = Offset.Zero)
|
drawRect(color = headerBackground, topLeft = Offset.Zero)
|
||||||
headers.forEachIndexed { index, text ->
|
clippedText.headers.forEachIndexed { index, text ->
|
||||||
drawCell(text, index, 0f, headerHeight, headerStyle)
|
drawCell(text, index, 0f, headerHeight, headerPaint)
|
||||||
}
|
}
|
||||||
|
|
||||||
rows.forEachIndexed { rowIndex, row ->
|
clippedText.rows.forEachIndexed { rowIndex, row ->
|
||||||
val rowTop = headerHeight + rowIndex * rowHeight
|
val rowTop = headerHeight + rowIndex * rowHeight
|
||||||
drawRect(
|
drawRect(
|
||||||
color = rowBackground,
|
color = rowBackground,
|
||||||
@@ -540,72 +665,118 @@ private fun PurchaseSummaryTable(
|
|||||||
size = androidx.compose.ui.geometry.Size(size.width, rowHeight)
|
size = androidx.compose.ui.geometry.Size(size.width, rowHeight)
|
||||||
)
|
)
|
||||||
row.forEachIndexed { columnIndex, text ->
|
row.forEachIndexed { columnIndex, text ->
|
||||||
drawCell(text, columnIndex, rowTop, rowHeight, rowStyle)
|
drawCell(text, columnIndex, rowTop, rowHeight, rowPaint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hiddenCount > 0) {
|
clippedText.moreText?.let { text ->
|
||||||
val moreTop = headerHeight + rows.size * rowHeight + 2.dp.toPx()
|
val moreTop = headerHeight + clippedText.rows.size * rowHeight + 2.dp.toPx()
|
||||||
val result = textMeasurer.measure(
|
drawNativeText(
|
||||||
text = AnnotatedString("+$hiddenCount"),
|
text = text,
|
||||||
style = moreStyle,
|
x = 8.dp.toPx(),
|
||||||
overflow = TextOverflow.Ellipsis,
|
top = moreTop,
|
||||||
maxLines = 1,
|
height = (size.height - moreTop).coerceAtLeast(1f),
|
||||||
constraints = Constraints(maxWidth = size.width.toInt().coerceAtLeast(1))
|
paint = morePaint
|
||||||
)
|
|
||||||
drawText(
|
|
||||||
textLayoutResult = result,
|
|
||||||
topLeft = Offset(8.dp.toPx(), moreTop)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private data class PurchaseStateChip(
|
||||||
|
val text: String,
|
||||||
|
val selected: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun StateList(hasPrice: Boolean, hasTare: Boolean, hasConfirm: Boolean, hasPay: Boolean) {
|
fun StateList(hasPrice: Boolean, hasTare: Boolean, hasConfirm: Boolean, hasPay: Boolean) {
|
||||||
Row(
|
val selectedBackgroundColor = MyColors.LightLightBlueGreen
|
||||||
|
val normalBackgroundColor = Color(0xFFF4F6F8)
|
||||||
|
val selectedTextColor = MyColors.BlueGreen
|
||||||
|
val normalTextColor = MyColors.Gray
|
||||||
|
val fontSize = MaterialTheme.typography.labelSmall.fontSize
|
||||||
|
val states = remember(hasPrice, hasTare, hasConfirm, hasPay) {
|
||||||
|
listOf(
|
||||||
|
PurchaseStateChip("定价", hasPrice),
|
||||||
|
PurchaseStateChip("扣皮", hasTare),
|
||||||
|
PurchaseStateChip(if (hasConfirm) "确认" else "确认售", hasConfirm),
|
||||||
|
PurchaseStateChip("支付", hasPay)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
modifier = M
|
modifier = M
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(38.dp)
|
.height(38.dp)
|
||||||
.padding(horizontal = 4.dp, vertical = 3.dp),
|
.drawWithCache {
|
||||||
verticalAlignment = Alignment.CenterVertically
|
val selectedPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||||
) {
|
color = selectedTextColor.toArgb()
|
||||||
StateItem("定价", hasPrice, M.weight(1f))
|
textSize = fontSize.toPx()
|
||||||
StateItem("扣皮", hasTare, M.weight(1f))
|
textAlign = Paint.Align.CENTER
|
||||||
StateItem(if (hasConfirm) "确认" else "确认售", hasConfirm, M.weight(1f))
|
isSubpixelText = true
|
||||||
StateItem("支付", hasPay, M.weight(1f))
|
|
||||||
}
|
}
|
||||||
}
|
val normalPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||||
|
color = normalTextColor.toArgb()
|
||||||
@Composable
|
textSize = fontSize.toPx()
|
||||||
fun StateItem(text: String, isSelect: Boolean, modifier: Modifier) {
|
textAlign = Paint.Align.CENTER
|
||||||
val backgroundColor = if (isSelect) MyColors.LightLightBlueGreen else Color(0xFFF4F6F8)
|
isSubpixelText = true
|
||||||
val borderColor = if (isSelect) MyColors.BlueGreen else MyColors.LightGray
|
}
|
||||||
Box(
|
val outerPaddingX = 4.dp.toPx()
|
||||||
modifier = modifier
|
val chipGap = 4.dp.toPx()
|
||||||
.height(28.dp)
|
val chipPaddingX = 6.dp.toPx()
|
||||||
.padding(horizontal = 2.dp)
|
val chipHeight = 28.dp.toPx()
|
||||||
.background(backgroundColor, RoundedCornerShape(50))
|
val chipTop = (size.height - chipHeight) / 2f
|
||||||
.padding(horizontal = 6.dp, vertical = 4.dp),
|
val chipWidth = (size.width - outerPaddingX * 2 - chipGap * 3) / 4f
|
||||||
contentAlignment = Alignment.Center
|
val dotRadius = 3.dp.toPx()
|
||||||
) {
|
val dotGap = 4.dp.toPx()
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
val cornerRadius = chipHeight / 2f
|
||||||
if (isSelect) {
|
val maxTextWidth = (chipWidth - chipPaddingX * 2 - dotRadius * 2 - dotGap).coerceAtLeast(1f)
|
||||||
Box(
|
val clippedStates = states.map { state ->
|
||||||
modifier = M
|
state.copy(
|
||||||
.padding(end = 4.dp)
|
text = TextUtils.ellipsize(
|
||||||
.width(6.dp)
|
state.text,
|
||||||
.height(6.dp)
|
if (state.selected) selectedPaint else normalPaint,
|
||||||
.background(borderColor, CircleShape)
|
maxTextWidth,
|
||||||
|
TextUtils.TruncateAt.END
|
||||||
|
).toString()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Text(
|
|
||||||
text = text,
|
onDrawBehind {
|
||||||
color = if (isSelect) MyColors.BlueGreen else MyColors.Gray,
|
clippedStates.forEachIndexed { index, state ->
|
||||||
modifier = M.padding(horizontal = 5.dp),
|
val left = outerPaddingX + index * (chipWidth + chipGap)
|
||||||
fontSize = MaterialTheme.typography.labelSmall.fontSize,
|
val backgroundColor = if (state.selected) selectedBackgroundColor else normalBackgroundColor
|
||||||
|
val textPaint = if (state.selected) selectedPaint else normalPaint
|
||||||
|
drawRoundRect(
|
||||||
|
color = backgroundColor,
|
||||||
|
topLeft = Offset(left, chipTop),
|
||||||
|
size = androidx.compose.ui.geometry.Size(chipWidth, chipHeight),
|
||||||
|
cornerRadius = CornerRadius(cornerRadius, cornerRadius)
|
||||||
|
)
|
||||||
|
|
||||||
|
val textWidth = textPaint.measureText(state.text)
|
||||||
|
val groupWidth = if (state.selected) dotRadius * 2 + dotGap + textWidth else textWidth
|
||||||
|
val textCenterX = if (state.selected) {
|
||||||
|
left + chipWidth / 2f + (dotRadius * 2 + dotGap) / 2f
|
||||||
|
} else {
|
||||||
|
left + chipWidth / 2f
|
||||||
|
}
|
||||||
|
if (state.selected) {
|
||||||
|
val dotCenterX = left + (chipWidth - groupWidth) / 2f + dotRadius
|
||||||
|
drawCircle(
|
||||||
|
color = selectedTextColor,
|
||||||
|
radius = dotRadius,
|
||||||
|
center = Offset(dotCenterX, chipTop + chipHeight / 2f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
val baseline = chipTop + (chipHeight - textPaint.fontMetrics.ascent - textPaint.fontMetrics.descent) / 2f
|
||||||
|
drawIntoCanvas { canvas ->
|
||||||
|
canvas.nativeCanvas.drawText(state.text, textCenterX, baseline, textPaint)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,47 +3,47 @@ package com.bbitcn.f8.pad.ui.screen.view.common
|
|||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.gestures.detectDragGestures
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
import androidx.compose.foundation.lazy.grid.itemsIndexed
|
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.MutableState
|
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableLongStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.RectangleShape
|
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.bbitcn.f8.pad.M
|
import com.bbitcn.f8.pad.M
|
||||||
import com.bbitcn.f8.pad.R
|
import com.bbitcn.f8.pad.R
|
||||||
import com.bbitcn.f8.pad.base.RedPointBadge
|
|
||||||
import com.bbitcn.f8.pad.base.TableHeadLine
|
import com.bbitcn.f8.pad.base.TableHeadLine
|
||||||
import com.bbitcn.f8.pad.ui.theme.MyColors
|
import com.bbitcn.f8.pad.ui.theme.MyColors
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
|
||||||
|
private const val RANGE_NONE = -1
|
||||||
|
private const val RANGE_START = 0
|
||||||
|
private const val RANGE_END = 1
|
||||||
|
private const val RANGE_MIDDLE = 2
|
||||||
|
private const val RANGE_SINGLE = 3
|
||||||
|
|
||||||
@Preview(showBackground = true)
|
@Preview(showBackground = true)
|
||||||
@Composable
|
@Composable
|
||||||
fun DatePickerRangePV() {
|
fun DatePickerRangePV() {
|
||||||
@@ -148,28 +148,30 @@ fun DatePickerRange(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val firstDayOffset = currentDate.withDayOfMonth(1).dayOfWeek.value % 7
|
||||||
LazyVerticalGrid(
|
LazyVerticalGrid(
|
||||||
columns = GridCells.Fixed(7),
|
columns = GridCells.Fixed(7),
|
||||||
) {
|
) {
|
||||||
// 填充上个月的日期
|
// 填充上个月的日期
|
||||||
items((currentDate.withDayOfMonth(1).dayOfWeek.value - 1) % 7) {
|
items(firstDayOffset) {
|
||||||
CalendarDay(0)
|
CalendarDay(0)
|
||||||
}
|
}
|
||||||
// 填充当前月的日期
|
// 填充当前月的日期
|
||||||
items(currentDate.lengthOfMonth() + 1) { day ->
|
items(currentDate.lengthOfMonth()) { index ->
|
||||||
|
val day = index + 1
|
||||||
CalendarDay(
|
CalendarDay(
|
||||||
day,
|
day,
|
||||||
importantDaySet.contains(day),
|
importantDaySet.contains(day),
|
||||||
rangeType = if (day == selectedStartDay)
|
rangeType = when {
|
||||||
if (selectedEndDay == -1
|
day == selectedStartDay &&
|
||||||
|| selectedEndDay == selectedStartDay
|
(selectedEndDay == -1 || selectedEndDay == selectedStartDay) -> RANGE_SINGLE
|
||||||
)
|
day == selectedStartDay -> RANGE_START
|
||||||
3
|
day == selectedEndDay -> RANGE_END
|
||||||
else
|
selectedStartDay != -1 &&
|
||||||
0
|
selectedEndDay != -1 &&
|
||||||
else if (day == selectedEndDay) 1
|
day in selectedStartDay..selectedEndDay -> RANGE_MIDDLE
|
||||||
else if (day in selectedStartDay..selectedEndDay) 2
|
else -> RANGE_NONE
|
||||||
else -1,
|
},
|
||||||
) {
|
) {
|
||||||
if (selectedStartDay == -1 // 开始日期没选
|
if (selectedStartDay == -1 // 开始日期没选
|
||||||
|| selectedEndDay != -1 // 已经选了结束日期
|
|| selectedEndDay != -1 // 已经选了结束日期
|
||||||
@@ -194,28 +196,80 @@ fun DatePickerRange(
|
|||||||
fun CalendarDay(
|
fun CalendarDay(
|
||||||
day: Int,
|
day: Int,
|
||||||
isImportant: Boolean = false,
|
isImportant: Boolean = false,
|
||||||
rangeType: Int = -1,// -1:不在范围内 0:在第一天 1:在最后一天 2:在中间 3:只有一天
|
rangeType: Int = RANGE_NONE,
|
||||||
onClick: () -> Unit = {}
|
onClick: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
if (day != 0) {
|
|
||||||
Box(
|
Box(
|
||||||
modifier = M.background(
|
modifier = M
|
||||||
color = if (rangeType != -1) MyColors.Gray else MyColors.Transparent,
|
.fillMaxWidth()
|
||||||
shape = when (rangeType) {
|
.aspectRatio(1f)
|
||||||
-1, 3 -> CircleShape // 圆形
|
.padding(vertical = 2.dp),
|
||||||
0 -> RoundedCornerShape(topStart = 50.dp, bottomStart = 50.dp) // 左半圆右矩形
|
contentAlignment = Alignment.Center
|
||||||
1 -> RoundedCornerShape(topEnd = 50.dp, bottomEnd = 50.dp) // 右半圆左矩形
|
|
||||||
2 -> RectangleShape // 全矩形
|
|
||||||
else -> RectangleShape
|
|
||||||
}
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
if (isImportant) {
|
if (day == 0) {
|
||||||
RedPointBadge {
|
Spacer(modifier = M.fillMaxSize())
|
||||||
CalText(day.toString(), isSelect = rangeType != -1, onClick = onClick)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
CalText(day.toString(), isSelect = rangeType != -1, onClick = onClick)
|
val isEndpoint =
|
||||||
|
rangeType == RANGE_START || rangeType == RANGE_END || rangeType == RANGE_SINGLE
|
||||||
|
if (rangeType != RANGE_NONE && rangeType != RANGE_SINGLE) {
|
||||||
|
CalendarRangeTrack(rangeType)
|
||||||
|
}
|
||||||
|
CalText(
|
||||||
|
day.toString(),
|
||||||
|
isSelect = rangeType != RANGE_NONE,
|
||||||
|
isEndpoint = isEndpoint,
|
||||||
|
onClick = onClick
|
||||||
|
)
|
||||||
|
if (isImportant) {
|
||||||
|
Box(
|
||||||
|
modifier = M
|
||||||
|
.align(Alignment.TopEnd)
|
||||||
|
.padding(top = 6.dp, end = 7.dp)
|
||||||
|
.size(6.dp)
|
||||||
|
.background(MyColors.Red, CircleShape)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun BoxScope.CalendarRangeTrack(rangeType: Int) {
|
||||||
|
val trackColor = MyColors.LightBlueGreen.copy(alpha = 0.72f)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = M
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(28.dp)
|
||||||
|
.align(Alignment.Center)
|
||||||
|
) {
|
||||||
|
when (rangeType) {
|
||||||
|
RANGE_START -> {
|
||||||
|
Spacer(modifier = M.weight(1f))
|
||||||
|
Box(
|
||||||
|
modifier = M
|
||||||
|
.weight(1f)
|
||||||
|
.fillMaxHeight()
|
||||||
|
.background(trackColor)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
RANGE_END -> {
|
||||||
|
Box(
|
||||||
|
modifier = M
|
||||||
|
.weight(1f)
|
||||||
|
.fillMaxHeight()
|
||||||
|
.background(trackColor)
|
||||||
|
)
|
||||||
|
Spacer(modifier = M.weight(1f))
|
||||||
|
}
|
||||||
|
|
||||||
|
RANGE_MIDDLE -> {
|
||||||
|
Box(
|
||||||
|
modifier = M
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(trackColor)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,21 +279,40 @@ fun CalendarDay(
|
|||||||
fun CalText(
|
fun CalText(
|
||||||
day: String,
|
day: String,
|
||||||
isSelect: Boolean = false,
|
isSelect: Boolean = false,
|
||||||
|
isEndpoint: Boolean = false,
|
||||||
onClick: () -> Unit
|
onClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.aspectRatio(1f)
|
.fillMaxSize()
|
||||||
.clickable {
|
.clickable {
|
||||||
onClick()
|
onClick()
|
||||||
},
|
},
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
val animatedColor = if (isSelect) MyColors.White else MyColors.Black
|
val dayCircleModifier = if (isEndpoint) {
|
||||||
|
M
|
||||||
|
.size(30.dp)
|
||||||
|
.background(MyColors.BlueGreen, CircleShape)
|
||||||
|
} else {
|
||||||
|
M.size(30.dp)
|
||||||
|
}
|
||||||
|
val animatedColor = when {
|
||||||
|
isEndpoint -> MyColors.White
|
||||||
|
isSelect -> MyColors.BlueGreen
|
||||||
|
else -> MyColors.Black
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = dayCircleModifier,
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = day,
|
text = day,
|
||||||
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
|
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
|
||||||
color = animatedColor
|
color = animatedColor,
|
||||||
|
fontWeight = if (isSelect) FontWeight.Bold else FontWeight.Normal
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user