您的位置:首页 > Web前端

使用shader和xfermode自定义圆角view

2018-01-09 15:43 274 查看

自定义圆角View

最近有个产品需求,需要用圆角图片展示内容。在网上搜了一下,有两个方案:shader和xfermode。最终考虑到内存占用问题,我们选择了shader。
下面先分别说下shader和xfermode的基本用法,然后给出对应的圆角实现代码。

shader

shader被翻译成着色器,顾名思义:画笔的颜色。在android中,shader是有很多实现子类的,这里不多介绍,本文中使用到是BitmapShader:将一个位图转化成画笔的色彩。下面先给个画出图片的demo,来感受一下shader的效果。
public class RoundImageView2 extends android.support.v7.widget.AppCompatImageView {

private float[] roundArray = {0.f,0.f,0.f,0.f,0.f,0.f,0.f,0.f};

private Path mPath;

private Paint mPaint;

private Bitmap mBitmap;

private float moveX = 0.f;

private float moveY = 0.f;

public RoundImageView2(Context context) {
super(context);
}

public RoundImageView2(Context context, AttributeSet attrs) {
super(context, attrs);
init(context,attrs);
}

private void init(Context context,AttributeSet attrs){
mPath = new Path();
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(30f);
}

@Override
protected void onDraw(Canvas canvas) {

mBitmap = getBitMap();

BitmapShader bitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mPaint.setShader(bitmapShader);

canvas.drawPath(mPath,mPaint);

}

@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
//                mPath.reset();
mPath.moveTo(event.getX(),event.getY());
break;
case MotionEvent.ACTION_MOVE:
moveX = event.getX();
moveY = event.getY();
mPath.lineTo(moveX,moveY);
invalidate();
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}

private Bitmap getBitMap(){
Drawable drawable = getDrawable();
if(drawable == null){
return null;
}
Bitmap bitmap;
if(drawable instanceof BitmapDrawable){
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
bitmap = bitmapDrawable.getBitmap();
}else {
bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);

drawable.draw(new Canvas(bitmap));
}
return bitmap;
}





上面的demo展示了使用shader实现涂鸦的效果。下面看圆角怎么实现:
1.给创建一个图片的BitmapShader。
2.然后画出圆角的形状。在canvas中,圆角可以使用Path的addRoundRect()定出。再通过canvas.drawPath就可以完成形状的绘制。形状有了,只需要在画形状的时候添加着色器:
public class RoundImageView extends android.support.v7.widget.AppCompatImageView {

private float[] radiusArray = {0f,0f,0f,0f,0f,0f,0f,0f};

private Bitmap mBitmap;

/**
*绘制工具
*/
private Paint mPaint;
private BitmapShader mBitmapShader;
private Path mPath;
private RectF mRectF;

public RoundImageView(Context context) {
super(context);
}

public RoundImageView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context,attrs);
}

public RoundImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context,attrs);
}

private void init(Context context,AttributeSet attrs){
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPath = new Path();
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.RoundImageView);
radiusArray[0] = radiusArray[1] = array.getDimension(R.styleable.RoundImageView_top_left,0f);
radiusArray[2] = radiusArray[3] = array.getDimension(R.styleable.RoundImageView_top_right,0f);
radiusArray[4] = radiusArray[5] = array.getDimension(R.styleable.RoundImageView_bottom_left,0f);
radiusArray[6] = radiusArray[7] = array.getDimension(R.styleable.RoundImageView_bottom_left,0f);
array.recycle();
}

@Override
protected void onDraw(Canvas canvas) {
recycle();
mBitmap = getDrawableBitmap();
if(mBitmap != null){
mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mPaint.setShader(mBitmapShader);//添加着色器
mPath.addRoundRect(mRectF,radiusArray,Path.Direction.CW);//定制形状
canvas.drawPath(mPath,mPaint);
}
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mRectF = new RectF(0,0,w,h);
}

