圆形ImageView系列(一)-----Xfermode+View

来源:互联网 发布:linux 如何安装xampp 编辑:程序博客网 时间:2024/09/21 13:19

前言

看标题就知道,这是一个系列,因为实现圆形ImageView的方法有很多种,所以接下来准备将这几种方法都实现一遍,最后总结对比下各种方法。

虽然网上已经有很多现成的分析文章以及源代码,但是毕竟是别人总结的,还是没有自己实践来的真实。

纸上得来终觉浅,绝知此事要躬行哈。

下面就开始来说最简单常用的一种方法:自定义View+设置Xfermode的方式。先给大家看效果图:



提莫萌萌哒~

主要原理

关于Xfermode相关的知识,这里不多做介绍,只稍微提一下。会用ps的同学应该都用过选框工具,选框工具也有不同的模式,分别是:

  • 新选区
  • 添加到选区
  • 从选区减去
  • 与选区交叉

总之就是各种取交集、并集的过程。Xfermode的各种不同模式思想上跟这个有点类似,不过比ps的选区强大得多,大家看图就明白了:



可以看到Xfermode的取值有很多种,不同的取值可以达到不同的效果,类似于一些刮刮卡以及撕美女裙子等的游戏应该都可以利用设置Xfermode的不同值来实现。

16条Porter-Duff规则

对于上图中的不同取值,网上有人总结出来了下面16条规律:

  • 1.PorterDuff.Mode.CLEAR
    所绘制不会提交到画布上。

  • 2.PorterDuff.Mode.SRC
    显示上层绘制图片

  • 3.PorterDuff.Mode.DST
    显示下层绘制图片

  • 4.PorterDuff.Mode.SRC_OVER
    正常绘制显示,上下层绘制叠盖。

  • 5.PorterDuff.Mode.DST_OVER
    上下层都显示。下层居上显示。

  • 6.PorterDuff.Mode.SRC_IN
    取两层绘制交集。显示上层。

  • 7.PorterDuff.Mode.DST_IN
    取两层绘制交集。显示下层。

  • 8.PorterDuff.Mode.SRC_OUT
    取上层绘制非交集部分。

  • 9.PorterDuff.Mode.DST_OUT
    取下层绘制非交集部分。

  • 10.PorterDuff.Mode.SRC_ATOP
    取下层非交集部分与上层交集部分

  • 11.PorterDuff.Mode.DST_ATOP
    取上层非交集部分与下层交集部分

  • 12.PorterDuff.Mode.XOR

  • 13.PorterDuff.Mode.DARKEN

  • 14.PorterDuff.Mode.LIGHTEN

  • 15.PorterDuff.Mode.MULTIPLY

  • 16.PorterDuff.Mode.SCREEN

