您的位置:首页 > 移动开发 > Android开发

Android绘图基础之Shader

2017-06-30 17:57 120 查看
转载请注明出处:http://blog.csdn.net/crazy1235/article/details/74011243

Shader – 着色器

Shader 共有5个子类!



public Shader setShader(Shader shader) {
// If mShader changes, cached value of native shader aren't valid, since
// old shader's pointer may be reused by another shader allocation later
if (mShader != shader) {
mNativeShader = -1;
}
// Defer setting the shader natively until getNativeInstance() is called
mShader = shader;
return shader;
}


下面分别来说一下这五种着色器!

BitmapShader

图片着色器


来看下BitmapShader的构造函数:

public BitmapShader(@NonNull Bitmap bitmap, TileMode tileX, TileMode tileY) {
mBitmap = bitmap;
mTileX = tileX;
mTileY = tileY;
init(nativeCreate(bitmap, tileX.nativeInt, tileY.nativeInt));
}


第一个参数就是要处理的图片!

第二个和第三个参数表示贴图模式 – TileMode, 是一个枚举类型。

public enum TileMode {
/**
* 当图形尺寸大于bitmap尺寸时,用bitmap的边缘颜色进行填充剩余空间
*/
CLAMP   (0),
/**
* 当要绘制的图形尺寸大于Bitmap尺寸时,会用Bitmap重复平铺整个绘制的区域
*/
REPEAT  (1),
/**
* 当绘制的图形尺寸大于Bitmap尺寸时,MIRROR模式下也会用Bitmap重复平铺整个绘图区域,与REPEAT不同的是,两个相邻的Bitmap互为镜像。
*/
MIRROR  (2);

TileMode(int nativeInt) {
this.nativeInt = nativeInt;
}
final int nativeInt;
}


举例说明:

CLAMP + CLAMP

private Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.child);

private BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); // CLAMP + CLAMP


paint.setShader(bitmapShader);
canvas.drawRect(0, 0, bitmap.getWidth() * 2, bitmap.getHeight() * 2, paint);




REPEAT + REPEAT

private BitmapShader bitmapShader2 = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); // REPEAT + REPEAT

paint.setShader(bitmapShader2);
canvas.drawRect(0, 0, bitmap.getWidth() * 2.5F, bitmap.getHeight() * 2.5F, paint);




REPEAT + CLAMP

private BitmapShader bitmapShader2 = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.CLAMP); // REPEAT + CLAMP

paint.setShader(bitmapShader2);
canvas.drawRect(0, 0, bitmap.getWidth() * 2.5F, bitmap.getHeight() * 2.5F, paint);




MIRROR + MIRROR

private BitmapShader bitmapShader3 = new BitmapShader(bitmap, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR); // MIRROR + MIRROR

paint.setShader(bitmapShader3);
canvas.drawRect(0, 0, bitmap.getWidth() * 2.5F, bitmap.getHeight() * 2.5F, paint);




上下左右都是镜像图片

MIRROR + REPEAT

private BitmapShader bitmapShader3 = new BitmapShader(bitmap, Shader.TileMode.MIRROR, Shader.TileMode.REPEAT); // MIRROR + REPEAT

paint.setShader(bitmapShader3);
canvas.drawRect(0, 0, bitmap.getWidth() * 2.5F, bitmap.getHeight() * 2.5F, paint);




左右是镜像,上下是重复的 !

上面的demo都是基于绘制区域比原图(bitmap)要大的情况下。那么当绘制区域小于原图的时候会发生什么呢?


将绘制区域改成一个圆!

paint.setShader(bitmapShader2);
int radius = bitmap.getWidth() / 2;
if (bitmap.getHeight() < bitmap.getWidth()) {
radius = bitmap.getHeight() / 2;
}
canvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2, radius, paint);




哇擦!牛逼了,这不就是圆形图片嘛!!!

改一下圆的大小再看看!

private BitmapShader bitmapShader2 = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); //REPEAT


paint.setShader(bitmapShader2);
int radius = bitmap.getWidth() / 2;
if (bitmap.getHeight() < bitmap.getWidth()) {
radius = bitmap.getHeight() / 2;
}
canvas.drawCircle(bitmap.getWidth(), bitmap.getHeight(), radius * 2, paint);




此时圆形宽高比bitmap要大,设置的shader风格又是REPEAT,所以就出现了上图展现的结果!

但是在实际开发中,显示ImageView的大小可不是根据图片的大小来定的。如果想要这个圆形图片宽度占满屏幕呢? 又不像出现上面的重复情况

此时就需要针对bitmap进行缩放操作!

// 首先要设置测量宽高一直
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int size = Math.min(getMeasuredWidth(), getMeasuredHeight());
radius = size / 2;

setMeasuredDimension(size, size);
}


然后在onDraw()函数中:

int w = getWidth();
int h = getHeight();

float mScale = (radius * 2.0f) / Math.min(bitmap.getHeight(), bitmap.getWidth());

Matrix matrix = new Matrix();
matrix.setScale(mScale, mScale);

