Android之圆形头像裁切
PS:今天项目测试组发现,百度地图定位的数据坐标位置是正确的,但是显示的数据是错误的.最后查来查去发现,那个商厦在百度地图上根本就没有那条数据,这让我如何显示,当初就推崇使用高德地图定位,上面的数据量比较的完整,而且定位的也比较的精准,非得用百度地图定位,这下定位不到数据,懵逼了吧..
学习内容:
1.自定义View+Canvas+XferMode实现圆形头像裁切
头像裁切现如今在很多应用中都会得到使用,一般都是在个人资料页面设置头像,然后选择图片,或者是直接开启相机拍摄一张图片,通过裁切和缩放的手段最后显示在ImageView上就可以了.不过无论怎样裁切其实最后裁切出来仍然是一个方形的图片,因此我们需要自定义ImageView,将ImageView定义成我们想要的形状,然后将裁切到的图片显示到ImageView上就可以了.这里的ImageView我是使用的第三方框架,因为自己还没有打算自定义ImageView这块.因此就直接用了别人的东西.
i.Canvas的saveLayer(),restore()方法.
实现头像裁切需要使用几种技术,首先就需要Canvas的支持,首先说一下他的结构组成,这样更加方便理解.
Canvas的结构基本是这样的,在View绘制到屏幕上的时候在OnDraw()方法在调用的时候,所有的控件就会通过Paint绘制到Canvas上.其实就是画到画布上,默认情况下,我们可以把Canvas也看作成一个Layer,当我们在saveLayer()的时候,就表示我们开启一个新的图层,所有绘制的内容都会在当前图层完成,不会影响到前一张图层,相当于图层的覆盖,当我们调用restore()方法的时候,那么当前图层出栈,将所有的内容绘制到被覆盖的图层.简单的说说saveLayer()方法如何使用.
这里我们在Canvas上先画了一个红色的圆圈,然后又入栈一个带有透明度的Layer,在当前这个Layer画一个蓝色的圆圈.
package com.example.totem.canvastest.activity; import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.view.View; public class LayersActivity extends Activity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new SimpleView(this));
} private static class SimpleView extends View {
private static final int LAYER_FLAGS = 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; private Paint mPaint = new Paint(); public SimpleView(Context context) {
super(context);
} @Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
canvas.translate(10, 10);
mPaint.setColor(Color.RED);
canvas.drawCircle(75, 75, 75, mPaint);
/**
* 入栈一个Layer,当前现在有两个Layer,因为Canvas其实也可以看成是一个Layer
* 这里入栈了一个带有透明度的Layer
* */
canvas.saveLayerAlpha(0, 0, 200, 200, 0x88, LAYER_FLAGS);
mPaint.setColor(Color.BLUE);
canvas.drawCircle(125, 125, 75, mPaint);
canvas.restore();
}
}
}
然后我们调用restore()方法,那么画在透明层的蓝色圆圈就需要被重新绘制到当前的Canvas层上,因此可以看到,红色圆圈和蓝色圆圈叠加的状态,并且中间是有透明度的.如果比入栈一个新的图层,会出现明显的效果差异.大家可以将这句话注释掉运行一下看看效果.
因为在头像裁切的时候我们需要使用多层画布结合XferMode实现复杂图形,因此先在这里简单的介绍一下,以免在后续看到这块代码不知道具体是要做什么用的.
ii.Xfermode
Xfermode也是实现这个效果的一个核心,它是实现图形混合的一种模式,由Tomas Proter和 Tom Duff提出的概念,Xfermode只是一个基类,具有三个子类,分别是AvoidXfermode,PixelorXfermode,PorterDuffXfermode.不过前两个在api16以后就已经弃用了,因此前面这两个我也没打算说,主要还是说一下PorterDuffXfermode这种模式.
PorterDuffXfermode一共有18种图形混排模式.那么就来介绍一下这18种模式,以及这18中模式的出现所导致的效果.
PorterDuff.Mode |
模式+说明 | 计算方式 |
PorterDuff.Mode |
ADD(饱和相加) | Saturate(S + D) |
PorterDuff.Mode |
CLEAR(清除图像) | [0,0] |
PorterDuff.Mode |
DARKEN(变暗) | [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)]; |
PorterDuff.Mode |
DST(只绘制目标图像) | [Da, Dc] |
PorterDuff.Mode |
DST_ATOP(显示原图像非交集部分与目标图像交集部分) | [Sa, Sa * Dc + Sc * (1 - Da)] |
PorterDuff.Mode |
DST_IN(显示原图像与目标图像相交的目标图像部分) | [Sa * Da, Sa * Dc] |
PorterDuff.Mode |
DST_OVER(原图像目标图像均显示,目标图像在上层) | [Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc] |
PorterDuff.Mode |
LIGHTEN(变亮) | [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] |
PorterDuff.Mode |
MULTIPLY(显示原图像与目标图像交集部分叠加后的颜色) | [Sa * Da, Sc * Dc] |
PorterDuff.Mode |
OVERLAY(叠加) | 未知 |
PorterDuff.Mode |
SCREEN(取原图像和目标图像的全部区域,交集部分为透明色) | [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] |
PorterDuff.Mode |
SRC(只显示原图像) | [Sa, Sc] |
PorterDuff.Mode |
SRC_ATOP(显示原图像交集部分和目标图像的非交集部分) | [Da, Sc * Da + (1 - Sa) * Dc] |
PorterDuff.Mode |
SRC_IN(显示原图像与目标图像交集部分的原图像) | [Sa * Da, Sc * Da] |
PorterDuff.Mode |
SRC_OUT(显示原图像与目标图像非交集部分的目标图像) | [Sa * (1 - Da), Sc * (1 - Da)] |
PorterDuff.Mode |
SRC_OVER(在目标图像的顶部绘制源图像) | [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] |
PorterDuff.Mode |
XOR(去除两图层交集部分) | [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] |
PorterDuff.Mode |
DST_OUT(只在源图像和目标图像相交的地方绘制源图像) | [Da * (1-Sa), Dc * (1-Sa)] |
这就是18种PorterDuffMode的18种情况,相关的具体样式我就不在这里贴出来了.
public class XfermodeView extends View { /**
* 18种图形混合模式
*/
private static final PorterDuff.Mode PorterDuffMode[] = {PorterDuff.Mode.ADD, PorterDuff.Mode.CLEAR, PorterDuff.Mode.DARKEN,
PorterDuff.Mode.DST, PorterDuff.Mode.DST_ATOP, PorterDuff.Mode.DST_IN, PorterDuff.Mode.DST_OUT, PorterDuff.Mode.DST_OVER,
PorterDuff.Mode.LIGHTEN, PorterDuff.Mode.MULTIPLY, PorterDuff.Mode.OVERLAY, PorterDuff.Mode.SCREEN, PorterDuff.Mode.SRC,
PorterDuff.Mode.SRC_ATOP, PorterDuff.Mode.SRC_IN, PorterDuff.Mode.SRC_OUT, PorterDuff.Mode.SRC_OVER, PorterDuff.Mode.XOR}; private PorterDuffXfermode porterDuffXfermode; private int mode; private int defaultMode = 0; private static final int Layers = 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; /**
* 屏幕宽高
*/
private int screenW;
private int screenH;
private Bitmap srcBitmap;
private Bitmap dstBitmap; /**
* 源图和目标图宽高
*/
private int width = 120;
private int height = 120; public XfermodeView(Context context) {
super(context); } public XfermodeView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.XfermodeView);
mode = typedArray.getInt(R.styleable.XfermodeView_ModeNum, defaultMode);
screenW = ScreenUtil.getScreenW(context);
screenH = ScreenUtil.getScreenH(context);
porterDuffXfermode = new PorterDuffXfermode(PorterDuffMode[mode]);
srcBitmap = makeSrc(width, height);
dstBitmap = makeDst(width, height);
} @Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setFilterBitmap(false);
paint.setStyle(Paint.Style.FILL);
/**
* 绘制蓝色矩形+黄色圆形
* */
canvas.drawBitmap(srcBitmap, screenW / 8 - width / 4, screenH / 12 - height / 4, paint);
canvas.drawBitmap(dstBitmap, screenW / 2, screenH / 12, paint); /**
* 创建一个图层,在图层上演示图形混合后的效果
* */
canvas.saveLayer(0, 0, screenW, screenH, null, Layers); /**
* 绘制在设置了XferMode后混合后的图形
* */
canvas.drawBitmap(dstBitmap, screenW / 4, screenH / 3, paint);
paint.setXfermode(porterDuffXfermode);
canvas.drawBitmap(srcBitmap, screenW / 4, screenH / 3, paint);
paint.setXfermode(null);
// 还原画布
canvas.restore();
}
}
这段代码针对了18种不同模式显示的样式,原图像是一个蓝色的正方形,目标图像是一个黄色的圆形.然后我们另起了一个图层saveLayer(),将这18种模式出现的情况画在这个新的画布上.这里的代码并不是完全的,最后我会给出这个代码的地址,方便大家理解.
iii.自定义ClipView
简单的介绍了一下Canvas和Xfermode,我们就可以使用自定义View,然后结合这二者实现头像的裁切效果.简单的说一下原理.
上面这个图是实现头像裁切的原理,我们在Layer层放置一个ImageView,然后入栈一个图层,将ClipView画在Layer1上,然后使用Xfermode中的 DST_OUT 模式,这样取二者的相交部分,也就是ClipView这个圆圈与底部的ImageView的交集部分,并且显示ImageView部分.其他的地方就变成透明的了.
就像上面这张图一样,显示的地方是二者的交集部分,裁剪框+底部的ImageView的共同部分,然后其他的地方都是透明的.这样我们就可以只获取二者交集部分的图像.那么具体如何实现这里,需要我们去自定义View实现.在上层的Layer需要自定义一个ClipView.这个View相对而言还是非常简单的.只需要在新的图层上用Paint画一个圆圈和圆边框就行了.然后设置Xfermode就可以轻松的实现.
public ClipView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr); /**
* 去掉锯齿
* */
mPaint.setAntiAlias(true);
borderPaint.setStyle(Paint.Style.STROKE);
borderPaint.setColor(Color.WHITE);
borderPaint.setStrokeWidth(clipBorderWidth);
borderPaint.setAntiAlias(true);
xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);
} @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
width = this.getWidth();
height = this.getHeight(); /**
* 另启一个图层,以后所有的绘制操作都在此图层上完成.
* 不另外开启一个图层的话,Canvas在被掏空之后,背景色就不是透明的,而是黑的
* */
canvas.saveLayer(0, 0, width, height, null, LAYER_FLAGS);
canvas.drawColor(Color.parseColor("#a8000000"));
mPaint.setXfermode(xfermode);
/**
* 在画布上画透明的圆
* */
canvas.drawCircle(width / 2, height / 2, width * radiusWidthRatio, mPaint);
/**
* 圆边框
* */
canvas.drawCircle(width / 2, height / 2, width * radiusWidthRatio + clipBorderWidth, borderPaint);
/**
* 出栈,恢复到之前的图层,意味着新建的图层会被删除,新建图层上的内容会被绘制到canvas
* */
canvas.restore();
}
这里就不贴全部代码了,直接把核心代码粘贴出来就够了.代码和上面所说的思想基本是一致的.并且还有相关的注释,我就不多做解释了.这样我们也是仅仅实现了在新的Layer上画了这样一个圆圈.那么如何实现缩放和平移图片,然后获取到图片这才是比较重要的一个部分.
既然要实现缩放和平移,那么必须要重写手势事件.这基本是习以为常的事情了.先贴代码,然后再说其中的道理.
@Override
public boolean onTouch(View v, MotionEvent event) { ImageView view = (ImageView) v;
switch (event.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_POINTER_DOWN:
oldDist = spacing(event);
/**
* 如果间距大于10f,表示以后再MOVE的时候要进行缩放操作,而不是平移操作
* */
if (oldDist > 10f) {
saveMatrix.set(matrix);
midPoint(midPoint, event);
mode = ZOOM;
}
break;
case MotionEvent.ACTION_DOWN:
/**
* 单指触发按下事件
* */
saveMatrix.set(matrix);
startPoint.set(event.getX(), event.getY());
mode = DRAG;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
break;
case MotionEvent.ACTION_MOVE:
/**
* 拖动
* */
if (mode == DRAG) {
matrix.set(saveMatrix);
matrix.postTranslate(event.getX() - startPoint.x, event.getY() - startPoint.y);
} else if (mode == ZOOM) {
float newDist = spacing(event);
/**
* 如果两指拉开的间距大于10f那么就表示需要缩放
* */
if (newDist > 10f) {
matrix.set(saveMatrix);
float scale = newDist / oldDist;
matrix.postScale(scale, scale, midPoint.x, midPoint.y);
}
}
break;
}
/**
* 每次操作结束后都需要设置matrix
* */
view.setImageMatrix(matrix);
return true;
}
这里不难发现重写的事件要比以前多一些,因为这里不仅仅涉及到我们单指按下,单指按下屏幕一般就是平移图片,那么缩放的时候需要多指按下图片,通过拖动的方式实现缩放功能.
这里定义了三个标记位,一个是NONE表示没有进行任何的操作,DRAG则表示拖动操作,ZOOM表示缩放操作。
当我们单指按下的时候,首先需要记录下当前按下的坐标,然后改变标志位,因为单指按下一般后续就是DRAG操作,不可能发生ZOOM操作,因此在ACTION_DOWN之后需要改变标志位为DRAG如果我们后续进行了平移操作,也就是ACTION_MOVE 那么就会进行相关的处理,他会根据DRAG或ZOOM操作执行不同的逻辑,如果是DRAG,那么我们只需要根据移动后的坐标和起始按下的坐标对view进行平移操作就可以了,这个操作由matrix来决定.
当我们两个手指同时按压到屏幕上的时候,这里做了一个简单的判断,就是两指之间的距离,如果距离小于10f,那么就还是表示要执行平移操作,否则执行缩放操作,那么当需要执行缩放操作的时候首先需要记录两指按下的中心点坐标,然后根据初始两指之间的距离和移动后两指之间的距离做除法运算,就可以计算出我们具体要缩放多少,缩放就是通过根据最开始的中心点以及matrix的配合实现缩放效果.最后基本就是获取图片随机生成一个uri返回就可以了.
需要注意一点就是图片在放置到ImageView上的时候我们是需要对图片进行加工的,因为我们现在手机内部的图片已经不仅仅是720*1280那么简单了现在手机拍摄出来的图片像素一般是4000+*3000+的,这个取决于我们相机的像素,和屏幕的分辨率是没有什么关系的,因此在筛选完图片之后就需要对图片进行相关的处理.因此我为ClipView注册了一个视图树监听,也就是说当ClipView监听到整个视图树状态发生了相关的变化,那么就表示图片需要显示在ImageView上了,这时我们就需要对图片进行加工处理.每一个Layout都构成一个视图树,其实我感觉它和DOM树结构差不多,都是按层级划分的.还有注册完之后,触发的同时需要remove掉,否则会多次调用.
ViewTreeObserver observer = clipView.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
clipView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
initSrc();
}
});
这样就通过Canvas+Xfermode+自定义ClipView实现了头像的裁切.裁切出来是个矩形的,只需要显示在圆形的ImageView上就可以了.这里圆形的头像大家也可以选择其他的类库,或者是自己自定义.我这里就不多说了,我是使用的第三方.最后贴一下源代码方便大家的理解.
Canvas:http://pan.baidu.com/s/1mhSnkPM
XferMode:http://pan.baidu.com/s/1dES108T
圆形头像裁切:http://pan.baidu.com/s/1nvo5ORR
Android之圆形头像裁切的更多相关文章
- Android ImageView圆形头像
转载自:http://m.oschina.net/blog/321024 Android ImageView圆形头像 图片完全解析 我们在做项目的时候会用到圆形的图片,比如用户头像,类似QQ.用户在用 ...
- 【转】Android ImageView圆形头像
Android ImageView圆形头像 图片完全解析 我们在做项目的时候会用到圆形的图片,比如用户头像,类似QQ.用户在用QQ更换头像的时候,上传的图片都是矩形的,但显示的时候确是圆形的. 原理: ...
- Android 圆形头像 自己动手
圆形头像DIY 现在大部分app使用的都是圆形头像,网上开源的也很多,但是有没有考虑过DIY圆形头像呢?下面就自己实现一个,先看下demo展示 第一步:原理解释(图片很丑,原理很真) 1.画外框圆形, ...
- Android控件Gridview实现仿支付宝首页,Fragment底部按钮切换和登录圆形头像
此案例主要讲的是Android控件Gridview(九宫格)完美实现仿支付宝首页,包含添加和删除功能:Fragment底部按钮切换的效果,包含四个模块,登录页面圆形头像等,一个小项目的初始布局. 效果 ...
- Android特效专辑(五)——自定义圆形头像和仿MIUI卸载动画—粒子爆炸
Android特效专辑(五)--自定义圆形头像和仿MIUI卸载动画-粒子爆炸 好的,各位亲爱的朋友,今天讲的特效还是比较炫的,首先,我们会讲一个自定义圆形的imageView,接着,我们会来实现粒子爆 ...
- Android圆形头像,拍照后“无法加载此图片”的问题解决(适配Android7.0)
Feature: 点击选择拍照或者打开相册,选取图片进行裁剪最后设置为圆形头像. Problem: 拍好照片,点击裁剪,弹Toast"无法加载此图片". Solution: 在裁剪 ...
- Android自定义控件实例,圆形头像(图库 + 裁剪+设置),上传头像显示为圆形,附源码
Android项目开发中经常会遇见需要实现圆角或者圆形的图片功能,如果仅仅使用系统自带的ImageView控件显然无法实现此功能,所以通过系列文章的形式由简到繁全方位的介绍一下此功能的实现,巩固一下自 ...
- 【Android】自己定义圆形ImageView(圆形头像 可指定大小)
近期在仿手Q的UI,这里面常常要用到的就是圆形头像,看到 在android中画圆形图片的几种办法 这篇文章,了解了制作这样的头像的原理.只是里面提供的方法另一个不足的地方就是不能依据实际需求改变图片的 ...
- Android图片上传(头像裁切+原图原样)
下面简单铺一下代码: (一)头像裁切.上传服务器(代码) 这里上边的按钮是头像的点击事件,弹出底部的头像选择框,下边的按钮跳到下个页面,进行原图上传. ? 1 2 3 4 5 6 7 8 9 10 1 ...
随机推荐
- 个人查阅资料-Sql语句
SQL分类: DDL—数据定义语言(CREATE,ALTER,DROP,DECLARE) DML—数据操纵语言(SELECT,DELETE,UPDATE,INSERT) DCL—数据控制语言(GRAN ...
- Unity小游戏制作 - 暗影随行
用Unity制作小游戏 - 暗影惊吓 最近玩了一个小游戏,叫做暗影惊吓,虽然是一个十分简单的小游戏,但是感觉还是十分有趣的.这里就用Unity来实现一个类似的游戏. 项目源码:DarkFollow 主 ...
- ASP.NET Aries 开源开发框架:开发指南(一)
前言: 上周开源了Aries开发框架后,好多朋友都Download了源码,在运行过程里,有一些共性的问题会问到. 所以本篇打算写一下简单的开发指南,照顾一下不是太看的懂源码的同学,同时也会讲解一下框架 ...
- Lesson 5 No wrong numbers
Text Mr.James Scott has a garage in Silbury and now he has just bought another garage in Pinhurst. P ...
- 全新 Mac 安装指南(通用篇)(推荐设置、软件安装、推荐软件)
注:本文将会不定期维护与更新,有需要的朋友请在 Github 上订阅该条 Issues:<全新 Mac 安装指南(通用篇)>. 在 Mac 电脑上只用 Windows 操作系统的同学请看到 ...
- MySQL5.7 新增配置
1.log_timestamps 在5.7.2以后的版本中增加一个单独控制error log , general log,slow log的记录的时间,默认是UTC,需要配置成SYSTEM(本地时间) ...
- ubuntu下rhythmbox歌名显示乱码问题解决
问题描述:本人装有双系统,一个是win7,另一个是ubuntu12.04LTS版本,所有的歌曲都在windows磁盘下KuGou目录中,这个时候,使用ubuntu的rhythmbox播放的歌曲的时候, ...
- 《Entity Framework 6 Recipes》中文翻译系列 (34) ------ 第六章 继承与建模高级应用之多条件与QueryView
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 6-10 创建一个多条件过滤 问题 你想使用多个条件为实体过滤表中的行. 解决方案 ...
- Spark使用实例
1.介绍 Spark是基于Hadoop的大数据处理框架,相比较MapReduce,Spark对数据的处理是在本地内存中进行,中间数据不需要落地,因此速度有很大的提升.而MapReduce在map阶段和 ...
- How To Use Goto?
看到,网上很多人对于goto的询问, 因为本身在工作中经常使用到,所以写下此文, 如有错误, 请指出. 本人写博文的时候主要从事C++工作 对于goto的态度,本人目前成长如下: 学生时代 老师课堂上 ...