`
iaiai
  • 浏览: 2196712 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

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
分享到:
评论

相关推荐

    安卓实现方形头像裁剪

    该源码项目是一个安卓实现方形头像裁剪,源码AndroidClipSquare,安卓实现方形头像裁剪,实现思路,界面可见区域为2层View,最顶层的View是显示层,主要绘制半透明边框区域和白色裁剪区域,代码比较容易。...

    Android 仿微信 头像裁剪

    在Android开发中,为了提供类似微信的用户体验,经常会遇到实现头像裁剪功能的需求。"Android 仿微信头像裁剪"是一个常见的实践项目,它允许用户选择一张图片,并通过手势操作来裁剪出合适的头像。这个项目的核心是...

    三种不同方式实现头像裁剪

    总的来说,Android头像裁剪功能的实现有多种途径,从简单的系统调用到复杂的自定义视图,每种方法都有其适用场景。理解这些方法的工作原理和优缺点,可以帮助开发者快速而高效地完成头像裁剪功能的集成,提升应用的...

    头像裁剪Shader

    总之,"头像裁剪Shader"是一个实用的技术,它通过自定义Shader实现了图像的动态裁剪,使得在方形和圆形头像之间的切换变得轻松便捷。通过深入学习和实践,开发者可以将这种技术扩展到更广泛的图像处理需求中,提升...

    安卓头像制作图片圆角剪裁相关-android按比例裁剪上传.rar

    这个"安卓头像制作图片圆角剪裁相关-android按比例裁剪上传.rar"压缩包文件包含了实现这一功能的相关代码示例。下面我们将深入探讨这个主题。 首先,我们来理解“图片圆角剪裁”。在Android中,为了实现图片的圆角...

    安卓头像制作图片圆角剪裁相关-android-crop是一个图片裁剪工具可以选择裁剪照片进行裁剪裁剪区域可拖动.rar

    "安卓头像制作图片圆角剪裁相关-android-crop是一个图片裁剪工具可以选择裁剪照片进行裁剪裁剪区域可拖动"的资源就是针对这一需求提供的解决方案。这个压缩包包含了一个名为`android-crop`的开源库,它允许用户在...

    头像裁剪上传.zip

    "头像裁剪上传.zip"这个压缩包文件显然包含了实现这一功能的相关代码或资源。以下将详细介绍关于“mui 图像头像裁剪上传”涉及的技术点、应用场景以及可能用到的工具和方法。 首先,"mui"是一个轻量级的前端框架,...

    android 头像与封面的裁剪

    本教程将详细讲解如何实现"android 头像与封面的裁剪",并提供关键知识点。 一、头像裁剪与预览 头像通常需要呈现出圆形效果,以符合人眼的视觉习惯。在Android中,我们可以利用`BitmapShader`类创建圆形图片。首先...

    Android studio 实现头像上传功能

    实现了从相机获取图片和相册获得图片裁剪上传到服务器

    android头像裁剪选择

    2. 实现裁剪框,可以是一个可拖动、缩放的矩形或椭圆形,用于定义裁剪区域。 3. 处理手势事件,更新裁剪框的位置和大小。 4. 在用户完成裁剪后,根据裁剪框的位置和大小截取原始图片的相应部分。 四、图片处理 裁剪...

    Android裁剪头像的demo

    在Android开发中,实现用户头像裁剪是一项常见的需求,特别是在社交应用中。"Android裁剪头像的demo"是一个示例项目,它模仿了QQ应用程序的头像裁剪功能,提供了一种高效且用户体验良好的解决方案。这个项目的核心是...

    最好的android图片裁剪 头像截取工具

    本篇将详细介绍如何利用Android中的开源库来实现“最好的android图片裁剪及头像截取工具”。 首先,我们可以看到描述中提到的“cropper-master”这一文件名,这很可能是指一个名为“CropImage”的开源项目,它是...

    Android头像裁剪

    在Android开发中,实现用户头像裁剪是一项常见的功能,特别是在社交应用或者个人资料设置界面。这个"Android头像裁剪"项目提供了一个自定义的解决方案,允许用户通过单指操作来裁剪出正方形的头像。接下来,我们将...

    Android-Android仿微信的图片选择器带裁剪功能支持圆形和方形裁剪

    "Android-Android仿微信的图片选择器带裁剪功能支持圆形和方形裁剪"这个项目就是这样一个实现,它为开发者提供了完整的解决方案。 首先,我们来探讨一下该项目的核心知识点: 1. **自定义相册**: 在Android中,...

    flash头像裁剪工具

    在IT行业中,头像裁剪是一项常见的需求,特别是在社交媒体、论坛和在线社区等平台。"Flash头像裁剪工具"是这样一个解决方案,它允许用户自定义裁剪图像以创建适合自己需求的头像。这个工具的独特之处在于其能够生成...

    解决Android头像上传时裁剪图片过大问题

    在Android中,我们可以使用`android.graphics.Bitmap`类的`createBitmap`方法结合`Matrix`来实现裁剪。首先,我们需要获取用户选择图片的原始Bitmap,然后根据预设的裁剪区域计算出新的矩阵,最后通过矩阵创建新的...

    头像上传裁剪插件

    本插件专注于提供一个便捷的头像裁剪解决方案,结合前端与后端技术,实现了用户友好的拖动裁剪功能。 该插件的核心是利用JavaScript库JCrop,这是一个强大的图像裁剪工具,支持像素级精确选择和拖动裁剪区域。JCrop...

    android 把图片切成正方形

    本文将深入探讨如何在Android平台上实现这一功能。 首先,我们需要了解Android中的Bitmap类,它是处理图片的基础。Bitmap可以加载、创建、绘制和操作位图图像。当我们需要对图片进行裁剪成正方形时,通常会涉及到...

    Winfrom 图片裁剪 圆形头像

    6. **圆形裁剪**: 为了实现圆形裁剪,我们需要将方形裁剪区域转换为圆形。这可以通过在`DrawImage`之前,计算出裁剪区域内所有像素距离圆形边界的最短距离。如果距离大于半径,说明该像素在圆形外部,应忽略不绘制。...

    安卓第三方开源图片裁剪神器,功能强大,可实现自由裁剪

    github上第一个第三方开源的图片修剪截取利器,功能强大,设计良好,提供了非常丰富的图片截取修剪功能,涵盖常用的基本需求功能,如图片的按比例截取(4:3,16:9,7:5等等)、截取成圆形、自由裁剪、锁定比例裁剪、...

Global site tag (gtag.js) - Google Analytics