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

来源:互联网 发布:螺纹钢月线数据 编辑:程序博客网 时间:2024/06/14 18:19

有些时候,棱角分明的矩形可能不满足我们的需求。我们希望图片是圆角,显得图片更加圆滑,例如手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);// 先画DSTcanvas.drawBitmap(bm, 0, 0, mPaint);// 设置XfermodemPaint.setXfermode(xfermode);// 再画SRCcanvas.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,否则也可能会失败。

效果图


后记

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

  • android官方文档:http://www.androidcommunitydocs.com/reference/android/graphics/Xfermode.html
  • android官方例子 sdk-samples-android17-ApiDemo-graphics-Xfermode类
  • 鸿洋前辈的教程:http://blog.csdn.net/lmj623565791/article/details/42094215
  • 源码地址:http://download.csdn.net/detail/u012933743/8803383
  • github上使用Xfermode绘制各种形状的开源项目:https://github.com/MostafaGazar/CustomShapeImageView
0 0