圆形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》。
- 圆形ImageView系列(一)-----Xfermode+View
- 圆形ImageView系列(二)-----Xfermode+ImageView
- 自定义View 之 ImageView(一) 自定义圆形ImageView
- android自定义view-打造圆形ImageView(一)
- android自定义view-打造圆形ImageView(一)
- Android圆形ImageView(一)
- (转)【ImageView】自定义ImageView系列(一)——简单圆形图片
- 自定义View--圆形ImageView
- Android Xfermode 实战 实现圆形、圆角图片(重写ImageView)
- 自定义圆形ImageView(一)
- Android 完美实现图片圆角和圆形(Xfermode,重写View)
- 自定义view之圆形ImageView
- android绘图处理系列(一):bitmapShader和Xfermode
- Android 圆形图片 CircleImageView(Xfermode方式)
- Android 绘图进阶(一):PorterDuff与Xfermode(画笔风格)仿QQ圆形头像
- 圆形imageview(RoundedImageView)
- android自定义圆形头像view,继承imageview
- android自定义view-打造圆形ImageView(二)
- 【总结】深插浅出重拾Linux
- 为已有的web project项目加入maven支持,并使用myeclipse的插件.
- sp<> wp<>-Android中定义了两种智能指针强指针sp和弱指针wp
- RUST语法
- hdu3342 Legal or Not(简单拓扑排序判定)
- 圆形ImageView系列(一)-----Xfermode+View
- LightOJ--1149--Factors and Multiples(二分图好题)
- Android自定义属性限制
- ActionInvoker
- 如何在Java中对MongoDB按日期进行查询统计
- 〖原创〗使用Docker过程中注意事项之壹(文章末尾有彩蛋!)
- 【转】Android LockScreen admin API sample code
- iOS开发中@property的属性weak nonatomic strong readonly等介绍
- zookeeper学习-2(如何使用zookeeper进行配置管理-java版本)