Android自定义View之圆形头像

本文介绍如何自定义一个圆形头像控件CircleImageView,包括继承ImageView、实现圆形显示及描边效果,并通过XML自定义样式参数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

圆形头像是我们在开发中经常用到的控件,下面我们就来自定义这样一个控件:
这里写图片描述
1.自定义CircleImageView类继承ImageView

package com.example.qw.circleimageview;

/**
 * Created by quwei on 2015/5/13 0013.
 */

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.ImageView;


public class CircleImageView extends ImageView {

    private static final String TAG = "vvv";
    private Bitmap mask;
    private Paint paint;
    private Paint mBorderPaint;
    private int mBorderWidth = 2;
    private int mBorderColor = Color.BLUE;

    public CircleImageView(Context paramContext) {
        super(paramContext);
    }

    public CircleImageView(Context paramContext, AttributeSet paramAttributeSet) {
        this(paramContext, paramAttributeSet, 0);
    }

    public CircleImageView(Context context, AttributeSet paramAttributeSet, int paramInt) {
        super(context, paramAttributeSet, paramInt);
        TypedArray a = context.obtainStyledAttributes(paramAttributeSet, R.styleable.CircularImage);
        mBorderColor = a.getColor(R.styleable.CircularImage_border_color, mBorderColor);
//        float density = context.getResources().getDisplayMetrics().density; // 获取屏幕密度
        mBorderWidth = a.getDimensionPixelOffset(R.styleable.CircularImage_border_width, 0);
        a.recycle();
    }

    private boolean useDefaultStyle = false;

    public void setUseDefaultStyle(boolean useDefaultStyle) {
        this.useDefaultStyle = useDefaultStyle;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (useDefaultStyle) {
            super.onDraw(canvas);
            return;
        }

        Drawable drawable = getDrawable();
        if (drawable == null) {
            super.draw(canvas);
            return;
        }

        /**
         * 第一种方法,使用图层进行图片混合
         */
        Drawable localDrawable = editDrawable(drawable);
        drawCircleImage(canvas, localDrawable);

        /**
         * 第二种方法,创建新的画布,并在该画布上进行混合,生成混合后的bitmap,
         * 再将该bitmap画回到该View的原画布上
         */
//        Bitmap src = ((BitmapDrawable) drawable).getBitmap();
//        Bitmap bitmap = createCircleImage(src);
//        canvas.drawBitmap(bitmap, 0, 0, null);

        // 对圆形图像进行描边
        drawBorder(canvas, getWidth(), getHeight());
    }

    private void drawCircleImage(Canvas canvas, Drawable localDrawable) {
        canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
        localDrawable.draw(canvas);

        if (mask == null)
            mask = createOvalBitmap(getWidth(), getHeight());

        if (this.paint == null) {
            final Paint localPaint = new Paint();
            localPaint.setFilterBitmap(false);
            localPaint.setAntiAlias(true);
            // 关键!DST_IN具体情况请查阅官方文档
            localPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
            this.paint = localPaint;
        }
        canvas.drawBitmap(mask, 0, 0, paint);
        canvas.restore();
    }

    private void drawBorder(Canvas canvas, final int width, final int height) {
        if (mBorderWidth == 0) {
            return;
        }

        if (mBorderPaint == null) {
            mBorderPaint = new Paint();
            mBorderPaint.setStyle(Paint.Style.STROKE);
            mBorderPaint.setAntiAlias(true);
            mBorderPaint.setColor(mBorderColor);
            mBorderPaint.setStrokeWidth(mBorderWidth);
        }

        RectF rectF = new RectF(0 + mBorderWidth, 0 + mBorderWidth,
                width - mBorderWidth, height - mBorderWidth);
        canvas.drawOval(rectF, mBorderPaint);


    }

    private Drawable editDrawable(Drawable localDrawable) {
        int drawableWidth = localDrawable.getIntrinsicWidth();
        int drawableHeight = localDrawable.getIntrinsicHeight();
        Log.i(TAG, "" + drawableWidth + "," + drawableHeight);
        final int width = getWidth();
        final int height = getHeight();
        Log.i(TAG, "" + width + "," + height);
        float rw = (float) drawableWidth / (float) width;
        float rh = (float) drawableHeight / (float) height;
        int dWidth;
        int dHeight;
        if (rw < rh) {
            dWidth = width;
            dHeight = (int) (drawableHeight / rw);
            //移动到中间的偏移量
            int yOff = (dHeight - height) / 2;
            localDrawable.setBounds(0, -yOff, dWidth, dHeight - yOff);
        } else {
            dWidth = (int) (drawableWidth / rh);
            dHeight = height;
            int xOff = (dWidth - width) / 2;
            localDrawable.setBounds(-xOff, 0, dWidth - xOff, dHeight);
        }
        return localDrawable;
    }

    /**
     * 创建圆形画布
     *
     * @param width
     * @param height
     * @return
     */
    public Bitmap createOvalBitmap(final int width, final int height) {
        Bitmap localBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas localCanvas = new Canvas(localBitmap);
        Paint localPaint = new Paint();
        localPaint.setColor(Color.GREEN);
        localPaint.setAntiAlias(true); // 很重要!设置抗锯齿
        int padding = mBorderWidth;
        RectF localRectF = new RectF(padding, padding, width - padding, height - padding);
        localCanvas.drawOval(localRectF, localPaint);

        return localBitmap;
    }

    private Bitmap createCircleImage(Bitmap source) {
        Bitmap target = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
        /**
         * 产生一个同样大小的画布
         */
        Canvas canvas = new Canvas(target);
        final Paint paint = new Paint();
        paint.setAntiAlias(true);
        /**
         * 首先绘制圆形
         */
        int padding = mBorderWidth;
        RectF localRectF = new RectF(padding, padding, getWidth() - padding, getHeight() - padding);
        canvas.drawOval(localRectF, paint);
        /**
         * 使用SRC_IN
         */
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        /**
         * 绘制图片
         */
        int sourceWidth = source.getWidth();
        int sourceHeight = source.getHeight();
        Log.i(TAG, "sourceWidth:" + sourceWidth + ",sourceHeight:" + sourceHeight);
        final int width = getWidth();
        final int height = getHeight();
        Log.i(TAG, "width:" + width + ",height:" + height);
        float rw = (float) sourceWidth / (float) width;
        float rh = (float) sourceHeight / (float) height;
        int dWidth;
        int dHeight;
        if (rw < rh) {
            dWidth = width;
            dHeight = (int) (sourceHeight / rw);
            //移动到中间的偏移量
            int yOff = (dHeight - height) / 2;
            canvas.drawBitmap(source, new Rect(0, 0, sourceWidth, sourceHeight),
                    new Rect(0, -yOff, dWidth, dHeight - yOff), paint);

        } else {
            dWidth = (int) (sourceWidth / rh);
            dHeight = height;
            int xOff = (dWidth - width) / 2;
            canvas.drawBitmap(source, new Rect(0, 0, sourceWidth, sourceHeight),
                    new Rect(-xOff, 0, dWidth - xOff, dHeight), paint);
        }
        Log.i(TAG, "dWidth:" + dWidth + ",dHeight:" + dHeight);

        return target;
    }
}

代码没什么好说,有难度的地方都注释了
2.我们使用描边的时候自定义了属性,在value/atrr.xml文件中加上

    <declare-styleable name="CircularImage">
        <attr name="border_width" format="dimension" />
        <attr name="border_color" format="color" />
    </declare-styleable>

3.在布局文件中使用自定义View

    <com.example.qw.circleimageview.CircleImageView 
        xmlns:def="http://schemas.android.com/apk/res-auto"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:scaleType="centerCrop"
        android:src="@drawable/head"
        def:border_color="#F6170F"
        def:border_width="2dp" />

对混合模式有疑惑的请参考大神爱哥的博客:自定义控件其实很简单1/6

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值