notify 中 go 的 背景动画前期
This commit is contained in:
parent
0184772698
commit
e14736ed01
|
|
@ -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<Ripple>()
|
||||
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
|
||||
)
|
||||
|
|
@ -39,4 +39,26 @@
|
|||
<attr name="startAngle" format="integer" />
|
||||
</declare-styleable>
|
||||
|
||||
|
||||
<declare-styleable name="AutoRippleView" tools:ignore="ResourceName">
|
||||
<!-- 按钮基础颜色 -->
|
||||
<attr name="arv_backgroundColor" format="color" />
|
||||
<!-- 波纹颜色 -->
|
||||
<attr name="arv_rippleColor" format="color" />
|
||||
<!-- 波纹动画持续时间(毫秒) -->
|
||||
<attr name="arv_rippleDuration" format="integer" />
|
||||
<!-- 波纹之间的间隔时间(毫秒) -->
|
||||
<attr name="arv_rippleInterval" format="integer" />
|
||||
<!-- 最大波纹半径(相对于按钮宽度的比例,0.0-1.0) -->
|
||||
<attr name="arv_maxRippleRadius" format="float" />
|
||||
<!-- 按钮圆角半径 -->
|
||||
<attr name="arv_cornerRadius" format="dimension" />
|
||||
<!-- 按钮文字 -->
|
||||
<attr name="arv_text" format="string" />
|
||||
<!-- 按钮文字颜色 -->
|
||||
<attr name="arv_textColor" format="color" />
|
||||
<!-- 按钮文字大小 -->
|
||||
<attr name="arv_textSize" format="dimension" />
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
tools:ignore="ResourceName">
|
||||
|
||||
<!-- 第1层:基础紫色圆角矩形(最底层) -->
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="16dp" /> <!-- 圆角半径,与图片一致 -->
|
||||
<solid android:color="#FFBB86FC" /> <!-- 您图片中的紫色 -->
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<!-- 第2层:白色文字 "GO" -->
|
||||
<item>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="GO"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_gravity="center"
|
||||
android:id="@+id/go_text"/> <!-- 注意:RemoteViews 中不能直接使用 -->
|
||||
</item>
|
||||
|
||||
<!-- 第3层:内层白色波纹环(最小) -->
|
||||
<item android:left="16dp" android:right="16dp" android:top="16dp" android:bottom="16dp">
|
||||
<shape android:shape="oval">
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="#80FFFFFF"/> <!-- 80 = 50% 透明度的白色 -->
|
||||
<size
|
||||
android:width="88dp"
|
||||
android:height="88dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<!-- 第4层:中层白色波纹环(中等) -->
|
||||
<item android:left="8dp" android:right="8dp" android:top="8dp" android:bottom="8dp">
|
||||
<shape android:shape="oval">
|
||||
<stroke
|
||||
android:width="2.5dp"
|
||||
android:color="#60FFFFFF"/> <!-- 60 = 37.5% 透明度的白色 -->
|
||||
<size
|
||||
android:width="104dp"
|
||||
android:height="104dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<!-- 第5层:外层白色波纹环(最大) -->
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<stroke
|
||||
android:width="1.5dp"
|
||||
android:color="#40FFFFFF"/> <!-- 40 = 25% 透明度的白色 -->
|
||||
<size
|
||||
android:width="120dp"
|
||||
android:height="120dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
</layer-list>
|
||||
|
|
@ -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" />
|
||||
</LinearLayout>
|
||||
|
|
@ -49,6 +50,7 @@
|
|||
android:text="@string/vidi_go"
|
||||
android:textColor="#fff"
|
||||
android:textSize="14sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -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" />
|
||||
</LinearLayout>
|
||||
|
|
|
|||
Loading…
Reference in New Issue