转载请注明出处:
http://blog.csdn.net/u014163726?viewmode=contents
本文出自Wrh的博客

闲扯

最近刚好空闲下来又因为以前有人问到过这个仿qq上传头像的问题,抓紧时间撸出一篇博客

  1. 本章内容部分手势检测出自翔神的博客
  2. 本章内容匆忙赶出估计会有bug希望发现的人可以在评论告诉我
  3. 好好学习,天天向上,像大神学习

正文

还记得我上一篇关于仿qq上传头像时继承的是View,现在我们继承ImageView来实现,继承ImageView的好处有很多包括在缩放的处理上等等。

首先我们还是先从在图片上绘制阴影开始。

private RectF shelterR;
private Paint mPaint;
public CustomImageView(Context context) {this(context,null);
}public CustomImageView(Context context, AttributeSet attrs) {this(context, attrs,0);
}public CustomImageView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();
}private void init() {mPaint = new Paint();mPaint.setStyle(Paint.Style.FILL);mPaint.setStrokeWidth(2);mPaint.setAntiAlias(true);mPaint.setColor(getResources().getColor(R.color.background));
}@Override
protected void onDraw(Canvas canvas) {super.onDraw(canvas);if (getDrawable() == null){return;}float width = getDrawable().getIntrinsicWidth();float height = getDrawable().getIntrinsicHeight();if (shelterR == null || shelterR.isEmpty()) {shelterR = new RectF(0, 0,width, height);}// 画入前景圆形蒙板层int sc = canvas.saveLayer(shelterR, 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.ALL_SAVE_FLAG);canvas.drawRect(shelterR, mPaint);canvas.restoreToCount(sc);
}

可能很多朋友要问这段代码的意思了

    int sc = canvas.saveLayer(shelterR, 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.ALL_SAVE_FLAG);canvas.drawRect(shelterR, mPaint);canvas.restoreToCount(sc);

先放一个参考资料Android中的canvas介绍
我的理解可能比较简单就认为这其实就像拓印纸

canvas.saveLayer

就像是在一层新的画布上绘制东西

canvas.restoreToCount(sc)

则是把在新画布绘制的所有东西拓印到上一层

我们在新画布上的任何操作绘制出来的图形都会restore到上一层画布,阴影绘制完毕我们就开始绘制中心的圆,这个我已经讲过挺多次的了这次就不详细说明了Android PorterDuff.Mode与Canvas实际使用

这些前期准备都做完之后我们考虑一些细节,我们应该让我们的图片居中显示并且保证图片的长宽肯定要大于剪裁圆形的直径

  @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 (once){Drawable d = getDrawable();if (d == null)return;int width = getWidth();int height = getHeight();int diameter = mRadius * 2;int dw = d.getIntrinsicWidth();int dh = d.getIntrinsicHeight();float scale = 1.0f;if (dw < diameter && dh > diameter){scale = diameter * 1.0f / dw;}if (dh < diameter && dw > diameter){scale = diameter * 1.0f / dh;}if (dh < diameter && dw <diameter){scale = Math.max(diameter * 1.0f / dw, diameter * 1.0f / dh);}initScale = scale;// 图片移动至屏幕中心mScaleMatrix.postTranslate((width - dw) / 2, (height - dh) / 2);mScaleMatrix.postScale(scale, scale, getWidth() / 2, getHeight() / 2);setImageMatrix(mScaleMatrix);once = false;}
}

我们再onGlobalLayout()中保证了图片的宽高,ok到这一步我们已经初见模型了

移动图片

接下来我们要考虑的就是移动这个图片,但要注意我们要限制它移动的距离。

 @Override
public boolean onTouchEvent(MotionEvent event) {if (mGestureDetector.onTouchEvent(event))return true;mScaleGestureDetector.onTouchEvent(event);float x = 0, y = 0;// 拿到触摸点的个数final int pointerCount = event.getPointerCount();// 得到多个触摸点的x与y均值for (int i = 0; i < pointerCount; i++){x += event.getX(i);y += event.getY(i);}x = x / pointerCount;y = y / pointerCount;/*** 每当触摸点发生变化时,重置mLasX , mLastY*/if (pointerCount != lastPointerCount){mLastX = x;mLastY = y;}lastPointerCount = pointerCount;switch (event.getAction()){case MotionEvent.ACTION_MOVE:float dx = x - mLastX;float dy = y - mLastY;if (shelterR.left + dx >= circleR.left || shelterR.right + dx <= circleR.right){dx = 0;}if (shelterR.top + dy >  circleR.top || shelterR.bottom + dy <= circleR.bottom){dy = 0;}mScaleMatrix.postTranslate(dx, dy);setImageMatrix(mScaleMatrix);mLastX = x;mLastY = y;postInvalidate();break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:lastPointerCount = 0;break;}return true;
}

我们在Touch事件中做了判断以保证移动不会越界shelterR是我们图形的RectF,circleR是我们的中心圆形的RectF。

@Override
protected void onDraw(Canvas canvas) {super.onDraw(canvas);if (getDrawable() == null){return;}getShelterRectF();// 画入前景圆形蒙板层int sc = canvas.saveLayer(shelterR, 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.ALL_SAVE_FLAG);canvas.drawRect(shelterR, mPaint);mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));mPaint.setColor(Color.WHITE);canvas.drawCircle(getWidth() / 2, getHeight() / 2, mRadius, mPaint);if (circleR == null){circleR = new RectF(getWidth() / 2 - mRadius, getHeight() / 2 - mRadius,getWidth() / 2 + mRadius,getHeight() / 2 + mRadius);}canvas.restoreToCount(sc);mPaint.setXfermode(null);mPaint.setColor(getResources().getColor(R.color.background));
}   /*** 用来保证阴影的大小足以遮住图片* @return*/
private RectF getShelterRectF(){float x = (int) getTranslateX();float y = (int) getTranslateY();float width = getDrawable().getIntrinsicWidth() * getScale();float height = getDrawable().getIntrinsicHeight() * getScale();if (shelterR == null) {shelterR = new RectF(x, y,width + x, height + y);}else{shelterR.set(x, y,width + x, height + y);}return shelterR;
}

可以看到我们在onDraw()中做的事情并不多,这也是继承ImageView的好处之一,ok现在就让我享受移动的感觉(移动的信号真的差),效果如图

缩放图片

现在我们基本成型了,我们继续完成缩放功能,在这还是要说明本章手势检测功能出自Android 手势检测实战 打造支持缩放平移的图片预览效果

  @Override
public boolean onScale(ScaleGestureDetector detector) {float scale = getScale();float scaleFactor = detector.getScaleFactor();if (getDrawable() == null)return true;/*** 缩放的范围控制*/if ((scale < SCALE_MAX && scaleFactor > 1.0f)|| (scale > initScale && scaleFactor < 1.0f)){/*** 最大值最小值判断*/if (scaleFactor * scale < initScale){scaleFactor = initScale / scale;}if (scaleFactor * scale > SCALE_MAX){scaleFactor = SCALE_MAX / scale;}/*** 设置缩放比例*/mScaleMatrix.postScale(scaleFactor, scaleFactor,detector.getFocusX(), detector.getFocusX());checkBorderAndCenterWhenScale();setImageMatrix(mScaleMatrix);}return true;
}@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {return true;
}@Override
public void onScaleEnd(ScaleGestureDetector detector) {}
/*** 在缩放时,进行图片显示范围的控制*/
private void checkBorderAndCenterWhenScale()
{RectF rect = getMatrixRectF();float deltaX = 0;float deltaY = 0;int width = getWidth();int height = getHeight();// 如果宽或高大于屏幕,则控制范围if (rect.width() >= width){if (rect.left > 0){deltaX = -rect.left;}if (rect.right < width){deltaX = width - rect.right;}}if (rect.height() >= height){if (rect.top > 0){deltaY = -rect.top;}if (rect.bottom < height){deltaY = height - rect.bottom;}}// 如果宽或高小于屏幕,则让其居中if (rect.width() < width){deltaX = width * 0.5f - rect.right + 0.5f * rect.width();}if (rect.height() < height){deltaY = height * 0.5f - rect.bottom + 0.5f * rect.height();}mScaleMatrix.postTranslate(deltaX, deltaY);
}

