《Android自定义控件入门到精通》文章索引 ☞ https://blog.youkuaiyun.com/Jhone_csdn/article/details/118146683
《Android自定义控件入门到精通》所有源码 ☞ https://gitee.com/zengjiangwen/Code
文章目录
Shader
着色器
BitmapShader
BitmapShader(Bitmap bitmap, TileMode tileX, TileMode tileY)
- bitmap :用来着色(填充)的图像
- tileX : x轴方向上的填充策略
- tileY : y轴方向上的填充策略
- TileMode
- TileMode.CLAMP : 以边缘颜色填充
- TileMode.REPEAT : 重复图像填充
- TileMode.MIRROR : 重复镜像图像填充
TileMode.CLAMP
首先我准备了一张图片
private Paint mPaint;
//初始化画笔Paint
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.panda);
mPaint.setShader(new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawRect(new RectF(0,0,getWidth(),getHeight()),mPaint);
}
我们看到,右边的红色和下边的绿色是由图片的边缘色决定的,那右下角的咖啡色是怎么来的呢?我们放大图像看看
可以清楚的看到,右下角有一个咖啡色像素点,所以右下角填充什么颜色,就看右下角顶点是什么颜色
TileMode.REPEAT
代码是一样的,只是吧模式换成TileMode.REPEAT
TileMode.MIRROR
镜像填充
这个shader真是神奇,我们只是在onDraw中画了一个矩形,就能实现这么震撼的效果
canvas.drawRect(new RectF(0,0,getWidth(),getHeight()),mPaint);
我们扩展一下
private Paint mPaint;
private int cx,cy;
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.panda);
mPaint.setShader(new BitmapShader(bitmap, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR));
cx=bitmap.getWidth()/2;
cy=bitmap.getHeight()/2;
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawCircle(cx,cy,cx,mPaint);
canvas.drawRoundRect(cx*2,0,cx*4,cy*2,10,10,mPaint);
}
我们常常用的圆形和圆角头像,就可以这样实现,当然,各种形状的头像相信也不在话下了。
原理:用图片做一个底,然后用蒙版盖住,然后用Paint绘制出可见的区域:
基于这个原理,我们可以搞个有意思的效果(望远镜效果)
附上代码:
public class TestView extends View {
public TestView(Context context) {
this(context, null);
}
public TestView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private Paint mPaint;
private Bitmap mBitmap;
private boolean hasSetShader;
//初始化画笔Paint
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
//加载bitmap图片
mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.girl);
}
@Override
protected void onDraw(Canvas canvas) {
if (!hasSetShader) {
hasSetShader = true;
//用canvas.drawBitmap的方式,把上面加载的bitmap缩放到控件尺寸大小
Bitmap bitmapShader = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvasBg = new Canvas(bitmapShader);
canvasBg.drawBitmap(mBitmap, null, new RectF(0, 0, getWidth(), getHeight()), mPaint);
//设置Shader,模式随意,因为图片已经缩放到控件大小,没有多余的控件应用重复策略
mPaint.setShader(new BitmapShader(bitmapShader, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
}
if (mX > 0 && mY > 0) {
canvas.drawCircle(mX, mY, 100, mPaint);
canvas.drawCircle(mX + 150, mY, 100, mPaint);
}
}
private float mX, mY;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mX = event.getX();
mY = event.getY();
return true;
case MotionEvent.ACTION_MOVE:
mX = event.getX();
mY = event.getY();
break;
case MotionEvent.ACTION_UP:
mX = 0;
mY = 0;
break;
}
invalidate();
return super.onTouchEvent(event);
}
}
LinearGradient
线性渐变
LinearGradient(x0,y0,x1,y1,int color0, int color1, TileMode tile)
@Override
protected void onDraw(Canvas canvas) {
//当Shader的直线距离(x0,y0)-->(x1,y1)等于drawRect的宽度,正常渐变
LinearGradient linearGradient = new LinearGradient(0, 0, getWidth(), 0, 0xFFFF0000, 0xFF00FF00, Shader.TileMode.CLAMP);
mPaint.setShader(linearGradient);
canvas.drawRect(0, 0, getWidth(), 100, mPaint);
canvas.translate(0, 150);
//当Shader的直线距离(x0,y0)-->(x1,y1)大于drawRect的宽度,只显示正常渐变的一部分
LinearGradient linearGradient1 = new LinearGradient(0, 0, getWidth(), 0, 0xFFFF0000, 0xFF00FF00, Shader.TileMode.CLAMP);
mPaint.setShader(linearGradient1);
canvas.drawRect(0, 0, 200, 100, mPaint);
canvas.translate(0, 150);
//当Shader的直线距离(x0,y0)-->(x1,y1)小于drawRect的宽度,TileMode.CLAMP
LinearGradient linearGradient2 = new LinearGradient(0, 0, 200, 0, 0xFFFF0000, 0xFF00FF00, Shader.TileMode.CLAMP);
mPaint.setShader(linearGradient2);
canvas.drawRect(0, 0, getWidth(), 100, mPaint);
canvas.translate(0, 150);
//当Shader的直线距离(x0,y0)-->(x1,y1)小于drawRect的宽度,TileMode.REPEAT
LinearGradient linearGradient3 = new LinearGradient(0, 0, 200, 0, 0xFFFF0000, 0xFF00FF00, Shader.TileMode.REPEAT);
mPaint.setShader(linearGradient3);
canvas.drawRect(0, 0, getWidth(), 100, mPaint);
canvas.translate(0, 150);
//当Shader的直线距离(x0,y0)-->(x1,y1)小于drawRect的宽度,TileMode.MIRROR
LinearGradient linearGradient4 = new LinearGradient(0, 0, 200, 0, 0xFFFF0000, 0xFF00FF00, Shader.TileMode.MIRROR);
mPaint.setShader(linearGradient4);
canvas.drawRect(0, 0, getWidth(), 100, mPaint);
}
这个没什么难度,对着代码看就能明白
LinearGradient(x0,y0,x1,y1,int[] colors, float[] positions, TileMode tile)
- colors:多个颜色渐变
- positions:颜色占比 [0,1]
@Override
protected void onDraw(Canvas canvas) {
int[] colors = new int[]{0xffff0000, 0xff00ff00, 0xff0000ff, 0xff00ffff};
float[] positions = new float[]{0, 0.2f, 0.5f, 1f};
//当Shader的直线距离(x0,y0)-->(x1,y1)等于drawRect的宽度,正常渐变
LinearGradient linearGradient = new LinearGradient(0, 0, getWidth(), 0, colors, positions, Shader.TileMode.CLAMP);
mPaint.setShader(linearGradient);
canvas.drawRect(0, 0, getWidth(), 100, mPaint);
canvas.translate(0, 150);
//当Shader的直线距离(x0,y0)-->(x1,y1)大于drawRect的宽度,只显示正常渐变的一部分
LinearGradient linearGradient1 = new LinearGradient(0, 0, getWidth(), 0, colors, positions, Shader.TileMode.CLAMP);
mPaint.setShader(linearGradient1);
canvas.drawRect(0, 0, 200, 100, mPaint);
canvas.translate(0, 150);
//当Shader的直线距离(x0,y0)-->(x1,y1)小于drawRect的宽度,TileMode.CLAMP
LinearGradient linearGradient2 = new LinearGradient(0, 0, 200, 0, colors, positions, Shader.TileMode.CLAMP);
mPaint.setShader(linearGradient2);
canvas.drawRect(0, 0, getWidth(), 100, mPaint);
canvas.translate(0, 150);
//当Shader的直线距离(x0,y0)-->(x1,y1)小于drawRect的宽度,TileMode.REPEAT
LinearGradient linearGradient3 = new LinearGradient(0, 0, 200, 0, colors, positions, Shader.TileMode.REPEAT);
mPaint.setShader(linearGradient3);
canvas.drawRect(0, 0, getWidth(), 100, mPaint);
canvas.translate(0, 150);
//当Shader的直线距离(x0,y0)-->(x1,y1)小于drawRect的宽度,TileMode.MIRROR
LinearGradient linearGradient4 = new LinearGradient(0, 0, 200, 0, colors, positions, Shader.TileMode.MIRROR);
mPaint.setShader(linearGradient4);
canvas.drawRect(0, 0, getWidth(), 100, mPaint);
}
对于positions,我画个图,大家就能理解了
RadialGradient
径向渐变
RadialGradient (x0,y0,x1,y1,int color0, int color1, TileMode tile) 两个颜色渐变
RadialGradient (x0,y0,x1,y1,int[] colors, float[] stops, TileMode tile) 多个颜色渐变
@Override
protected void onDraw(Canvas canvas) {
int[] colors = new int[]{0xffff0000, 0xff00ff00, 0xff0000ff, 0xff00ffff};
float[] stops = new float[]{0, 0.2f, 0.5f, 1f};
RadialGradient radialGradient = new RadialGradient(200, 200, 100, 0xffff0000, 0xff00ff00, Shader.TileMode.CLAMP);
mPaint.setShader(radialGradient);
canvas.drawCircle(200, 200, 100, mPaint);
canvas.translate(250, 0);
RadialGradient radialGradient1 = new RadialGradient(200, 200, 100, colors, stops, Shader.TileMode.CLAMP);
mPaint.setShader(radialGradient1);
canvas.drawCircle(200, 200, 100, mPaint);
}
SweepGradient
扫描渐变(强行命名)
@Override
protected void onDraw(Canvas canvas) {
int[] colors = new int[]{0xffff0000, 0xff00ff00, 0xff0000ff, 0xff00ffff};
float[] stops = new float[]{0, 0.2f, 0.5f, 1f};
SweepGradient sweepGradient = new SweepGradient(200, 200, 0xffff0000, 0xff00ff00);
mPaint.setShader(sweepGradient);
canvas.drawCircle(200, 200, 100, mPaint);
canvas.translate(250, 0);
SweepGradient sweepGradient1 = new SweepGradient(200, 200, colors, stops);
mPaint.setShader(sweepGradient1);
canvas.drawCircle(200, 200, 100, mPaint);
}
ComposeShader
ComposeShader(Shader shaderA, Shader shaderB,PorterDuff.Mode mode)
从构造方法可以看出就是传入两个着色器shaderA和shaderB,通过mode混合模式构造新的着色器
shaderA:我们称之为目标dst着色器
shaderB:我们称之为源src着色器
注意,shaderA和shaderB不能为同类型Shader,比如不能两个都是BitmapShader或者两个都是LinearGradient,否则没有效果
@Override
protected void onDraw(Canvas canvas) {
//创建BitmapA,并填充0xffff0000红色
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.panda);
//创建BitmapShaderA
BitmapShader bitmapShaderA = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
//创建LinearGradientA
LinearGradient linearGradientA = new LinearGradient(0, 0, getWidth(), 0, 0xffff0000, 0xff00ff00, Shader.TileMode.CLAMP);
//组合两个Shader
ComposeShader composeShader = new ComposeShader(bitmapShaderA, linearGradientA, PorterDuff.Mode.ADD);
//设置画笔着色器(填充器)
mPaint.setShader(composeShader);
canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
}
可以看到两中着色器的效果都有了
其本质是颜色的混合效果,我们不防用一个纯色的图片BitmapShader和一个渐变LinearGradient来做下试验
我们把src和dst shader画出来,并让他们有一部分重叠,并在重叠的区域用ComposeShader重新画一遍,得效果:
可以看到,中间黄色区域为两种shader混合后(ComposeShader)的效果
我们可以借住Ps来观察一下
可以发现PorterDuff.Mode.ADD的效果跟Ps中的 变亮、滤色、线性减淡效果差不多