Android程序化合并LinearGradient与RadialGradient技术问询
Hey there! Let's tackle merging your blue-to-red LinearGradient with that polished RadialGradient glow effect, while making the entire setup fully parameterizable. Here's a practical, step-by-step approach with code examples:
Core Idea
Instead of using a fixed color for the glow, we’ll tie the glow’s color to the LinearGradient’s color at each point on the circle. This way, the glow transitions from blue to red just like the main circle, while retaining the soft, transparent halo effect from your radial gradient logic.
Step 1: Parameterize All Customizable Values
First, let’s turn hardcoded values into configurable attributes for your custom View. Add these to your attrs.xml:
<declare-styleable name="GradientGlowCircleView"> <attr name="circleRadius" format="dimension" /> <attr name="bodyColor" format="color" /> <attr name="secondaryColor" format="color" /> <attr name="bodyStrokeWidth" format="dimension" /> <attr name="glowStrokeWidth" format="dimension" /> <attr name="blurRadius" format="dimension" /> </declare-styleable>
Then parse these attributes in your custom View’s constructor:
class GradientGlowCircleView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : View(context, attrs, defStyleAttr) { private var circleRadius: Float = 100f private var bodyColor: Int = Color.BLUE private var secondaryColor: Int = Color.RED private var bodyStrokeWidthPx: Float = 8f private var glowStrokeWidthPx: Float = 16f private var blurRadiusPx: Float = 12f private val paint = Paint(Paint.ANTI_ALIAS_FLAG) private lateinit var combinedShader: Shader init { attrs?.let { val typedArray = context.obtainStyledAttributes(it, R.styleable.GradientGlowCircleView) circleRadius = typedArray.getDimension(R.styleable.GradientGlowCircleView_circleRadius, circleRadius) bodyColor = typedArray.getColor(R.styleable.GradientGlowCircleView_bodyColor, bodyColor) secondaryColor = typedArray.getColor(R.styleable.GradientGlowCircleView_secondaryColor, secondaryColor) bodyStrokeWidthPx = typedArray.getDimension(R.styleable.GradientGlowCircleView_bodyStrokeWidth, bodyStrokeWidthPx) glowStrokeWidthPx = typedArray.getDimension(R.styleable.GradientGlowCircleView_glowStrokeWidth, glowStrokeWidthPx) blurRadiusPx = typedArray.getDimension(R.styleable.GradientGlowCircleView_blurRadius, blurRadiusPx) typedArray.recycle() } } // Rest of the code goes here... }
Step 2: Create a LinearGradient Color Sampler
We need a way to fetch the LinearGradient color at any point on the circle. Let’s build a helper method to generate the base linear gradient, and another to sample colors from it:
private fun createBaseLinearGradient(centerX: Float, centerY: Float): LinearGradient { return LinearGradient( 0f, 0f, centerX * 2, centerY * 2, intArrayOf(bodyColor, secondaryColor), floatArrayOf(0f, 1f), Shader.TileMode.CLAMP ) } // Helper to get the LinearGradient color at a given (x,y) position private fun getLinearGradientColor(linearGradient: LinearGradient, x: Float, y: Float): Int { val tempPaint = Paint() tempPaint.shader = linearGradient return tempPaint.getColor(x, y) }
Step 3: Generate the Combined RadialGradient with Linear Color Transition
Modify your radial gradient logic to use the LinearGradient’s colors instead of a fixed glowingColor. We’ll calculate each glow stop’s color by sampling the linear gradient at the corresponding position:
private fun createCombinedGradient(centerX: Float, centerY: Float): Shader { val linearGradient = createBaseLinearGradient(centerX, centerY) val gradientRadiusPx = circleRadius + glowStrokeWidthPx / 2 + blurRadiusPx // Calculate stop positions (same as your original logic, using parameterized values) val innerEndGlowingPx = circleRadius - glowStrokeWidthPx / 2 - blurRadiusPx val innerStartGlowingPx = circleRadius - glowStrokeWidthPx / 4 val constantInnerGlowingPx = circleRadius - bodyStrokeWidthPx / 2 val innerBodyEndPx = circleRadius - bodyStrokeWidthPx / 2 - 1 val outerBodyEndPx = circleRadius + bodyStrokeWidthPx / 2 + 1 val constantOuterGlowingPx = circleRadius + bodyStrokeWidthPx / 2 val outerStartGlowingPx = circleRadius + glowStrokeWidthPx / 4 val outerEndGlowingPx = circleRadius + glowStrokeWidthPx / 2 + blurRadiusPx // Normalize stops to 0-1 range val stops = floatArrayOf( innerEndGlowingPx / gradientRadiusPx, innerStartGlowingPx / gradientRadiusPx, constantInnerGlowingPx / gradientRadiusPx, innerBodyEndPx / gradientRadiusPx, outerBodyEndPx / gradientRadiusPx, constantOuterGlowingPx / gradientRadiusPx, outerStartGlowingPx / gradientRadiusPx, outerEndGlowingPx / gradientRadiusPx ) // Generate colors: sample linear gradient for glow positions, use body color for main circle val colors = intArrayOf( Color.TRANSPARENT, getLinearGradientColor(linearGradient, innerStartGlowingPx, innerStartGlowingPx), getLinearGradientColor(linearGradient, constantInnerGlowingPx, constantInnerGlowingPx), bodyColor, bodyColor, getLinearGradientColor(linearGradient, constantOuterGlowingPx, constantOuterGlowingPx), getLinearGradientColor(linearGradient, outerStartGlowingPx, outerStartGlowingPx), Color.TRANSPARENT ) // Adjust glow transparency for a softer, more natural effect colors[1] = adjustAlpha(colors[1], 0.7f) colors[2] = adjustAlpha(colors[2], 0.9f) colors[5] = adjustAlpha(colors[5], 0.9f) colors[6] = adjustAlpha(colors[6], 0.7f) return RadialGradient( centerX, centerY, gradientRadiusPx, colors, stops, Shader.TileMode.CLAMP ) } // Helper to adjust color alpha without changing RGB values private fun adjustAlpha(color: Int, alpha: Float): Int { val newAlpha = (Color.alpha(color) * alpha).toInt() return Color.argb(newAlpha, Color.red(color), Color.green(color), Color.blue(color)) }
Step 4: Set Up Drawing Logic
Override onSizeChanged to recalculate the shader when the view size changes, and onDraw to render the circle:
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) val centerX = w / 2f val centerY = h / 2f combinedShader = createCombinedGradient(centerX, centerY) paint.shader = combinedShader } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) val centerX = width / 2f val centerY = height / 2f canvas.drawCircle(centerX, centerY, circleRadius + glowStrokeWidthPx / 2 + blurRadiusPx, paint) }
Key Notes
- Performance: Sampling the LinearGradient with
getColor()is straightforward, but for better performance, you can pre-calculate color transitions usingColor.lerp()based on the linear gradient’s stop positions. - TileMode: Switched to
CLAMPinstead ofREPEAT/MIRRORto avoid unwanted gradient repetition outside the circle bounds. - Transparency: The
adjustAlphamethod softens the glow—tweak the alpha values to match your desired visual effect.
Now you can use this view in XML with all configurable parameters, or adjust values dynamically in code!
内容的提问来源于stack exchange,提问作者Boysteuf