private Bitmap getDrawableBitmap(){
Drawable drawable = getDrawable();
if(drawable == null){
return null;
}
Bitmap bitmap;
if(drawable instanceof BitmapDrawable){
BitmapDrawable bitmapDrawable = (BitmapDrawable)drawable;
bitmap = bitmapDrawable.getBitmap();
}else {
int w = drawable.getIntrinsicWidth();
int h = drawable.getIntrinsicHeight();
bitmap = Bitmap.createBitmap(w,h, Bitmap.Config.ARGB_8888);
drawable.setBounds(0, 0, w, h);
drawable.draw(new Canvas(bitmap));
}
return bitmap;
}

private void  recycle(){
if(mBitmap != null){
mBitmap.recycle();
}
}


总结,使用shader实现圆角图片:在圆角图形上添加图片着色器


xfermode

xfermode也是Paint的一个特性,用来处理合成渲染效果。网上有个很经典的图,这里就不show了。
那么什么是合成渲染效果?其实就是用来控制两个图形叠加的效果。下面先看一个使用:

public class XfermodeView extends View {

private Paint mPaint;
public XfermodeView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}

private void init(Context context) {
mPaint = new Paint();
}

@Override
protected void onDraw(Canvas canvas) {

xfermodeWithLayer(canvas);

}

private void drawCircle(Canvas canvas, int w, int h) {
mPaint.setColor(Color.RED);
canvas.drawCircle(w/2,h/2,50,mPaint);
}

private void drawText(Canvas canvas,int w, int h) {
mPaint.setTextSize(40);
canvas.drawText("XfermodeView",w/2,h/2,mPaint);

}

private void xfermodeWithLayer(Canvas canvas){
int width = getWidth();
int height = getHeight();

//        int save = canvas.saveLayer(0,0,width,height,null,Canvas.ALL_SAVE_FLAG);

drawText(canvas,width,height);

mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

drawCircle(canvas,width,height);
mPaint.setXfermode(null);

//        canvas.restoreToCount(save);
}

private void xfermodeWithBitmap(Canvas canvas){
int width = getWidth();
int height = getHeight();

Bitmap textBitmap = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);
Canvas textCanvas = new Canvas(textBitmap);
drawText(textCanvas,width,height);

Bitmap circleBitmap = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);

Canvas circleCanvas = new Canvas(circleBitmap);

drawCircle(circleCanvas,width,height);

mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));

textCanvas.drawBitmap(circleBitmap,0,0,mPaint);

mPaint.setXfermode(null);

canvas.drawBitmap(textBitmap,0,0,mPaint);
}

运行上面的代码的效果(图1):



        把注释打开:

int save = canvas.saveLayer(0,0,width,height,null,Canvas.ALL_SAVE_FLAG);

drawText(canvas,width,height);

mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

drawCircle(canvas,width,height);
mPaint.setXfermode(null);

canvas.restoreToCount(save);

       效果如下(图2):



     虽然上面是本文想要的结果,但是它和代码中SRC_IN描述的效果似乎不太一样(图3):



  src_in应该展示出红色那一小部分的字符。
出现上面效果的原因:src并不是红色圆而是上面那个红色圆盖住文字的整体图形。而dist和我们的理解一样就是文字那部分。所以最后使用src_in会把文字整个文字也展示出来。

下面尝试一下代码中的第二种方案

@Override
protected void onDraw(Canvas canvas) {

//        xfermodeWithLayer(canvas);
xfermodeWithBitmap(canvas);

}

最后效果和我们预期的一样,就不展示了,见图2。这里说下第二种方案:使用位图承接图形。圆形和文字分别使用下面两个bitmap承接:


Bitmap textBitmap = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);
Canvas textCanvas = new Canvas(textBitmap);
drawText(textCanvas,width,height);

Bitmap circleBitmap = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);

Canvas circleCanvas = new Canvas(circleBitmap);

drawCircle(circleCanvas,width,height);

