三:自定义view,绘制圆形图片

1、BitmapShader

可以通过为paint指定一个渐变(BitmapShader,Paint.setShader(shader)),以图片填充BitmapShader,就可以得到如下圆形图片。

使用BitmapShader绘制圆形
1、渐变模式

BitmapShader,位图渐变,是将图片作为背景,绘制到指定的位图中,如果图片比为图小,则以以下模式进行填充:

  • TileMode.CLAMP :不平铺,即AABB型
  • TileMode.REPEAT :平铺,即ABABA型
  • TileMode.MIRROR:镜像平铺,即ABBA型

效果如下

位图渐变下的CLAMP
位图渐变下的REPEAT
位图渐变下的MIRROR
线性渐变下的CLAMP 、MIRROR、REPEAT
2、Matrix

可以使用 shader.setLocalMatrix(localM),使Matrix 和渐变结合,实现 位移、旋转、缩放、拉斜的渐变效果,如下可以缩放被填充的图片,移动其中心点到位图的中心点。

主要代码如下


        BitmapShader shader=new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

         //计算缩放比例,缩放被填充的图片,移动其中心点到位图的中心点
        Matrix localM=new Matrix();
        int bitmapWidth=bitmap.getWidth();
        int bitmapHeight=bitmap.getHeight();

        float scaleX=width/(bitmapWidth*1.0f);
        float scaleY=height/(bitmapHeight*1.0f);
        float scale=Math.max(scaleX,scaleY);
        localM.setScale(scale,scale);

        if(scaleX<scaleY){
            localM.postTranslate((bitmapWidth*scaleY-bitmapWidth*scaleX)/2,0);
        }else {
            localM.postTranslate(0,(bitmapHeight*scaleX-bitmapHeight*scaleY)/2);
        }
        
        //缩放、平移渐变
        shader.setLocalMatrix(localM);
        //为paint指定一个渐变
        paint.setShader(shader);

        canvas.drawCircle(centerX,centerY,radius,paint);

当然除了圆形,也可以绘制其他任意形状

        path.moveTo(0,height/3);
        path.lineTo(width,height/3);
        path.lineTo(width/4,height);
        path.lineTo(width/2,0);
        path.lineTo(3*width/4,height);
        path.close();
        canvas.drawPath(path,paint);
使用BitmapShader绘制

2、xfermode

可以先在画布上画图片,在将xfermode设为DST_IN,在画圆,如下:

xfermode,DST_IN

代码如下

        xfermode=new PorterDuffXfermode(PorterDuff.Mode.DST_IN);

        ...

        int width=getMeasuredWidth();
        int height=getMeasuredHeight();
        int radius=Math.min(width/2,height/2);
        int centerX=width/2;
        int centerY=height/2;

        desbitmap=Bitmap.createScaledBitmap(bitmap,width,height,true);

        circleBitmap=Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);
        Canvas canvas=new Canvas(circleBitmap);
        canvas.drawCircle(centerX,centerY,radius,paint);


        int layer=canvas.saveLayer(0,0,getMeasuredWidth(),getMeasuredHeight(),null,Canvas.ALL_SAVE_FLAG);
        paint.setXfermode(xfermode);
        canvas.drawBitmap(desbitmap,0,0,null);
        canvas.drawBitmap(circleBitmap,0,0,paint);
        paint.setXfermode(null);
        canvas.restoreToCount(layer);


当然,也可以是其他形状

panda

1、 关于xfermode

PorterDuffXfermode,可以将所绘制的图形的像素与Canvas中对应位置的像素按照一定规则进行混合,形成新的像素值。当使用PorterDuffXfermode时,需要将将其作为参数传给Paint.setXfermode(Xfermode xfermode)方法,这样在用该画笔paint进行绘图时,Android就会使用传入的PorterDuffXfermode,如果不想再使用Xfermode,那么可以执行Paint.setXfermode(null)。

一般我们在调用canvas.drawXXX()方法时都会传入一个画笔Paint对象,Android在绘图时会先检查该画笔Paint对象有没有设置Xfermode,如果没有设置Xfermode,那么直接将绘制的图形覆盖Canvas对应位置原有的像素;如果设置了Xfermode,那么会按照Xfermode具体的规则来更新Canvas中对应位置的像素颜色。就本例来说,在执行canvas.drawCirlce()方法时,画笔Paint没有设置Xfermode对象,所以绘制的黄色圆形直接覆盖了Canvas上的像素。当我们调用canvas.drawRect()绘制矩形时,画笔Paint已经设置Xfermode的值为PorterDuff.Mode.CLEAR,此时Android首先是在内存中绘制了这么一个矩形,所绘制的图形中的像素称作源像素(source,简称src),所绘制的矩形在Canvas中对应位置的矩形内的像素称作目标像素(destination,简称dst)。源像素的ARGB四个分量会和Canvas上同一位置处的目标像素的ARGB四个分量按照Xfermode定义的规则进行计算,形成最终的ARGB值,然后用该最终的ARGB值更新目标像素的ARGB值。
本例中的Xfermode是PorterDuff.Mode.CLEAR,该规则比较简单粗暴,直接要求目标像素的ARGB四个分量全置为0,即(0,0,0,0),即透明色,所以我们通过canvas.drawRect()在Canvas上绘制了一个透明的矩形,由于Activity本身屏幕的背景时白色的,所以此处就显示了一个白色的矩形。


PorterDuff.Mode.CLEAR,没有使用layer

PorterDuffXfermode支持以下十几种像素颜色的混合模式,分别为:CLEAR、SRC、DST、SRC_OVER、DST_OVER、SRC_IN、DST_IN、SRC_OUT、DST_OUT、SRC_ATOP、DST_ATOP、XOR、DARKEN、LIGHTEN、MULTIPLY、SCREEN。

我们知道一个像素的颜色由四个分量组成,即ARGB,第一个分量A表示的是Alpha值,后面三个分量RGB表示了颜色。我们用S代表源像素,源像素的颜色值可表示为[Sa, Sc],Sa中的a是alpha的缩写,Sa表示源像素的Alpha值,Sc中的c是颜色color的缩写,Sc表示源像素的RGB。我们用D代表目标像素,目标像素的颜色值可表示为[Da, Dc],Da表示目标像素的Alpha值,Dc表示目标像素的RGB。

源像素与目标像素在不同混合模式下计算颜色的规则如下所示:

SRC:[Sa, Sc],显示上层位图
1、SRC

2、SRC
DST:[Da, Dc],只显示下层位图
1、DST
SRC_OVER:[Sa + (1 - Sa)Da, Rc = Sc + (1 - Sa)Dc],上下层都显示,运算后上层显示在上面
1、SRC_OVER
DST_OVER:[Sa + (1 - Sa)Da, Rc = Dc + (1 - Da)Sc],上下层都显示,运算后下层显示在上面
1、DST
SRC_IN:[Sa * Da, Sc ** Da],取两层交集部分,内容取决于上层
1、SRC
DST_IN:[Sa * Da, Sa ** Dc],取两层交集部分,内容取决于下层
1、DST_IN

2、DST_IN
SRC_OUT:[Sa * (1 - Da), Sc (1 - Da)],取上层非交集部分
SRC_OUT,这里,Da * (1 - Sa),透明度始终是0
DST_OUT:[Da * (1 - Sa), Dc (1 - Sa)],取下层非交集部分
DST_OUT
SRC_ATOP:[Da, Sc * Da + (1 - Sa) Dc]
SRC_ATOP
DST_ATOP:[Sa, Sa * Dc + Sc (1 - Da)]
DST_ATOP
XOR:[Sa + Da - 2 Sa * Da, Sc (1 - Da) + (1 - Sa) Dc]
XOR

。。。

xfermode

注:xfermode,计算的是重叠部分的最终像素
1、des(图片)、src(空心圆)和最终位图大小相等,src是空心圆
2、des(黄圆)、src(蓝,矩形)和最终位图大小相等

参考:Android中Canvas绘图之PorterDuffXfermode使用及工作原理详解

2、关于 Canvas c=new Canvas(mybitmap):

        ...
        Bitmap mybitmap=BitmapFactory.decodeResource(context.getResources(),R.drawable.test3);
        boolean isMutable=mybitmap.isMutable();
        Canvas c=new Canvas(mybitmap);
        ...

如果这样设置,会报错:

 Immutable bitmap passed to Canvas constructor

就是说如果bitmap不可改变的情况下,canvas是不允许进行绘制的, 当你用BitmapFactory.decodeResource,返回的bitmap是默认状态下的mIsMutable=false。而使用Bitmap.createBitmap()返回的是 true。

        ...
        Bitmap b=BitmapFactory.decodeResource(context.getResources(),R.drawable.test3,options);
        mybitmap=Bitmap.createScaledBitmap(b,b.getWidth(),b.getHeight(),true);
        boolean isMutable=mybitmap.isMutable();
        Canvas c=new Canvas(mybitmap);
        ...

这种情况下mybitmap依然是b,因为大小一样,就返回了相同的bitmap。

    /**
     * Creates a new bitmap, scaled from an existing bitmap, when possible. If the
     * specified width and height are the same as the current width and height of
     * the source bitmap, the source bitmap is returned and no new bitmap is
     * created.
     */
    public static Bitmap createScaledBitmap(Bitmap src, int dstWidth, int dstHeight,
            boolean filter) {
        ...
        Bitmap b = Bitmap.createBitmap(src, 0, 0, width, height, m, filter);
        ...
        return b;
    }

参考: 关于new Canvas(Bitmap)中Bitmap的isMutable的要求PorterDuffXferMode不正确的真正原因PorterDuffXferMode深入试验) 在android中画圆形图片的几种办法自定义控件三部曲之绘图篇(十)——Paint之setXfermode(一)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容