有的同学对于上面的src和dest可能分不清楚,究竟先画的是src还是dest呢?分析google给的android apidemo里面graphics/xfermode里面的onDraw方法可以看到

        @Override        protected void onDraw(Canvas canvas) {            canvas.drawColor(Color.WHITE);            Paint labelP = new Paint(Paint.ANTI_ALIAS_FLAG);            labelP.setTextAlign(Paint.Align.CENTER);            Paint paint = new Paint();            paint.setFilterBitmap(false);            canvas.translate(15, 35);            int x = 0;            int y = 0;            for (int i = 0; i < sModes.length; i++) {                // draw the border                paint.setStyle(Paint.Style.STROKE);                paint.setShader(null);                canvas.drawRect(x - 0.5f, y - 0.5f,                                x + W + 0.5f, y + H + 0.5f, paint);                // draw the checker-board pattern                paint.setStyle(Paint.Style.FILL);                paint.setShader(mBG);                canvas.drawRect(x, y, x + W, y + H, paint);                // draw the src/dst example into our offscreen bitmap                int sc = canvas.saveLayer(x, y, x + W, y + H, null,                                          Canvas.MATRIX_SAVE_FLAG |                                          Canvas.CLIP_SAVE_FLAG |                                          Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |                                          Canvas.FULL_COLOR_LAYER_SAVE_FLAG |                                          Canvas.CLIP_TO_LAYER_SAVE_FLAG);                canvas.translate(x, y);                canvas.drawBitmap(mDstB, 0, 0, paint);                paint.setXfermode(sModes[i]);                canvas.drawBitmap(mSrcB, 0, 0, paint);                paint.setXfermode(null);                canvas.restoreToCount(sc);                // draw the label                canvas.drawText(sLabels[i],                                x + W/2, y - labelP.getTextSize()/2, labelP);                x += W + 10;                // wrap around when we've drawn enough for one row                if ((i % ROW_MAX) == ROW_MAX - 1) {                    x = 0;                    y += H + 30;                }            }        }

这段代码完成的效果也就是上面那个xfermode效果对照表所展示的效果了,可以看到onDraw里面的第35~38行中,可以非常清楚的知道:

  • 先画mDstB

  • 然后设置xfermode

  • 最后画mSrc

也就是先画的图是作为dest画在canvas 的下层的,后画的图是作为src画在canvas的上层的。

对于我们这个圆形的ImageView的话,主要是使用srcIn的模式即可:在canvas下层画一个圆,然后将图片画在圆上,取两者的交集并且显示上层,就可以达到圆形图片的效果了。

方法如下:

  • 在canvas上画一个圆;
  • 调用paint.setXfermode设置为srcIn模式;
  • 将要显示的图作为Src画在上面的那个圆上;
  • 两次绘图之后叠加起来的效果就是圆形的图片了;

那么能不能先画图片然后画圆,将xfermode设置成destIn呢?试了一下发现不行,这是为什么呢?(用官方的demo调换绘制顺序并且改成destin是可以实现srcin的效果的)

主要原理已经介绍完了,当然除了设置Xfermode的值,还需要考虑其他一些小细节,比如图片和圆的相对位置、图片和view的相对大小等,纯粹就是计算了,也很简单。

具体实现

先贴代码吧。

先在attr.xml中声明两个自定义属性,以支持设置默认图片和初始图片,比如从网络获取一张图片时你可能需要一张默认图片显示,就可以设置默认图片了,代码如下

<?xml version="1.0" encoding="utf-8"?><resources>    <attr name="image" format="reference" />    <attr name="defaultImage" format="reference" />    <declare-styleable name="CircleImageView">        <attr name="image" />        <attr name="defaultImage" />    </declare-styleable></resources>

然后在布局文件中声明xml命名空间,如下

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

接下来就可以使用自定义属性了

<com.passerby.androidadvanced.circleimage.xfermode.CircleImageView        android:layout_width="200dp"        android:layout_height="200dp"        circleImage:defaultImage="@drawable/image"/>

CircleImageView源码如下:

package com.passerby.androidadvanced.circleimage.xfermode;import com.passerby.circleimage.R;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.Paint;import android.graphics.PorterDuff;import android.graphics.PorterDuffXfermode;import android.graphics.Rect;import android.util.AttributeSet;import android.view.View;/** * Created by mac on 16/2/1. */public class CircleImageView extends View {    private Paint mPaint;    private Bitmap mSrcBitmap;    private Bitmap mDefaultBitmap;    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);        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyleAttr, 0);        int count = a.getIndexCount();        for (int i = 0; i < count; i++) {            int resId;            int index = a.getIndex(i);            switch (index) {                case R.styleable.CircleImageView_defaultImage:                    resId = a.getResourceId(index, 0);                    mDefaultBitmap = BitmapFactory.decodeResource(getResources(), resId);                    break;                case R.styleable.CircleImageView_image:                    resId = a.getResourceId(index, 0);                    mSrcBitmap = BitmapFactory.decodeResource(getResources(), resId);                    break;            }        }        a.recycle();        if (null == mSrcBitmap) {            mSrcBitmap = mDefaultBitmap;        }        mPaint = new Paint();        mPaint.setAntiAlias(true);    }    @Override    protected void onDraw(Canvas canvas) {        int width = getMeasuredWidth();        int height = getMeasuredHeight();        //取宽、高中的较小值        int min = width > height ? height : width;        if (null != mSrcBitmap) {            canvas.drawBitmap(createCircleBitmap(mSrcBitmap, min), 0, 0, mPaint);           }    }    /***     * 将传入的bitmap转换成圆的正方形图片     *     * @param bitmap     * @param min    目标bitmap的高     * @return     */    private Bitmap createCircleBitmap(Bitmap bitmap, int min) {        Paint paint = new Paint();        paint.setAntiAlias(true);        final int tWidth = getMeasuredWidth();        final int tHeight = getMeasuredHeight();        //创建一个新的bitmap,大小跟imageview一样        Bitmap target = Bitmap.createBitmap(tWidth, tHeight, Bitmap.Config.ARGB_8888);                //下面所有的绘图操作均在这个target上面完成        Canvas canvas = new Canvas(target);        //1.画圆形        //计算圆的圆心在imageview中的坐标        int halfWidth = tWidth >> 1;        int halfHeight = tHeight >> 1;        //得到圆的半径        int radius = min;        //画圆        canvas.drawCircle(halfWidth, halfHeight, radius >> 1, paint);        //2.设置Xfermode        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));        //3.画图片        //将图片画在imageview居中的位置        Rect srcRect = new Rect(0, 0, mSrcBitmap.getWidth(), mSrcBitmap.getHeight());        int offsetX = (tWidth - min) >> 1;        int offsetY = (tHeight - min) >> 1;        Rect dstRect = new Rect(offsetX, offsetY, offsetX + min, offsetY + min);        canvas.drawBitmap(bitmap, srcRect, dstRect, paint);        return target;    }    public void setImage(int resId) {        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), resId);        setImage(bitmap);    }    public void setImage(Bitmap bitmap) {        mSrcBitmap = bitmap;        invalidate();    }}

代码第37~53行是获取属性,得到用户指定的默认图片和直接显示的图片。

55~58行,如果用户没有指定直接显示的图片,就使用默认图片。

67~70行,获取控件宽和高中的较小值min,因为要显示圆形图片,所以要以min作为那个圆的直径。

71~74行,画圆形图片。createCircleBitmap()方法是核心,主要注释都写好了,就不多提了。

对于上面代码实现的思路,是直接继承View然后将图片弄成圆形画出来,这种方法适合只需要圆形图片的需求,像用户头像之类的。

当然也可以继承ImageView然后在上面盖一个遮罩层,也可以实现圆形ImageView的效果,而且可以同时保留ImageView相关的很多属性。

第二种方法请看这里《 圆形ImageView系列(二)—–Xfermode+ImageView》。

0 0
原创粉丝点击