最后使用xfermode的SRC_ATOP策略将两个图形重叠在一起:

mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));

textCanvas.drawBitmap(circleBitmap,0,0,mPaint);

mPaint.setXfermode(null);

canvas.drawBitmap(textBitmap,0,0,mPaint); //最后将重叠的图形画ondraw的canvas面板上。

总结:这里展示了两种使用xfermode的方式,第一种方式小编也不太熟悉,就不多废话了。第二方式的实现比较符合图形重叠的理解,在使用过程我们可以很好的把控。但是第二种方式有一个缺点:内存消耗大。(有文章说第一种方式也很消耗性能)

最后把xfermode实现圆角的代码也贴出来(随便在网上都能搜到)
public class RoundImageView1 extends android.support.v7.widget.AppCompatImageView {

private float[] radiusArray = {0f,0f,0f,0f,0f,0f,0f,0f};

private Bitmap mBitmap;

/**
*绘制工具
*/
private Paint mPaint;
private BitmapShader mBitmapShader;
private Path mPath;
private RectF mRectF;

public RoundImageView1(Context context) {
super(context);
}

public RoundImageView1(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context,attrs);
}

public RoundImageView1(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context,attrs);
}

private void init(Context context,AttributeSet attrs){
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPath = new Path();
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.RoundImageView);
radiusArray[0] = radiusArray[1] = array.getDimension(R.styleable.RoundImageView_top_left,0f);
radiusArray[2] = radiusArray[3] = array.getDimension(R.styleable.RoundImageView_top_right,0f);
radiusArray[4] = radiusArray[5] = array.getDimension(R.styleable.RoundImageView_bottom_left,0f);
radiusArray[6] = radiusArray[7] = array.getDimension(R.styleable.RoundImageView_bottom_left,0f);
array.recycle();
}

@Override
protected void onDraw(Canvas canvas) {
//        super.onDraw(canvas);
//创建原图的bitmap
mBitmap = getDrawableBitmap();

//创建view的边框,并把原图设置进去

Bitmap bitmapFrame = createBitmapFrame(mBitmap,getWidth(),getHeight());

canvas.drawBitmap(bitmapFrame,0,0,mPaint);

bitmapFrame.recycle();

}

@Override
protected void onDetachedFromWindow() {
recycle();
super.onDetachedFromWindow();
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mRectF = new RectF(0,0,w,h);
}

private Bitmap createBitmapFrame(Bitmap source, int w, int h){
Bitmap bitmap = Bitmap.createBitmap(w,h,Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
mPath.addRoundRect(mRectF,radiusArray, Path.Direction.CW);
canvas.drawPath(mPath,mPaint);

mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

canvas.drawBitmap(source,0,0,mPaint);
mPaint.setXfermode(null);
return bitmap;
}

private Bitmap getDrawableBitmap(){
Drawable drawable = getDrawable();
if(drawable == null){
return null;
}
Bitmap bitmap;
if(drawable instanceof BitmapDrawable){
BitmapDrawable bitmapDrawable = (BitmapDrawable)drawable;
bitmap = Bitmap.createBitmap(bitmapDrawable.getBitmap());
}else {
int w = drawable.getIntrinsicWidth();
int h = drawable.getIntrinsicHeight();
bitmap = Bitmap.createBitmap(w,h, Bitmap.Config.ARGB_8888);
drawable.setBounds(0, 0, w, h);
drawable.draw(new Canvas(bitmap));
}
return bitmap;
}

private void  recycle(){
if(mBitmap != null){
mBitmap.recycle();
}
}

最后:
         不建议使用xfermode实现圆角或者其他任何形状的图片展示。
1. 任何形状的图片的实现无非是对图片形状的处理。
2. 虽然通过xfermode的重叠策略中截图效果可以实现。但是会因此多构建出一个形状位图。
3. 最直观的方式应该是使用裁图的方式(有兴趣可以自己尝试一下)。但小编认为使用shader的方式相比于裁图更为巧妙。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: