安卓实现方形头像裁剪

实现思路。界面可见区域为2层View

最顶层的View是显示层,主要绘制半透明边框区域和白色裁剪区域,代码比較easy。

第二层继承ImageView,使用ImageView的Matrix实现显示部分图片,及挪动,放大缩小等操作。

比較复杂的地方在于多指操作对ImageView的影响,详见代码:

ClipSquareImageView.java

package com.h3c.androidclipsquare;

import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewTreeObserver;
import android.widget.ImageView; /**
* Created by H3c on 12/13/14.
*/
public class ClipSquareImageView extends ImageView implements View.OnTouchListener, ViewTreeObserver.OnGlobalLayoutListener {
private static final int BORDERDISTANCE = ClipSquareView.BORDERDISTANCE; public static final float DEFAULT_MAX_SCALE = 4.0f;
public static final float DEFAULT_MID_SCALE = 2.0f;
public static final float DEFAULT_MIN_SCALE = 1.0f; private float minScale = DEFAULT_MIN_SCALE;
private float midScale = DEFAULT_MID_SCALE;
private float maxScale = DEFAULT_MAX_SCALE; private MultiGestureDetector multiGestureDetector;
private boolean isIniting;// 正在初始化 private Matrix defaultMatrix = new Matrix();// 初始化的图片矩阵,控制图片撑满屏幕及显示区域
private Matrix dragMatrix = new Matrix();// 拖拽放大过程中动态的矩阵
private Matrix finalMatrix = new Matrix();// 终于显示的矩阵
private final RectF displayRect = new RectF();// 图片的真实大小
private final float[] matrixValues = new float[9]; private int borderlength; public ClipSquareImageView(Context context, AttributeSet attrs) {
super(context, attrs);
super.setScaleType(ScaleType.MATRIX);
setOnTouchListener(this);
multiGestureDetector = new MultiGestureDetector(context);
} @Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getViewTreeObserver().addOnGlobalLayoutListener(this);
} @SuppressWarnings("deprecation")
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
getViewTreeObserver().removeGlobalOnLayoutListener(this);
} @Override
public void onGlobalLayout() {
if(isIniting) {
return;
}
// 调整视图位置
initBmpPosition();
} /**
* 初始化图片位置
*/
private void initBmpPosition() {
isIniting = true;
super.setScaleType(ScaleType.MATRIX);
Drawable drawable = getDrawable(); if(drawable == null) {
return;
} final float viewWidth = getWidth();
final float viewHeight = getHeight();
final int drawableWidth = drawable.getIntrinsicWidth();
final int drawableHeight = drawable.getIntrinsicHeight();
if(viewWidth < viewHeight) {
borderlength = (int) (viewWidth - 2 * BORDERDISTANCE);
} else {
borderlength = (int) (viewHeight - 2 * BORDERDISTANCE);
} float screenScale = 1f;
// 小于屏幕的图片会被撑满屏幕
if(drawableWidth <= drawableHeight) {// 竖图片
screenScale = (float) borderlength / drawableWidth;
} else {// 横图片
screenScale = (float) borderlength / drawableHeight;
} defaultMatrix.setScale(screenScale, screenScale); if(drawableWidth <= drawableHeight) {// 竖图片
float heightOffset = (viewHeight - drawableHeight * screenScale) / 2.0f;
if(viewWidth <= viewHeight) {// 竖照片竖屏幕
defaultMatrix.postTranslate(BORDERDISTANCE, heightOffset);
} else {// 竖照片横屏幕
defaultMatrix.postTranslate((viewWidth - borderlength) / 2.0f, heightOffset);
}
} else {
float widthOffset = (viewWidth - drawableWidth * screenScale) / 2.0f;
if(viewWidth <= viewHeight) {// 横照片,竖屏幕
defaultMatrix.postTranslate(widthOffset, (viewHeight - borderlength) / 2.0f);
} else {// 横照片,横屏幕
defaultMatrix.postTranslate(widthOffset, BORDERDISTANCE);
}
} resetMatrix();
} /**
* Resets the Matrix back to FIT_CENTER, and then displays it.s
*/
private void resetMatrix() {
if(dragMatrix == null) {
return;
} dragMatrix.reset();
setImageMatrix(getDisplayMatrix());
} private Matrix getDisplayMatrix() {
finalMatrix.set(defaultMatrix);
finalMatrix.postConcat(dragMatrix);
return finalMatrix;
} @Override
public boolean onTouch(View view, MotionEvent motionEvent) {
return multiGestureDetector.onTouchEvent(motionEvent);
} private class MultiGestureDetector extends GestureDetector.SimpleOnGestureListener implements
ScaleGestureDetector.OnScaleGestureListener { private final ScaleGestureDetector scaleGestureDetector;
private final GestureDetector gestureDetector;
private final float scaledTouchSlop; private VelocityTracker velocityTracker;
private boolean isDragging; private float lastTouchX;
private float lastTouchY;
private float lastPointerCount;// 上一次是几个手指事件 public MultiGestureDetector(Context context) {
scaleGestureDetector = new ScaleGestureDetector(context, this);
gestureDetector = new GestureDetector(context, this);
gestureDetector.setOnDoubleTapListener(this); final ViewConfiguration configuration = ViewConfiguration.get(context);
scaledTouchSlop = configuration.getScaledTouchSlop();
} @Override
public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
float scale = getScale();
float scaleFactor = scaleGestureDetector.getScaleFactor();
if(getDrawable() != null && ((scale < maxScale && scaleFactor > 1.0f) || (scale > minScale && scaleFactor < 1.0f))){
if(scaleFactor * scale < minScale){
scaleFactor = minScale / scale;
}
if(scaleFactor * scale > maxScale){
scaleFactor = maxScale / scale;
}
dragMatrix.postScale(scaleFactor, scaleFactor, getWidth() / 2, getHeight() / 2);
checkAndDisplayMatrix();
}
return true;
} @Override
public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
return true;
} @Override
public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) {} public boolean onTouchEvent(MotionEvent event) {
if (gestureDetector.onTouchEvent(event)) {
return true;
} scaleGestureDetector.onTouchEvent(event); /*
* Get the center x, y of all the pointers
*/
float x = 0, y = 0;
final int pointerCount = event.getPointerCount();
for (int i = 0; i < pointerCount; i++) {
x += event.getX(i);
y += event.getY(i);
}
x = x / pointerCount;
y = y / pointerCount; /*
* If the pointer count has changed cancel the drag
*/
if (pointerCount != lastPointerCount) {
isDragging = false;
if (velocityTracker != null) {
velocityTracker.clear();
}
lastTouchX = x;
lastTouchY = y;
lastPointerCount = pointerCount;
} switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (velocityTracker == null) {
velocityTracker = VelocityTracker.obtain();
} else {
velocityTracker.clear();
}
velocityTracker.addMovement(event); lastTouchX = x;
lastTouchY = y;
isDragging = false;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
lastPointerCount = 0;
if (velocityTracker != null) {
velocityTracker.recycle();
velocityTracker = null;
}
break;
case MotionEvent.ACTION_MOVE: {
final float dx = x - lastTouchX, dy = y - lastTouchY; if (isDragging == false) {
// Use Pythagoras to see if drag length is larger than
// touch slop
isDragging = Math.sqrt((dx * dx) + (dy * dy)) >= scaledTouchSlop;
} if (isDragging) {
if (getDrawable() != null) {
dragMatrix.postTranslate(dx, dy);
checkAndDisplayMatrix();
} lastTouchX = x;
lastTouchY = y; if (velocityTracker != null) {
velocityTracker.addMovement(event);
}
}
break;
}
} return true;
} @Override
public boolean onDoubleTap(MotionEvent event) {
try {
float scale = getScale();
float x = getWidth() / 2;
float y = getHeight() / 2; if (scale < midScale) {
post(new AnimatedZoomRunnable(scale, midScale, x, y));
} else if ((scale >= midScale) && (scale < maxScale)) {
post(new AnimatedZoomRunnable(scale, maxScale, x, y));
} else {// 双击缩小小于最小值
post(new AnimatedZoomRunnable(scale, minScale, x, y));
}
} catch (Exception e) {
// Can sometimes happen when getX() and getY() is called
} return true;
}
} private class AnimatedZoomRunnable implements Runnable {
// These are 'postScale' values, means they're compounded each iteration
static final float ANIMATION_SCALE_PER_ITERATION_IN = 1.07f;
static final float ANIMATION_SCALE_PER_ITERATION_OUT = 0.93f; private final float focalX, focalY;
private final float targetZoom;
private final float deltaScale; public AnimatedZoomRunnable(final float currentZoom, final float targetZoom,
final float focalX, final float focalY) {
this.targetZoom = targetZoom;
this.focalX = focalX;
this.focalY = focalY; if (currentZoom < targetZoom) {
deltaScale = ANIMATION_SCALE_PER_ITERATION_IN;
} else {
deltaScale = ANIMATION_SCALE_PER_ITERATION_OUT;
}
} @Override
public void run() {
dragMatrix.postScale(deltaScale, deltaScale, focalX, focalY);
checkAndDisplayMatrix(); final float currentScale = getScale(); if (((deltaScale > 1f) && (currentScale < targetZoom))
|| ((deltaScale < 1f) && (targetZoom < currentScale))) {
// We haven't hit our target scale yet, so post ourselves
// again
postOnAnimation(ClipSquareImageView.this, this); } else {
// We've scaled past our target zoom, so calculate the
// necessary scale so we're back at target zoom
final float delta = targetZoom / currentScale;
dragMatrix.postScale(delta, delta, focalX, focalY);
checkAndDisplayMatrix();
}
}
} @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void postOnAnimation(View view, Runnable runnable) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.postOnAnimation(runnable);
} else {
view.postDelayed(runnable, 16);
}
} /**
* Returns the current scale value
*
* @return float - current scale value
*/
public final float getScale() {
dragMatrix.getValues(matrixValues);
return matrixValues[Matrix.MSCALE_X];
} /**
* Helper method that simply checks the Matrix, and then displays the result
*/
private void checkAndDisplayMatrix() {
checkMatrixBounds();
setImageMatrix(getDisplayMatrix());
} private void checkMatrixBounds() {
final RectF rect = getDisplayRect(getDisplayMatrix());
if (null == rect) {
return;
} float deltaX = 0, deltaY = 0;
final float viewWidth = getWidth();
final float viewHeight = getHeight();
// 推断移动或缩放后,图片显示是否超出裁剪框边界
final float heightBorder = (viewHeight - borderlength) / 2;
final float weightBorder = (viewWidth - borderlength) / 2;
if(rect.top > heightBorder){
deltaY = heightBorder - rect.top;
}
if(rect.bottom < (viewHeight - heightBorder)){
deltaY = viewHeight - heightBorder - rect.bottom;
}
if(rect.left > weightBorder){
deltaX = weightBorder - rect.left;
}
if(rect.right < viewWidth - weightBorder){
deltaX = viewWidth - weightBorder - rect.right;
}
// Finally actually translate the matrix
dragMatrix.postTranslate(deltaX, deltaY); } /**
* 获取图片相对Matrix的距离
*
* @param matrix
* - Matrix to map Drawable against
* @return RectF - Displayed Rectangle
*/
private RectF getDisplayRect(Matrix matrix) {
Drawable d = getDrawable();
if (null != d) {
displayRect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
matrix.mapRect(displayRect);
return displayRect;
} return null;
} /**
* 剪切图片,返回剪切后的bitmap对象
*
* @return
*/
public Bitmap clip(){
Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
draw(canvas);
return Bitmap.createBitmap(bitmap, (getWidth() - borderlength) / 2, (getHeight() - borderlength) / 2, borderlength, borderlength);
}
}