bitmapShader2 = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); // 什么模式无所谓了
bitmapShader2.setLocalMatrix(matrix);
paint.setShader(bitmapShader2);
canvas.drawCircle(radius, radius, radius, paint);




此时将自定义图片在布局中的大小改变一下!

<com.jacksen.demo.view.canvas.MyCanvasView
android:id="@+id/my_canvas_view"
android:layout_width="200dp"
android:layout_height="200dp" />




此时圆形图片的大小也有变化!

至此一个最简单的自定义圆形图片的功能就能实现了!

这是除了用PorterDuff.Mode 方式之外 另一种实现自定义圆形图片的方式!

当然不只能实现圆形图片那么简单!!!!

各种图形都可以,因为Canvas 不仅有drawCircle()函数,还有各种drawRect(), drawOval() ,drawText(),甚至还有drawPath() !!!

来看一个drawText()的效果!

float mScale = (radius * 2.0f) / Math.min(bitmap.getHeight(), bitmap.getWidth());

Matrix matrix = new Matrix();
matrix.setScale(mScale, mScale);

bitmapShader2 = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); // 模式无所谓了
bitmapShader2.setLocalMatrix(matrix);
paint.setShader(bitmapShader2);
paint.setTextSize(300.0f);
paint.setColor(Color.BLACK);
paint.setTextSkewX(-0.5f); // X轴错切
canvas.drawText("笨小孩", 0, radius, paint);




效果是不是很神奇!!!

在用drawPath() 函数来画一个五角形的图案!

public Path getPentagonPath() {
Path path = new Path();

float radian = degree2Radian(36);// 36为五角星的角度
float radius_in = (float) (radius * Math.sin(radian / 2) / Math
.cos(radian)); // 中间五边形的半径 (就是内接圆的半径)

path.moveTo((float) (radius * Math.cos(radian / 2)), 0);// 此点为多边形的起点
path.lineTo((float) (radius * Math.cos(radian / 2) + radius_in
* Math.sin(radian)),
(float) (radius - radius * Math.sin(radian / 2)));
path.lineTo((float) (radius * Math.cos(radian / 2) * 2),
(float) (radius - radius * Math.sin(radian / 2)));
path.lineTo((float) (radius * Math.cos(radian / 2) + radius_in
* Math.cos(radian / 2)),
(float) (radius + radius_in * Math.sin(radian / 2)));
path.lineTo(
(float) (radius * Math.cos(radian / 2) + radius
* Math.sin(radian)), (float) (radius + radius
* Math.cos(radian)));
path.lineTo((float) (radius * Math.cos(radian / 2)),
(float) (radius + radius_in));
path.lineTo(
(float) (radius * Math.cos(radian / 2) - radius
* Math.sin(radian)), (float) (radius + radius
* Math.cos(radian)));
path.lineTo((float) (radius * Math.cos(radian / 2) - radius_in
* Math.cos(radian / 2)),
(float) (radius + radius_in * Math.sin(radian / 2)));
path.lineTo(0, (float) (radius - radius * Math.sin(radian / 2)));
path.lineTo((float) (radius * Math.cos(radian / 2) - radius_in
* Math.sin(radian)),
(float) (radius - radius * Math.sin(radian / 2)));

path.close();// 使这些点构成封闭的多边形

return path;

}

private float degree2Radian(int degree) {
return (float) (Math.PI * degree / 180);
}


canvas.drawPath(getPentagonPath(), paint);




LinearGradient

线性渐变!

就是类似于PS里面的渐变效果,给定两个色值,中间的颜色根据这两个色值来进行计算填充,形成一个渐变的效果!


看一下LinearGradient的构造函数!

// x0 和y0是颜色渐变的起点坐标。
// x1和y1是颜色渐变的终点坐标。
// color0是起点颜色值
// color0是终点颜色值。
// tile 就是TileMode类型参数
public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1,
TileMode tile) {
// ...
}


LinearGradient linearGradient = new LinearGradient(0, 0, w, h, Color.BLACK, Color.YELLOW, Shader.TileMode.CLAMP);
paint.setShader(linearGradient);
canvas.drawCircle(radius, radius, radius, paint);




LinearGradient还有另外一个构造函数:

// colors 表示颜色数组,渐变的过程中可以设置很多个颜色,各个颜色之间进行渐变效果
// positions 数组表示颜色的占比,范围是[0...1]
public LinearGradient(float x0, float y0, float x1, float y1, int colors[], float positions[],
TileMode tile) {
// ...
}


举例说明:

LinearGradient linearGradient = new LinearGradient(0, 0, w, 0,
new int[]{Color.RED,
Color.BLUE,
Color.BLACK,
Color.YELLOW}, null, Shader.TileMode.CLAMP);
paint.setShader(linearGradient);
canvas.drawRect(0, 0, w, h, paint);




一共四个渐变色,由于设置positions为null,所以就均分了整个宽度。

来段文字试试!

paint.setTextSize(70.0f);
canvas.drawText("世间安得双全法,不负如来不负卿", 0, radius, paint);




来动起来!



// 闪动文字 textview
public class FlickerTextView extends android.support.v7.widget.AppCompatTextView {

private Paint paint;

private LinearGradient linearGradient;

private int offset = 0;

private Matrix matrix = new Matrix();

public FlickerTextView(Context context) {
this(context, null);
}

public FlickerTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public FlickerTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);

init();
}

private void init() {
paint = getPaint();

}

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);

ValueAnimator animator = ValueAnimator.ofInt(0, 2 * getMeasuredWidth());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
offset = (int) animation.getAnimatedValue();
postInvalidate();
}
});

animator.setRepeatMode(ValueAnimator.RESTART);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setDuration(1200);
animator.start();

// new
linearGradient = new LinearGradient(-getMeasuredWidth(), 0, 0, 0, new int[]{getCurrentTextColor(), Color.RED, Color.BLUE, getCurrentTextColor()}, null, Shader.TileMode.CLAMP);
paint.setShader(linearGradient);
}

@Override
protected void onDraw(Canvas canvas) {

matrix.setTranslate(offset, 0);
linearGradient.setLocalMatrix(matrix);

paint.setShader(linearGradient);

super.onDraw(canvas); // 上面用的是getPanit(),这里讲super.onDraw()放到最后一行,就可以应用我们修改后的paint!

}
}


将上面的代码再改一改,加上自定义属性,再加上动画开始和停止的函数,就可以作为一个闪动文字TextView 来使用了!

SweepGradient

扫描渐变, 从X轴正方向开始,顺时针进行扫描渲染360°!


// cx, cy表示中心点坐标
// color0是起始颜色
// color1是终止颜色
public SweepGradient(float cx, float cy, int color0, int color1) {
// ...
}


SweepGradient sweepGradient = new SweepGradient(radius, radius, Color.YELLOW, Color.BLUE);
paint.setShader(sweepGradient);
canvas.drawCircle(radius, radius, radius, paint);




第二个构造函数!!

public SweepGradient(float cx, float cy,
int colors[], float positions[]) {
// ...
}


SweepGradient sweepGradient = new SweepGradient(radius, radius, new int[]{Color.YELLOW, Color.BLUE, Color.GREEN, Color.BLACK}, null);
paint.setShader(sweepGradient);
canvas.drawCircle(radius, radius, radius, paint);




RadialGradient

径向渐变,由中心向四周辐射


//centerX  圆心的X坐标
//centerY  圆心的Y坐标
//radius   圆的半径
//centerColor  中心颜色
//edgeColor   边缘颜色
//tileMode
public RadialGradient(float centerX, float centerY, float radius,
int centerColor, int edgeColor, @NonNull TileMode tileMode) {
// ...
}


跟上面的LinearGradient用法很相似!

RadialGradient radialGradient = new RadialGradient(radius, radius, radius, Color.YELLOW, Color.RED, Shader.TileMode.REPEAT);
paint.setShader(radialGradient);
canvas.drawCircle(radius, radius, radius, paint);




而另外一个构造函数与 LinearGradient 的第二个构造函数也是相似的!

public RadialGradient(float centerX, float centerY, float radius,
@NonNull int colors[], @Nullable float stops[], @NonNull TileMode tileMode)


RadialGradient radialGradient = new RadialGradient(radius, radius, radius, new int[]{Color.YELLOW, Color.RED, Color.BLUE, Color.MAGENTA}, new float[]{0.2f, 0.2f, 0.5f, 1f}, Shader.TileMode.REPEAT);
paint.setShader(radialGradient);
canvas.drawCircle(radius, radius, radius, paint);




ComposeShader

组合渲染器!


为什么成为组合渲染器恩?

来看构造函数!

public ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode)

public ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode)


不仅可以设置两个Shader,也可以设置Xfermode。作用就是将两个Shader通过Xfermode来进行组合!

shaderA 就相当于 DST图像

shaderB 就相当于 SRC图像

关于Xfermode详见:http://blog.csdn.net/crazy1235/article/details/73835933

举例:

// 创建圆形bitmap
static Bitmap getCircleBitmap(int radius) {
Bitmap bm = Bitmap.createBitmap(radius * 2, radius * 2, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bm);
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);

p.setColor(Color.WHITE);
c.drawCircle(radius, radius, radius, p);
return bm;
}


BitmapShader bitmapShader = new BitmapShader(getCircleBitmap(radius / 4), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); // shaderA
LinearGradient linearGradient = new LinearGradient(0, 0, w, h,
new int[]{Color.RED,
Color.BLUE,
Color.BLACK,
Color.YELLOW}, null, Shader.TileMode.CLAMP); // shaderB

ComposeShader composeShader = new ComposeShader(bitmapShader, linearGradient, PorterDuff.Mode.SRC_IN); // SRC_IN
paint.setShader(composeShader);
canvas.drawRect(0, 0, w, h, paint);


效果如下:



over~~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: