diff --git a/CHANGELOG.md b/CHANGELOG.md index 7789ec29..33626c2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [unreleased x.x.x] - ### Added - Vertical-only and horizontal-only cropping modes [#76]((https://github.com/CanHub/Android-Image-Cropper/pull/76)) +- Option to disable movement of the crop window by dragging the center [#79](https://github.com/CanHub/Android-Image-Cropper/pull/79) ### Fixed - Turkish Translations [#72](https://github.com/CanHub/Android-Image-Cropper/pull/72) diff --git a/cropper/src/main/java/com/canhub/cropper/CropImage.java b/cropper/src/main/java/com/canhub/cropper/CropImage.java index 523b6a70..895551b8 100644 --- a/cropper/src/main/java/com/canhub/cropper/CropImage.java +++ b/cropper/src/main/java/com/canhub/cropper/CropImage.java @@ -676,6 +676,15 @@ public ActivityBuilder setMultiTouchEnabled(boolean multiTouchEnabled) { return this; } + /** + * if the crop window can be moved by dragging the center.
+ * default: true + */ + public ActivityBuilder setCenterMoveEnabled(boolean centerMoveEnabled) { + mOptions.centerMoveEnabled = centerMoveEnabled; + return this; + } + /** * The max zoom allowed during cropping.
* Default: 4 diff --git a/cropper/src/main/java/com/canhub/cropper/CropImageOptions.kt b/cropper/src/main/java/com/canhub/cropper/CropImageOptions.kt index a9f4631e..a43148b4 100644 --- a/cropper/src/main/java/com/canhub/cropper/CropImageOptions.kt +++ b/cropper/src/main/java/com/canhub/cropper/CropImageOptions.kt @@ -73,6 +73,10 @@ open class CropImageOptions : Parcelable { @JvmField var multiTouchEnabled: Boolean + /** if the the crop window can be moved by dragging the center; default: true */ + @JvmField + var centerMoveEnabled: Boolean + /** The max zoom allowed during cropping. */ @JvmField var maxZoom: Int @@ -259,6 +263,7 @@ open class CropImageOptions : Parcelable { showProgressBar = true autoZoomEnabled = true multiTouchEnabled = false + centerMoveEnabled = true maxZoom = 4 initialCropWindowPaddingRatio = 0.1f fixAspectRatio = false @@ -312,6 +317,7 @@ open class CropImageOptions : Parcelable { showProgressBar = parcel.readByte().toInt() != 0 autoZoomEnabled = parcel.readByte().toInt() != 0 multiTouchEnabled = parcel.readByte().toInt() != 0 + centerMoveEnabled = parcel.readByte().toInt() != 0 maxZoom = parcel.readInt() initialCropWindowPaddingRatio = parcel.readFloat() fixAspectRatio = parcel.readByte().toInt() != 0 @@ -363,6 +369,7 @@ open class CropImageOptions : Parcelable { dest.writeByte((if (showProgressBar) 1 else 0).toByte()) dest.writeByte((if (autoZoomEnabled) 1 else 0).toByte()) dest.writeByte((if (multiTouchEnabled) 1 else 0).toByte()) + dest.writeByte((if (centerMoveEnabled) 1 else 0).toByte()) dest.writeInt(maxZoom) dest.writeFloat(initialCropWindowPaddingRatio) dest.writeByte((if (fixAspectRatio) 1 else 0).toByte()) diff --git a/cropper/src/main/java/com/canhub/cropper/CropImageView.java b/cropper/src/main/java/com/canhub/cropper/CropImageView.java index e3a7e9c0..d3fe4cd0 100644 --- a/cropper/src/main/java/com/canhub/cropper/CropImageView.java +++ b/cropper/src/main/java/com/canhub/cropper/CropImageView.java @@ -26,7 +26,6 @@ import android.os.Parcelable; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.exifinterface.media.ExifInterface; import androidx.fragment.app.FragmentActivity; @@ -217,6 +216,9 @@ public CropImageView(Context context, AttributeSet attrs) { options.multiTouchEnabled = ta.getBoolean( R.styleable.CropImageView_cropMultiTouchEnabled, options.multiTouchEnabled); + options.centerMoveEnabled = + ta.getBoolean( + R.styleable.CropImageView_cropCenterMoveEnabled, options.centerMoveEnabled); options.maxZoom = ta.getInteger(R.styleable.CropImageView_cropMaxZoom, options.maxZoom); options.cropShape = CropShape.values()[ @@ -412,6 +414,14 @@ public void setMultiTouchEnabled(boolean multiTouchEnabled) { } } + /** Set moving of the crop window by dragging the center to enabled/disabled. */ + public void setCenterMoveEnabled(boolean centerMoveEnabled) { + if (mCropOverlayView.setCenterMoveEnabled(centerMoveEnabled)) { + handleCropWindowChanged(false, false); + mCropOverlayView.invalidate(); + } + } + /** The max zoom allowed during cropping. */ public int getMaxZoom() { return mMaxZoom; diff --git a/cropper/src/main/java/com/canhub/cropper/CropOverlayView.java b/cropper/src/main/java/com/canhub/cropper/CropOverlayView.java index 4a3e838c..ea7837e9 100644 --- a/cropper/src/main/java/com/canhub/cropper/CropOverlayView.java +++ b/cropper/src/main/java/com/canhub/cropper/CropOverlayView.java @@ -40,6 +40,9 @@ public class CropOverlayView extends View { /** Boolean to see if multi touch is enabled for the crop rectangle */ private boolean mMultiTouchEnabled; + /** Boolean to see if movement via dragging center is enabled for the crop rectangle */ + private boolean mCenterMoveEnabled = true; + /** Handler from crop window stuff, moving and knowing possition. */ private final CropWindowHandler mCropWindowHandler = new CropWindowHandler(); @@ -330,6 +333,15 @@ public boolean setMultiTouchEnabled(boolean multiTouchEnabled) { return false; } + /** Set movement of the crop window by dragging the center to enabled/disabled. */ + public boolean setCenterMoveEnabled(boolean centerMoveEnabled) { + if (mCenterMoveEnabled != centerMoveEnabled) { + mCenterMoveEnabled = centerMoveEnabled; + return true; + } + return false; + } + /** * the min size the resulting cropping image is allowed to be, affects the cropping window limits * (in pixels).
@@ -402,6 +414,8 @@ public void setInitialAttributeValues(CropImageOptions options) { setMultiTouchEnabled(options.multiTouchEnabled); + setCenterMoveEnabled(options.centerMoveEnabled); + mTouchRadius = options.touchRadius; mInitialCropWindowPaddingRatio = options.initialCropWindowPaddingRatio; @@ -918,7 +932,8 @@ public boolean onTouchEvent(MotionEvent event) { * if press is far from crop window then no move handler is returned (null). */ private void onActionDown(float x, float y) { - mMoveHandler = mCropWindowHandler.getMoveHandler(x, y, mTouchRadius, mCropShape); + mMoveHandler = mCropWindowHandler + .getMoveHandler(x, y, mTouchRadius, mCropShape, mCenterMoveEnabled); if (mMoveHandler != null) { invalidate(); } diff --git a/cropper/src/main/java/com/canhub/cropper/CropWindowHandler.kt b/cropper/src/main/java/com/canhub/cropper/CropWindowHandler.kt index cd44e9ae..f230dd80 100644 --- a/cropper/src/main/java/com/canhub/cropper/CropWindowHandler.kt +++ b/cropper/src/main/java/com/canhub/cropper/CropWindowHandler.kt @@ -160,11 +160,13 @@ class CropWindowHandler { /** * Determines which, if any, of the handles are pressed given the touch coordinates, the bounding - * box, and the touch radius. + * box, the touch radius, the crop shape and whether movement of the crop window is enabled. * * @param x the x-coordinate of the touch point * @param y the y-coordinate of the touch point * @param targetRadius the target radius in pixels + * @param cropShape the shape of the crop window + * @param isCenterMoveEnabled whether movement of the crop window by dragging center is enabled * @return the Handle that was pressed; null if no Handle was pressed */ fun getMoveHandler( @@ -172,12 +174,13 @@ class CropWindowHandler { y: Float, targetRadius: Float, cropShape: CropImageView.CropShape, + isCenterMoveEnabled: Boolean ): CropWindowMoveHandler? { val type: CropWindowMoveHandler.Type? = when (cropShape) { - RECTANGLE -> getRectanglePressedMoveType(x, y, targetRadius) - OVAL -> getOvalPressedMoveType(x, y) - RECTANGLE_VERTICAL_ONLY -> getRectangleVerticalOnlyPressedMoveType(x, y, targetRadius) - RECTANGLE_HORIZONTAL_ONLY -> getRectangleHorizontalOnlyPressedMoveType(x, y, targetRadius) + RECTANGLE -> getRectanglePressedMoveType(x, y, targetRadius, isCenterMoveEnabled) + OVAL -> getOvalPressedMoveType(x, y, isCenterMoveEnabled) + RECTANGLE_VERTICAL_ONLY -> getRectangleVerticalOnlyPressedMoveType(x, y, targetRadius, isCenterMoveEnabled) + RECTANGLE_HORIZONTAL_ONLY -> getRectangleHorizontalOnlyPressedMoveType(x, y, targetRadius, isCenterMoveEnabled) } return if (type != null) CropWindowMoveHandler(type, this, x, y) else null @@ -192,12 +195,14 @@ class CropWindowHandler { * @param x the x-coordinate of the touch point * @param y the y-coordinate of the touch point * @param targetRadius the target radius in pixels + * @param isCenterMoveEnabled whether movement of the crop window by dragging center is enabled * @return the Handle that was pressed; null if no Handle was pressed */ private fun getRectanglePressedMoveType( x: Float, y: Float, - targetRadius: Float + targetRadius: Float, + isCenterMoveEnabled: Boolean ): CropWindowMoveHandler.Type? { // Note: corner-handles take precedence, then side-handles, then center. @@ -214,7 +219,8 @@ class CropWindowHandler { isInCornerTargetZone(x, y, mEdges.right, mEdges.bottom, targetRadius) -> { CropWindowMoveHandler.Type.BOTTOM_RIGHT } - isInCenterTargetZone(x, y, mEdges.left, mEdges.top, mEdges.right, mEdges.bottom) && + isCenterMoveEnabled && + isInCenterTargetZone(x, y, mEdges.left, mEdges.top, mEdges.right, mEdges.bottom) && focusCenter() -> { CropWindowMoveHandler.Type.CENTER } @@ -230,7 +236,8 @@ class CropWindowHandler { isInVerticalTargetZone(x, y, mEdges.right, mEdges.top, mEdges.bottom, targetRadius) -> { CropWindowMoveHandler.Type.RIGHT } - isInCenterTargetZone(x, y, mEdges.left, mEdges.top, mEdges.right, mEdges.bottom) && + isCenterMoveEnabled && + isInCenterTargetZone(x, y, mEdges.left, mEdges.top, mEdges.right, mEdges.bottom) && !focusCenter() -> { CropWindowMoveHandler.Type.CENTER } @@ -244,9 +251,14 @@ class CropWindowHandler { * * @param x the x-coordinate of the touch point * @param y the y-coordinate of the touch point + * @param isCenterMoveEnabled whether movement of the crop window by dragging center is enabled * @return the Handle that was pressed; null if no Handle was pressed */ - private fun getOvalPressedMoveType(x: Float, y: Float): CropWindowMoveHandler.Type { + private fun getOvalPressedMoveType( + x: Float, + y: Float, + isCenterMoveEnabled: Boolean + ): CropWindowMoveHandler.Type? { /* Use a 6x6 grid system divided into 9 "handles", with the center the biggest region. While this is not perfect, it's a good quick-to-ship approach. @@ -276,7 +288,9 @@ class CropWindowHandler { x < rightCenter -> { when { y < topCenter -> CropWindowMoveHandler.Type.TOP - y < bottomCenter -> CropWindowMoveHandler.Type.CENTER + y < bottomCenter -> if (isCenterMoveEnabled) { + CropWindowMoveHandler.Type.CENTER + } else null else -> CropWindowMoveHandler.Type.BOTTOM } } @@ -297,12 +311,14 @@ class CropWindowHandler { * @param x the x-coordinate of the touch point * @param y the y-coordinate of the touch point * @param targetRadius the target radius in pixels + * @param isCenterMoveEnabled whether movement of the crop window by dragging center is enabled * @return the Handle that was pressed; null if no Handle was pressed */ private fun getRectangleVerticalOnlyPressedMoveType( x: Float, y: Float, - targetRadius: Float + targetRadius: Float, + isCenterMoveEnabled: Boolean ): CropWindowMoveHandler.Type? { // Note: top and bottom handles take precedence, then center. @@ -315,7 +331,8 @@ class CropWindowHandler { distance(x, y, mEdges.centerX(), mEdges.bottom) <= targetRadius -> { CropWindowMoveHandler.Type.BOTTOM } - isInCenterTargetZone(x, y, mEdges.left, mEdges.top, mEdges.right, mEdges.bottom) -> { + isCenterMoveEnabled && + isInCenterTargetZone(x, y, mEdges.left, mEdges.top, mEdges.right, mEdges.bottom) -> { CropWindowMoveHandler.Type.CENTER } else -> null @@ -329,12 +346,14 @@ class CropWindowHandler { * @param x the x-coordinate of the touch point * @param y the y-coordinate of the touch point * @param targetRadius the target radius in pixels + * @param isCenterMoveEnabled whether movement of the crop window by dragging center is enabled * @return the Handle that was pressed; null if no Handle was pressed */ private fun getRectangleHorizontalOnlyPressedMoveType( x: Float, y: Float, - targetRadius: Float + targetRadius: Float, + isCenterMoveEnabled: Boolean ): CropWindowMoveHandler.Type? { // Note: left and right handles take precedence, then center. @@ -347,7 +366,8 @@ class CropWindowHandler { distance(x, y, mEdges.right, mEdges.centerY()) <= targetRadius -> { CropWindowMoveHandler.Type.RIGHT } - isInCenterTargetZone(x, y, mEdges.left, mEdges.top, mEdges.right, mEdges.bottom) -> { + isCenterMoveEnabled && + isInCenterTargetZone(x, y, mEdges.left, mEdges.top, mEdges.right, mEdges.bottom) -> { CropWindowMoveHandler.Type.CENTER } else -> null diff --git a/cropper/src/main/res/values/attrs.xml b/cropper/src/main/res/values/attrs.xml index 50c25032..754bc4a9 100644 --- a/cropper/src/main/res/values/attrs.xml +++ b/cropper/src/main/res/values/attrs.xml @@ -21,6 +21,7 @@ + diff --git a/sample/src/main/java/com/canhub/cropper/sample/camera/app/CameraFragment.kt b/sample/src/main/java/com/canhub/cropper/sample/camera/app/CameraFragment.kt index 62ec6555..4a6bfe58 100644 --- a/sample/src/main/java/com/canhub/cropper/sample/camera/app/CameraFragment.kt +++ b/sample/src/main/java/com/canhub/cropper/sample/camera/app/CameraFragment.kt @@ -117,6 +117,7 @@ internal class CameraFragment : .setMaxZoom(8) .setAutoZoomEnabled(false) .setMultiTouchEnabled(false) + .setCenterMoveEnabled(true) .setShowCropOverlay(false) .setAllowFlipping(false) .setSnapRadius(10f) @@ -165,6 +166,7 @@ internal class CameraFragment : .setMaxZoom(4) .setAutoZoomEnabled(true) .setMultiTouchEnabled(true) + .setCenterMoveEnabled(true) .setShowCropOverlay(true) .setAllowFlipping(true) .setSnapRadius(3f) diff --git a/sample/src/main/java/com/canhub/cropper/sample/crop_image_view/app/CropImageViewFragment.kt b/sample/src/main/java/com/canhub/cropper/sample/crop_image_view/app/CropImageViewFragment.kt index 3d9d71f2..2f0799d9 100644 --- a/sample/src/main/java/com/canhub/cropper/sample/crop_image_view/app/CropImageViewFragment.kt +++ b/sample/src/main/java/com/canhub/cropper/sample/crop_image_view/app/CropImageViewFragment.kt @@ -111,6 +111,7 @@ internal class CropImageViewFragment : setAspectRatio(options.ratio.first, options.ratio.second) } setMultiTouchEnabled(options.multiTouch) + setCenterMoveEnabled(options.centerMove) isShowCropOverlay = options.showCropOverlay isShowProgressBar = options.showProgressBar isAutoZoomEnabled = options.autoZoom diff --git a/sample/src/main/java/com/canhub/cropper/sample/crop_image_view/presenter/CropImageViewPresenter.kt b/sample/src/main/java/com/canhub/cropper/sample/crop_image_view/presenter/CropImageViewPresenter.kt index 4154a25f..fe46b4b3 100644 --- a/sample/src/main/java/com/canhub/cropper/sample/crop_image_view/presenter/CropImageViewPresenter.kt +++ b/sample/src/main/java/com/canhub/cropper/sample/crop_image_view/presenter/CropImageViewPresenter.kt @@ -28,6 +28,7 @@ internal class CropImageViewPresenter : CropImageViewContract.Presenter { autoZoom = true, maxZoomLvl = 2, multiTouch = true, + centerMove = true, showCropOverlay = true, showProgressBar = true, flipHorizontal = false, diff --git a/sample/src/main/java/com/canhub/cropper/sample/options_dialog/app/OptionsDialogBottomSheet.kt b/sample/src/main/java/com/canhub/cropper/sample/options_dialog/app/OptionsDialogBottomSheet.kt index 196d5845..8feb0bb1 100644 --- a/sample/src/main/java/com/canhub/cropper/sample/options_dialog/app/OptionsDialogBottomSheet.kt +++ b/sample/src/main/java/com/canhub/cropper/sample/options_dialog/app/OptionsDialogBottomSheet.kt @@ -168,6 +168,10 @@ internal class OptionsDialogBottomSheet : BottomSheetDialogFragment(), OptionsCo presenter.onMultiTouchSelect(isChecked) } + binding.centerMoveEnabled.toggle.setOnCheckedChangeListener { _, isChecked -> + presenter.onCenterMoveSelect(isChecked) + } + binding.progressBar.toggle.setOnCheckedChangeListener { _, isChecked -> presenter.onProgressBarSelect(isChecked) } @@ -210,6 +214,7 @@ internal class OptionsDialogBottomSheet : BottomSheetDialogFragment(), OptionsCo binding.autoZoom.toggle.isChecked = options.autoZoom binding.multiTouch.toggle.isChecked = options.multiTouch + binding.centerMoveEnabled.toggle.isChecked = options.centerMove binding.cropOverlay.toggle.isChecked = options.showCropOverlay binding.progressBar.toggle.isChecked = options.showProgressBar binding.flipHorizontal.toggle.isChecked = options.flipHorizontal diff --git a/sample/src/main/java/com/canhub/cropper/sample/options_dialog/domain/OptionsContract.kt b/sample/src/main/java/com/canhub/cropper/sample/options_dialog/domain/OptionsContract.kt index c2b94ea3..38e870bf 100644 --- a/sample/src/main/java/com/canhub/cropper/sample/options_dialog/domain/OptionsContract.kt +++ b/sample/src/main/java/com/canhub/cropper/sample/options_dialog/domain/OptionsContract.kt @@ -26,6 +26,7 @@ internal interface OptionsContract { fun onAutoZoomSelect(enable: Boolean) fun onMaxZoomLvlSelect(maxZoom: Int) fun onMultiTouchSelect(enable: Boolean) + fun onCenterMoveSelect(enable: Boolean) fun onCropOverlaySelect(show: Boolean) fun onProgressBarSelect(show: Boolean) fun onFlipHorizontalSelect(enable: Boolean) diff --git a/sample/src/main/java/com/canhub/cropper/sample/options_dialog/domain/OptionsDomain.kt b/sample/src/main/java/com/canhub/cropper/sample/options_dialog/domain/OptionsDomain.kt index 17c6ab57..f622a8ee 100644 --- a/sample/src/main/java/com/canhub/cropper/sample/options_dialog/domain/OptionsDomain.kt +++ b/sample/src/main/java/com/canhub/cropper/sample/options_dialog/domain/OptionsDomain.kt @@ -14,6 +14,7 @@ internal data class OptionsDomain( val maxZoomLvl: Int, val autoZoom: Boolean, val multiTouch: Boolean, + val centerMove: Boolean, val showCropOverlay: Boolean, val showProgressBar: Boolean, val flipHorizontal: Boolean, diff --git a/sample/src/main/java/com/canhub/cropper/sample/options_dialog/presenter/OptionsPresenter.kt b/sample/src/main/java/com/canhub/cropper/sample/options_dialog/presenter/OptionsPresenter.kt index bf557dfd..1fca6aa4 100644 --- a/sample/src/main/java/com/canhub/cropper/sample/options_dialog/presenter/OptionsPresenter.kt +++ b/sample/src/main/java/com/canhub/cropper/sample/options_dialog/presenter/OptionsPresenter.kt @@ -54,6 +54,10 @@ internal class OptionsPresenter : OptionsContract.Presenter { options = options.copy(multiTouch = enable) } + override fun onCenterMoveSelect(enable: Boolean) { + options = options.copy(centerMove = enable) + } + override fun onCropOverlaySelect(show: Boolean) { options = options.copy(showCropOverlay = show) } @@ -78,6 +82,7 @@ internal class OptionsPresenter : OptionsContract.Presenter { maxZoomLvl = 2, autoZoom = true, multiTouch = true, + centerMove = true, showCropOverlay = true, showProgressBar = true, flipHorizontal = false, diff --git a/sample/src/main/res/layout/fragment_options.xml b/sample/src/main/res/layout/fragment_options.xml index 44ee5cd7..8a9976a1 100644 --- a/sample/src/main/res/layout/fragment_options.xml +++ b/sample/src/main/res/layout/fragment_options.xml @@ -85,6 +85,10 @@ android:id="@+id/multiTouch" layout="@layout/switch_multi_touch" /> + + diff --git a/sample/src/main/res/layout/switch_center_move.xml b/sample/src/main/res/layout/switch_center_move.xml new file mode 100644 index 00000000..84808ab1 --- /dev/null +++ b/sample/src/main/res/layout/switch_center_move.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml index 374e61bd..fc9d9854 100644 --- a/sample/src/main/res/values/strings.xml +++ b/sample/src/main/res/values/strings.xml @@ -30,6 +30,7 @@ Set initial crop rectangle Reset crop rectangle to initial Multitouch: %1s + Center move enabled: %1s Show Overlay: %1s Show Progress Bar: %1s @@ -64,6 +65,7 @@ Auto Zoom Fix Aspect Ratio Multi Touch + Enable movement by dragging center Show Crop Overlay Show Progress Bar Flip Horizontal