Skip to content

Instantly share code, notes, and snippets.

@hjhjw1991
Last active July 19, 2023 06:54
Show Gist options
  • Save hjhjw1991/de59100c379d556cbfa0ecf733939eb4 to your computer and use it in GitHub Desktop.
Save hjhjw1991/de59100c379d556cbfa0ecf733939eb4 to your computer and use it in GitHub Desktop.
透明度渐变、线性渐变的 ImageView
package com.huangjun.barney.views
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatImageView
import kotlin.math.max
import kotlin.math.min
/**
* 给 ImageView 指定渐变透明度,可以实现上下、左右的渐隐效果
* alpha [0.0, 1.0]
* @author: huangjun.barney
*/
class HJGradientAlphaImageView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr) {
private var startX = 0f
private var startY = 0f
private var endX = 0f
private var endY = 0f
private var startAlpha = 1f
private var endAlpha = 0f
private var paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
isDither = true
xfermode = PorterDuffXfermode(PorterDuff.Mode.XOR)
}
private val gradientMatrix = Matrix()
private val clipRectF = RectF()
private val clipRect = Rect()
private val cachedRect = Rect()
private var cachedGradient: LinearGradient? = null
init {
setLayerType(LAYER_TYPE_SOFTWARE, null)
val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.HJGradientAlphaImageView)
startX = styledAttrs.getFloat(R.styleable.HJGradientAlphaImageView_startX, 0f)
startY = styledAttrs.getFloat(R.styleable.HJGradientAlphaImageView_startY, 0f)
endX = styledAttrs.getFloat(R.styleable.HJGradientAlphaImageView_endX, 0f)
endY = styledAttrs.getFloat(R.styleable.HJGradientAlphaImageView_endY, 0f)
startAlpha = styledAttrs.getFloat(R.styleable.HJGradientAlphaImageView_startAlpha, 1f)
endAlpha = styledAttrs.getFloat(R.styleable.HJGradientAlphaImageView_endAlpha, 0f)
styledAttrs.recycle()
}
fun setLineGradientRange(xStart: Float, xEnd: Float, yStart: Float, yEnd: Float) {
startX = xStart
startY = yStart
endX = xEnd
endY = yEnd
cachedGradient = null
invalidate()
}
fun setLineGradientAlpha(startA: Float, endA: Float) {
startAlpha = startA
endAlpha = endA
cachedGradient = null
invalidate()
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
drawable ?: return // do nothing if src is null
canvas?.let { c ->
val saveCount = c.saveCount
c.save()
// the result of c.getClipBounds is not exact, get it manually
gradientMatrix.reset()
c.getClipBounds(clipRect)
clipRectF.set(clipRect)
gradientMatrix.postTranslate(paddingStart.toFloat(), paddingTop.toFloat())
imageMatrix?.let { gradientMatrix.postConcat(it) }
gradientMatrix.invert(gradientMatrix) // matrix of drawable is the inverted one of the canvas
gradientMatrix.mapRect(clipRectF)
clipRectF.roundOut(clipRect) // useful in center_crop mode especially
// cropToPadding is not supported
c.translate(paddingStart.toFloat(), paddingTop.toFloat())
imageMatrix?.let { c.concat(it) }
// adjust the rect by drawable.bounds
drawable?.bounds?.let {
clipRect.left = max(clipRect.left, it.left)
clipRect.top = max(clipRect.top, it.top)
clipRect.right = min(clipRect.right, it.right)
clipRect.bottom = min(clipRect.bottom, it.bottom)
}
c.drawRect(clipRect, paint.apply {
shader = getOrCreateLinearGradient(clipRect)
})
c.restoreToCount(saveCount)
}
}
private fun getOrCreateLinearGradient(r: Rect): LinearGradient {
if (r == cachedRect && cachedGradient != null) {
return cachedGradient!!
}
val w = (r.right - r.left).coerceAtMost(width).coerceAtLeast(0)
val h = (r.bottom - r.top).coerceAtMost(height).coerceAtLeast(0)
return LinearGradient(
r.left + w * startX, r.top + h * startY,
r.left + w * endX, r.top + h * endY,
ColorUtil.getColorWithAlpha(1.0f - startAlpha, Color.BLACK),
ColorUtil.getColorWithAlpha(1.0f - endAlpha, Color.BLACK),
Shader.TileMode.CLAMP
).apply {
cachedGradient = this
cachedRect.set(r)
}
}
}
object ColorUtil {
/**
* 给color添加透明度
* @param alpha 透明度 0f~1f
* @param baseColor 基本颜色
* @return
*/
@JvmStatic
fun getColorWithAlpha(alpha: Float, baseColor: Int): Int {
val a = 255.coerceAtMost(0.coerceAtLeast((alpha * 255).toInt())) shl 24
val rgb = 0x00ffffff and baseColor
return a + rgb
}
}
/*
attrs.xml 里这么写
<attr name="startX" format="float" />
<attr name="startY" format="float" />
<attr name="endX" format="float" />
<attr name="endY" format="float" />
<declare-styleable name="HJGradientAlphaImageView">
<attr name="startX" />
<attr name="startY" />
<attr name="endX" />
<attr name="endY" />
<attr name="startAlpha" format="float" />
<attr name="endAlpha" format="float" />
</declare-styleable>
*/
package com.huangjun.barney.views
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatImageView
/**
* 给 ImageView 指定颜色渐变,实现线性渐变色遮罩的效果
* @author: huangjun.barney
*/
class HJGradientColorImageView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr) {
companion object {
const val INVALID_COLOR = -1
private const val INVALID_POSITION = -1f
}
private var startX = 0f
private var startY = 0f
private var endX = 0f
private var endY = 0f
private var centerPosition = INVALID_POSITION
private var startColor = Color.TRANSPARENT
private var centerColor = INVALID_COLOR
private var endColor = Color.TRANSPARENT
private var startStrokeColor = INVALID_COLOR
private var centerStrokeColor = INVALID_COLOR
private var endStrokeColor = INVALID_COLOR
private var radius = 0f
private var roundTop = true
private var roundBottom = true
private var strokeWidth = 0f
private var paint = Paint(Paint.ANTI_ALIAS_FLAG)
private var cachedGradient: LinearGradient? = null
private var cachedStrokeGradient: LinearGradient? = null
private var rect = RectF()
init {
val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.HJGradientColorImageView)
startX = styledAttrs.getFloat(R.styleable.HJGradientColorImageView_startX, 0f)
startY = styledAttrs.getFloat(R.styleable.HJGradientColorImageView_startY, 0f)
endX = styledAttrs.getFloat(R.styleable.HJGradientColorImageView_endX, 0f)
endY = styledAttrs.getFloat(R.styleable.HJGradientColorImageView_endY, 0f)
centerPosition = styledAttrs.getFloat(R.styleable.HJGradientColorImageView_centerPosition, INVALID_POSITION)
startColor = styledAttrs.getColor(R.styleable.HJGradientColorImageView_startColor, Color.TRANSPARENT)
centerColor = styledAttrs.getColor(R.styleable.HJGradientColorImageView_centerColor, INVALID_COLOR)
endColor = styledAttrs.getColor(R.styleable.HJGradientColorImageView_endColor, Color.TRANSPARENT)
startStrokeColor = styledAttrs.getColor(R.styleable.HJGradientColorImageView_strokeStartColor, INVALID_COLOR)
centerStrokeColor = styledAttrs.getColor(R.styleable.HJGradientColorImageView_strokeCenterColor, INVALID_COLOR)
endStrokeColor = styledAttrs.getColor(R.styleable.HJGradientColorImageView_strokeEndColor, INVALID_COLOR)
radius = styledAttrs.getDimension(R.styleable.HJGradientColorImageView_radius, 0f)
roundTop = styledAttrs.getBoolean(R.styleable.HJGradientColorImageView_roundTop, true)
roundBottom = styledAttrs.getBoolean(R.styleable.HJGradientColorImageView_roundBottom, true)
strokeWidth = styledAttrs.getDimension(R.styleable.HJGradientColorImageView_strokeWidth, 0f)
styledAttrs.recycle()
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
val rw = width - paddingStart - paddingEnd
val rh = height - paddingTop - paddingBottom
val x0 = paddingStart + rw * startX
val y0 = paddingTop + rh * startY
val x1 = paddingStart + rw * endX
val y1 = paddingTop + rh * endY
paint.shader = getOrCreateLinearGradient(x0, y0, x1, y1)
paint.style = Paint.Style.FILL
rect.left = 0f
rect.top = 0f
rect.right = rw.toFloat()
rect.bottom = rh.toFloat()
if (radius != 0f) {
// 绘制上半部
canvas?.save()
canvas?.clipRect(0f, 0f, rw.toFloat(), rh.toFloat() / 2)
if (roundTop) {
canvas?.drawRoundRect(rect, radius, radius, paint)
} else {
canvas?.drawRect(0f, 0f, rw.toFloat(), rh.toFloat(), paint)
}
canvas?.restore()
// 绘制下半部
canvas?.save()
canvas?.clipRect(0f, rh.toFloat() / 2, rw.toFloat(), rh.toFloat())
if (roundBottom) {
canvas?.drawRoundRect(rect, radius, radius, paint)
} else {
canvas?.drawRect(0f, 0f, rw.toFloat(), rh.toFloat(), paint)
}
canvas?.restore()
} else {
canvas?.drawRect(0f, 0f, rw.toFloat(), rh.toFloat(), paint)
}
if (shouldDrawStroke()) {
// 暂不支持和roundTop/roundBottom配合使用
paint.shader = getOrCreateStrokeLinearGradient(x0, y0, x1, y1)
paint.style = Paint.Style.STROKE
paint.strokeWidth = strokeWidth
canvas?.drawRoundRect(rect, radius, radius, paint)
}
}
private fun getOrCreateLinearGradient(startX: Float, startY: Float, endX: Float, endY: Float): LinearGradient {
if (cachedGradient != null) {
return cachedGradient!!
}
return LinearGradient(
startX, startY,
endX, endY,
mutableListOf<Int>().apply {
add(startColor)
if (centerColor != INVALID_COLOR) add(centerColor)
add(endColor)
}.toIntArray(),
if (centerPosition != INVALID_POSITION) FloatArray(3).apply {
this[0] = 0f
this[1] = centerPosition
this[2] = 1f
} else null,
Shader.TileMode.CLAMP
).apply {
cachedGradient = this
}
}
private fun shouldDrawStroke(): Boolean {
return startStrokeColor != INVALID_COLOR
&& endStrokeColor != INVALID_COLOR
}
private fun getOrCreateStrokeLinearGradient(startX: Float, startY: Float, endX: Float, endY: Float): LinearGradient? {
if (cachedStrokeGradient != null) {
return cachedStrokeGradient!!
}
return LinearGradient(
startX, startY,
endX, endY,
mutableListOf<Int>().apply {
add(startStrokeColor)
if (centerStrokeColor != INVALID_COLOR) add(centerStrokeColor)
add(endStrokeColor)
}.toIntArray(),
if (centerPosition != INVALID_POSITION) FloatArray(3).apply {
this[0] = 0f
this[1] = centerPosition
this[2] = 1f
} else null,
Shader.TileMode.CLAMP
).apply {
cachedStrokeGradient = this
}
}
fun setStart(startX: Float?, startY: Float?) {
startX?.let { this.startX = it }
startY?.let { this.startY = it }
invalidate()
}
fun setEnd(endX: Float?, endY: Float?) {
endX?.let { this.endX = it }
endY?.let { this.endY = it }
invalidate()
}
fun setCenterPosition(position: Float?) {
position?.let { this.centerPosition = it }
invalidate()
}
fun setColor(startColor: Int, endColor: Int, centerColor: Int?) {
this.startColor = startColor
this.endColor = endColor
centerColor?.let { this.centerColor = it }
cachedGradient = null
invalidate()
}
}
/*
attrs.xml 里这么写
<attr name="startX" format="float" />
<attr name="startY" format="float" />
<attr name="endX" format="float" />
<attr name="endY" format="float" />
<declare-styleable name="HJGradientColorImageView" >
<attr name="startX" />
<attr name="startY" />
<attr name="endX" />
<attr name="endY" />
<attr name="centerPosition" format="float" />
<attr name="startColor" format="color" />
<attr name="centerColor" format="color" />
<attr name="endColor" format="color" />
<attr name="strokeStartColor" format="color" />
<attr name="strokeCenterColor" format="color" />
<attr name="strokeEndColor" format="color" />
<attr name="strokeWidth" format="dimension" />
<attr name="radius" format="dimension" />
<attr name="roundTop" format="boolean" />
<attr name="roundBottom" format="boolean" />
</declare-styleable>
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment