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