Android圆形头像的绘制(一)之绘制的几种方法

现在很多应用都有涉及到自定义图像,现在我们看下圆形图像是怎么绘制的。在绘制之前,我们需要对PorterDuff.Mode进行初步的了解,后面圆形图像的绘制会用到该方法,看下PorterDuff.Mode定义的枚举参数

public enum Mode {
        /** [0, 0] */
        CLEAR       (0),
        /** [Sa, Sc] */
        SRC         (1),
        /** [Da, Dc] */
        DST         (2),
        /** [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] */
        SRC_OVER    (3),
        /** [Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc] */
        DST_OVER    (4),
        /** [Sa * Da, Sc * Da] */
        SRC_IN      (5),
        /** [Sa * Da, Sa * Dc] */
        DST_IN      (6),
        /** [Sa * (1 - Da), Sc * (1 - Da)] */
        SRC_OUT     (7),
        /** [Da * (1 - Sa), Dc * (1 - Sa)] */
        DST_OUT     (8),
        /** [Da, Sc * Da + (1 - Sa) * Dc] */
        SRC_ATOP    (9),
        /** [Sa, Sa * Dc + Sc * (1 - Da)] */
        DST_ATOP    (10),
        /** [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] */
        XOR         (11),
        /** [Sa + Da - Sa*Da,
             Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)] */
        DARKEN      (16),
        /** [Sa + Da - Sa*Da,
             Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] */
        LIGHTEN     (17),
        /** [Sa * Da, Sc * Dc] */
        MULTIPLY    (13),
        /** [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] */
        SCREEN      (14),
        /** Saturate(S + D) */
        ADD         (12),
        OVERLAY     (15);
    }

其中 Sa 代表source alpha ,即源 alpha 值 ,Da 代表 Destination alpha ,即 目标alpha值 ,Sc 代表 source color ,即源色值 ,Dc 代表 Destination color ,即目标色值,并且这所有的计算都以像素为单位,在某一种混合模式下,对每一个像素的alpha 和 color 通过对应算法进行运算,得出新的像素值。

ProterDuff.Mode16个枚举值代表的意思如下:

PorterDuff.Mode.CLEAR  所绘制不会提交到画布上。

PorterDuff.Mode.SRC 显示上层绘制图片

PorterDuff.Mode.DST 显示下层绘制图片

PorterDuff.Mode.SRC_OVER 正常绘制显示,上下层绘制叠盖。

PorterDuff.Mode.DST_OVER 上下层都显示。下层居上显示。

PorterDuff.Mode.SRC_IN  取两层绘制交集。显示上层。

PorterDuff.Mode.DST_IN 取两层绘制交集。显示下层。

PorterDuff.Mode.SRC_OUT 取上层绘制非交集部分。

PorterDuff.Mode.DST_OUT 取下层绘制非交集部分。

PorterDuff.Mode.SRC_ATOP 取下层非交集部分与上层交集部分

PorterDuff.Mode.DST_ATOP 取上层非交集部分与下层交集部分

PorterDuff.Mode.XOR 异或:去除两图层交集部分

PorterDuff.Mode.DARKEN 取两图层全部区域,交集部分颜色加深

PorterDuff.Mode.LIGHTEN 取两图层全部,点亮交集部分颜色

PorterDuff.Mode.MULTIPLY 取两图层交集部分叠加后颜色

PorterDuff.Mode.SCREEN 取两图层全部区域,交集部分变为透明色


看下16个枚举值展示的效果图:


Android圆形头像的绘制(一)之绘制的几种方法_第1张图片

实现的代码如下(代码来自ApiDemos/Graphics/XferModes):
package com.dylan.porterduffdemo;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.Xfermode;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.View;

public class MainActivity extends AppCompatActivity {
    static Bitmap makeDst(int w, int h) {
        Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(bm);
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);

