自定义圆角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的方式相比于裁图更为巧妙。