参考链接:
由于在上个版本的开发过程中,接到了一个更改头像的任务:选择一张图片,支持双击缩放功能,更具手势支持缩放和平移功能!由于开发任务周期短,时间紧迫,只能去网上找一些开源的框架,于是就有了开头的参考链接,由于将代码移植过来以后,根据具体的项目需求又做了一些更改,但是在提测的过程中发现了一些问题,在对这些问题更改过后,提测完没有BUG,才敢将源代码开放出来,供大家参考讨论!
package com.example.customclipimage.view; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.opengl.GLES10; import android.os.Build; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.View; import com.example.customclipimage.R; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLContext; import javax.microedition.khronos.egl.EGLDisplay; import javax.microedition.khronos.egl.EGLSurface; public class ClipImageView extends android.support.v7.widget.AppCompatImageView implements ScaleGestureDetector.OnScaleGestureListener, View.OnTouchListener { private final Paint mPaint; private final int mMaskColor; private int mAspectX; private int mAspectY; private String mTipText; private final int mClipPadding; private float mScaleMax = 4.0f; private float mScaleMin = 2.0f; /** * 初始化时的缩放比例 */ private float mInitScale = 1.0f; /** * 用于存放矩阵 */ private final float[] mMatrixValues = new float[9]; /** * 缩放的手势检查 */ private ScaleGestureDetector mScaleGestureDetector = null; private final Matrix mScaleMatrix = new Matrix(); /** * 用于双击 */ private GestureDetector mGestureDetector; private boolean isAutoScale; private float mLastX; private float mLastY; private boolean isCanDrag; private int lastPointerCount; private Rect mClipBorder = new Rect(); private int mMaxOutputWidth = 0; private boolean mDrawCircleFlag; private float mRoundCorner; public ClipImageView(Context context) { this(context, null); } public ClipImageView(Context context, AttributeSet attrs) { super(context, attrs); //不改变原图的大小,从ImageView的左上角开始绘制原图, 原图超过ImageView的部分作裁剪处理 setScaleType(ScaleType.MATRIX); mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { @Override public boolean onDoubleTap(MotionEvent e) { if (isAutoScale) return true; float x = e.getX(); float y = e.getY(); if (getScale() < mScaleMin) { //如果当前的缩放倍数小于一开始适配裁剪框缩放倍数的两倍 ClipImageView.this.postDelayed(new AutoScaleRunnable(mScaleMin, x, y), 16); } else { ClipImageView.this.postDelayed(new AutoScaleRunnable(mInitScale, x, y), 16); } isAutoScale = true; return true; } }); mScaleGestureDetector = new ScaleGestureDetector(context, this); this.setOnTouchListener(this); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(Color.WHITE); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ClipImageView); mAspectX = ta.getInteger(R.styleable.ClipImageView_civWidth, 1); mAspectY = ta.getInteger(R.styleable.ClipImageView_civHeight, 1); //mClipPadding = ta.getDimensionPixelSize(R.styleable.ClipImageView_civClipPadding, 0); //产品要求: 裁剪框300dp*300dp mClipPadding = dip2px(getScreenDpWidth() - 300) / 2; mTipText = ta.getString(R.styleable.ClipImageView_civTipText); mMaskColor = ta.getColor(R.styleable.ClipImageView_civMaskColor, 0xB2000000); mDrawCircleFlag = ta.getBoolean(R.styleable.ClipImageView_civClipCircle, false); mRoundCorner = ta.getDimension(R.styleable.ClipImageView_civClipRoundCorner, 0); final int textSize = ta.getDimensionPixelSize(R.styleable.ClipImageView_civTipTextSize, 24); mPaint.setTextSize(textSize); ta.recycle(); mPaint.setDither(true); //设置防抖动 } /** * * 自动缩放的任务 * 不断放大1.07倍,直到达到放大的目标倍数 * 不断缩小0.93倍,知道达到缩小的目标倍数 */ 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); checkBorder(); setImageMatrix(mScaleMatrix); final float currentScale = getScale(); // 如果值在合法范围内,继续缩放 if (((tmpScale > 1f) && (currentScale < mTargetScale)) || ((tmpScale < 1f) && (mTargetScale < currentScale))) { ClipImageView.this.postDelayed(this, 16); } else { // 设置为目标的缩放比例 final float deltaScale = mTargetScale / currentScale; mScaleMatrix.postScale(deltaScale, deltaScale, x, y); checkBorder(); setImageMatrix(mScaleMatrix); isAutoScale = false; } } } @Override public boolean onScale(ScaleGestureDetector detector) { float scale = getScale(); float scaleFactor = detector.getScaleFactor(); if (getDrawable() == null) return true; /** * 缩放的范围控制 */ if ((scale < mScaleMax && scaleFactor > 1.0f) || (scale > mInitScale && scaleFactor < 1.0f)) { /** * 缩放阙值最小值判断 */ if (scaleFactor * scale < mInitScale) { scaleFactor = mInitScale / scale; } if (scaleFactor * scale > mScaleMax) { scaleFactor = mScaleMax / scale; } /** * 设置缩放比例 */ mScaleMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY()); checkBorder(); setImageMatrix(mScaleMatrix); } return true; } /** * 根据当前图片的Matrix获得图片的范围 * * @return */ private RectF getMatrixRectF() { Matrix matrix = mScaleMatrix; RectF rect = new RectF(); Drawable d = getDrawable(); if (null != d) { rect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); matrix.mapRect(rect); } return rect; } @Override public boolean onScaleBegin(ScaleGestureDetector detector) { return true; } @Override public void onScaleEnd(ScaleGestureDetector detector) { } @Override public boolean onTouch(View v, 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 /= pointerCount; y /= pointerCount; /** * 每当触摸点发生变化时,重置mLasX , mLastY */ if (pointerCount != lastPointerCount) { isCanDrag = false; mLastX = x; mLastY = y; } lastPointerCount = pointerCount; switch (event.getAction()) { case MotionEvent.ACTION_MOVE: float dx = x - mLastX; float dy = y - mLastY; if (!isCanDrag) { isCanDrag = isCanDrag(dx, dy); } if (isCanDrag) { if (getDrawable() != null) { RectF rectF = getMatrixRectF(); // 如果宽度小于屏幕宽度,则禁止左右移动 if ((int) rectF.width() <= mClipBorder.width()) { dx = 0; } // 如果高度小雨屏幕高度,则禁止上下移动 if ((int) rectF.height() <= mClipBorder.height()) { dy = 0; } // 这里主要是 当宽或者高 大于 裁剪框的高或宽时,移动到与裁剪框边重合时,可以继续移动 if (rectF.left > mClipBorder.left || rectF.top > mClipBorder.top || rectF.right < mClipBorder.right || rectF.bottom < mClipBorder.bottom) { dx = dx * 0.25f; dy = dy * 0.25f; } mScaleMatrix.postTranslate(dx, dy); setImageMatrix(mScaleMatrix); } } mLastX = x; mLastY = y; break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: lastPointerCount = 0; // 当抬起手指时,如果划过了,没有填满裁剪框,就要自动弹回 checkBorder(); setImageMatrix(mScaleMatrix); break; } return true; } /** * 获得当前的缩放比例 * * @return */ public final float getScale() { mScaleMatrix.getValues(mMatrixValues); return mMatrixValues[Matrix.MSCALE_X]; } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); updateBorder(); } /** * @author * date 2018/2/2 13:32 * description 设置裁剪框的 上下左右 * @params */ private void updateBorder() { final int width = getWidth(); final int height = getHeight(); mClipBorder.left = mClipPadding; mClipBorder.right = width - mClipPadding; final int borderHeight = mClipBorder.width() * mAspectY / mAspectX; if (mDrawCircleFlag == true) { // 如果是圆形,宽高比例是1:1 final int borderTempHeight = mClipBorder.width() * 1 / 1; mClipBorder.top = (height - borderTempHeight) / 2; mClipBorder.bottom = mClipBorder.top + borderTempHeight; } else { // 如果不是圆形,根据宽高比例 mClipBorder.top = (height - borderHeight) / 2; mClipBorder.bottom = mClipBorder.top + borderHeight; } } public void setAspect(int aspectX, int aspectY) { mAspectX = aspectX; mAspectY = aspectY; } public void setTip(String tip) { mTipText = tip; } @Override public void setImageDrawable(Drawable drawable) { super.setImageDrawable(drawable); postResetImageMatrix(); } @Override public void setImageResource(int resId) { super.setImageResource(resId); postResetImageMatrix(); } @Override public void setImageURI(Uri uri) { super.setImageURI(uri); postResetImageMatrix(); } @Override public void setImageBitmap(Bitmap bm) { //开启硬件加速的情况下,图片超出最大值会无法显示。需把bitmap缩小到允许加载的宽高 int mMaxSize = getMaxTextureSize(); if (Math.max(bm.getWidth(), bm.getHeight()) > mMaxSize) { //计算缩放比例 float scale = bm.getWidth() > bm.getHeight() ? (float) mMaxSize / (float) bm.getWidth() : (float) mMaxSize / (float) bm.getHeight(); // 计算新的大小 int newWidth = (int) (bm.getWidth() * scale); int newHeight = (int) (bm.getHeight() * scale); // 取得想要缩放的matrix参数 Matrix matrix = new Matrix(); matrix.postScale(scale, scale); // 得到新的图片 bm = Bitmap.createBitmap(bm, 0, 0, newWidth, newHeight, matrix, true); } super.setImageBitmap(bm); postResetImageMatrix(); } /** * 这里没有使用post方式,因为图片会有明显的从初始位置移动到需要缩放的位置 * 将图片 */ private void postResetImageMatrix() { if (getWidth() != 0) { resetImageMatrix(); } else { post(new Runnable() { @Override public void run() { resetImageMatrix(); } }); } } /** * 垂直方向与View的边矩 * 图片去适配这个裁剪框 */ public void resetImageMatrix() { final Drawable d = getDrawable(); if (d == null) { return; } final int dWidth = d.getIntrinsicWidth(); //图片宽 final int dHeight = d.getIntrinsicHeight(); //图片高 final int cWidth = mClipBorder.width(); //边框的宽 final int cHeight = mClipBorder.height(); //边框的高 final int vWidth = getWidth(); //组件的宽 final int vHeight = getHeight(); //组件的高 final float scale; //缩放倍数 final float dx; //x轴移动的距离 final float dy; //y轴移动的距离 if (dWidth * cHeight > cWidth * dHeight) { scale = cHeight / (float) dHeight; } else { scale = cWidth / (float) dWidth; } dx = (vWidth - dWidth * scale) * 0.5f; dy = (vHeight - dHeight * scale) * 0.5f; mScaleMatrix.setScale(scale, scale); //图片缩放成多少倍 mScaleMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f)); //图片移动 setImageMatrix(mScaleMatrix); mInitScale = scale; mScaleMin = mInitScale * 2; // 放大的最小的倍数为2倍 mScaleMax = mInitScale * 2; // 放大的最大倍数也为2倍 } /** * @author * date 2018/2/24 16:14 * description 裁剪时,判断边界是不是正常 * @params */ public boolean isBoderValid() { RectF rectF = getMatrixRectF(); if (rectF.left > mClipBorder.left || rectF.right < mClipBorder.right || rectF.top > mClipBorder.top || rectF.bottom < mClipBorder.bottom) { return false; } else { return true; } } /** * 剪切图片 * * @return 返回剪切后的bitmap对象 */ public Bitmap clip() { final Drawable drawable = getDrawable(); final Bitmap originalBitmap = ((BitmapDrawable) drawable).getBitmap(); final float[] matrixValues = new float[9]; mScaleMatrix.getValues(matrixValues); final float scale = matrixValues[Matrix.MSCALE_X] * drawable.getIntrinsicWidth() / originalBitmap.getWidth(); final float transX = matrixValues[Matrix.MTRANS_X]; final float transY = matrixValues[Matrix.MTRANS_Y]; final float cropX = (-transX + mClipBorder.left) / scale; final float cropY = (-transY + mClipBorder.top) / scale; float cropWidth = mClipBorder.width() / scale; float cropHeight = mClipBorder.height() / scale; Matrix outputMatrix = null; /*if (mMaxOutputWidth > 0 && cropWidth > mMaxOutputWidth) { final float outputScale = mMaxOutputWidth / cropWidth; outputMatrix = new Matrix(); outputMatrix.setScale(outputScale, outputScale); }*/ // 会出现cropX+cropWidth>originalBitmap.getWidth() if (((int) cropWidth + (int) cropX) > originalBitmap.getWidth()) { cropWidth = originalBitmap.getWidth() - (int) cropX; } // cropY+cropHeight>originalBitmap.getHeight() if (((int) cropY + (int) cropHeight) > originalBitmap.getHeight()) { cropHeight = originalBitmap.getHeight() - (int) cropY; } return Bitmap.createBitmap(originalBitmap, (int) cropX, (int) cropY, (int) cropWidth, (int) cropHeight, outputMatrix, false); } /** * 边界检查 */ private void checkBorder() { RectF rect = getMatrixRectF(); float deltaX = 0; float deltaY = 0; // 如果宽或高大于屏幕,则控制范围 if (rect.width() >= mClipBorder.width()) { if (rect.left > mClipBorder.left) { deltaX = -rect.left + mClipBorder.left; } if (rect.right < mClipBorder.right) { deltaX = mClipBorder.right - rect.right; } } if (rect.height() >= mClipBorder.height()) { if (rect.top > mClipBorder.top) { deltaY = -rect.top + mClipBorder.top; } if (rect.bottom < mClipBorder.bottom) { deltaY = mClipBorder.bottom - rect.bottom; } } //这里有一个BUG:放大缩小后,会有白边 if (rect.width() < mClipBorder.width()) { if (rect.left > mClipBorder.left) { deltaX = -rect.left + mClipBorder.left; } if (rect.right < mClipBorder.right) { deltaX = mClipBorder.right - rect.right; } } if (rect.height() < mClipBorder.height()) { if (rect.top > mClipBorder.top) { deltaY = -rect.top + mClipBorder.top; } if (rect.bottom < mClipBorder.bottom) { deltaY = mClipBorder.bottom - rect.bottom; } } mScaleMatrix.postTranslate(deltaX, deltaY); } /** * 是否是拖动行为 * * @param dx * @param dy * @return */ private boolean isCanDrag(float dx, float dy) { return Math.sqrt((dx * dx) + (dy * dy)) >= 0; } public Rect getClipBorder() { return mClipBorder; } public void setMaxOutputWidth(int maxOutputWidth) { mMaxOutputWidth = maxOutputWidth; } public float[] getClipMatrixValues() { final float[] matrixValues = new float[9]; mScaleMatrix.getValues(matrixValues); return matrixValues; } /** * 参考showtipsview的做法 */ public void drawRectangleOrCircle(Canvas canvas) { Bitmap bitmap = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Bitmap.Config.ARGB_8888); Canvas temp = new Canvas(bitmap); Paint transparentPaint = new Paint(Paint.ANTI_ALIAS_FLAG); PorterDuffXfermode porterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.CLEAR); transparentPaint.setColor(Color.TRANSPARENT); temp.drawRect(0, 0, temp.getWidth(), temp.getHeight(), mPaint); transparentPaint.setXfermode(porterDuffXfermode); if (mDrawCircleFlag) { // 画圆 float cx = mClipBorder.left + mClipBorder.width() / 2f; float cy = mClipBorder.top + mClipBorder.height() / 2f; float radius = mClipBorder.height() / 2f; temp.drawCircle(cx, cy, radius, transparentPaint); } else { // 画矩形(可以设置矩形的圆角) RectF rectF = new RectF(mClipBorder.left, mClipBorder.top, mClipBorder.right, mClipBorder.bottom); temp.drawRoundRect(rectF, mRoundCorner, mRoundCorner, transparentPaint); } canvas.drawBitmap(bitmap, 0, 0, null); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); final int width = getWidth(); final int height = getHeight(); mPaint.setColor(mMaskColor); mPaint.setStyle(Paint.Style.FILL); canvas.drawRect(0, 0, width, mClipBorder.top, mPaint); canvas.drawRect(0, mClipBorder.bottom, width, height, mPaint); canvas.drawRect(0, mClipBorder.top, mClipBorder.left, mClipBorder.bottom, mPaint); canvas.drawRect(mClipBorder.right, mClipBorder.top, width, mClipBorder.bottom, mPaint); mPaint.setColor(Color.WHITE); mPaint.setStrokeWidth(dip2px(2)); //drawRectangleOrCircle(canvas); mPaint.setStyle(Paint.Style.STROKE); // 画中间的框 canvas.drawRect(mClipBorder.left, mClipBorder.top, mClipBorder.right, mClipBorder.bottom, mPaint); if (mTipText != null) { final float textWidth = mPaint.measureText(mTipText); final float startX = (width - textWidth) / 2; final Paint.FontMetrics fm = mPaint.getFontMetrics(); final float startY = mClipBorder.bottom + mClipBorder.top / 2 - (fm.descent - fm.ascent) / 2; mPaint.setStyle(Paint.Style.FILL); canvas.drawText(mTipText, startX, startY, mPaint); } } public int dip2px(float dipValue) { return (int) (dipValue * getScreenScale() + 0.5f); } public float getScreenScale() { try { final float scale = getResources().getDisplayMetrics().density; return scale; } catch (Throwable e) { return 1; } } public static int getMaxTextureSize() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { return getGLESTextureLimitEqualAboveLollipop(); } else { return getGLESTextureLimitBelowLollipop(); } } private static int getGLESTextureLimitBelowLollipop() { int[] maxSize = new int[1]; GLES10.glGetIntegerv(GLES10.GL_MAX_TEXTURE_SIZE, maxSize, 0); return maxSize[0]; } private static int getGLESTextureLimitEqualAboveLollipop() { EGL10 egl = (EGL10) EGLContext.getEGL(); EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); int[] vers = new int[2]; egl.eglInitialize(dpy, vers); int[] configAttr = { EGL10.EGL_COLOR_BUFFER_TYPE, EGL10.EGL_RGB_BUFFER, EGL10.EGL_LEVEL, 0, EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT, EGL10.EGL_NONE }; EGLConfig[] configs = new EGLConfig[1]; int[] numConfig = new int[1]; egl.eglChooseConfig(dpy, configAttr, configs, 1, numConfig); if (numConfig[0] == 0) {// TROUBLE! No config found. } EGLConfig config = configs[0]; int[] surfAttr = { EGL10.EGL_WIDTH, 64, EGL10.EGL_HEIGHT, 64, EGL10.EGL_NONE }; EGLSurface surf = egl.eglCreatePbufferSurface(dpy, config, surfAttr); final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; // missing in EGL10 int[] ctxAttrib = { EGL_CONTEXT_CLIENT_VERSION, 1, EGL10.EGL_NONE }; EGLContext ctx = egl.eglCreateContext(dpy, config, EGL10.EGL_NO_CONTEXT, ctxAttrib); egl.eglMakeCurrent(dpy, surf, surf, ctx); int[] maxSize = new int[1]; GLES10.glGetIntegerv(GLES10.GL_MAX_TEXTURE_SIZE, maxSize, 0); egl.eglMakeCurrent(dpy, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); egl.eglDestroySurface(dpy, surf); egl.eglDestroyContext(dpy, ctx); egl.eglTerminate(dpy); return maxSize[0]; } public int getScreenDpWidth() { float density = getResources().getDisplayMetrics().density; int width = getResources().getDisplayMetrics().widthPixels; int dpwidth = (int) Math.ceil((float) width / density); return dpwidth; } }资源链接下载: https://download.csdn.net/download/tocong2015/10381378
由于在博客上懒得写很多文字,有什么问题可以在评论区探讨,我看到了会及时回复的,谢谢!