Android圆形头像的绘制(二)之自定义视图

一般在项目中,我们用到的圆形头像都是采用自定义视图的方式,这种圆形头像一般分为普通的圆形头像、带边框的圆形图像、随机背景头像,在上一篇Android圆形图像的绘制(一)中,提到了圆形头像绘制的基本方法,这遍文章主要是用到上篇文章中SRC_IN这个方法,下面看一下效果图。

首先,创建一个类CircleImageView,让它继承ImageView,代码如下:

    public 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);
        }

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
        }
    }
在创建自定义视图时,一般都会在res/values/attrs.xml创建declare-styleable,如果values下面没有attrs.xml这个文件,可以自己新建,将新建的declare-styleable命名为CircleImageView。为了得到上图中的效果,需要知道边缘宽度、边缘颜色、随机头像的背景色、随机背景上展示的文字,以及文字的大小,代码如下:

    <declare-styleable name="CircleImageView">
        <attr name="stroke_width" format="dimension"></attr>
        <attr name="stroke_color" format="color"></attr>
        <attr name="text_size" format="dimension"></attr>
        <attr name="random_backgroud" format="color"></attr>
        <attr name="text" format="string"></attr>
    </declare-styleable>
此时,需要在CircleImageView的构造函数里,获取attrs.xml名为CircleImageView的属性,在获取属性的时候,需要定位文本字体大小,和颜色的默认值,边缘宽度默认值为0,也就是没有边缘。代码如下:

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

        //#5AC3B2
        int defaultColor = getResources().getColor(R.color.colorGreen);
        //14sp
        int defaultTextSize = getResources().getDimensionPixelSize(R.dimen.dimen_default_text_size);

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyleAttr, 0);
        mStrokeWidth = typedArray.getDimensionPixelSize(R.styleable.CircleImageView_stroke_width, 0);
        mStrokeColor = typedArray.getColor(R.styleable.CircleImageView_stroke_color, defaultColor);
        mTextSize = typedArray.getDimensionPixelSize(R.styleable.CircleImageView_text_size, defaultTextSize);
        mBackground = typedArray.getColor(R.styleable.CircleImageView_random_backgroud, defaultColor);
        mText = typedArray.getString(R.styleable.CircleImageView_text);
        //一定要记得回收
        typedArray.recycle();
    }

定义一个mBitmap的全局变量,并重写setImageResource、setImageDrawable、setImageBitmap这三个方法,获取位图信息,并进行重新的绘制,代码如下:

    /**
     * 绘制图片的位图
     */
    private Bitmap mBitmap;
    @Override
    public void setImageResource(int resId) {
        super.setImageResource(resId);
        mBitmap = getBitmapFromDrawable(getDrawable());
        invalidate();
    }

    @Override
    public void setImageDrawable(Drawable drawable) {
        super.setImageDrawable(drawable);
        mBitmap = getBitmapFromDrawable(drawable);
        invalidate();
    }

    @Override
    public void setImageBitmap(Bitmap bm) {
        super.setImageBitmap(bm);
        mBitmap = bm;
        invalidate();
    }
 /**
     * 获取bitmap
     *
     * @param drawable
     * @return
     */
    private Bitmap getBitmapFromDrawable(Drawable drawable) {
        if (drawable == null) {
            return null;
        }

        if (drawable instanceof BitmapDrawable) {
            return ((BitmapDrawable) drawable).getBitmap();
        }

        try {
            Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(bitmap);
            drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
            drawable.draw(canvas);
            return bitmap;
        } catch (OutOfMemoryError e) {
            return null;
        }
    }
运行代码,效果图如下:


运行出来,什么效果都没有,是因为没有实现onDraw这个方法,在实现onDraw之前,需要重写onSizeChanged方法,获取到当前view的长宽,代码如下:

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
    }
