自定义View—使用Xfermode实现圆角图片

有些时候,棱角分明的矩形可能不满足我们的需求。我们希望图片是圆角,显得图片更加圆滑,例如手q的头像是圆形的。虽说我到现在没做过一个像样的项目,但是还是先学习下,厉兵秣马。

这篇博客主要学习自hongyang前辈的教程,同时也增加自己,作为一个新手,的学习过程,使之详细。此次的圆角图片继承自ImageView,这样可以节省onMeasure步骤。


Xfermode和他的儿子们

实现圆角图片,我们需要用到Xfermode这个类。Xfermode有三个子类:

AvoidXfermode , PixelXorXfermode , PorterDuffXfermode

然而,前两个子类已是弃子,在Android官方文档中描述为“Deprecated since API level 16”,因此就不做介绍。

PorterDuffXfermode是画布绘制的合成模式,也就是两张图的相交之后的显示方式。正常绘制时,新的不透明的会覆盖旧的图,而通过Paint.setXfermode()可以设置相交的显示方式。

使用PorterDuffXfermode,需要创建PorterDuffXfermode的对象,例如:

Xfermode xfermode = new PorterDuffXfermode( PorterDuff.Mode.Clear)

这里传进去的PorterDuff.Mode在官方文档上介绍说有18种,常用的为下面的16种:

引用:

  • 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
    取两图层全部区域,交集部分变为透明色

下面为图说明,先画DST图,设置不同的Mode,再画SRC图:

PorterDuffXfermode

值得说明的是,我并没有对所有的模式进行测试,而是对DST_IN和SRC_IN进行了测试,其他的我相信都大同小异,测试代码如下:

int sc = canvas.saveLayer(0, 0, width, height, mPaint,         Canvas.ALL_SAVE_FLAG);
// 先画DST
canvas.drawBitmap(bm, 0, 0, mPaint);
// 设置Xfermode
mPaint.setXfermode(xfermode);
// 再画SRC
canvas.drawBitmap(bm, 0, 0, mPaint);
// 设置Xfermode为空
mPaint.setXfermode(null);
canvas.restoreToCount(sc);

圆角图片步骤

自定义属性

我们这个圆角图片可以定义图片的圆角度数,因此需要自定义这个属性如些:

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

    <declare-styleable name="RoundImageView">
        <attr name="radius" format="dimension" />
    </declare-styleable>

</resources>

在构造器中获得自定义属性

    private static final String TAG = "RoundImageView";

    private Paint mPaint;

    private Xfermode xfermode;
    /** 图片缩放的比例 */
    private float scale = 1.0f;
    /** 圆角半径 */
    private float mRadius;
    /** 默认的圆角半径 */
    private static final int DEFAULT_RADIUS = 10;
    public RoundImageView(Context context) {
        this(context, null);
    }

    public RoundImageView(Context context, AttributeSet attr) {
        super(context, attr);

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
        TypedArray ta = context.getTheme().obtainStyledAttributes(attr, R.styleable.RoundImageView,
                0, 0);
        mRadius = ta.getDimensionPixelSize(R.styleable.RoundImageView_radius, (int) TypedValue
                .applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_RADIUS, getResources()
                        .getDisplayMetrics()));
        Log.e(TAG, "mRadius:" + mRadius);
        ta.recycle();
    }

重写onDraw方法

① 绘制图片我们需要通过canvas.drawBitmap(Bitmap bm)方法,因此,我们第一步需要获得资源图片的Bitmap对象。而父类ImageView只提供getDrawable()方法来获得Drawable对象,因此需要把Drawable转为Bitmap。

② 另一方面,如果图片资源比较小,而我们设置的width和height过大,我们需要根据比例进行缩放。

考虑以上两点后,onDraw的代码就大致确定了:

@Override
    protected void onDraw(Canvas canvas) {
        Bitmap bm = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), Config.ARGB_8888);
        Drawable drawable = getDrawable();
        Canvas drawableCanvas = new Canvas(bm);
        // 计算缩放
        int drawablewidth = drawable.getIntrinsicWidth();
        int drawableheight = drawable.getIntrinsicHeight();
        int width = getMeasuredWidth();
        int height = getMeasuredHeight();
        scale = Math.min(width * 1.0f / drawablewidth, height * 1.0f / drawableheight);
        Log.e(TAG, scale + "");

        // 缩放
        drawable.setBounds(0, 0, (int) (drawablewidth * scale), (int) (drawableheight * scale));
        drawable.draw(drawableCanvas);
        // 绘制圆角矩形
        // 下面的方法api已经变为21了,所以不能使用
        // canvas.drawRoundRect(0, 0, width, height, mRadius, mRadius, mPaint);
        int sc = canvas.saveLayer(0, 0, width, height, mPaint, Canvas.ALL_SAVE_FLAG);
        // 绘制资源图片
        canvas.drawBitmap(bm, 0, 0, mPaint);
        mPaint.setXfermode(xfermode);
        // 绘制形状产生的图片
        canvas.drawBitmap(makeSrc(width, height), 0, 0, mPaint);
        mPaint.setXfermode(null);
        canvas.restoreToCount(sc);
    }

    private Bitmap makeSrc(int width, int height) {
        Bitmap bm = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(bm);
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
        p.setColor(Color.BLACK);
        c.drawRoundRect(new RectF(0, 0, width, height), mRadius, mRadius, p);
        return bm;
    }

以上代码注意点:

  • canvas.drawRoundRect(0, 0, width, height, mRadius, mRadius, mPaint); 只有api21和以上才能使用,因此需要用drawRoundRect(new RectF(0, 0, width, height), mRadius, mRadius, p);来代替。
  • saveLayer和restoreToCount 不要忘记了,否则可能会失败,至少我真机测试时会失败。
  • 绘制形状时,不直接使用drawRoundRect方法,而是像makeSrc方法里那样产生一张绘制有形状的Bitmap,否则也可能会失败。

效果图


后记

如有问题,可以在评论中追问。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值