转载请注明出处: http://blog.csdn.net/u014163726?viewmode=contents 本文出自Wrh的博客
最近刚好空闲下来又因为以前有人问到过这个仿qq上传头像的问题,抓紧时间撸出一篇博客
本章内容部分手势检测出自翔神的博客本章内容匆忙赶出估计会有bug希望发现的人可以在评论告诉我好好学习,天天向上,像大神学习还记得我上一篇关于仿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; } } @Override public 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; }我们来实际测试一下感受一下
