|
|
@@ -0,0 +1,378 @@
|
|
|
+package com.adealink.frame.widget.ripple.ripple
|
|
|
+
|
|
|
+import android.content.Context
|
|
|
+import android.graphics.Canvas
|
|
|
+import android.graphics.Color
|
|
|
+import android.graphics.Paint
|
|
|
+import android.os.Build
|
|
|
+import android.util.AttributeSet
|
|
|
+import android.util.Log
|
|
|
+import android.view.View
|
|
|
+import android.view.WindowManager
|
|
|
+import androidx.annotation.ColorInt
|
|
|
+import androidx.core.content.ContextCompat
|
|
|
+import androidx.lifecycle.LifecycleOwner
|
|
|
+import com.adealink.frame.R
|
|
|
+import com.adealink.frame.util.DisplayUtil
|
|
|
+import com.adealink.weparty.commonui.ripple.lifecyle.RippleLifecycle
|
|
|
+import com.adealink.frame.widget.ripple.ripple.lifecyle.RippleLifecycleAdapter
|
|
|
+import java.util.LinkedList
|
|
|
+import kotlin.math.min
|
|
|
+
|
|
|
+/**
|
|
|
+ * 水波纹扩散View
|
|
|
+ * https://github.com/Leo199206/RippleView
|
|
|
+ */
|
|
|
+class RippleView : View {
|
|
|
+ constructor(context: Context?) : this(context, null)
|
|
|
+ constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)
|
|
|
+ constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
|
|
|
+ context,
|
|
|
+ attrs,
|
|
|
+ defStyleAttr
|
|
|
+ ) {
|
|
|
+ initAttributes(attrs)
|
|
|
+ initPaint()
|
|
|
+ if (isStart) {
|
|
|
+ onStart()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ companion object {
|
|
|
+ private const val MAX_ALPHA = 255
|
|
|
+ private const val ALPHA_RANGE = 200
|
|
|
+ }
|
|
|
+
|
|
|
+ private var paint = Paint()
|
|
|
+
|
|
|
+ //private var circleMaxRadius: Int = 0
|
|
|
+ private var circleList: MutableList<RippleCircle> = LinkedList()
|
|
|
+ private var isStart: Boolean = false
|
|
|
+ private var isPause: Boolean = false
|
|
|
+
|
|
|
+ //private var circleCenterX: Float = 0f
|
|
|
+ //private var circleCenterY: Float = 0f
|
|
|
+ private val rippleLifecycle: RippleLifecycle by lazy {
|
|
|
+ RippleLifecycle(this)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ @ColorInt
|
|
|
+ private var circleColor: Int = Color.RED
|
|
|
+ private var circleMinRadius: Float = 0f
|
|
|
+ private var circleCount: Int = 5
|
|
|
+ private var circleStyle: Paint.Style = Paint.Style.FILL
|
|
|
+ private var speed: Float = 0.5f
|
|
|
+
|
|
|
+ private var duration: Int = 200
|
|
|
+ private var circleStrokeWidth: Float = 3f
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 配置参数初始化
|
|
|
+ * @param attrs AttributeSet?
|
|
|
+ */
|
|
|
+ private fun initAttributes(attrs: AttributeSet?) {
|
|
|
+ val array = context.obtainStyledAttributes(attrs, R.styleable.RippleView)
|
|
|
+ for (index in 0 until array.indexCount) {
|
|
|
+ when (val indexedValue = array.getIndex(index)) {
|
|
|
+ R.styleable.RippleView_ripple_circle_color -> {
|
|
|
+ circleColor = array.getColor(indexedValue, Color.RED)
|
|
|
+ }
|
|
|
+
|
|
|
+ R.styleable.RippleView_ripple_circle_min_radius -> {
|
|
|
+ circleMinRadius = array.getDimension(indexedValue, 0f)
|
|
|
+ }
|
|
|
+
|
|
|
+ R.styleable.RippleView_ripple_circle_count -> {
|
|
|
+ circleCount = array.getInt(indexedValue, 2)
|
|
|
+ }
|
|
|
+
|
|
|
+ R.styleable.RippleView_ripple_circle_duration -> {
|
|
|
+ duration = array.getInt(indexedValue, duration)
|
|
|
+ }
|
|
|
+
|
|
|
+ R.styleable.RippleView_ripple_circle_stroke_width -> {
|
|
|
+ circleStrokeWidth = array.getDimension(indexedValue, circleStrokeWidth)
|
|
|
+ }
|
|
|
+
|
|
|
+ R.styleable.RippleView_ripple_circle_style -> {
|
|
|
+ circleStyle = array.getInt(indexedValue, Paint.Style.FILL.ordinal).let {
|
|
|
+ if (it == Paint.Style.FILL.ordinal) {
|
|
|
+ Paint.Style.FILL
|
|
|
+ } else {
|
|
|
+ Paint.Style.STROKE
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ R.styleable.RippleView_ripple_circle_start -> {
|
|
|
+ isStart = array.getBoolean(indexedValue, false)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fun setRipple(
|
|
|
+ color: Int,
|
|
|
+ minRadius: Float,
|
|
|
+ circleCount: Int,
|
|
|
+ speed: Float,
|
|
|
+ strokeWidth: Float
|
|
|
+ ) {
|
|
|
+ circleColor = color
|
|
|
+ circleMinRadius = minRadius
|
|
|
+ this.circleCount = circleCount
|
|
|
+ this.speed = speed
|
|
|
+ circleStrokeWidth = strokeWidth
|
|
|
+
|
|
|
+ initPaint()
|
|
|
+// circleList.clear()
|
|
|
+// initCircle()
|
|
|
+// if (isStart) {
|
|
|
+// onStart()
|
|
|
+// }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 圆形半径、透明度参数初始化
|
|
|
+ */
|
|
|
+ private fun initCircle() {
|
|
|
+ //circleMaxRadius = (width / 2 - circleStrokeWidth).toInt()
|
|
|
+// circleCenterX = width / 2f
|
|
|
+// circleCenterY = height / 2f
|
|
|
+ circleList.clear()
|
|
|
+ circleList.add(RippleCircle(circleMinRadius, MAX_ALPHA))
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun initSpeed() {
|
|
|
+ val refreshRate = getDisplayRefreshRate(context)
|
|
|
+ val refreshInterval = 1000f / refreshRate
|
|
|
+ val refreshCount = duration / refreshInterval
|
|
|
+ speed = (circleMaxRadius() - circleMinRadius) / refreshCount
|
|
|
+ Log.d(
|
|
|
+ "zhangfei",
|
|
|
+ "refreshRate:$refreshRate, refreshInterval:$refreshInterval, refreshCount:$refreshCount, " +
|
|
|
+ "\nspeed:$speed, circleMaxRadius:${circleMaxRadius()}"
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ private var refreshRate: Int = 0
|
|
|
+ fun getDisplayRefreshRate(context: Context): Int {
|
|
|
+ if (refreshRate != 0) {
|
|
|
+ return refreshRate
|
|
|
+ }
|
|
|
+ val display = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
|
+ context.display.refreshRate
|
|
|
+ } else {
|
|
|
+ ContextCompat.getSystemService(
|
|
|
+ context,
|
|
|
+ WindowManager::class.java
|
|
|
+ )?.defaultDisplay?.refreshRate ?: 60f
|
|
|
+ }
|
|
|
+ refreshRate = display.toInt()
|
|
|
+ return refreshRate
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun circleMaxRadius(): Int {
|
|
|
+ return (width / 2 - circleStrokeWidth).toInt()
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 画笔初始化
|
|
|
+ */
|
|
|
+ private fun initPaint() {
|
|
|
+ paint.style = circleStyle
|
|
|
+ paint.strokeWidth = circleStrokeWidth
|
|
|
+ paint.isDither = true
|
|
|
+ paint.isAntiAlias = true
|
|
|
+ paint.color = circleColor
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 测量控件尺寸
|
|
|
+ * @param widthMeasureSpec Int
|
|
|
+ * @param heightMeasureSpec Int
|
|
|
+ */
|
|
|
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
|
+ val defaultWidth = 200
|
|
|
+ var defaultHeight = 200
|
|
|
+ val width = measureSize(widthMeasureSpec, defaultWidth)
|
|
|
+ val height = measureSize(heightMeasureSpec, defaultHeight)
|
|
|
+ val size = min(width, height)
|
|
|
+ setMeasuredDimension(size, size)
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 测量尺寸
|
|
|
+ *
|
|
|
+ * @param measureSpec
|
|
|
+ * @param defaultSize
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private fun measureSize(measureSpec: Int, defaultSize: Int): Int {
|
|
|
+ var result: Int
|
|
|
+ val mode = MeasureSpec.getMode(measureSpec)
|
|
|
+ val size = MeasureSpec.getSize(measureSpec)
|
|
|
+ if (mode == MeasureSpec.EXACTLY) {
|
|
|
+ result = size
|
|
|
+ } else {
|
|
|
+ result = defaultSize
|
|
|
+ if (mode == MeasureSpec.AT_MOST) {
|
|
|
+ result = result.coerceAtMost(size)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 尺寸变动回调
|
|
|
+ * @param w Int
|
|
|
+ * @param h Int
|
|
|
+ * @param oldw Int
|
|
|
+ * @param oldh Int
|
|
|
+ */
|
|
|
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
|
|
+ super.onSizeChanged(w, h, oldw, oldh)
|
|
|
+ initCircle()
|
|
|
+ initSpeed()
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 视图绘制
|
|
|
+ * @param canvas Canvas
|
|
|
+ */
|
|
|
+ override fun onDraw(canvas: Canvas) {
|
|
|
+ super.onDraw(canvas)
|
|
|
+ onDrawRippleCircle(canvas)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 绘制水波纹
|
|
|
+ * @param canvas Canvas
|
|
|
+ */
|
|
|
+ private fun onDrawRippleCircle(canvas: Canvas) {
|
|
|
+ if (!isStart) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ Log.d(
|
|
|
+ "zhangfei",
|
|
|
+ "onDrawRippleCircle, width:$width, height:$height, circleMaxRadius:${circleMaxRadius()}"
|
|
|
+ )
|
|
|
+ var i = 0
|
|
|
+
|
|
|
+ with(circleList.iterator()) {
|
|
|
+ while (hasNext()) {
|
|
|
+ i++
|
|
|
+ next().also {
|
|
|
+ Log.d("zhangfei", " $i -> , alpha:${it.alpha}, radius:${it.radius}, ")
|
|
|
+
|
|
|
+ paint.alpha = it.alpha
|
|
|
+ canvas.drawCircle(
|
|
|
+ width / 2f,
|
|
|
+ //circleCenterX,
|
|
|
+ height / 2f,
|
|
|
+ //circleCenterY,
|
|
|
+ it.radius,
|
|
|
+ paint
|
|
|
+ )
|
|
|
+ it.radius += speed
|
|
|
+ if (it.radius > circleMaxRadius()) {
|
|
|
+ remove()
|
|
|
+ } else {
|
|
|
+ var length = (circleMaxRadius().toFloat() - circleMinRadius)
|
|
|
+ if (length <= 0) {
|
|
|
+ length = 1f
|
|
|
+ }
|
|
|
+ var currentLength = it.radius - circleMinRadius
|
|
|
+ if (currentLength <= 0) {
|
|
|
+ currentLength = 0f
|
|
|
+ }
|
|
|
+ val percent = currentLength / length
|
|
|
+ it.alpha =
|
|
|
+ (MAX_ALPHA - (percent) * ALPHA_RANGE).toInt()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ addNewRippleCircle()
|
|
|
+ }
|
|
|
+ postInvalidate()
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 添加新水波纹
|
|
|
+ */
|
|
|
+ private fun addNewRippleCircle() {
|
|
|
+ if (circleList.size <= 0) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ val minMeet = (circleMaxRadius() - circleMinRadius) / circleCount
|
|
|
+ val add = circleList.last().radius > (minMeet + circleMinRadius)
|
|
|
+ Log.d(
|
|
|
+ "zhangfei",
|
|
|
+ " addNewRippleCircle(add:$add), minMeet:$minMeet, circleMinRadius:$circleMinRadius"
|
|
|
+ )
|
|
|
+ if (add) {
|
|
|
+ circleList.add(RippleCircle(circleMinRadius, MAX_ALPHA))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 绑定页面生命周期,自动进行资源释放
|
|
|
+ * @param lifecycleOwner LifecycleOwner?
|
|
|
+ */
|
|
|
+ private fun bindLifecycle(lifecycleOwner: LifecycleOwner?) {
|
|
|
+ lifecycleOwner
|
|
|
+ ?.lifecycle
|
|
|
+ ?.addObserver(RippleLifecycleAdapter(rippleLifecycle))
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 开始播放动画
|
|
|
+ * @param lifecycleOwner LifecycleOwner?
|
|
|
+ */
|
|
|
+ fun onStart(lifecycleOwner: LifecycleOwner? = null) {
|
|
|
+ bindLifecycle(lifecycleOwner)
|
|
|
+ isStart = true
|
|
|
+ circleList.add(RippleCircle(circleMinRadius, MAX_ALPHA))
|
|
|
+ postInvalidate()
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 动画暂停后,恢复动画播放
|
|
|
+ */
|
|
|
+ fun onResume() {
|
|
|
+ if (isPause) {
|
|
|
+ isStart = true
|
|
|
+ isPause = false
|
|
|
+ postInvalidate()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 停止播放水波纹动画
|
|
|
+ */
|
|
|
+ fun onStop() {
|
|
|
+ isPause = false
|
|
|
+ isStart = false
|
|
|
+ circleList.clear()
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 暂停水波纹动画播放
|
|
|
+ */
|
|
|
+ fun onPause() {
|
|
|
+ if (isStart) {
|
|
|
+ isPause = true
|
|
|
+ isStart = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+}
|