【Android】 自定义View那点事(二)Xfermode篇

369 查看

回顾

上一节我们学会了使用Canvas和Paint实现自定义View的绘制,我们已经知道如何简单实现自定义View的方法。这一节我们主要来说说Paint的setXfermode方法,对于Android开发中碰到最多是实现圆角图片或是圆形图片显示,只要掌握了setXfermode的使用就能够实现我们想要的图片显示效果。

setXfermode知多少

想了解Xfermode是什么,当然先去翻翻Android官方开发文档啦。文档中Xfermode提供了三个Subclasses:AvoidXfermode,PixelXorXfermode,PorterDuffXfermode而这节我们先介绍PorterDuffXfermode,这也是和本节要讲的内容相关。其他的放在下次再讲吧。

说说PorterDuffXfermode

既然要说PorterDuffXfermode,它是实现图形混合模式,当要实现复杂显示多个图形重叠时呈现特别的显示方式PorterDuffXfermode就派上用处。PorterDuffXfermode的构造方法需要传递一个PorterDuff.Mode 参数。再查查文档吧,一看吓一跳,Enum Values不少,一共有18种mode。具体每一种模式的实现效果是什么样的?把代码敲起来就知道结果了啊。

public class PorterDuffView extends View {

    Paint mPaint;
    Context mContext;
    int BlueColor;
    int PinkColor;
    int mWith;
    int mHeight;
    public PorterDuffView(Context context) {
        super(context);
        init(context);
    }
    public PorterDuffView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public PorterDuffView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mHeight = getMeasuredHeight();
        mWith = getMeasuredWidth();
    }

    private void init(Context context) {
        mContext = context;
        BlueColor = ContextCompat.getColor(mContext, R.color.colorPrimary);
        PinkColor = ContextCompat.getColor(mContext, R.color.colorAccent);
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setAntiAlias(true);
    }
    private  Bitmap drawRectBm(){
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(BlueColor);
        paint.setStyle(Paint.Style.FILL);
        paint.setAntiAlias(true);
        Bitmap bm = Bitmap.createBitmap(200,200, Bitmap.Config.ARGB_8888);
        Canvas cavas = new Canvas(bm);
        cavas.drawRect(new RectF(0,0,70,70),paint);
        return bm;
    }
    private  Bitmap drawCircleBm(){
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(PinkColor);
        paint.setStyle(Paint.Style.FILL);
        paint.setAntiAlias(true);
        Bitmap bm = Bitmap.createBitmap(200,200, Bitmap.Config.ARGB_8888);
        Canvas cavas = new Canvas(bm);
        cavas.drawCircle(70,70,35,paint);
        return bm;
    }
    @Override
    protected void onDraw(Canvas canvas) {
        mPaint.setFilterBitmap(false);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setTextSize(20);
        RectF recf = new RectF(20,20,60,60);
        mPaint.setColor(BlueColor);
        canvas.drawRect(recf,mPaint);
        mPaint.setColor(PinkColor);
        canvas.drawCircle(100,40,20,mPaint);
        int sc = canvas.saveLayer(0, 0,mWith,mHeight, null, Canvas.MATRIX_SAVE_FLAG |
                Canvas.CLIP_SAVE_FLAG |
                Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
                Canvas.FULL_COLOR_LAYER_SAVE_FLAG |
                Canvas.CLIP_TO_LAYER_SAVE_FLAG);
        int y = 180;
        int x = 50;
            for(PorterDuff.Mode mode : PorterDuff.Mode.values()){
                if(y >= 900){
                    y = 180;
                    x += 200;
                }
                mPaint.setXfermode(null);
                canvas.drawText(mode.name(),x + 100,y,mPaint);
                canvas.drawBitmap(drawRectBm(),x,y,mPaint);
                mPaint.setXfermode(new PorterDuffXfermode(mode));
                canvas.drawBitmap(drawCircleBm(),x,y,mPaint);
                y += 120;
            }
        mPaint.setXfermode(null);
        // 还原画布
        canvas.restoreToCount(sc);
    }
}


实现效果图,每个模式的效果一目了然。

实践求真知

