Android 圆形图片 CircleImageView(Xfermode方式)

Android中实现圆形图片,总的说来有2种方法。

  1. Xfermode方式,就是本文要讲的实现方式。
  2. BitmapShader(着色器)和Matrix(矩阵)方式

第二种实现方式的代表作,就是Henning Dodenhof的开源项目https://github.com/hdodenhof/CircleImageView,会在下一篇文章中对其分析。
Android 圆形图片开源项目CircleImageView源码分析

废话不多说,先上图看效果。
这里写图片描述

这里写图片描述
从图一中,可以看到思路:
1、从图片正中心切一个最大的正方形。
2、然后把这个正方形变成圆形。
3、(可选)加边框。

下面我们就一步一步来实现圆形图片。

自定义attr文件

在values文件夹下新建attr.xml文件,文件代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="CircleImageView">
        <attr name="border_width" format="dimension" />
        <attr name="border_corlor" format="color" />
    </declare-styleable>
</resources>

在自定义属性中,我们定义了边框的宽度和边框的颜色。

新建CircleImageView类

新建的CircleImageView继承自ImageView,类中变量和构造函数如以下代码:

private static final int DEFAULT_BORDER_WIDTH = 0;
private static final int DEFAULT_BORDER_COLOR = Color.WHITE;

private int borderWidth = DEFAULT_BORDER_WIDTH;
private int borderColor = DEFAULT_BORDER_COLOR;

private Paint paint;

public CircleImageView(Context context) {
    this(context, null);
}

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

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

    TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);
    borderWidth = typedArray.getDimensionPixelSize(R.styleable.CircleImageView_border_width, DEFAULT_BORDER_WIDTH);
    borderColor = typedArray.getColor(R.styleable.CircleImageView_border_corlor, DEFAULT_BORDER_COLOR);
    typedArray.recycle();

    init();
}

在类中,定义了2个int变量,用来保存从属性中获取的边框宽度和边框颜色,定义了一支画笔用于绘图。

在init函数中,对画笔进行初始化。

private void init() {
    paint = new Paint();
    paint.setAntiAlias(true);
    paint.setFilterBitmap(true);
    paint.setDither(true);
}

到此,初始化工作就算完成了。初始化完成之后,我们就按照思路,一步一步来实现圆形图片。

从图片正中心切一个最大的正方形

代码如下:

/**
 * 把矩形图片裁剪成正方形图片
 * @param bitmap
 * @return
 */
private Bitmap squareBitmap(Bitmap bitmap) {
    Bitmap squareBitmap = null;

    int bitmapWidth = bitmap.getWidth();
    int bitmapHeight = bitmap.getHeight();
    int squareSide = 0;
    int x = 0, y = 0;
    if(bitmapHeight > bitmapWidth) {
        squareSide = bitmapWidth;
        y = (bitmapHeight - bitmapWidth) / 2;
        squareBitmap = Bitmap.createBitmap(bitmap, x, y, squareSide, squareSide);
    }
    else if(bitmapHeight < bitmapWidth) {
        squareSide = bitmapHeight;
        x = (bitmapWidth - bitmapHeight) / 2;
        squareBitmap = Bitmap.createBitmap(bitmap, x, y, squareSide, squareSide);
    }
    else {
        squareBitmap = bitmap;
    }

    return squareBitmap;
}

代码中,我们先获取到了图片的宽和高。
比较图片的宽和高那个小,取小的作为正方形的边长。
然后调用Bitmap.createBitmap函数,从原图中生成一张正方形的图片。

y = (bitmapHeight - bitmapWidth) / 2;
x = (bitmapWidth - bitmapHeight) / 2;

这两句代码,用于保证获取到的是从图片中心生成的最大的正方形。

把正方形图片变成圆形

代码如下:

/**
 * 把矩形图片转换成圆形图片
 * @param bitmap 原始图片最好为正方形,否则会因为拉伸而失真。
 * @return
 */
private Bitmap circleBitmap(Bitmap bitmap) {
    int width = bitmap.getWidth();
    int height = bitmap.getHeight();
    Bitmap circleBitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
    Canvas canvas = new Canvas(circleBitmap);
    Rect rect = new Rect(0, 0, width, height);

    Paint paint = new Paint();
    paint.setAntiAlias(true);
    paint.setFilterBitmap(true);
    paint.setDither(true);

    canvas.drawCircle(width / 2.0f, height / 2.0f, width / 2.0f, paint);
    paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
    canvas.drawBitmap(bitmap, rect, rect, paint);

    return circleBitmap;
}

这个函数,是整个圆形图片实现的核心代码。
Xfermode方式也是在这里应用。
我们先创建一块画布和一个矩形区域。
然后在画布上画一个圆,圆的直径,就是正方形图片的边长。
接下来,这句很重要的代码:

paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));

最后,我们把正方形图片画上去。

经过这3步之后,我们就会得到一个圆形图片。

为什么经过这3步之后,就能得到一个圆形图片呢?
在画布上画图,画一次是一层,再画一次又是一层。
所以,我们画一个圆和一张图片,就是画了2层。
这2层是有交集的,交集就是圆,这个圆,就是我们要的形状。
那内容怎么来呢?
我们要取的是正方形图片的内容,就用到了Xfermode的功能。

paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));

这一句代码中的SRC_IN模式,就决定了,我们取到的是圆交集中图片的内容。

关于Xfermode各种模式的效果,如下图所示:
这里写图片描述
关于Xfermode的资料,可以参考http://www.cnblogs.com/jacktu/archive/2012/01/02/2310326.html

拿到了圆形图片,那剩下的工作就是在onDraw中,把图片画出来。

覆写onDraw

代码如下:

@Override
protected void onDraw(Canvas canvas) {
    Drawable drawable = getDrawable();
    if (drawable == null) {
        return;
    }

    if (getWidth() == 0 || getHeight() == 0) {
        return;
    }

    if (drawable.getClass() == NinePatchDrawable.class) {
        super.onDraw(canvas);
        return;
    }

    Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
    Bitmap squareBitmap = squareBitmap(bitmap);

    // 处理padding
    int defaultWidth = getWidth() - getPaddingLeft() - getPaddingRight();
    int defaultHeight = getHeight() - getPaddingTop() - getPaddingBottom();
    int scaleSize = Math.min(defaultWidth, defaultHeight);
    float top = getPaddingTop() + (defaultHeight - scaleSize) / 2.0f;
    float left = getPaddingLeft() + (defaultWidth - scaleSize) / 2.0f;

    // temp 用来处理边框与图像的边缘
    int temp = 0;
    if(borderWidth > 0)
        temp = 1;
    bitmap = Bitmap.createScaledBitmap(squareBitmap, scaleSize - borderWidth * 2 + temp, scaleSize - borderWidth * 2 + temp, true);
    Bitmap circleBitmap = circleBitmap(bitmap);
    canvas.drawBitmap(circleBitmap, borderWidth - temp / 2.0f + left, borderWidth - temp / 2.0f + top, paint);
    drawCircleBorder(canvas, scaleSize / 2.0f + left, scaleSize / 2.0f + top, scaleSize / 2.0f);
}

我们调用getDrawable获得drawable对象,然后从drawable对象中获得原始的bitmap对象。
然后把原始bitmap切成正方形。
切成正方形之后,经过一次缩放,使图片符合我们的大小,代码如下:

bitmap = Bitmap.createScaledBitmap(squareBitmap, scaleSize - borderWidth * 2 + temp, scaleSize - borderWidth * 2 + temp, true);

得到合适的正方形后,再把正方形变成圆形。

其中一些代码,用来处理padding值,和计算坐标的,大家自行去理解。
以下代码,是用于处理边框与图片边缘误差问题:

// temp 用来处理边框与图像的边缘
    int temp = 0;
    if(borderWidth > 0)
        temp = 1;

最后一句代码,用于话边框。

drawCircleBorder(canvas, scaleSize / 2.0f + left, scaleSize / 2.0f + top, scaleSize / 2.0f);

画边框

代码如下:

/**
 * 画边框
 * @param canvas
 * @param x
 * @param y
 * @param radius
 */
private void drawCircleBorder(Canvas canvas, float x, float y, float radius) {
    if(borderWidth <= 0)
        return;

    Paint paint = new Paint();
    paint.setAntiAlias(true);
    paint.setColor(borderColor);
    paint.setStyle(Paint.Style.STROKE);
    paint.setStrokeWidth(borderWidth);
    canvas.drawCircle(x, y, radius - borderWidth / 2.0f, paint);
}

到此,我们的CircleImageView就写完了,还有一些取值,设置函数,用于在代码动态添加圆形图片,大家自己去添加。

接下来,我们就看看使用的效果:

CircleImageView使用

布局文件代码如下:

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:zcw="http://schemas.android.com/apk/res/com.zcw.circleimageview"
    android:id="@+id/scrollView1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/darker_gray" >

    <LinearLayout
        android:id="@+id/linearLayout1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >

        <Button
            android:id="@+id/button"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="View" />

        <com.zcw.circleimageview.view.CircleImageView 
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:layout_marginTop="5dp"
            android:src="@drawable/pic3" />

        <com.zcw.circleimageview.view.CircleImageView 
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:layout_marginTop="5dp"
            android:src="@drawable/pic4"
            zcw:border_width="5dp"
            zcw:border_corlor="@android:color/holo_purple" />

        <com.zcw.circleimageview.view.CircleImageView 
            android:layout_width="300dp"
            android:layout_height="200dp"
            android:layout_marginTop="5dp"
            android:padding="10dp"
            android:src="@drawable/pic3"
            zcw:border_width="5dp"
            zcw:border_corlor="@android:color/holo_purple"
            android:background="@android:color/holo_orange_light" />
    </LinearLayout>

</ScrollView>

eclipse布局文件中使用自定义属性,需要在布局文件的根控件下添加以下语句(com.zcw.circleimageview为包名):

xmlns:zcw="http://schemas.android.com/apk/res/com.zcw.circleimageview"

Android studio中,使用自定义属性,需要添加的语句,和eclipse中略有不同,没有了包名。

xmlns:app="http://schemas.android.com/apk/res-auto"

运行出来的效果图,就是第二张图的样子了。

项目完整代码下载

下一遍文章,将会分析圆形图片ShaderBitmap实现方式的代表作,Henning Dodenhof的开源项目https://github.com/hdodenhof/CircleImageView

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值