如何像Facebook个人资料图像选择工具那样移动和缩放图像?

时间:2014-03-27 09:53:18

标签: android image crop

我希望裁剪图像像Android上的Facebook个人资料图片选择,用户可以移动和缩放图像,从而调整图像大小和/或裁剪:

screenshot of the tool in question

我怎么能做到这一点?

4 个答案:

答案 0 :(得分:15)

我有同样的要求。我通过在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);
}

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

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;
    }

答案 1 :(得分:2)

我对@Nikola Despotoski的答案有一些补充。 首先,您不必将R.layout.crop_image_view中的ImageView更改为PhotoView,因为PhotoView逻辑可以简单地作为新的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答案的第二部分

答案 2 :(得分:1)

这个lib simple-crop-image-lib

可以完全实现你想要的

答案 3 :(得分:0)

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

https://github.com/ozeetee/AndroidImageZoomCrop