        p.setColor(0xFFFFCC44);
        c.drawOval(new RectF(0, 0, w * 3 / 4, h * 3 / 4), p);
        return bm;
    }

    // create a bitmap with a rect, used for the "src" image
    static Bitmap makeSrc(int w, int h) {
        Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(bm);
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);

        p.setColor(0xFF66AAFF);
        c.drawRect(w / 3, h / 3, w * 19 / 20, h * 19 / 20, p);
        return bm;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new SampleView(this));
    }

    private static class SampleView extends View {
        private static final int W = 192;
        private static final int H = 192;
        private static final int ROW_MAX = 4;   // number of samples per row

        private Bitmap mSrcB;
        private Bitmap mDstB;
        private Shader mBG;     // background checker-board pattern

        private static final Xfermode[] sModes = {
                new PorterDuffXfermode(PorterDuff.Mode.CLEAR),
                new PorterDuffXfermode(PorterDuff.Mode.SRC),
                new PorterDuffXfermode(PorterDuff.Mode.DST),
                new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER),
                new PorterDuffXfermode(PorterDuff.Mode.DST_OVER),
                new PorterDuffXfermode(PorterDuff.Mode.SRC_IN),
                new PorterDuffXfermode(PorterDuff.Mode.DST_IN),
                new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT),
                new PorterDuffXfermode(PorterDuff.Mode.DST_OUT),
                new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP),
                new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP),
                new PorterDuffXfermode(PorterDuff.Mode.XOR),
                new PorterDuffXfermode(PorterDuff.Mode.DARKEN),
                new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN),
                new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY),
                new PorterDuffXfermode(PorterDuff.Mode.SCREEN)
        };

        private static final String[] sLabels = {
                "Clear", "Src", "Dst", "SrcOver",
                "DstOver", "SrcIn", "DstIn", "SrcOut",
                "DstOut", "SrcATop", "DstATop", "Xor",
                "Darken", "Lighten", "Multiply", "Screen"
        };

        public SampleView(Context context) {
            super(context);

            mSrcB = makeSrc(W, H);
            mDstB = makeDst(W, H);

            // make a ckeckerboard pattern
            Bitmap bm = Bitmap.createBitmap(new int[]{0xFFFFFFFF, 0xFFCCCCCC,
                            0xFFCCCCCC, 0xFFFFFFFF}, 2, 2,
                    Bitmap.Config.RGB_565);
            mBG = new BitmapShader(bm,
                    Shader.TileMode.REPEAT,
                    Shader.TileMode.REPEAT);
            Matrix m = new Matrix();
            m.setScale(6, 6);
            mBG.setLocalMatrix(m);
        }

        @Override
        protected void onDraw(Canvas canvas) {
            canvas.drawColor(Color.WHITE);

            Paint labelP = new Paint(Paint.ANTI_ALIAS_FLAG);
            labelP.setTextAlign(Paint.Align.CENTER);

            Paint paint = new Paint();
            paint.setFilterBitmap(false);

            canvas.translate(15, 35);

            int x = 0;
            int y = 0;
            for (int i = 0; i < sModes.length; i++) {
                // draw the border
                paint.setStyle(Paint.Style.STROKE);
                paint.setShader(null);
                canvas.drawRect(x - 0.5f, y - 0.5f,
                        x + W + 0.5f, y + H + 0.5f, paint);

                // draw the checker-board pattern
                paint.setStyle(Paint.Style.FILL);
                paint.setShader(mBG);
                canvas.drawRect(x, y, x + W, y + H, paint);

                // draw the src/dst example into our offscreen bitmap
                int sc = canvas.saveLayer(x, y, x + W, y + H, 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);
                canvas.translate(x, y);
                canvas.drawBitmap(mDstB, 0, 0, paint);
                paint.setXfermode(sModes[i]);
                canvas.drawBitmap(mSrcB, 0, 0, paint);
                paint.setXfermode(null);
                canvas.restoreToCount(sc);

                // draw the label
                canvas.drawText(sLabels[i],
                        x + W / 2, y - labelP.getTextSize() / 2, labelP);

                x += W + 10;

                // wrap around when we've drawn enough for one row
                if ((i % ROW_MAX) == ROW_MAX - 1) {
                    x = 0;
                    y += H + 30;
                }
            }
        }
    }
}

从上面的效果图可以知道,假设创建一个矩形的bitmap(这里用A表示)和一个圆形的bitmap(这里用B表示),并且让B在A里居中展示,在调用PorterDuff.Mode.SRC_IN或者PorterDuff.Mode.DST_IN,合并后的图像就是我们想要的圆形图像,下面我们按照这样的思路,用代码进行实现,我们先创建一个Activity,在自定义一个View,并将 View设置在setContentView里,代码如下:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        CircleImageView circleImageView = new CircleImageView(this);
        setContentView(circleImageView);
    }
    
    private class CircleImageView extends ImageView {
        public CircleImageView(Context context) {
            this(context, null);
        }

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

        public CircleImageView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    }
}
运行代码,出来的界面是空白的,因为没有实现CircleImageView里的onDraw方法,下面我们实现onDraw方法,在实现onDraw方法之前,需要在CircleImageView的构造函数里进行初始化操作,获取将要绘制图片的bitmap,代码如下:

       private void init() {
            mSrcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
        }
