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之圆形头像裁切的更多相关文章

  1. Android ImageView圆形头像

    转载自:http://m.oschina.net/blog/321024 Android ImageView圆形头像 图片完全解析 我们在做项目的时候会用到圆形的图片,比如用户头像,类似QQ.用户在用 ...

  2. 【转】Android ImageView圆形头像

    Android ImageView圆形头像 图片完全解析 我们在做项目的时候会用到圆形的图片,比如用户头像,类似QQ.用户在用QQ更换头像的时候,上传的图片都是矩形的,但显示的时候确是圆形的. 原理: ...

  3. Android 圆形头像 自己动手

    圆形头像DIY 现在大部分app使用的都是圆形头像,网上开源的也很多,但是有没有考虑过DIY圆形头像呢?下面就自己实现一个,先看下demo展示 第一步:原理解释(图片很丑,原理很真) 1.画外框圆形, ...

  4. Android控件Gridview实现仿支付宝首页,Fragment底部按钮切换和登录圆形头像

    此案例主要讲的是Android控件Gridview(九宫格)完美实现仿支付宝首页,包含添加和删除功能:Fragment底部按钮切换的效果,包含四个模块,登录页面圆形头像等,一个小项目的初始布局. 效果 ...

  5. Android特效专辑(五)——自定义圆形头像和仿MIUI卸载动画—粒子爆炸

    Android特效专辑(五)--自定义圆形头像和仿MIUI卸载动画-粒子爆炸 好的,各位亲爱的朋友,今天讲的特效还是比较炫的,首先,我们会讲一个自定义圆形的imageView,接着,我们会来实现粒子爆 ...

  6. Android圆形头像,拍照后“无法加载此图片”的问题解决(适配Android7.0)

    Feature: 点击选择拍照或者打开相册,选取图片进行裁剪最后设置为圆形头像. Problem: 拍好照片,点击裁剪,弹Toast"无法加载此图片". Solution: 在裁剪 ...

  7. Android自定义控件实例,圆形头像(图库 + 裁剪+设置),上传头像显示为圆形,附源码

    Android项目开发中经常会遇见需要实现圆角或者圆形的图片功能,如果仅仅使用系统自带的ImageView控件显然无法实现此功能,所以通过系列文章的形式由简到繁全方位的介绍一下此功能的实现,巩固一下自 ...

  8. 【Android】自己定义圆形ImageView(圆形头像 可指定大小)

    近期在仿手Q的UI,这里面常常要用到的就是圆形头像,看到 在android中画圆形图片的几种办法 这篇文章,了解了制作这样的头像的原理.只是里面提供的方法另一个不足的地方就是不能依据实际需求改变图片的 ...

  9. Android图片上传(头像裁切+原图原样)

    下面简单铺一下代码: (一)头像裁切.上传服务器(代码) 这里上边的按钮是头像的点击事件,弹出底部的头像选择框,下边的按钮跳到下个页面,进行原图上传. ? 1 2 3 4 5 6 7 8 9 10 1 ...

随机推荐

  1. WPF整理-跨程序集访问资源

    “Sometimes binary resources are defined in one assembly (typically a class library), but areneeded i ...

  2. CQOI 2016 不同的最小割

    题目大意:一个无向图,求所有点对不同的最小割种类数 最小割最多有n-1个,这n-1个最小割构成一个最小割树 分治法寻找n-1个最小割.对于当前点集X,任选两点为ST做最小割,然后找出与S相连的所有点和 ...

  3. The Myths about Transactions (ACID) and NoSQL

    There has been widespread characterization of one of the major distinctions between NoSQL and tradit ...

  4. The Swiss Army Knife of Data Structures … in C#

    "I worked up a full implementation as well but I decided that it was too complicated to post in ...

  5. Redis 的性能幻想与残酷现实

    2011 年,当初选择 Redis 作为主要的内存数据存储,主要吸引我的是它提供多样的基础数据结构可以很方便的实现业务需求.另一方面又比较担心它的性能是否足以支撑,毕竟当时 Redis 还属于比较新的 ...

  6. 初试ASP.NET Web API/MVC API(附Demo)

    写在前面 HTTP RESTful 创建Web API 调用Web API 运行截图及Demo下载 ASP.NET Web API是​​一个框架,可以很容易构建达成了广泛的HTTP服务客户端,包括浏览 ...

  7. 配置NHibernate将枚举保存为Oracle数据库中的字符串

    假设有这样一个枚举: /// <summary> /// 字典项类型 /// </summary> public enum DicItemType { [EnumDescrip ...

  8. Android-Notification-简单的demo

    通知,几乎存在于每一个 Android 的应用中,新闻客户端来一条新闻的时候会给你发一条通知,QQ.微信有新消息来的时候会给你发一条通知,反正,通知就像魔鬼般一直缠绕在我们的左右.所以,Android ...

  9. MyBatis-Exception:org.apache.ibatis.exceptions.PersistenceException

    错误信息如下: HTTP Status 500 - org.mybatis.spring.MyBatisSystemException: nested exception is org.apache. ...

  10. c#利用泛型集合,为自己偷偷懒。

    有人说"越懒"的程序员进步的越快!其实还挺有道理.亲身体验,从刚出来工作到现在,自己变"懒"了许多,但感觉写出来的代码确有了不少提升.刚开始啊,同样的代码,赋值 ...