Last active
July 19, 2023 06:54
-
-
Save hjhjw1991/de59100c379d556cbfa0ecf733939eb4 to your computer and use it in GitHub Desktop.
透明度渐变、线性渐变的 ImageView
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | |
*/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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