优化细节
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
package com.bbitcn.f8.pad.ui.screen.mainFunc
|
||||
|
||||
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.border
|
||||
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.padding
|
||||
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.material3.DatePickerDefaults
|
||||
import androidx.compose.material3.DatePickerDialog
|
||||
@@ -31,7 +31,6 @@ import androidx.compose.material3.rememberDateRangePickerState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -40,18 +39,16 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
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.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.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.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.unit.Constraints
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.bbitcn.f8.pad.M
|
||||
import com.bbitcn.f8.pad.base.MainFuncFrame
|
||||
@@ -362,10 +359,7 @@ fun InfoList(
|
||||
},
|
||||
columns = columns,
|
||||
item = { data, index ->
|
||||
val isSelected by remember(data.czSysid) {
|
||||
derivedStateOf { data.czSysid == selectedId.value }
|
||||
}
|
||||
InfoItem(isSelect = isSelected, data = data) {
|
||||
InfoItem(isSelect = data.czSysid == selectedId.value, data = data) {
|
||||
selectedId.value = data.czSysid
|
||||
onClick(data)
|
||||
}
|
||||
@@ -397,55 +391,12 @@ fun InfoItem(
|
||||
)
|
||||
.clickable(onClick = onClick)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = M
|
||||
.fillMaxWidth()
|
||||
.height(32.dp)
|
||||
.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
|
||||
PurchaseCardHeader(
|
||||
isSelect = isSelect,
|
||||
name = data.nhName,
|
||||
billCode = data.billCode.toString(),
|
||||
billState = data.billState
|
||||
)
|
||||
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(
|
||||
visibleItems = visibleItems,
|
||||
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
|
||||
private fun PurchaseSummaryTable(
|
||||
visibleItems: List<PurchaseDataResponse.Data.ChengZhongItemSum>,
|
||||
hiddenCount: Int
|
||||
) {
|
||||
val textMeasurer = rememberTextMeasurer()
|
||||
val bodyFontSize = MaterialTheme.typography.bodyMedium.fontSize
|
||||
val labelFontSize = MaterialTheme.typography.labelSmall.fontSize
|
||||
val headerBackground = MyColors.White
|
||||
val headerTextColor = MyColors.Black
|
||||
val rowBackground = MyColors.White
|
||||
val rowTextColor = MyColors.Black
|
||||
val moreTextColor = MyColors.Gray
|
||||
val headers = remember {
|
||||
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
|
||||
.fillMaxWidth()
|
||||
.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 rowHeight = 24.dp.toPx()
|
||||
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) {
|
||||
val result = textMeasurer.measure(
|
||||
text = AnnotatedString(text),
|
||||
style = style,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
constraints = cellConstraints
|
||||
)
|
||||
drawText(
|
||||
textLayoutResult = result,
|
||||
topLeft = Offset(
|
||||
x = column * cellWidth,
|
||||
y = top + (height - result.size.height) / 2f
|
||||
)
|
||||
onDrawBehind {
|
||||
fun drawNativeText(
|
||||
text: String,
|
||||
x: Float,
|
||||
top: Float,
|
||||
height: Float,
|
||||
paint: TextPaint
|
||||
) {
|
||||
val baseline = top + (height - paint.fontMetrics.ascent - paint.fontMetrics.descent) / 2f
|
||||
drawIntoCanvas { canvas ->
|
||||
canvas.nativeCanvas.drawText(text, x, baseline, paint)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
headers.forEachIndexed { index, text ->
|
||||
drawCell(text, index, 0f, headerHeight, headerStyle)
|
||||
clippedText.headers.forEachIndexed { index, text ->
|
||||
drawCell(text, index, 0f, headerHeight, headerPaint)
|
||||
}
|
||||
|
||||
rows.forEachIndexed { rowIndex, row ->
|
||||
clippedText.rows.forEachIndexed { rowIndex, row ->
|
||||
val rowTop = headerHeight + rowIndex * rowHeight
|
||||
drawRect(
|
||||
color = rowBackground,
|
||||
@@ -540,72 +665,118 @@ private fun PurchaseSummaryTable(
|
||||
size = androidx.compose.ui.geometry.Size(size.width, rowHeight)
|
||||
)
|
||||
row.forEachIndexed { columnIndex, text ->
|
||||
drawCell(text, columnIndex, rowTop, rowHeight, rowStyle)
|
||||
drawCell(text, columnIndex, rowTop, rowHeight, rowPaint)
|
||||
}
|
||||
}
|
||||
|
||||
if (hiddenCount > 0) {
|
||||
val moreTop = headerHeight + rows.size * rowHeight + 2.dp.toPx()
|
||||
val result = textMeasurer.measure(
|
||||
text = AnnotatedString("+$hiddenCount"),
|
||||
style = moreStyle,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
constraints = Constraints(maxWidth = size.width.toInt().coerceAtLeast(1))
|
||||
)
|
||||
drawText(
|
||||
textLayoutResult = result,
|
||||
topLeft = Offset(8.dp.toPx(), moreTop)
|
||||
clippedText.moreText?.let { text ->
|
||||
val moreTop = headerHeight + clippedText.rows.size * rowHeight + 2.dp.toPx()
|
||||
drawNativeText(
|
||||
text = text,
|
||||
x = 8.dp.toPx(),
|
||||
top = moreTop,
|
||||
height = (size.height - moreTop).coerceAtLeast(1f),
|
||||
paint = morePaint
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private data class PurchaseStateChip(
|
||||
val text: String,
|
||||
val selected: Boolean
|
||||
)
|
||||
|
||||
@Composable
|
||||
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
|
||||
.fillMaxWidth()
|
||||
.height(38.dp)
|
||||
.padding(horizontal = 4.dp, vertical = 3.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
StateItem("定价", hasPrice, M.weight(1f))
|
||||
StateItem("扣皮", hasTare, M.weight(1f))
|
||||
StateItem(if (hasConfirm) "确认" else "确认售", hasConfirm, M.weight(1f))
|
||||
StateItem("支付", hasPay, M.weight(1f))
|
||||
.drawWithCache {
|
||||
val selectedPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = selectedTextColor.toArgb()
|
||||
textSize = fontSize.toPx()
|
||||
textAlign = Paint.Align.CENTER
|
||||
isSubpixelText = true
|
||||
}
|
||||
val normalPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = normalTextColor.toArgb()
|
||||
textSize = fontSize.toPx()
|
||||
textAlign = Paint.Align.CENTER
|
||||
isSubpixelText = true
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StateItem(text: String, isSelect: Boolean, modifier: Modifier) {
|
||||
val backgroundColor = if (isSelect) MyColors.LightLightBlueGreen else Color(0xFFF4F6F8)
|
||||
val borderColor = if (isSelect) MyColors.BlueGreen else MyColors.LightGray
|
||||
Box(
|
||||
modifier = modifier
|
||||
.height(28.dp)
|
||||
.padding(horizontal = 2.dp)
|
||||
.background(backgroundColor, RoundedCornerShape(50))
|
||||
.padding(horizontal = 6.dp, vertical = 4.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
if (isSelect) {
|
||||
Box(
|
||||
modifier = M
|
||||
.padding(end = 4.dp)
|
||||
.width(6.dp)
|
||||
.height(6.dp)
|
||||
.background(borderColor, CircleShape)
|
||||
val outerPaddingX = 4.dp.toPx()
|
||||
val chipGap = 4.dp.toPx()
|
||||
val chipPaddingX = 6.dp.toPx()
|
||||
val chipHeight = 28.dp.toPx()
|
||||
val chipTop = (size.height - chipHeight) / 2f
|
||||
val chipWidth = (size.width - outerPaddingX * 2 - chipGap * 3) / 4f
|
||||
val dotRadius = 3.dp.toPx()
|
||||
val dotGap = 4.dp.toPx()
|
||||
val cornerRadius = chipHeight / 2f
|
||||
val maxTextWidth = (chipWidth - chipPaddingX * 2 - dotRadius * 2 - dotGap).coerceAtLeast(1f)
|
||||
val clippedStates = states.map { state ->
|
||||
state.copy(
|
||||
text = TextUtils.ellipsize(
|
||||
state.text,
|
||||
if (state.selected) selectedPaint else normalPaint,
|
||||
maxTextWidth,
|
||||
TextUtils.TruncateAt.END
|
||||
).toString()
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = text,
|
||||
color = if (isSelect) MyColors.BlueGreen else MyColors.Gray,
|
||||
modifier = M.padding(horizontal = 5.dp),
|
||||
fontSize = MaterialTheme.typography.labelSmall.fontSize,
|
||||
|
||||
onDrawBehind {
|
||||
clippedStates.forEachIndexed { index, state ->
|
||||
val left = outerPaddingX + index * (chipWidth + chipGap)
|
||||
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.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.detectDragGestures
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
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.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
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.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
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.mutableLongStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
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.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.bbitcn.f8.pad.M
|
||||
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.ui.theme.MyColors
|
||||
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)
|
||||
@Composable
|
||||
fun DatePickerRangePV() {
|
||||
@@ -148,28 +148,30 @@ fun DatePickerRange(
|
||||
)
|
||||
)
|
||||
|
||||
val firstDayOffset = currentDate.withDayOfMonth(1).dayOfWeek.value % 7
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Fixed(7),
|
||||
) {
|
||||
// 填充上个月的日期
|
||||
items((currentDate.withDayOfMonth(1).dayOfWeek.value - 1) % 7) {
|
||||
items(firstDayOffset) {
|
||||
CalendarDay(0)
|
||||
}
|
||||
// 填充当前月的日期
|
||||
items(currentDate.lengthOfMonth() + 1) { day ->
|
||||
items(currentDate.lengthOfMonth()) { index ->
|
||||
val day = index + 1
|
||||
CalendarDay(
|
||||
day,
|
||||
importantDaySet.contains(day),
|
||||
rangeType = if (day == selectedStartDay)
|
||||
if (selectedEndDay == -1
|
||||
|| selectedEndDay == selectedStartDay
|
||||
)
|
||||
3
|
||||
else
|
||||
0
|
||||
else if (day == selectedEndDay) 1
|
||||
else if (day in selectedStartDay..selectedEndDay) 2
|
||||
else -1,
|
||||
rangeType = when {
|
||||
day == selectedStartDay &&
|
||||
(selectedEndDay == -1 || selectedEndDay == selectedStartDay) -> RANGE_SINGLE
|
||||
day == selectedStartDay -> RANGE_START
|
||||
day == selectedEndDay -> RANGE_END
|
||||
selectedStartDay != -1 &&
|
||||
selectedEndDay != -1 &&
|
||||
day in selectedStartDay..selectedEndDay -> RANGE_MIDDLE
|
||||
else -> RANGE_NONE
|
||||
},
|
||||
) {
|
||||
if (selectedStartDay == -1 // 开始日期没选
|
||||
|| selectedEndDay != -1 // 已经选了结束日期
|
||||
@@ -194,28 +196,80 @@ fun DatePickerRange(
|
||||
fun CalendarDay(
|
||||
day: Int,
|
||||
isImportant: Boolean = false,
|
||||
rangeType: Int = -1,// -1:不在范围内 0:在第一天 1:在最后一天 2:在中间 3:只有一天
|
||||
rangeType: Int = RANGE_NONE,
|
||||
onClick: () -> Unit = {}
|
||||
) {
|
||||
if (day != 0) {
|
||||
Box(
|
||||
modifier = M.background(
|
||||
color = if (rangeType != -1) MyColors.Gray else MyColors.Transparent,
|
||||
shape = when (rangeType) {
|
||||
-1, 3 -> CircleShape // 圆形
|
||||
0 -> RoundedCornerShape(topStart = 50.dp, bottomStart = 50.dp) // 左半圆右矩形
|
||||
1 -> RoundedCornerShape(topEnd = 50.dp, bottomEnd = 50.dp) // 右半圆左矩形
|
||||
2 -> RectangleShape // 全矩形
|
||||
else -> RectangleShape
|
||||
}
|
||||
)
|
||||
modifier = M
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(1f)
|
||||
.padding(vertical = 2.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
if (isImportant) {
|
||||
RedPointBadge {
|
||||
CalText(day.toString(), isSelect = rangeType != -1, onClick = onClick)
|
||||
}
|
||||
if (day == 0) {
|
||||
Spacer(modifier = M.fillMaxSize())
|
||||
} 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(
|
||||
day: String,
|
||||
isSelect: Boolean = false,
|
||||
isEndpoint: Boolean = false,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.aspectRatio(1f)
|
||||
.fillMaxSize()
|
||||
.clickable {
|
||||
onClick()
|
||||
},
|
||||
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 = day,
|
||||
fontSize = MaterialTheme.typography.bodyMedium.fontSize,
|
||||
color = animatedColor
|
||||
color = animatedColor,
|
||||
fontWeight = if (isSelect) FontWeight.Bold else FontWeight.Normal
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user