From e14736ed01130ebb7be58bcbd5ebc5a34094e8ce Mon Sep 17 00:00:00 2001 From: renhaoting <370797079@qq.com> Date: Wed, 31 Dec 2025 10:42:21 +0800 Subject: [PATCH] =?UTF-8?q?notify=20=E4=B8=AD=20go=20=E7=9A=84=20=E8=83=8C?= =?UTF-8?q?=E6=99=AF=E5=8A=A8=E7=94=BB=E5=89=8D=E6=9C=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../architecture/widget/AutoRippleView.kt | 202 ++++++++++++++++++ .../src/main/res/values/attrs.xml | 22 ++ .../main/res/drawable/static_wave_effect.xml | 63 ++++++ .../layout/layout_notification_withdraw.xml | 4 +- .../layout_notification_withdraw_big.xml | 3 +- 5 files changed, 292 insertions(+), 2 deletions(-) create mode 100644 core/architecture/src/main/java/com/ama/core/architecture/widget/AutoRippleView.kt create mode 100644 notification/src/main/res/drawable/static_wave_effect.xml diff --git a/core/architecture/src/main/java/com/ama/core/architecture/widget/AutoRippleView.kt b/core/architecture/src/main/java/com/ama/core/architecture/widget/AutoRippleView.kt new file mode 100644 index 0000000..ea2dba1 --- /dev/null +++ b/core/architecture/src/main/java/com/ama/core/architecture/widget/AutoRippleView.kt @@ -0,0 +1,202 @@ +package com.ama.core.architecture.widget + +import android.animation.ValueAnimator +import android.content.Context +import android.graphics.* +import android.util.AttributeSet +import android.view.View +import com.ama.core.architecture.R + + +class AutoRippleView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : View(context, attrs, defStyleAttr) { + + // 默认值和实际属性 + private var backgroundColor = Color.parseColor("#FFBB86FC") // 淡紫色背景 + private var rippleColor = Color.parseColor("#80FFFFFF") // 半透明白色波纹 + private var rippleDuration = 1200L // 单次波纹动画时长(ms) + private var rippleInterval = 300L // 波纹间隔(ms) + private var maxRippleRadiusRatio = 0.9f // 最大波纹半径(相对于宽度) + private var cornerRadius = 16f.dp // 圆角半径 + private var buttonText = "GO" // 按钮文字 + private var textColor = Color.WHITE // 文字颜色 + private var textSize = 18f.sp // 文字大小 + + // 绘图工具 + private val paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val ripplePaint = Paint(Paint.ANTI_ALIAS_FLAG) + private val backgroundRect = RectF() + private val textBounds = Rect() + + // 文字绘制相关 + private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + textAlign = Paint.Align.CENTER + } + + // 波纹数据类 + private data class Ripple( + val startTime: Long, + var progress: Float = 0f + ) + + // 存储所有活动的波纹 + private val ripples = mutableListOf() + private var lastRippleTime = 0L + + // 动画控制器 + private var animator: ValueAnimator? = null + + init { + // 加载自定义属性 + attrs?.let { loadAttributes(it) } + + // 初始化画笔 + ripplePaint.style = Paint.Style.STROKE + ripplePaint.strokeWidth = 4f.dp + + // 初始化文字画笔 + textPaint.color = textColor + textPaint.textSize = textSize + } + + private fun loadAttributes(attrs: AttributeSet) { + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.AutoRippleView) + + backgroundColor = typedArray.getColor( + R.styleable.AutoRippleView_arv_backgroundColor, + backgroundColor + ) + rippleColor = typedArray.getColor( + R.styleable.AutoRippleView_arv_rippleColor, + rippleColor + ) + rippleDuration = typedArray.getInt( + R.styleable.AutoRippleView_arv_rippleDuration, + rippleDuration.toInt() + ).toLong() + rippleInterval = typedArray.getInt( + R.styleable.AutoRippleView_arv_rippleInterval, + rippleInterval.toInt() + ).toLong() + maxRippleRadiusRatio = typedArray.getFloat( + R.styleable.AutoRippleView_arv_maxRippleRadius, + maxRippleRadiusRatio + ) + cornerRadius = typedArray.getDimension( + R.styleable.AutoRippleView_arv_cornerRadius, + cornerRadius + ) + buttonText = typedArray.getString(R.styleable.AutoRippleView_arv_text) ?: buttonText + textColor = typedArray.getColor(R.styleable.AutoRippleView_arv_textColor, textColor) + textSize = typedArray.getDimension(R.styleable.AutoRippleView_arv_textSize, textSize) + + typedArray.recycle() + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + // 更新背景矩形区域 + backgroundRect.set(0f, 0f, w.toFloat(), h.toFloat()) + // 更新文字大小 + textPaint.textSize = textSize + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + startRippleAnimation() + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + stopRippleAnimation() + } + + private fun startRippleAnimation() { + animator = ValueAnimator.ofFloat(0f, 1f).apply { + duration = 16 + repeatCount = ValueAnimator.INFINITE + addUpdateListener { + // 检查是否需要添加新波纹 + val currentTime = System.currentTimeMillis() + if (currentTime - lastRippleTime >= rippleInterval) { + ripples.add(Ripple(currentTime)) + lastRippleTime = currentTime + } + + // 更新所有波纹的进度 + ripples.forEach { ripple -> + ripple.progress = (currentTime - ripple.startTime).toFloat() / rippleDuration + } + + // 移除已完成动画的波纹 + ripples.removeAll { it.progress >= 1f } + + // 请求重绘 + invalidate() + } + start() + } + } + + private fun stopRippleAnimation() { + animator?.cancel() + animator = null + ripples.clear() + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + // 1. 绘制背景 + paint.color = backgroundColor + canvas.drawRoundRect(backgroundRect, cornerRadius, cornerRadius, paint) + + // 2. 绘制所有波纹 + val centerX = width / 2f + val centerY = height / 2f + val maxRadius = (width.coerceAtMost(height) * maxRippleRadiusRatio) / 2 + + ripples.forEach { ripple -> + val progress = ripple.progress + if (progress in 0f..1f) { + // 计算当前波纹半径 + val currentRadius = progress * maxRadius + + // 计算透明度(从0到1再到0) + val alpha = when { + progress < 0.3f -> (progress / 0.3f) // 淡入 + progress > 0.7f -> (1 - (progress - 0.7f) / 0.3f) // 淡出 + else -> 1f // 保持完全不透明 + } + + // 设置波纹颜色和透明度 + ripplePaint.color = rippleColor + ripplePaint.alpha = (alpha * 255).toInt() + + // 绘制波纹圆环 + canvas.drawCircle(centerX, centerY, currentRadius, ripplePaint) + } + } + + // 3. 绘制文字 + textPaint.getTextBounds(buttonText, 0, buttonText.length, textBounds) + val textY = centerY - (textBounds.top + textBounds.bottom) / 2f + canvas.drawText(buttonText, centerX, textY, textPaint) + } +} + +// 扩展函数:dp、sp 转 px +val Number.dp: Float get() = toFloat() * android.util.TypedValue.applyDimension( + android.util.TypedValue.COMPLEX_UNIT_DIP, + toFloat(), + android.content.res.Resources.getSystem().displayMetrics +) + +val Number.sp: Float get() = toFloat() * android.util.TypedValue.applyDimension( + android.util.TypedValue.COMPLEX_UNIT_SP, + toFloat(), + android.content.res.Resources.getSystem().displayMetrics +) \ No newline at end of file diff --git a/core/architecture/src/main/res/values/attrs.xml b/core/architecture/src/main/res/values/attrs.xml index 5669c59..e5cad63 100644 --- a/core/architecture/src/main/res/values/attrs.xml +++ b/core/architecture/src/main/res/values/attrs.xml @@ -39,4 +39,26 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/notification/src/main/res/drawable/static_wave_effect.xml b/notification/src/main/res/drawable/static_wave_effect.xml new file mode 100644 index 0000000..bf97f9e --- /dev/null +++ b/notification/src/main/res/drawable/static_wave_effect.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/notification/src/main/res/layout/layout_notification_withdraw.xml b/notification/src/main/res/layout/layout_notification_withdraw.xml index 89bd28d..fe51647 100644 --- a/notification/src/main/res/layout/layout_notification_withdraw.xml +++ b/notification/src/main/res/layout/layout_notification_withdraw.xml @@ -3,6 +3,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" + xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical" tools:ignore="ResourceName"> @@ -32,7 +33,7 @@ android:id="@+id/tvDesc" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="xxxR$0,1 está pronto por você! Saque agora!" + android:text="desc" android:textColor="#666666" android:textSize="12sp" /> @@ -49,6 +50,7 @@ android:text="@string/vidi_go" android:textColor="#fff" android:textSize="14sp" /> + \ No newline at end of file diff --git a/notification/src/main/res/layout/layout_notification_withdraw_big.xml b/notification/src/main/res/layout/layout_notification_withdraw_big.xml index 2210b6f..d0d208b 100644 --- a/notification/src/main/res/layout/layout_notification_withdraw_big.xml +++ b/notification/src/main/res/layout/layout_notification_withdraw_big.xml @@ -3,6 +3,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" + xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical" tools:ignore="ResourceName"> @@ -32,7 +33,7 @@ android:id="@+id/tvDesc" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="xxxR$0,1 está pronto por você! Saque agora!" + android:text="desc" android:textColor="#666666" android:textSize="12sp" />