AndroidClipSquare安卓實現方形頭像裁剪

jopen 9年前發布 | 15K 次閱讀 Android開發 移動開發 AndroidClipSquare

安卓實現方形頭像裁剪

實現思路,界面可見區域為2層View

最頂層的View是顯示層,主要繪制半透明邊框區域和白色裁剪區域,代碼比較容易。

第二層繼承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

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!