07.6 精通自定义 View 之 绘图进阶——Shader 之 RadialGradient

返回自定义 View 目录

RadialGradient 是 Shader 的另一种实现,它的含义是放射渐变,即它会像一个放射源一样,从一个点开始向外扩散,从一种颜色变成另一种颜色。

7.6.1 双色渐变

RadialGradient 有两个构造函数,分别能完成双色渐变和多色渐变。双色渐变的构造函数如下:

1
2
RadialGradient(float centerX, float centerY, float radius,
int centerColor, int edgeColor, Shader.TileMode tileMode)

  • centerX:渐变中心点X坐标。
  • centerY:渐变中心点Y坐标。
  • radius:渐变半径。
  • centerColor:渐变的起始颜色,即渐变中心点的颜色,取值类型必须是八位的0xAARRGGBB色值!透明底Alpha值不能省略,不然不会显示出颜色。
  • edgeColor:渐变结束时的颜色,即渐变圆边缘的颜色,同样,取值类型必须是八位的0xAARRGGBB色值。
  • TileMode:与我们前面讲的各个Shader一样,用于指定当控件区域大于指定的渐变区域时,空白区域的颜色填充方式。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class TestView extends AppCompatTextView {
private Paint mPaint;
private RadialGradient mRadialGradient;
private int mRadius;
public TestView(Context context, AttributeSet attrs) {
super(context, attrs);
setLayerType(LAYER_TYPE_SOFTWARE, null);
mPaint = getPaint();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mRadius = w / 5;
mRadialGradient = new RadialGradient(w / 2f, h / 2f, mRadius, 0xffff0000, 0xff00ff00, Shader.TileMode.CLAMP);
mPaint.setShader(mRadialGradient);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(getWidth()/2f, getHeight()/2f, mRadius, mPaint);
}
}

效果如下图所示。

7.6.2 多色渐变

1
2
RadialGradient(float centerX, float centerY, float radius,
int[] colors, float[] stops, Shader.TileMode tileMode)
  • int[] colors:表示所需要的渐变颜色数组。
    +float[] stops:表示每个渐变颜色所在的位置百分点,取值 0~1,数量必须与 colors 数组保持一致,不然直接 crash。一般第一个数值取 0,最后一个数值取 1,比如,取一个位置数组:{0.2,0.5,0.8},起始点是0.2百分比位置,结束点是0.8百分比位置,而 0~0.2 百分比位置和 0.8~1.0 百分比的位置都是没有指定颜色的。这些位置的颜色就是根据我们指定的 TileMode 空白区域填充模式来自行填充。但有时效果我们是不可控的。所以,为了方便起见,建议大家 stop 数组的起始和终止数值设为 0 和 1。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class TestView extends AppCompatTextView {
private Paint mPaint;
private RadialGradient mRadialGradient;
private int mRadius;
public TestView(Context context, AttributeSet attrs) {
super(context, attrs);
setLayerType(LAYER_TYPE_SOFTWARE, null);
mPaint = getPaint();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mRadius = w / 5;
int[] colors = {0xffff0000, 0xff00ff00, 0xff0000ff, 0xffffff00};
float[] stops = {0f, 0.2f, 0.5f, 1f};
mRadialGradient = new RadialGradient(w / 2f, h / 2f, mRadius, colors, stops, Shader.TileMode.CLAMP);
mPaint.setShader(mRadialGradient);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(getWidth()/2f, getHeight()/2f, mRadius, mPaint);
}
}

效果图如下:

7.6.3 TileMode 填充模式

在 RadialGradient 构造函数中,只有一个 TileMode 参数,这说明当填充空白区域时,X 轴和 Y 轴使用同一种填充模式。而不能像 BitmapShader 那样分别指定 X 轴与 Y 轴的填充参数。

分别设置填充模式为:TileMode.CLAMP、TileMode.MIRROR、TileMode.REPEAT。

TileMode.CLAMP

TileMode.MIRROR

TileMode.REPEAT

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class TestView extends AppCompatTextView {
private Paint mPaint;
private RadialGradient mGradient;
private int mRadius;
public TestView(Context context, AttributeSet attrs) {
super(context, attrs);
setLayerType(LAYER_TYPE_SOFTWARE, null);
mPaint = getPaint();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mRadius = w / 10;
mGradient = new RadialGradient(w / 2f, h / 2f, mRadius, 0xffff0000, 0xff00ff00, Shader.TileMode.REPEAT);
mPaint.setShader(mGradient);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
}
}

7.6.4 示例:水波纹按钮效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
public class TestView extends AppCompatButton {
private int mX, mY;
private ObjectAnimator mAnimator;
private int DEFAULT_RADIUS = 50;
private int mCurRadius = 0;
private RadialGradient mRadialGradient;
private Paint mPaint;
public TestView(Context context) {
super(context);
init();
}
public TestView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public TestView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
setLayerType(LAYER_TYPE_SOFTWARE,null);
mPaint = new Paint();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mX != event.getX() || mY != mY) {
mX = (int) event.getX();
mY = (int) event.getY();
setRadius(DEFAULT_RADIUS);
}
if (event.getAction() == MotionEvent.ACTION_DOWN) {
return true;
} else if (event.getAction() == MotionEvent.ACTION_UP) {
if (mAnimator != null && mAnimator.isRunning()) {
mAnimator.cancel();
}
if (mAnimator == null) {
mAnimator = ObjectAnimator.ofInt(this, "radius",
DEFAULT_RADIUS, getWidth());
}
mAnimator.setInterpolator(new AccelerateInterpolator());
mAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {}
@Override
public void onAnimationEnd(Animator animation) {
setRadius(0);
}
@Override
public void onAnimationCancel(Animator animation) {}
@Override
public void onAnimationRepeat(Animator animation) {}
});
mAnimator.start();
}
return super.onTouchEvent(event);
}
public void setRadius(final int radius) {
mCurRadius = radius;
if (mCurRadius > 0) {
mRadialGradient = new RadialGradient(mX, mY, mCurRadius, 0x00FFFFFF,
0xFF58FAAC, Shader.TileMode.CLAMP);
mPaint.setShader(mRadialGradient);
}
postInvalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(mX, mY, mCurRadius, mPaint);
}
}