Android 自定义 View进阶 - Shader
Posted 星火燎原2016
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 自定义 View进阶 - Shader相关的知识,希望对你有一定的参考价值。
不知曾几何时,渐变色变得流行起来了,各大手机厂商都发布了各自的渐变色手机,同时越来越多的 App ,也开始应用了渐变色的设计。为了满足工作需要,我们也要学习下 android 中的渐变着色器 Shader。
效果图
Shader 介绍
Shader 单词为 着色器,在 Android 中的文档解释为:
Shader is the based class for objects that return horizontal spans of colors during drawing. A subclass of Shader is installed in a Paint calling paint.setShader(shader). After that any object (other than a bitmap) that is drawn with that paint will get its color(s) from the shader
着色器是在绘制过程中返回水平颜色范围的对象的基类。 Shader的子类安装在Paint中,调用paint.setShader(着色器)之后,使用该绘制绘制的任何对象(位图除外)都将从着色器中获取其颜色
Shader 相关类:
可以看出: Shader 有五个子类
- BitmapShader 位图渲染
- ComposeShader 组合渲染
- LinearGradient 线性渲染
- SweepGradient 梯度渲染
- RadialGradient 光束渲染
Shader 用法
(1)调用 Shader 子类对象的构造方法创建着色器,设定 渲染模式。
// 创建线性着色器
linearGradient = new LinearGradient(0, 0, mViewWidth, 0,
new int[]Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW,
new float[]0.2f, 0.5f, 0.8f, 0.9f,
Shader.TileMode.REPEAT);
其中渲染模式 TileMode 有:
- CLAMP 边缘拉伸模式,他会拉伸边缘的一个像素来填充其他区域。
- REPEAT 重复模式,水平和垂直重复着色器的图像
- MIRROR 镜像模式,水平和垂直重复着色器的图像,交替镜像,使相邻的图像始终接缝
(2)给画笔设置 Shader 着色器
// 为画笔设置渐变渲染器
paint.setShader(linearGradient);
(3)在 onDraw() 方法中使用 paint 进行绘制。
canvas.drawRect(rectF, paint);
LinearGradient
LinearGradient 构造方法:
public LinearGradient(float x0, float y0, float x1, float y1,@ColorInt int color0, @ColorInt int color1, @NonNull TileMode tile)
public LinearGradient(float x0, float y0, float x1, float y1,
@NonNull @ColorInt int colors[],@Nullable float positions[], @NonNull TileMode tile)
上面一个构造函数很好理解,(x0,y0),(x1,y1) 分别表示起点和终点的坐标,颜色从 color0 到 color1 线性变化;
下面一个构造函数参数有点不好理解了。我们先看下使用这个构造函数后的效果:
/**
* 线性渐变
*/
public class LinearGradientView extends View
private Paint paint;
private Paint linePaint;
private LinearGradient linearGradient;
private int mViewWidth;
private int mViewHeight;
private RectF rectF;
public LinearGradientView(Context context)
super(context);
init();
public LinearGradientView(Context context, AttributeSet attrs)
super(context, attrs);
init();
private void init()
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
linePaint.setStrokeWidth(DensityUtil.dp2Px(getContext(), 2));
linePaint.setStyle(Paint.Style.FILL);
linePaint.setColor(Color.BLACK);
linePaint.setTextSize(DensityUtil.dp2Px(getContext(), 14));
linePaint.setTextAlign(Paint.Align.CENTER);
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
super.onSizeChanged(w, h, oldw, oldh);
mViewWidth = w;
mViewHeight = h;
rectF = new RectF(0, 0, mViewWidth, mViewHeight);
/**
* 渐变范围为 左上角顶点 (0,0) -> 右上角顶点 (mViewWidth,0) ,
* 超出这个范围以 Shader.TileMode.REPEAT 重复的形式填充, 颜色渐变的方向为 水平方向,
*
*
* 如果渐变范围: (0,0) -> (mViewWidth,mViewHeight), 则 渐变方向为 对角线方向
*/
linearGradient = new LinearGradient(0, 0, mViewWidth, 0,
new int[]Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW,
new float[]0.2f, 0.5f, 0.8f, 0.9f,
Shader.TileMode.REPEAT);
paint.setShader(linearGradient);
@Override
protected void onDraw(Canvas canvas)
super.onDraw(canvas);
canvas.drawRect(rectF, paint);
for (int i = 0; i <= 10; i++)
canvas.drawLine(rectF.width() * i * 0.1f, rectF.bottom, rectF.width() * i * 0.1f, rectF.bottom - 20, linePaint);
canvas.drawText(String.valueOf(i / 10f), rectF.width() * i * 0.1f, rectF.bottom - 30, linePaint);
结论: int colors[] 数组表示颜色数组,float positions[] 数组表示各个颜色在渲染范围内的位置点,上面我们在 0.2,0.5,0.8,0.9 这四个位置点上设置了 RED, GREEN, BLUE, YELLOW 四个颜色值, 从效果图上看,这四种颜色值刚好出现在了这四个位置点上,并且与相邻位置点上颜色形成渐变效果,同时,0.5 与 0.2 相间隔 0.3 ,0.5 与 0.8 也是相间隔 0.3,所以 GREEN 左边绿色渐变色和右边绿色渐变色的宽度是一样的,而 0.8 与 0.5 相间隔 0.3,0.8 与 0.9 相间隔 0.1 ,所以左边蓝色渐变色要比右边蓝色渐变色宽些,调整 当前位置点的左右位置点的间距大小可以调整这个颜色的渐变宽度。
仿 iPhone 滑动解锁
通过属性动画不断的改变 Matrix X 坐标值,重新绘制可以实现文字渐变动画效果
public class LinearShaderTextView extends AppCompatTextView
private static final String TAG = "LinearShaderTextView";
private int mViewWidth;
private int mViewHeight;
private LinearGradient linearGradient;
private Matrix gradientMatrix;
private float value;
public LinearShaderTextView(Context context)
this(context, null);
public LinearShaderTextView(Context context, AttributeSet attrs)
super(context, attrs);
init();
private void init()
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
super.onSizeChanged(w, h, oldw, oldh);
mViewWidth = w;
mViewHeight = h;
value = -mViewWidth;
// 获取当前绘制 TextView 的 画笔对象
Paint paint = getPaint();
int paintColor = paint.getColor();
Log.e(TAG, "onSizeChanged: paintColor = " + paintColor);
int currentTextColor = getCurrentTextColor();
Log.e(TAG, "onSizeChanged: currentTextColor = " + currentTextColor);
// 自定义渐变渲染器
linearGradient = new LinearGradient(0, 0,
mViewWidth, mViewHeight,
new int[]currentTextColor, Color.WHITE, currentTextColor,
new float[]0.25f, 0.5f, 0.75f,
Shader.TileMode.CLAMP);
// 为画笔设置渐变渲染器
paint.setShader(linearGradient);
// 创建矩阵
gradientMatrix = new Matrix();
startAnimator();
@Override
protected void onDraw(Canvas canvas)
super.onDraw(canvas);
if (linearGradient != null)
gradientMatrix.setTranslate(value, 0);
// 为着色器设置矩阵
linearGradient.setLocalMatrix(gradientMatrix);
public void startAnimator()
ValueAnimator valueAnimator = ValueAnimator.ofFloat(-mViewWidth, mViewWidth);
valueAnimator.setDuration(1500);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
@Override
public void onAnimationUpdate(ValueAnimator animation)
value = (float) animation.getAnimatedValue();
invalidate();
);
valueAnimator.start();
效果:
BitmapShader
BitmapShader 可以实现各种形状的图片控件,下面实现圆形图片。
/**
* BitmapShader 实现各形状的图片,继承自 ImageView
*/
public class BitmapShaderShapeImageView extends android.support.v7.widget.AppCompatImageView
private Paint paint;
private BitmapShader bitmapShader;
private int mViewWidth;
private int mViewHeight;
private Bitmap bitmap;
private Random random;
private String[] shapes = "RoundRectangle", "Circle", "Triangle";
private int index;
private RectF rectF;
public BitmapShaderShapeImageView(Context context)
super(context);
init();
public BitmapShaderShapeImageView(Context context, AttributeSet attrs)
super(context, attrs);
init();
private void init()
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.key);
bitmapShader = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
paint.setShader(bitmapShader);
random = new Random();
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredSize(widthMeasureSpec, bitmap.getWidth());
int height = getMeasuredSize(heightMeasureSpec, bitmap.getHeight());
setMeasuredDimension(width, height);
private int getMeasuredSize(int measureSpec, int defSize)
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
int value;
if (mode == MeasureSpec.EXACTLY)
value = size;
else
value = defSize;
return value;
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
super.onSizeChanged(w, h, oldw, oldh);
mViewWidth = w;
mViewHeight = h;
rectF = new RectF(mViewWidth / 5f, mViewHeight / 4f, mViewWidth * 4 / 5f, mViewHeight * 3 / 4f);
@Override
protected void onDraw(Canvas canvas)
// super.onDraw(canvas);
switch (index % shapes.length)
case 0:
canvas.drawRoundRect(rectF, 30, 30, paint);
break;
case 1:
canvas.drawCircle(mViewWidth / 2f, mViewHeight / 2f, 100, paint);
break;
case 2:
canvas.drawOval(rectF, paint);
break;
@Override
public boolean onTouchEvent(MotionEvent event)
if (event.getAction() == MotionEvent.ACTION_DOWN)
index++;
invalidate();
return true;
效果:
SweepGradient
SweepGradient 结合属性动画可以扫描效果。
public class SweepGradientView extends View
private static final String TAG = "SweepGradientView";
private Paint paint;
private int mViewWidth;
private int mViewHeight;
private float centerX;
private float centerY;
private float radius;
private int degree = -90;
public SweepGradientView(Context context)
super(context);
init();
public SweepGradientView(Context context, AttributeSet attrs)
super(context, attrs);
init();
private void init()
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
super.onSizeChanged(w, h, oldw, oldh);
Log.e(TAG, "onSizeChanged: ");
mViewWidth = w;
mViewHeight = h;
centerX = mViewWidth / 2f;
centerY = mViewHeight / 2f;
radius = Math.min(mViewWidth, mViewHeight) / 2f;
/**
* 扇形,绕着一个中心点进行扫描的渐变着色器。
* 中心点为 ( mViewWidth / 2, mViewHeight / 2 ) , 顺时针 0 度开始渐变
*/
SweepGradient sweepGradient = new SweepGradient(centerX, centerY,
Color.parseColor("#FFB9B3B3"),
Color.parseColor("#ffcc1111"));
paint.setShader(sweepGradient);
startAnimator();
@Override
protected void onDraw(Canvas canvas)
super.onDraw(canvas);
canvas.save();
canvas.rotate(degree, centerX, centerY);
canvas.drawCircle(centerX, centerY, radius, paint);
canvas.restore();
private void startAnimator()
ValueAnimator valueAnimator = ValueAnimator.ofInt(-90, 270);
valueAnimator.setDuration(3000);
// 动画重复次数
valueAnimator.setRepeatCount(100);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
@Override
public void onAnimationUpdate(ValueAnimator animation)
degree = (int) animation.getAnimatedValue();
invalidate();
);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.start();
RadialGradient
RadialGradient 可以实现类似水波纹效果
/**
* 径向渐变着色器
*/
public class RadialGradientView extends View
private Paint paint;
private int mViewWidth;
private int mViewHeight;
private RectF rectF;
public RadialGradientView(Context context)
super(context);
init();
public RadialGradientView(Context context, AttributeSet attrs)
super(context, attrs);
init();
private void init()
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
super.onSizeChanged(w, h, oldw, oldh);
mViewWidth = w;
mViewHeight = h;
rectF = new RectF(0, 0, mViewWidth, mViewHeight);
/**
* 径向着色,中心点为 view 的 中心点,半径为 100px, 超过这个范围的区域以 repeat 重复的形式填充
*/
RadialGradient radialGradient = new RadialGradient(mViewWidth / 2,
mViewHeight / 2, 100,
Color.parseColor("#ff0000"),
Color.parseColor("#0000ff"),
Shader.TileMode.REPEAT);
paint.setShader(radialGradient);
@Override
protected void onDraw(Canvas canvas)
super.onDraw(canvas);
canvas.drawRect(rectF, paint);
效果:
ComposeShader
组合渲染模式可以将两种渲染模式组合到一起,需要结合 Xfermode 一起使用,下面利用 BitmapShader 和 LinearGradient 实现图片底案 loading 效果。
/**
* 组合着色器,它通过 Xfermode 将两个着色以上是关于Android 自定义 View进阶 - Shader的主要内容,如果未能解决你的问题,请参考以下文章
我的Android进阶之旅------>Android自定义View实现带数字的进度条(NumberProgressBar)