在Android实现光影移动效果【流光效果】
说明
本文是在Android实现光影移动效果【流光效果】
效果如下
|
|
ShimmerView.kt
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.LinearGradient
import android.graphics.Paint
import android.graphics.Path
import android.graphics.Point
import android.graphics.RectF
import android.graphics.Shader
import android.util.AttributeSet
import android.view.View
import android.view.animation.LinearInterpolator
class ShimmerView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private var mWidth = -1
private var mSlope: Float = -1F
private var mAnimMode = 0
private var mDuration = 1600L
private var mRepeatCount = 0
private var mColors = intArrayOf(0x00FFFFFF, 0x5AFFFFFF, 0x5AFFFFFF, 0x00FFFFFF)
private var mPositions = floatArrayOf(0f, 0.5f, 0.51f, 1f)
private var mRadius = 0
private var mPaint: Paint = Paint()
private var mPath: Path? = null
private var mClipPath: Path? = null
private var mValueAnimator: ValueAnimator? = null
init {
attrs?.let {
context.obtainStyledAttributes(attrs, R.styleable.ShimmerView).apply {
try {
mWidth = getDimensionPixelSize(R.styleable.ShimmerView_csWidth, mWidth)
mSlope = getFloat(R.styleable.ShimmerView_csSlope, mSlope)
mRadius = getDimensionPixelSize(R.styleable.ShimmerView_csRadius, mRadius)
mAnimMode = getInt(R.styleable.ShimmerView_csAnimMode, mAnimMode)
mDuration =
getInt(R.styleable.ShimmerView_csDuration, mDuration.toInt()).toLong()
mRepeatCount = getInt(R.styleable.ShimmerView_csRepeat, mRepeatCount)
val colorsStr = getString(R.styleable.ShimmerView_csColors)
val positionsStr = getString(R.styleable.ShimmerView_csPositions)
if (!colorsStr.isNullOrBlank() && !positionsStr.isNullOrBlank()) {
val colorArr =
colorsStr.split(",".toRegex()).dropLastWhile { it.isEmpty() }
.toTypedArray()
val positionArr =
positionsStr.split(",".toRegex()).dropLastWhile { it.isEmpty() }
.toTypedArray()
val size = colorArr.size
if (size == positionArr.size) {
mColors = IntArray(size)
mPositions = FloatArray(size)
for (i in 0 until size) {
mColors[i] = Color.parseColor(colorArr[i])
mPositions[i] = positionArr[i].toFloat()
}
}
}
} finally {
recycle()
}
}
}
}
public override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
initSetup(widthSize, heightSize)
if (mAnimMode == 0) {
showAnimation(widthSize, heightSize, mRepeatCount, mDuration)
}
}
private fun initSetup(width: Int, height: Int) {
if (mRepeatCount < 0) {
mRepeatCount = -1
}
if (mWidth < 0) {
mWidth = width / 3
}
if (mSlope < 0) {
mSlope = height / width.toFloat()
}
val point1 = Point(0, 0)
val point2 = Point(width, 0)
val point3 = Point(width, height)
val point4 = Point(0, height)
mPath = Path()
mPath?.moveTo(point1.x.toFloat(), point1.y.toFloat())
mPath?.lineTo(point2.x.toFloat(), point2.y.toFloat())
mPath?.lineTo(point3.x.toFloat(), point3.y.toFloat())
mPath?.lineTo(point4.x.toFloat(), point4.y.toFloat())
mPath?.close()
mClipPath = Path()
val mRect = RectF()
mRect[0f, 0f, width.toFloat()] = height.toFloat()
mClipPath?.addRoundRect(mRect, mRadius.toFloat(), mRadius.toFloat(), Path.Direction.CW)
}
public override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//绘制圆角
mClipPath?.let { canvas.clipPath(it) }
//绘制流光
mPath?.let { canvas.drawPath(it, mPaint) }
}
private fun showAnimation(width: Int, height: Int, repeatCount: Int, duration: Long) {
val offset = mWidth.toFloat()
mValueAnimator?.cancel()
mValueAnimator = ValueAnimator.ofFloat(0f - offset * 2, width + offset * 2)
mValueAnimator?.repeatCount = repeatCount
mValueAnimator?.interpolator = LinearInterpolator()
mValueAnimator?.duration = duration
mValueAnimator?.addUpdateListener { animation: ValueAnimator ->
val value = animation.animatedValue as Float
mPaint.shader = LinearGradient(
value,
mSlope * value,
value + offset,
mSlope * (value + offset),
mColors,
mPositions,
Shader.TileMode.CLAMP
)
invalidate()
}
mValueAnimator?.start()
}
public override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
mValueAnimator?.cancel()
mValueAnimator = null
}
fun setColorAndPositions(colors: IntArray, positions: FloatArray) {
if (colors.size != positions.size) {
throw RuntimeException("colors&positions的Array.size必须一致")
}
this.mColors = colors
this.mPositions = positions
}
fun setSlope(mSlope: Float) {
this.mSlope = mSlope
}
fun setWidth(mWidth: Int) {
this.mWidth = mWidth
}
fun startLightingAnimation(duration: Long = mDuration, repeatCount: Int = mRepeatCount) {
showAnimation(width, height, repeatCount, duration)
}
}
ShimmerView定义的 attrs:
<declare-styleable name="ShimmerView">
<!--自动还是手动-->
<attr name="csAnimMode" format="enum">
<enum name="auto" value="0" />
<enum name="manual" value="1" />
</attr>
<!--光影宽度-->
<attr name="csWidth" format="dimension" />
<!--光影斜率 范围【-1 ~ 1】-->
<attr name="csSlope" format="float" />
<!--控件的圆角大小-->
<attr name="csRadius" format="dimension" />
<!-- -1:无限循环,0:其他代表重复执行几次-->
<attr name="csRepeat" format="integer" />
<!--动画时长 单位ms-->
<attr name="csDuration" format="integer" />
<!--颜色值 举例:{0x00FFFFFF, 0x88FFFFFF, 0x00FFFFFF}-->
<attr name="csColors" format="string" />
<!--颜色值对应的位置数组 (值范围0~1) 举例:[0f,0.5f,1f] 与csAngle数组大小必须一致-->
<attr name="csPositions" format="string" />
</declare-styleable>
ShimmerTextView.kt
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.LinearGradient
import android.graphics.Matrix
import android.graphics.Paint
import android.graphics.Shader
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatTextView
class ShimmerTextView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AppCompatTextView(context, attrs, defStyleAttr) {
private var mLinearGradient: LinearGradient? = null
private var mGradientMatrix: Matrix? = null
private var mViewWidth = 0
private var mTranslate = 0
private var mAnimating = true
private val speed = 50
private var mPaint: Paint? = null
private var textColor = 0
private var shimmerColor = 0
init {
attrs?.let {
context.obtainStyledAttributes(attrs, R.styleable.ShimmerTextView).apply {
try {
textColor = getColor(R.styleable.ShimmerTextView_stvTextColor, Color.BLACK)
shimmerColor =
getColor(R.styleable.ShimmerTextView_stvShimmerColor, Color.WHITE)
mPaint = paint
mGradientMatrix = Matrix()
} finally {
recycle()
}
}
}
}
fun setShimmer(isShimmer: Boolean) {
mAnimating = isShimmer
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
mViewWidth = measuredWidth
}
fun initLinearGradient() {
mLinearGradient = LinearGradient(
0f,
0f,
mViewWidth.toFloat(),
0f,
intArrayOf(textColor, shimmerColor, textColor),
null,
Shader.TileMode.CLAMP
)
mPaint?.shader = mLinearGradient
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (mAnimating) {
if (mGradientMatrix != null && mLinearGradient != null) {
mTranslate += mViewWidth / 10
if (mTranslate > 2 * mViewWidth) {
mTranslate = -mViewWidth
}
mGradientMatrix?.setTranslate(mTranslate.toFloat(), 0f)
mLinearGradient?.setLocalMatrix(mGradientMatrix)
} else {
initLinearGradient()
}
postInvalidateDelayed(speed.toLong())
}
}
}
ShimmerTextView 定义的 attrs:
<declare-styleable name="ShimmerTextView">
<attr name="stvTextColor" format="color" />
<attr name="stvShimmerColor" format="color" />
</declare-styleable>
DEMO
- Demo.apk 点击下载
项目和演示效果可以去Github查看
项目地址: https://github.com/logan0817/shinningview 。
如果你有任何疑问可以留言。
如果这篇文章对你有帮助,可以赏个赞支持一下作者。
原文地址:https://blog.csdn.net/notwalnut/article/details/136044650
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!