使用shader和xfermode自定义圆角view

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

猜你喜欢

转载自blog.csdn.net/weixin_38801333/article/details/79013630