代码下载:https://github.com/h3clikejava/AndroidClipSquare

AndroidClipSquare安卓实现方形头像裁剪的更多相关文章

  1. 用JQuery仿造QQ头像裁剪功能

    最近工作真心忙碌,几乎没时间写博客.今天趁周末来仿一个QQ头像裁剪功能插件.效果如下: 所有文件都可在我的Github上下载,从头到尾从简到繁按步骤一共分了9个HTML文件,每个步骤文件里的注释都写的 ...

  2. 如何用CropBox实现头像裁剪并与java后台交互

    如何用CropBox实现头像裁剪并与java后台交互 参考网站:https://developer.mozilla.org/zh-CN/docs/Web/API/Blob 参考: http://blo ...

  3. 第三百九十节,Django+Xadmin打造上线标准的在线教育平台—Django+cropper插件头像裁剪上传

    第三百九十节,Django+Xadmin打造上线标准的在线教育平台—Django+cropper插件头像裁剪上传 实现原理 前台用cropper插件,将用户上传头像时裁剪图片的坐标和图片,传到逻辑处理 ...

  4. 基于jQuery头像裁剪插件cropbox

    今天给大家分享一款基于jQuery头像裁剪插件cropbox,这是一款简单实用的jQuery头像在线裁剪插件.该插件适用于适用浏览器:IE8.360.FireFox.Chrome.Safari.Ope ...

  5. springMVC 头像裁剪上传并等比压

    第一次写头像裁剪上传,原本想着直接本地预览裁剪再上传,可是时间有限,jquery.jcrop貌似并没有对 假设是ie下图片预览效果是滤镜做的  做出对应处理,也没有时间去改;仅仅好将就一下先把图片上传 ...

  6. 【绝对干货】仿微信QQ设置图形头像裁剪,让你的App从此炫起来~

    最近在做毕业设计,想有一个功能和QQ一样可以裁剪头像并设置圆形头像,额,这是设计狮的一种潮流. 而纵观现在主流的APP,只要有用户系统这个功能,这个需求一般都是在(bu)劫(de)难(bu)逃(xue ...

  7. springmvc 多数据源 SSM java redis shiro ehcache 头像裁剪

    获取下载地址   QQ 313596790  A 调用摄像头拍照,自定义裁剪编辑头像 B 集成代码生成器 [正反双向](单表.主表.明细表.树形表,开发利器)+快速构建表单;  技术:31359679 ...

  8. 【jQuery插件】使用cropper实现简单的头像裁剪并上传

    插件介绍 这是一个我在写以前的项目的途中发现的一个国人写的jQuery图像裁剪插件,当时想实现用户资料的头像上传功能,并且能够预览图片,和对图片进行简单的裁剪.旋转,花了不少时间才看到了这个插件,感觉 ...

  9. jfinal头像裁剪上传服务器

    前端页面完整代码,复制可用,记得导入库文件 <!DOCTYPE html> <html lang="en"> <head> <title& ...

随机推荐

  1. 关于http请求指定本地ip

    static void Main(string[] args) { //ssl证书验证问题(没有校验) ServicePointManager.ServerCertificateValidationC ...

  2. GO语言学习(九)Go 语言运算符

    运算符用于在程序运行时执行数学或逻辑运算. Go 语言内置的运算符有: 算术运算符 关系运算符 逻辑运算符 位运算符 赋值运算符 其他运算符 接下来让我们来详细看看各个运算符的介绍. 算术运算符 下表 ...

  3. Android 自己定义主菜单

    本文介绍一个超简单的自己定义主菜单.效果例如以下: 原理:事实上就是对原生的Dialog的一个简单的封装.并加上显示和隐藏的动画效果.再给控件加上回调事件. TestDialog.java publi ...

  4. Python 爬虫从入门到进阶之路(五)

    在之前的文章中我们带入了 opener 方法,接下来我们看一下 opener 应用中的 ProxyHandler 处理器(代理设置). 使用代理IP,这是爬虫/反爬虫的第二大招,通常也是最好用的. 很 ...

  5. 如何使用SVN协调代源代码,多人同步开发

    转自linFen原文如何使用SVN协调代源代码,多人同步开发 1.什么是SVN SVN是一种版本管理系统,前身是CVS,是开源软件的基石.即使在沟通充分的情况下,多人维护同一份源代码的一定也会出现混乱 ...

  6. 可直接复制粘贴的boostrap图标库网址

    1:http://fontawesome.dashgame.com/ 2:http://www.kuiyu.net/art-34.html 3:http://www.bootcss.com/p/fon ...

  7. [Node] Use babel-preset-env with Native Node Features and Also Use Babel Plugins

    In this lesson we'll show how to setup a .babelrc file with presets and plugins. Then create npm scr ...

  8. generating permunation——全排列(算法汇总)

    本文一共提供了4种全排列的方法,包括递归非字典序版本.递归字典序版本.标准库版本和BFS字典序版本,当然BFS非字典序实现相对于BFS字典序版本更加简洁,稍加修改即可. 说明:递归版本基于网上现有代码 ...

  9. 计算git树上随意两点的近期切割点。

    1.git是一种分布式代码管理工具,git通过树的形式记录文件的更改历史,比方: base'<--base<--A<--A' ^ | --- B<--B' 小米project师 ...

  10. Linux系统编程_8_进程控制之fork_wait_waitpid函数

    fork函数: #include <unistd.h>        pid_t fork(void); fork用来创建一个子进程: 特点: fork调用后会返回两次,子进程返回0,父进 ...