下面看下onDraw的实现,代码如下:

    @Override
    protected void onDraw(Canvas canvas) {
        //一定要注释掉,否则圆形图像没法生效
//        super.onDraw(canvas);
        if (mWidth > 0 && mHeight > 0) {
            Bitmap bitmap = createCircleBitmapForSRC_IN(canvas);
            if (bitmap != null) {
                canvas.drawBitmap(bitmap, 0, 0, new Paint());
            }
        }
    }
 /**
     * 创建圆形图像
     * @param targetCanvs
     * @return
     */
    private Bitmap createCircleBitmapForSRC_IN(Canvas targetCanvs) {
        //创建一个和图片大小差不多的正方形矩阵
        int size = Math.min(mWidth, mHeight);

        Bitmap newBitmap = null;
        if (mBitmap != null) {
            int width = mBitmap.getWidth();
            int height = mBitmap.getHeight();

            // 对bitmap进行缩放,缩放到指定view的大小
            Matrix matrix = new Matrix();
            matrix.postScale((float) mWidth / width, (float) mHeight / height);
            newBitmap = Bitmap.createBitmap(mBitmap, 0, 0, width,
                    height, matrix, true);
        } else {
            newBitmap = createRandomMaskBitmap(size);
        }

        if (newBitmap == null) {
            return null;
        }

        int center = size / 2;
        Paint paint = new Paint();
        boolean isDrawBorder = drawCircleBorder(targetCanvs, center, paint);

        Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);

        if (isDrawBorder) {
            paint.setColor(Color.WHITE);
            paint.setStyle(Paint.Style.FILL);
            canvas.scale(DEFAULT_SCALE, DEFAULT_SCALE, center, center);
        }

        //在矩阵中心画圆,与矩阵的四边相切
        canvas.drawCircle(center, center, center, paint);
        //设置Xfermode为SRC_IN
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        //绘制图片
        canvas.drawBitmap(newBitmap, 0, 0, paint);
        return bitmap;
    }
    /**
     * 创建随机背景
     * @param size
     * @return
     */
    private Bitmap createRandomMaskBitmap(int size) {
        Bitmap output = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
        final Paint paint = new Paint();
        paint.setAntiAlias(true);// 抗锯齿
        paint.setFilterBitmap(true);
        paint.setColor(mBackground);

        int center = size / 2;//获取画布的中心位置

        //创建canvas对象,绘制随机背景
        Canvas canvas = new Canvas(output);
        canvas.drawCircle(center, center, center, paint);

        //绘制随机背景上的文字
        setText(canvas, size, paint);

        return output;
    }
    /**
     * 绘制文本
     * @param canvas
     * @param size
     * @param paint
     */
    private void setText(Canvas canvas, int size, Paint paint) {
        Rect targetRect = new Rect(0, 0, size, size);
        //设置绘制文本字体的颜色
        paint.setColor(Color.WHITE);
        //设置绘制文本的大小
        paint.setTextSize(mTextSize);
        //获取文本展示的居中位置
        Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt();
        int baseline = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
        paint.setTextAlign(Paint.Align.CENTER);
        canvas.drawText(mText, targetRect.centerX(), baseline, paint);
    }

 
/**
     * 绘制边界圆
     *
     * @param canvas
     * @param size
     * @param paint
     * @return
     */
    private boolean drawCircleBorder(Canvas canvas, int size, Paint paint) {
        if (mStrokeWidth > 0) {
            paint.setAntiAlias(true);
            paint.setColor(mStrokeColor);
            paint.setStyle(Paint.Style.STROKE);
            paint.setStrokeWidth(mStrokeWidth);
            canvas.drawCircle(size, size, size - mStrokeWidth, paint);
            return true;
        }
        return false;
    }

Activity的布局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="10dp"
    android:orientation="vertical">

    <com.dylan.circleimageview.CircleImageView xmlns:circle_icon="http://schemas.android.com/apk/res/com.dylan.circleimageview"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_margin="10dp"
        android:src="@drawable/test"
        circle_icon:stroke_color="#ff0000"
        circle_icon:stroke_width="1dp" />

    <com.dylan.circleimageview.CircleImageView xmlns:circle_icon="http://schemas.android.com/apk/res/com.dylan.circleimageview"
        android:id="@+id/random_icon"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_margin="10dp"
        circle_icon:stroke_color="#000000"
        circle_icon:stroke_width="1dp"
        circle_icon:text="A"
        circle_icon:text_size="25sp"
        circle_icon:random_backgroud="@color/colorAccent"/>

    <com.dylan.circleimageview.CircleImageView xmlns:circle_icon="http://schemas.android.com/apk/res/com.dylan.circleimageview"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_margin="10dp"
        android:src="@drawable/test" />

    <com.dylan.circleimageview.CircleImageView xmlns:circle_icon="http://schemas.android.com/apk/res/com.dylan.circleimageview"
        android:id="@+id/random_icon1"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_margin="10dp"
        circle_icon:text="B"
        circle_icon:text_size="25sp"
        circle_icon:random_backgroud="@color/colorPrimary"/>

</LinearLayout>
在Activity里需要进行如下初始化:

MainActivity代码:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        CircleImageView circleImageView = (CircleImageView) findViewById(R.id.random_icon);
        circleImageView.drawRandomBackground();

        CircleImageView circleImageView1 = (CircleImageView) findViewById(R.id.random_icon1);
        circleImageView1.drawRandomBackground();
    }
CircleImageView里的drawRandomBackground方法的实现如下:

    /**
     * 绘制随机背景
     */
    public void drawRandomBackground() {
        invalidate();
    }
到这里,运行代码就可以得到文章最开始的那张效果图了。

我们需要注意的问题如下:

1:onDraw方法里的super.onDraw(canvas)一定要注释掉,否则后面的绘制无法生效;

2:对位图进行绘制的时候,需要对位图进行缩放,大小跟view的大小一致;

3:在绘制边缘的时候,圆的半径要剪出边缘宽度的大小,否则边缘线会有展示不全的效果;

4:在获取文本居中的时,应该通过Paint.FontMetricsInt计算获取


Github地址
源码地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值