知道PorterXfermode的使用模式下面我们实践到实际开发中,我们来绘制圆形图片和微信聊天图片。在设计自定义ImageView之前先简单回顾一下Canvas的绘制,对于Canvas的onDraw每执行一次就是在屏幕上增加一个Bitmap覆盖在原来的图层上。绘制自定义形状图片是通过将自定义图形图层和图片资源图层的内容使用PorterDuffXfermode绘制合并而成,那在图层之间PorterDuffXfermode绘制之前。需要使用 canvas.saveLayerAlpha将原图层的内容保存,使得图形图层是和图片资源图层进行绘制操作。在图层绘制结束之后再使用canvas.restoreToCount恢复保存的原图层内容。若不使用原图层保存和恢复的方法的情况肯定达不到原来所想要的效果,但也可以试试了解canvas的绘制原理,看看结果会是怎样。

绘制圆形图片

//
public class CircleImgView extends ImageView {

    private int mWidth;
    private int mHeight;
    private Paint mPaint;
    private Bitmap CircleBitmap;

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

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

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

    private void ImgCircle(){
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setAntiAlias(true);
        paint.setFilterBitmap(true);
        paint.setColor(Color.GRAY);
        paint.setStrokeWidth(5);
        paint.setStyle(Paint.Style.FILL);
        CircleBitmap = Bitmap.createBitmap(mWidth,mHeight, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(CircleBitmap);
        canvas.drawCircle(mWidth/2,mHeight/2,mWidth/2,paint);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setFilterBitmap(true);
        ImgCircle();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        int count = canvas.saveLayerAlpha(0, 0, mWidth, mHeight, 250, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);
        super.onDraw(canvas);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        canvas.drawBitmap(CircleBitmap,0,0, mPaint);
        canvas.restoreToCount(count);
    }
}

绘制微信聊天图片

public class ImgCustView extends ImageView{

    private int mWidth;
    private int mHeight;
    private Paint mPaint;
    private Bitmap canvasBitmap;
    public ImgCustView(Context context) {
        super(context);
    }

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

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

    private void ImgShape(){
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setAntiAlias(true);
        paint.setFilterBitmap(true);
        paint.setColor(Color.GRAY);
        paint.setStyle(Paint.Style.FILL);
        canvasBitmap = Bitmap.createBitmap(mWidth,mHeight, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(canvasBitmap);
        Path path = new Path();
        path.moveTo(mWidth - 25,50);
        path.lineTo(mWidth - 25,80);
        path.lineTo(mWidth,65);
        path.close();
        RectF rectF = new RectF(0,0,mWidth - 25,mHeight);
        canvas.drawRoundRect(rectF,20,20,paint);
        canvas.drawPath(path,paint);
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setFilterBitmap(true);
        ImgShape();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        int count = canvas.saveLayerAlpha(0, 0, mWidth, mHeight, 250, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);
        super.onDraw(canvas);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        canvas.drawBitmap(canvasBitmap,0,0, mPaint);
        canvas.restoreToCount(count);
    }

}

主程序

public class ImgCustActivity extends BaseActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(500,500);
        layoutParams.setMargins(20,20,20,20);
        //实例化ImageView并加载图片资源
        ImageView imageView = new ImageView(this);
        imageView.setScaleType(ImageView.ScaleType.FIT_XY);
        imageView.setImageResource(R.drawable.background);
        imageView.setLayoutParams(layoutParams);
        //实例化圆形ImageView并加载图片资源
        ImgCustView imgCustView = new ImgCustView(this);
        imgCustView.setScaleType(ImageView.ScaleType.FIT_XY);
        imgCustView.setImageResource(R.drawable.background);
        imgCustView.setLayoutParams(layoutParams);
        //实例化聊天ImageView并加载图片资源
        CircleImgView circleView = new CircleImgView(this);
        circleView.setScaleType(ImageView.ScaleType.FIT_XY);
        circleView.setImageResource(R.drawable.background);
        circleView.setLayoutParams(layoutParams);

        llContent.setBackgroundColor(Color.LTGRAY);
        llContent.addView(imageView);
        llContent.addView(imgCustView);
        llContent.addView(circleView);
    }
}


最后的效果呈现

写在最后

这节主要学习PorterDuffXfermode模式并通过几个简单例子实践了一下。结合简单例子我们还可以深入学习绘制更为复杂有趣的自定义图形。本节只做到了抛砖迎玉的作用,以后我还会结合其他内容绘制更有意思和创造性的图形。之后还会讲讲关于View的动画,下次见 see you!