还是要像大神学习(づ ̄ 3 ̄)づ,再加上双击判断

 private class AutoScaleRunnable implements Runnable
{static final float BIGGER = 1.07f;static final float SMALLER = 0.93f;private float mTargetScale;private float tmpScale;/*** 缩放的中心*/private float x;private float y;/*** 传入目标缩放值,根据目标值与当前值,判断应该放大还是缩小** @param targetScale*/public AutoScaleRunnable(float targetScale, float x, float y){this.mTargetScale = targetScale;this.x = x;this.y = y;if (getScale() < mTargetScale){tmpScale = BIGGER;} else{tmpScale = SMALLER;}}@Overridepublic void run(){// 进行缩放mScaleMatrix.postScale(tmpScale, tmpScale, x, y);checkBorderAndCenterWhenScale();setImageMatrix(mScaleMatrix);final float currentScale = getScale();// 如果值在合法范围内,继续缩放if (((tmpScale > 1f) && (currentScale < mTargetScale))|| ((tmpScale < 1f) && (mTargetScale < currentScale))){WrhImageView.this.postDelayed(this, 16);} else// 设置为目标的缩放比例{final float deltaScale = mTargetScale / currentScale;mScaleMatrix.postScale(deltaScale, deltaScale, x, y);checkBorderAndCenterWhenScale();setImageMatrix(mScaleMatrix);isAutoScale = false;}}
}

剪裁图片

最后一步了,想必大家的心情都很激动。

 /*** 剪裁头像* @return*/
public Bitmap clipBitmap(){final Paint paint = new Paint();paint.setAntiAlias(true);int dw = getDrawable().getIntrinsicWidth();int dh = getDrawable().getIntrinsicHeight();float x = getTranslateX() - (getWidth() - dw) / 2;float y = getTranslateY() - (getHeight() - dh) / 2;mBitmap = zoomBitmap(mBitmap);Bitmap target = Bitmap.createBitmap(mRadius * 2, mRadius * 2, Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(target);canvas.drawCircle(mRadius, mRadius, mRadius, paint);paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));canvas.drawBitmap(mBitmap,- (dw / 2 - mRadius) + x ,- (dh / 2 - mRadius) + y, paint);return target;
}

我们来实际测试一下感受一下

结束语

啊,啊,啊,代码基于Android Studio所写,下一篇又不知道要猴年马月了,和大神还是有差距要更努力↖(^ω^)↗
Bye

项目代码

Android 仿照QQ剪裁头像(完结篇)相关推荐

  1. 《Android开发艺术探索》完结篇

    笔记链接: <Android开发艺术探索>之Activity的生命周期和启动模式(一) <Android开发艺术探索>之IPC机制上(二) <Android开发艺术探索& ...

  2. android 仿qq修改头像,Qt:小项目仿QQ修改头像界面,技术点记录

    最近写了一个修改头像功能的UI,布局参考了QQ目前的修改头像界面.如下图 这里主要说明一下两个地方的技术:1.头像图片上层的遮罩层,圆形外部为灰色,内部为全透明:2.上传图片宽高比例可以通过鼠标拖拽移 ...

  3. Android实现QQ换头像的对话框

    之前一直觉得QQ换头像弹出的对话框挺好看的,而且在项目中也有这个 需求,于是写一个Demo出来分享一下. 话不多说,先来张效果图看看. 上面是QQ中的效果. 上面的效果图为需要实现的效果. 简单来说, ...

  4. Android仿QQ主界面-------完善篇

    在我前面的博文中,做出了仿QQ主界面的主要工作,博文地址:Android仿QQ主界面. 但是在那一篇中还有一个不起眼的地方没做,今天就完善它. 今天要实现在文字下面来个ImageView,实现动画.先 ...

  5. android 仿qq群头像,Android仿微信和QQ多图合并框架(类似群头像)的实现方法

    Android仿微信和QQ多图合并框架(类似群头像)的实现方法 发布时间:2020-10-21 10:33:03 来源:脚本之家 阅读:97 作者:jyb_96 前言 现在多数app里面加入聊天已经是 ...

  6. android qq 圆形头像,Android仿QQ圆形头像个性名片

    先看看效果图: 中间的圆形头像和光环波形讲解请看:https://www.jb51.net/article/96508.htm 周围的气泡布局,因为布局RatioLayout是继承自ViewGroup ...

  7. Android仿QQ圆形头像

    先上效果图: 实现思路: 自定义一个View,继承ImageView,通过PorterDuffXfermode实现一个Mask效果,并在onDraw中画出来. 自定义View的代码: public c ...

  8. Android Service(7)--完结篇

    傻蛋在Android Service(4) 中讲述了使用AIDL语言,来让ADT帮助我们自动生成一个Stub类(Binder的子类),来实现不同进程中Service的调用.通过研究ADT自动生成的代码 ...

  9. Android 集成QQ登录,获取头像与昵称

    QQ登录集成 腾讯开放平台地址:http://open.qq.com/ 如果没注册过的同学需要先注册,还要上传自己的手持身份证的证件照,腾讯审核通过后才能注册成功. 创建应用,获取appID.如果测试 ...

最新文章

  1. antd自定义分页器_自定义分页器
  2. tanh函数matlab_MATLAB 基本函数
  3. arcgis怎么用python重新排序,使用ArcGIS脚本工具将点数据进行排序并编号
  4. 全国计算机等级考试题库二级C操作题100套(第05套)
  5. 【ElasticSearch】Es 源码之 Discovery DiscoveryModule 源码解读
  6. thinkphp数据表操作恐怖事件。
  7. Spring Cloud Feign 1(声明式服务调用Feign 简介)
  8. Delphi7下安装ICS组件
  9. 2020-12-30 PMP 群内练习题 - 光环
  10. 【AI SoC】全志R329 高算力低功耗,当下智能音箱的最优解?
  11. ::设计·创意·插画::相关网站巨集!
  12. 华为电脑wrtw29安装Linux,华为MateBook13WRT-W29对比苹果 MacBook Air
  13. 舞台音效控制软件_苹果舞台现场演奏音效控制工具 MainStage 3 v3.3.1
  14. 全职刷机工具PhoenixSuit的使用教程
  15. Virtual Box Ubuntu16.04中导入SEED LABS虚拟机后无法全屏显示
  16. 浅谈数字化管道建设[胜利油田集输信息中心 常贵宁]
  17. [C语言]初始数据类型
  18. 用Python制作抢购脚本,自动抢购飞天茅台,再也不要担心手慢无了
  19. 学习使人进步:操作系统之进程
  20. javascript实现划词标记+划词搜索功能

热门文章

  1. Ubuntu 配置 远程桌面 ssh
  2. 千万不要在朋友圈发“原图”,不然你的信息分分钟泄露(一)
  3. ubuntu系统下安装rapidjson
  4. linux下磁盘管理之利器 lvm使用介绍
  5. 交叉熵损失函数VS均方差损失函数
  6. 求一个3×3矩阵对角线元素之和
  7. 观察者Observe(刷新数据)
  8. linux内核映像查看,RockPI 4A 查看Linux内核映像编译信息
  9. python 获取指定字符前面或后面的所有字符
  10. 车间生产设备管理有哪些问题?低代码来助力