签到日历初步
This commit is contained in:
parent
1b78dce8f9
commit
517004aab3
|
|
@ -0,0 +1,12 @@
|
|||
package com.gamedog.vididin.main.fragments.task
|
||||
|
||||
// DayStatus.kt
|
||||
data class DayStatus(
|
||||
val day: Int, // 第几天 (1-7)
|
||||
val reward: String, // 奖励数值 ("100", "300"等)
|
||||
val isCompleted: Boolean = false, // 是否已完成
|
||||
val isToday: Boolean = false, // 是否是今天
|
||||
val icon: String = if (isCompleted) "✓" else "G" // 图标
|
||||
) {
|
||||
val displayText: String get() = "Dia $day"
|
||||
}
|
||||
|
|
@ -0,0 +1,224 @@
|
|||
package com.gamedog.vididin.main.fragments.task
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import com.ama.core.common.util.dp
|
||||
import com.ama.core.common.util.sp
|
||||
import com.gamedog.vididin.R
|
||||
import android.graphics.*
|
||||
import java.util.*
|
||||
|
||||
class WeekStatusView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : View(context, attrs, defStyleAttr) {
|
||||
|
||||
// 默认颜色配置(匹配图片样式)
|
||||
private var completedColor = Color.parseColor("#4CAF50") // 已完成绿色
|
||||
private var pendingColor = Color.parseColor("#FF9800") // 未完成橙色
|
||||
private var textColor = Color.parseColor("#757575") // 文字灰色
|
||||
private var rewardTextColor = Color.parseColor("#FF9800") // 奖励数值橙色
|
||||
private var todayHighlightColor = Color.parseColor("#4CAF50") // 今天高亮边框颜色
|
||||
|
||||
private var bgRectColor = Color.parseColor("#ffffedd7")
|
||||
|
||||
|
||||
|
||||
// 默认尺寸配置
|
||||
private var circleRadius = 40f.dp.toFloat()
|
||||
private var textSize = 12f.sp.toFloat()
|
||||
private var rewardTextSize = 14f.sp.toFloat()
|
||||
private var itemSpacing = 16f.dp.toFloat()
|
||||
private var todayStrokeWidth = 3f.dp.toFloat()
|
||||
private var bgRectHeight = 10.dp.toFloat()
|
||||
|
||||
// Paints
|
||||
private val bgPaint = Paint().apply {
|
||||
isAntiAlias = true
|
||||
style = Paint.Style.FILL
|
||||
color = bgRectColor
|
||||
}
|
||||
private val circlePaint = Paint().apply {
|
||||
isAntiAlias = true
|
||||
style = Paint.Style.FILL
|
||||
}
|
||||
private val textPaint = Paint().apply {
|
||||
isAntiAlias = true
|
||||
textAlign = Paint.Align.CENTER
|
||||
}
|
||||
private val todayHighlightPaint = Paint().apply {
|
||||
isAntiAlias = true
|
||||
style = Paint.Style.STROKE
|
||||
color = todayHighlightColor
|
||||
strokeWidth = todayStrokeWidth.toFloat()
|
||||
}
|
||||
|
||||
// 数据
|
||||
private var dayStatusList: List<DayStatus> = emptyList()
|
||||
private var currentDay = 1
|
||||
|
||||
init {
|
||||
setupAttributes(attrs)
|
||||
setupDefaultData()
|
||||
}
|
||||
|
||||
private fun setupAttributes(attrs: AttributeSet?) {
|
||||
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.WeekStatusView)
|
||||
|
||||
completedColor = typedArray.getColor(R.styleable.WeekStatusView_completedColor, completedColor)
|
||||
pendingColor = typedArray.getColor(R.styleable.WeekStatusView_pendingColor, pendingColor)
|
||||
textColor = typedArray.getColor(R.styleable.WeekStatusView_textColor, textColor)
|
||||
rewardTextColor = typedArray.getColor(R.styleable.WeekStatusView_rewardTextColor, rewardTextColor)
|
||||
todayHighlightColor = typedArray.getColor(R.styleable.WeekStatusView_todayHighlightColor, todayHighlightColor)
|
||||
|
||||
circleRadius = typedArray.getDimension(R.styleable.WeekStatusView_circleRadius, circleRadius)
|
||||
textSize = typedArray.getDimension(R.styleable.WeekStatusView_textSize, textSize)
|
||||
rewardTextSize = typedArray.getDimension(R.styleable.WeekStatusView_rewardTextSize, rewardTextSize)
|
||||
itemSpacing = typedArray.getDimension(R.styleable.WeekStatusView_itemSpacing, itemSpacing)
|
||||
todayStrokeWidth = typedArray.getDimension(R.styleable.WeekStatusView_todayStrokeWidth, todayStrokeWidth)
|
||||
|
||||
todayHighlightPaint.strokeWidth = todayStrokeWidth
|
||||
todayHighlightPaint.color = todayHighlightColor
|
||||
|
||||
typedArray.recycle()
|
||||
}
|
||||
|
||||
private fun setupDefaultData() {
|
||||
currentDay = getCurrentDayOfWeek()
|
||||
dayStatusList = generateWeekData(currentDay)
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
val desiredWidth = MeasureSpec.getSize(widthMeasureSpec)
|
||||
val desiredHeight = (circleRadius * 2 + textSize * 3 + itemSpacing * 2).toInt()
|
||||
|
||||
setMeasuredDimension(
|
||||
resolveSize(desiredWidth, widthMeasureSpec),
|
||||
resolveSize(desiredHeight, heightMeasureSpec)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
super.onDraw(canvas)
|
||||
|
||||
if (dayStatusList.isEmpty()) return
|
||||
|
||||
val totalWidth = measuredWidth.toFloat()
|
||||
val totalHeight = measuredHeight.toFloat()
|
||||
val totalItems = dayStatusList.size
|
||||
val availableWidth = totalWidth - (itemSpacing * (totalItems - 1))
|
||||
val itemWidth = availableWidth / totalItems
|
||||
|
||||
// bg
|
||||
canvas.drawRect(itemWidth/2, (totalHeight - bgRectHeight)/2, totalWidth - itemWidth/2, totalHeight - (totalHeight - bgRectHeight)/2, bgPaint)
|
||||
|
||||
dayStatusList.forEachIndexed { index, dayStatus ->
|
||||
val centerX = itemWidth / 2 + index * (itemWidth + itemSpacing)
|
||||
drawDayStatus(canvas, dayStatus, centerX)
|
||||
}
|
||||
}
|
||||
|
||||
private fun drawDayStatus(canvas: Canvas, dayStatus: DayStatus, centerX: Float) {
|
||||
val centerY = height / 2f
|
||||
val circleCenterY = centerY - circleRadius
|
||||
|
||||
// 绘制圆形背景
|
||||
circlePaint.color = if (dayStatus.isCompleted) completedColor else pendingColor
|
||||
canvas.drawCircle(centerX, circleCenterY, circleRadius, circlePaint)
|
||||
|
||||
// 绘制图标(对勾或G)
|
||||
textPaint.color = Color.WHITE
|
||||
textPaint.textSize = circleRadius * 0.6f
|
||||
val iconBounds = Rect()
|
||||
textPaint.getTextBounds(dayStatus.icon, 0, dayStatus.icon.length, iconBounds)
|
||||
val iconY = circleCenterY + (iconBounds.height() / 2) - iconBounds.bottom
|
||||
canvas.drawText(dayStatus.icon, centerX, iconY, textPaint)
|
||||
|
||||
// 绘制天数文字 (Dia X)
|
||||
textPaint.color = textColor
|
||||
textPaint.textSize = textSize
|
||||
canvas.drawText(dayStatus.displayText, centerX, circleCenterY + circleRadius + textSize + 5, textPaint)
|
||||
|
||||
// 绘制奖励数值
|
||||
textPaint.color = rewardTextColor
|
||||
textPaint.textSize = rewardTextSize
|
||||
canvas.drawText(dayStatus.reward, centerX, circleCenterY + circleRadius + textSize + rewardTextSize + 15, textPaint)
|
||||
|
||||
// 如果是今天,绘制高亮边框
|
||||
if (dayStatus.isToday) {
|
||||
canvas.drawCircle(centerX, circleCenterY, circleRadius + 2, todayHighlightPaint)
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 公开API方法 ====================
|
||||
|
||||
/**
|
||||
* 更新显示的数据
|
||||
* @param data 新的天数状态列表
|
||||
*/
|
||||
fun updateData(data: List<DayStatus>) {
|
||||
dayStatusList = data
|
||||
invalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据目标日期更新状态(自动计算完成状态)
|
||||
* @param targetDate 目标日期
|
||||
*/
|
||||
fun updateForDate(targetDate: Date) {
|
||||
val targetDay = getDayOfWeekFromDate(targetDate)
|
||||
dayStatusList = generateWeekData(targetDay)
|
||||
invalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新为当前日期
|
||||
*/
|
||||
fun refreshToCurrentDate() {
|
||||
updateForDate(Date())
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前显示的数据
|
||||
*/
|
||||
fun getCurrentData(): List<DayStatus> = dayStatusList
|
||||
|
||||
/**
|
||||
* 获取当前高亮的天数(今天)
|
||||
*/
|
||||
fun getCurrentDay(): Int = currentDay
|
||||
|
||||
// ==================== 工具方法 ====================
|
||||
private fun generateWeekData(targetDay: Int): List<DayStatus> {
|
||||
val rewards = listOf("100", "300", "300", "500", "300", "300", "800")
|
||||
|
||||
return (1..7).map { day ->
|
||||
DayStatus(
|
||||
day = day,
|
||||
reward = rewards[day - 1],
|
||||
isCompleted = day < targetDay,
|
||||
isToday = day == targetDay
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCurrentDayOfWeek(): Int {
|
||||
val calendar = Calendar.getInstance()
|
||||
return convertCalendarDayToWeekDay(calendar.get(Calendar.DAY_OF_WEEK))
|
||||
}
|
||||
|
||||
private fun getDayOfWeekFromDate(date: Date): Int {
|
||||
val calendar = Calendar.getInstance().apply { time = date }
|
||||
return convertCalendarDayToWeekDay(calendar.get(Calendar.DAY_OF_WEEK))
|
||||
}
|
||||
|
||||
private fun convertCalendarDayToWeekDay(calendarDay: Int): Int {
|
||||
// Calendar: 周日=1, 周一=2, ..., 周六=7
|
||||
// 转换为: 周一=1, 周二=2, ..., 周日=7
|
||||
return if (calendarDay == 1) 7 else calendarDay - 1
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:background="#666666"
|
||||
tools:ignore="Overdraw">
|
||||
|
||||
|
|
@ -372,6 +373,21 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<com.gamedog.vididin.main.fragments.task.WeekStatusView
|
||||
android:id="@+id/weekStatusView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="15dp"
|
||||
android:layout_marginTop="15dp"
|
||||
app:completedColor="#FF4CAF50"
|
||||
app:pendingColor="#FFFF9800"
|
||||
app:textColor="#FF757575"
|
||||
app:rewardTextColor="#FFFF9800"
|
||||
app:todayHighlightColor="#FF4CAF50"
|
||||
app:circleRadius="20dp"
|
||||
app:textSize="12sp"
|
||||
app:rewardTextSize="14sp"
|
||||
app:itemSpacing="8dp" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<declare-styleable name="WeekStatusView" tools:ignore="ResourceName">
|
||||
<attr name="completedColor" format="color" />
|
||||
<attr name="pendingColor" format="color" />
|
||||
<attr name="textColor" format="color" />
|
||||
<attr name="rewardTextColor" format="color" />
|
||||
<attr name="todayHighlightColor" format="color" />
|
||||
|
||||
<attr name="circleRadius" format="dimension" />
|
||||
<attr name="textSize" format="dimension" />
|
||||
<attr name="rewardTextSize" format="dimension" />
|
||||
<attr name="itemSpacing" format="dimension" />
|
||||
<attr name="todayStrokeWidth" format="dimension" />
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
||||
|
|
@ -32,13 +32,12 @@ class PopMenuIconView @JvmOverloads constructor(
|
|||
private val mMenuItemList = mutableListOf<MenuItem>()
|
||||
private var isMenuShowing = false
|
||||
private var itemSpacing = 0
|
||||
private var itemSize = 0
|
||||
|
||||
|
||||
|
||||
|
||||
private var mBinding: LayoutPopIconMenuViewBinding? = null
|
||||
init {
|
||||
itemSize = 30.dp
|
||||
itemSpacing = 20.dp
|
||||
mBinding = LayoutPopIconMenuViewBinding.inflate(LayoutInflater.from(context), this, true)
|
||||
mBinding?.run {
|
||||
|
|
@ -58,15 +57,13 @@ class PopMenuIconView @JvmOverloads constructor(
|
|||
private fun updateMenuItems() {
|
||||
mBinding!!.llMenuContainer.removeAllViews()
|
||||
|
||||
mMenuItemList.forEachIndexed { index, menuItem ->
|
||||
mMenuItemList.asReversed().forEachIndexed { index, menuItem ->
|
||||
ImageView(context).apply {
|
||||
setImageResource(menuItem.iconResId)
|
||||
|
||||
layoutParams = LinearLayout.LayoutParams(itemSize, itemSize).apply {
|
||||
if (/*index < mMenuItemList.size - 1*/true) {
|
||||
layoutParams = LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
|
||||
bottomMargin = itemSpacing
|
||||
}
|
||||
}
|
||||
|
||||
setOnClickListener {
|
||||
menuItem.onClick(this)
|
||||
|
|
|
|||
Loading…
Reference in New Issue