PorterDuff.Mode.SRC_IN实现onDraw方法,代码如下:

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            Bitmap bitmap = createCirclrBitmapForSRC_IN();
            if (bitmap != null) {
                canvas.drawBitmap(bitmap, 0, 0, new Paint());
            }
        }
        private Bitmap createCirclrBitmapForSRC_IN() {
            if (mSrcBitmap != null) {
                int width = mSrcBitmap.getWidth();
                int height = mSrcBitmap.getHeight();
                if (width > 0 && height > 0) {
                    //创建一个和图片大小差不多的正方形矩阵
                    int size = Math.min(width, height);
                    Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
                    Canvas canvas = new Canvas(bitmap);

                    Paint paint = new Paint();
                    int center = size / 2;
                    //在矩阵中心画圆,与矩阵的四边相切
                    canvas.drawCircle(center, center, center, paint);
                    //设置Xfermode为SRC_IN
                    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
                    //绘制图片
                    canvas.drawBitmap(mSrcBitmap, 0, 0, paint);
                    return bitmap;
                }
            }
            return null;
        }
PorterDuff.Mode.DST_IN实现onDraw方法,代码如下:
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            Bitmap bitmap = createCirclrBitmapForDST_IN();
            if (bitmap != null) {
                canvas.drawBitmap(bitmap, 0, 0, new Paint());
            }
        }
	private Bitmap createCirclrBitmapForDST_IN() {
            if (mSrcBitmap != null) {
                int width = mSrcBitmap.getWidth();
                int height = mSrcBitmap.getHeight();
                if (width > 0 && height > 0) {
                    //创建一个和图片大小差不多的正方形矩阵
                    int size = Math.min(width, height);
                    Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
                    Canvas canvas = new Canvas(bitmap);

                    Paint paint = new Paint();
                    //绘制图片
                    canvas.drawBitmap(mSrcBitmap, 0, 0, paint);
                    //设置Xfermode为SRC_IN
                    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
                    //创建圆形bitmap,并画在canvas上
                    canvas.drawBitmap(createDstBitmap(size), 0, 0, paint);
                    return bitmap;
                }
            }
            return null;
        }
        private Bitmap createDstBitmap(int size) {
            Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(bitmap);
            int center = size / 2;
            Paint paint = new Paint();
            paint.setColor(Color.WHITE);
            canvas.drawCircle(center, center, center, paint);
            return bitmap;
        }
运行代码,两个方法的效果图如下:

Android圆形头像的绘制(一)之绘制的几种方法_第2张图片

除上面两张方法实现圆形图像外,还有一种方法,记得上篇文章Android绘制之Canvas中的clipPath方法么?裁剪出一个圆形的canvas,在把bitmap画在canvas上,这样也能到达我们想要的效果,代码如下:

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            Bitmap bitmap = createCirclrBitmapForClip();
            if (bitmap != null) {
                canvas.drawBitmap(bitmap, 0, 0, new Paint());
            }
        }
	private Bitmap createCirclrBitmapForClip() {
            if (mSrcBitmap != null) {
                int width = mSrcBitmap.getWidth();
                int height = mSrcBitmap.getHeight();
                if (width > 0 && height > 0) {
                    int size = Math.min(width, height);
                    Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
                    Canvas canvas = new Canvas(bitmap);
                    clipCirclePath(canvas, size);

                    Paint paint = new Paint();
                    canvas.drawBitmap(mSrcBitmap, 0, 0, paint);
                    return bitmap;
                }
            }
            return null;
        }
       private void clipCirclePath(Canvas canvas, int size) {
            int center = size / 2;
            Path path = new Path();
            path.addCircle(center, center, center, Path.Direction.CW);
            canvas.clipPath(path);
        }

运行代码,效果图如下:

Android圆形头像的绘制(一)之绘制的几种方法_第3张图片

源码链接

你可能感兴趣的:(android)