【问题标题】:How can I move and scale an image like the Facebook profile image selection tool does?如何像 Facebook 个人资料图像选择工具那样移动和缩放图像?
【发布时间】:2014-05-06 04:57:12
【问题描述】:

我想在 Android 上像 Facebook 个人资料图像选择那样裁剪图像,用户可以在其中移动和缩放图像,从而调整其大小和/或裁剪:

我该如何做到这一点?

【问题讨论】:

标签: android image crop


【解决方案1】:

谢谢大家。能够使用上面的答案,使用 Photoview 和 Cropper 库来实现这一点。添加了从相机或图库中选择图像的选项。在 Github 上分享项目。在项目中添加了一个 apk 文件。使用真实设备测试相机,因为模拟器不能很好地处理相机。这是我的项目的链接。

https://github.com/ozeetee/AndroidImageZoomCrop

【讨论】:

    【解决方案2】:

    我对@Nikola Despotoski 的回答有一些补充。 首先,您不必将 R.layout.crop_image_view 中的 ImageView 更改为 PhotoView,因为 PhotoView 逻辑可以简单地作为 new PhotoViewAttacher(mImageView) 附加到代码中。

    同样在默认逻辑中,CropView 的覆盖大​​小仅根据 imageView 位图大小在其初始化时计算。所以这对我们来说是不合适的逻辑,因为我们根据需要通过触摸来改变位图大小。因此,我们应该在 CropOverlayView 中更改存储的位图大小,并在每次更改主图像时使其无效。

    最后是一个范围,用户可以根据图像大小正常进行裁剪,但是如果我们将图像放大,它可以超出屏幕边界,因此用户可以移动裁剪超越边界,这是不正确的。所以我们也应该处理这种情况并提供限制。

    以及这三个问题的相应代码部分: 在 CropImageView 中:

     private void init(Context context) {
    
        final LayoutInflater inflater = LayoutInflater.from(context);
        final View v = inflater.inflate(R.layout.crop_image_view, this, true);
    
        mImageView = (ImageView) v.findViewById(R.id.ImageView_image);
    
        setImageResource(mImageResource);
        mCropOverlayView = (CropOverlayView) v.findViewById(R.id.CropOverlayView);
        mCropOverlayView.setInitialAttributeValues(mGuidelines, mFixAspectRatio, mAspectRatioX, mAspectRatioY);
        mCropOverlayView.setOutlineTouchEventReceiver(mImageView);
    
        final PhotoViewAttacher photoAttacher = new PhotoViewAttacher(mImageView);
        photoAttacher.setOnMatrixChangeListener(new PhotoViewAttacher.OnMatrixChangedListener() {
            @Override
            public void onMatrixChanged(RectF imageRect) {
            final Rect visibleRect = ImageViewUtil.getBitmapRectCenterInside(photoAttacher.getVisibleRectangleBitmap(), photoAttacher.getImageView());
    
            imageRect.top = Math.max(imageRect.top, visibleRect.top);
            imageRect.left = Math.max(imageRect.left, visibleRect.left);
            imageRect.right = Math.min(imageRect.right, visibleRect.right);
            imageRect.bottom = Math.min(imageRect.bottom, visibleRect.bottom);
    
            Rect bitmapRect = new Rect();
            imageRect.round(bitmapRect);
    
            mCropOverlayView.changeBitmapRectInvalidate(bitmapRect);
            }
        });
    }
    

    在 CropOverlayView 中:

    @Override
    public boolean onTouchEvent(MotionEvent event) {
    
        // If this View is not enabled, don't allow for touch interactions.
        if (!isEnabled()) {
            return false;
        }
    
        switch (event.getAction()) {
    
            case MotionEvent.ACTION_DOWN:
                return onActionDown(event.getX(), event.getY());
    
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                getParent().requestDisallowInterceptTouchEvent(false);
                return onActionUp();
    
            case MotionEvent.ACTION_MOVE:
                boolean result = onActionMove(event.getX(), event.getY());
                getParent().requestDisallowInterceptTouchEvent(true);
                return result;
    
            default:
                return false;
        }
    }
    
    public void changeBitmapRectInvalidate(Rect bitmapRect) {
        mBitmapRect = bitmapRect;
        invalidate();
    }
    
    private boolean onActionDown(float x, float y) {
    
        final float left = Edge.LEFT.getCoordinate();
        final float top = Edge.TOP.getCoordinate();
        final float right = Edge.RIGHT.getCoordinate();
        final float bottom = Edge.BOTTOM.getCoordinate();
    
        mPressedHandle = HandleUtil.getPressedHandle(x, y, left, top, right, bottom, mHandleRadius);
    
        if (mPressedHandle == null){
            return false;
        }
    
        // Calculate the offset of the touch point from the precise location
        // of the handle. Save these values in a member variable since we want
        // to maintain this offset as we drag the handle.
        mTouchOffset = HandleUtil.getOffset(mPressedHandle, x, y, left, top, right, bottom);
    
        invalidate();
        return true;
    }
    
    /**
     * Handles a {@link MotionEvent#ACTION_UP} or
     * {@link MotionEvent#ACTION_CANCEL} event.
     * @return true if event vas handled, else - false
     */
    private boolean onActionUp() {
    
        if (mPressedHandle == null)
            return false;
    
        mPressedHandle = null;
    
        invalidate();
        return true;
    }
    
    /**
     * Handles a {@link MotionEvent#ACTION_MOVE} event.
     * 
     * @param x the x-coordinate of the move event
     * @param y the y-coordinate of the move event
     */
    private boolean onActionMove(float x, float y) {
    
        if (mPressedHandle == null)
            return false;
    
        // Adjust the coordinates for the finger position's offset (i.e. the
        // distance from the initial touch to the precise handle location).
        // We want to maintain the initial touch's distance to the pressed
        // handle so that the crop window size does not "jump".
        x += mTouchOffset.first;
        y += mTouchOffset.second;
    
        // Calculate the new crop window size/position.
        if (mFixAspectRatio) {
            mPressedHandle.updateCropWindow(x, y, mTargetAspectRatio, mBitmapRect, mSnapRadius);
        } else {
            mPressedHandle.updateCropWindow(x, y, mBitmapRect, mSnapRadius);
        }
        invalidate();
        return true;
    }
    

    为了正确获得裁剪图像,您应该使用@Nikola Despotoski 答案的第二部分

    【讨论】:

    • CropOverlayView的setOutlineTouchEventReceiver(ImageView imageView)需要写什么?
    • @Ramesh Akula 什么都没有,除了默认库代码。 public void setOutlineTouchEventReceiver(查看 outlineTouchEventReceiver) { this.outlineTouchEventReceiver = outlineTouchEventReceiver; }
    【解决方案3】:

    我也有同样的要求。我通过在cropper lib中将ImageView替换为PhotoView,结合PhotoViewCropper解决了这个问题。

    我不得不修改CropWindow 类以避免触摸事件未被正确处理:

       public void setImageView(PhotoView pv){
            mPhotoView = pv;
        }
        @Override
        public boolean onTouchEvent(MotionEvent event) {
    
            // If this View is not enabled, don't allow for touch interactions.
            if (!isEnabled()) {
                return false;
            }
    
            switch (event.getAction()) {
    
                case MotionEvent.ACTION_DOWN:
                      boolean dispatch = onActionDown(event.getX(), event.getY());
                      if(!dispatch)
                        mPhotoView.dispatchTouchEvent(event);
    
                    return dispatch;
    
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    getParent().requestDisallowInterceptTouchEvent(false);
                    onActionUp();
                    return true;
    
                case MotionEvent.ACTION_MOVE:
                    onActionMove(event.getX(), event.getY());
                    getParent().requestDisallowInterceptTouchEvent(true);
                    return true;
    
                default:
                    return false;
            }
        }
    

    CropImageView 类中也发生了一些变化:

    private void init(Context context) {
    
        final LayoutInflater inflater = LayoutInflater.from(context);
        final View v = inflater.inflate(R.layout.crop_image_view, this, true);
    
        mImageView = (PhotoView) v.findViewById(R.id.ImageView_image2);
    
        setImageResource(mImageResource);
        mCropOverlayView = (CropOverlayView) v.findViewById(R.id.CropOverlayView);
        mCropOverlayView.setInitialAttributeValues(mGuidelines, mFixAspectRatio, mAspectRatioX, mAspectRatioY);
        mCropOverlayView.setImageView(mImageView);
    }
    

    您可以注意到我在 Cropper 库中的 R.layout.crop_image_view 中将 ImageView 替换为 PhotoView

    Cropper 库支持固定大小,PhotoView 允许您移动和缩放照片,为您提供两全其美的效果。 :)

    希望对你有帮助。

    编辑,对于那些询问如何获取仅在裁剪区域内的图像的人:

    private Bitmap getCurrentDisplayedImage(){
            Bitmap result = Bitmap.createBitmap(mImageView.getWidth(), mImageView.getHeight(), Bitmap.Config.RGB_565);
            Canvas c = new Canvas(result);
            mImageView.draw(c);
            return result;
        }
        public Bitmap getCroppedImage() {
    
            Bitmap mCurrentDisplayedBitmap = getCurrentDisplayedImage();
            final Rect displayedImageRect = ImageViewUtil2.getBitmapRectCenterInside(mCurrentDisplayedBitmap, mImageView);
    
            // Get the scale factor between the actual Bitmap dimensions and the
            // displayed dimensions for width.
            final float actualImageWidth =mCurrentDisplayedBitmap.getWidth();
            final float displayedImageWidth = displayedImageRect.width();
            final float scaleFactorWidth = actualImageWidth / displayedImageWidth;
    
            // Get the scale factor between the actual Bitmap dimensions and the
            // displayed dimensions for height.
            final float actualImageHeight = mCurrentDisplayedBitmap.getHeight();
            final float displayedImageHeight = displayedImageRect.height();
            final float scaleFactorHeight = actualImageHeight / displayedImageHeight;
    
            // Get crop window position relative to the displayed image.
            final float cropWindowX = Edge.LEFT.getCoordinate() - displayedImageRect.left;
            final float cropWindowY = Edge.TOP.getCoordinate() - displayedImageRect.top;
            final float cropWindowWidth = Edge.getWidth();
            final float cropWindowHeight = Edge.getHeight();
    
            // Scale the crop window position to the actual size of the Bitmap.
            final float actualCropX = cropWindowX * scaleFactorWidth;
            final float actualCropY = cropWindowY * scaleFactorHeight;
            final float actualCropWidth = cropWindowWidth * scaleFactorWidth;
            final float actualCropHeight = cropWindowHeight * scaleFactorHeight;
    
            // Crop the subset from the original Bitmap.
            final Bitmap croppedBitmap = Bitmap.createBitmap(mCurrentDisplayedBitmap,
                                                             (int) actualCropX,
                                                             (int) actualCropY,
                                                             (int) actualCropWidth,
                                                             (int) actualCropHeight);
    
            return croppedBitmap;
        }
    
        public RectF getActualCropRect() {
    
            final Rect displayedImageRect = ImageViewUtil.getBitmapRectCenterInside(mBitmap, mImageView);
    
            final float actualImageWidth = mBitmap.getWidth();
            final float displayedImageWidth = displayedImageRect.width();
            final float scaleFactorWidth = actualImageWidth / displayedImageWidth;
    
            // Get the scale factor between the actual Bitmap dimensions and the displayed
            // dimensions for height.
            final float actualImageHeight = mBitmap.getHeight();
            final float displayedImageHeight = displayedImageRect.height();
            final float scaleFactorHeight = actualImageHeight / displayedImageHeight;
    
            // Get crop window position relative to the displayed image.
            final float displayedCropLeft = Edge.LEFT.getCoordinate() - displayedImageRect.left;
            final float displayedCropTop = Edge.TOP.getCoordinate() - displayedImageRect.top;
            final float displayedCropWidth = Edge.getWidth();
            final float displayedCropHeight = Edge.getHeight();
    
            // Scale the crop window position to the actual size of the Bitmap.
            float actualCropLeft = displayedCropLeft * scaleFactorWidth;
            float actualCropTop = displayedCropTop * scaleFactorHeight;
            float actualCropRight = actualCropLeft + displayedCropWidth * scaleFactorWidth;
            float actualCropBottom = actualCropTop + displayedCropHeight * scaleFactorHeight;
    
            // Correct for floating point errors. Crop rect boundaries should not exceed the
            // source Bitmap bounds.
            actualCropLeft = Math.max(0f, actualCropLeft);
            actualCropTop = Math.max(0f, actualCropTop);
            actualCropRight = Math.min(mBitmap.getWidth(), actualCropRight);
            actualCropBottom = Math.min(mBitmap.getHeight(), actualCropBottom);
    
            final RectF actualCropRect = new RectF(actualCropLeft,
                                                   actualCropTop,
                                                   actualCropRight,
                                                   actualCropBottom);
    
            return actualCropRect;
        }
    
    
    
    
    private boolean onActionDown(float x, float y) {    
            final float left = Edge.LEFT.getCoordinate();
            final float top = Edge.TOP.getCoordinate();
            final float right = Edge.RIGHT.getCoordinate();
            final float bottom = Edge.BOTTOM.getCoordinate();    
            mPressedHandle = HandleUtil.getPressedHandle(x, y, left, top, right, bottom, mHandleRadius);    
            if (mPressedHandle == null)
                return false;
            mTouchOffset = HandleUtil2.getOffset(mPressedHandle, x, y, left, top, right, bottom);
    
            invalidate();
            return true;
        }
    

    【讨论】:

    • 你好尼古拉,很好的答案。我想问一下您是如何裁剪缩放图像的。因为我无法得到完全正确的比例。我尝试的一种方法是使用 PhotoViewAttacher 类中的 getscale 函数跟踪图像缩放,并将这些缩放应用到cropImageView 的 getcroppedImage 中。你能告诉我你是怎么做到的吗?
    • @kesari 很抱歉我的回复晚了。请检查我上次的编辑,我添加了一些与裁剪相关的方法。
    • @NikolaDespotoski 嗨,我也有同样的要求,我尝试了你的代码,但它不起作用。不知道确切的问题是什么。你能分享一下你的代码吗?
    • @NikolaDespotoski 我们可以先修一下盒子吗?我尝试了上面的代码块,它可以工作。但在我的场景中,盒子的宽度和高度是固定的。我们能做到这一点吗?
    • @NikolaDespotoski 我也使用相同的库进行裁剪,我遇到了这个问题。我会解释的。我在应用矫直后使用矩阵对图像应用矫直我无法在整个图像中移动cropoverlay视图,即使我也将cropoverlay更改为最小。我想在整个图像中移动cropoverlay。我怎样才能解决这个问题?请帮助我..谢谢..
    【解决方案4】:

    你想要的完全可以通过这个库simple-crop-image-lib实现

    【讨论】:

      猜你喜欢
      • 2014-05-19
      • 2012-07-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-08
      相关资源
      最近更新 更多