Shader与BitmapShader

本篇blog内容和​​hencoder学习自定义view(1)​​部分内容相似,有些知识就直接跳过了。

Shader是着色器,是用来给空白图形上色的。
当设置了Shader后,Paint就不会使用setColor或者setARGB里面的颜色了,而是直接使用Shader中的方案。

public Shader setShader(Shader shader)

Shader类只是一个基类,其中只有两个函数setLocalMatrix(Matrix localM)和getLocalMatrix(Matrix localM),用来设置坐标变换矩阵。有关设置矩阵的内容,这里就先略过。
Shader类下的几个派生类为:BitmapShader,ComposeShader,LinearGradient,RadialGradient,SweepGradient

BitmapShader

public BitmapShader(Bitmap bitmap,TileMode tileX,TileMode tileY)

TileMode的取值如下:

  • TileMode.CLAMP:用边缘颜色来填充多余空间
  • TileMode.REPEAT:重复原图像来填充多余空间
  • TileMode.MIRROR:重复使用镜像模式的图像来填充多余空间

这里就不再写介绍了,注意:填充空间是先填充Y轴,再填充X轴。

示例:望远镜效果:

如果我们用一个setShader的paint去在一个控件上绘制一个图形,并且我们绘制的图形大小 小于 控件的大小,那这样只会显示绘制出的区域,别的区域已经绘制好了但是并不显示,我们根据该原理来制作一个望远镜效果:

Android自定义控件开发入门与实战(10)Shader_控件


就是在点击时产生一个圆形的望远镜,然后可以看到图片

首先我们先初始化背景图片和画笔:

public TelescopeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint();
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bg_telescope);
}

接着我们重写onTouchEvent方法,当点击时记录坐标并重绘界面, 不点击时将坐标置为默认值。

@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mDx = (int) event.getX();
mDy = (int) event.getY();
postInvalidate();
return true;
case MotionEvent.ACTION_MOVE:
mDx = (int) event.getX();
mDy = (int) event.getY();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mDx = -1;
mDy = -1;
break;

}
postInvalidate();
return super.onTouchEvent(event);
}

最后重写onDraw函数
这里分成两步。第一步将图片mbitmapBG缩放到控件大小,以完全覆盖控件,否则就会使用BitmapShader的填充模式。
这里先新建一张空白的位图cavasbg,这张位图的大小与控件大小一致,然后对背景位图进行拉伸,画到这张空白的位图上。
第二步,在mDx、mDy都不是-1时,将新建的mBitmapBG作为BitmapShader设置给Paint,然后在手指所在的位置画圆,把圆圈部分的图像显示出来。

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mBitmapBG == null) {
mBitmapBG = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvasbg = new Canvas(mBitmapBG);
canvasbg.drawBitmap(mBitmap, null, new Rect(0, 0, getWidth(), getHeight()), mPaint);
}

if (mDx != -1 && mDy != -1) {
mPaint.setShader(new BitmapShader(mBitmapBG, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT));
canvas.drawCircle(mDx, mDy, 150, mPaint);
}
}

下面的示例来讲解生成不规则的头像。
首先初始化bitmap:

public AvatorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.avator_xizuka);
mPaint = new Paint();
mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
}

接下来,将BitmapShader缩放到与控件的宽、高一致(setScale),由于我们要画的是一幅圆形图像,所以必须将图像缩放成一个正方形,只要正方形的边长与控件的宽度一致即可。

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Matrix matrix = new Matrix();
float scale = (float) getWidth() / mBitmap.getWidth();
matrix.setScale(scale, scale);
mBitmapShader.setLocalMatrix(matrix);
mPaint.setShader(mBitmapShader);

float half = getWidth() / 2;
canvas.drawCircle(half, half, getWidth() / 2, mPaint);
}

Android自定义控件开发入门与实战(10)Shader_缩放_02


同样是缩放BitmapShader,这里使用Matrix进行缩放,而前面的望远镜示例则是通过新建一张位图来进行缩放,这两种缩放方式都可用。

在缩放BitmapShader以后,在控件的正中央画一个圆形,显示出一副圆形区域的图像。这就是我们想要的圆形头像。

Shader之LinearGradient

通过LinearGradient可以实现渐变的效果
下面是构造函数

public LinearGradient(float x0,float y0,float x1,float y1,int color0,int color1,TileMode tile)

其中(x0,y0) (x1,y1)分别表示起点、终点坐标。color0、color1表示起点终点颜色,tile和BitmapShader一样。
其中color用0xAARRGGBB显示,AA不能少。
第二个构造函数为:

public LinearGradient(float x0,float y0,float x1,float y1,int colors[],float positions[],TileMode tile)

其中colors[]用于指定颜色值数值,同样用0xAARRGGBB形式。
positions[]与渐变的颜色相对应,取值是0-1的Float类型数据。表示color中的每种颜色在渐变线中百分比的位置。

示例:闪光文字效果
这里做一个跑马灯式的文字渐变效果。
原理是做一个渐变的图像,和一段文字,然后渐变头像在文字上进行移动,使文字发光,变换颜色。
该渐变图像移动两个文字长度的距离。

首先我们让View继承自TextView,因为TextView本身可以绘制文字,不需要我们自己处理,所以这样方便一点。

public ShimmerTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);

mPaint = getPaint();
int length = (int) mPaint.measureText(getText().toString());
createAnim(length);
createLinearGradient(length);
}

接下里就是创建动画,动画让LinearGradient去移动,所以动画的起始到终止值应该是(0,2*text_Length),每次有新进度则重绘文字:

private void createAnim(int length) {
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 2 * length);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mDx = (int) animation.getAnimatedValue();
postInvalidate();
}
});
valueAnimator.setRepeatMode(ValueAnimator.RESTART);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.setDuration(2000);
valueAnimator.start();
}

然后创建LinearGradient,其大小和TextView大小一样

private void createLinearGradient(int length) {
mLinearGradient = new LinearGradient(-length, 0, 0, 0, new int[]{getCurrentTextColor(), 0xff00ff00, getCurrentTextColor()},
new float[]{0, 0.5f, 1}, Shader.TileMode.CLAMP);
}

最后重写onDraw方法,根据setTranslate(mDx,0)来让其移动:

@Override
protected void onDraw(Canvas canvas) {
Matrix matrix = new Matrix();
matrix.setTranslate(mDx,0);
mLinearGradient.setLocalMatrix(matrix);
mPaint.setShader(mLinearGradient);

super.onDraw(canvas);
}

效果如下:

Android自定义控件开发入门与实战(10)Shader_缩放_03

Shader之RadialGradient

RadialGradient就是放射源式的渐变,从一个中心点发散。
双色渐变构造函数如下:

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

其中 centerColor表示渐变的起始颜色,edgeColor表示渐变的边缘颜色
两个颜色都必须用0xAARRGGBB来表示。
其他的没什么必要介绍

多色渐变构造函数如下:

RadialGradient(float X,float centerY,float radius,int[] colors,float[] stops,Shader.TileMode tileMode)

其中 int[] colors表示颜色数组
而 float[] stops表示colors颜色在渐变线上的范围,取值0-1。

示例什么的用的少,之前另一篇Blog也讲过,这里就不再赘述了。