Android设置ImageView可以移动、旋转、缩放
<com.example.testdemo.CropImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="matrix" />
设置scaleType模式可以使用矩阵
import android.content.Context
import android.graphics.Matrix
import android.util.AttributeSet
import android.view.GestureDetector
import android.view.GestureDetector.SimpleOnGestureListener
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import android.view.ScaleGestureDetector.SimpleOnScaleGestureListener
import androidx.appcompat.widget.AppCompatImageView
/**
* @author Kawa
* @date 2023/4/21
* @Description
*/
class CropImageView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null,
) : AppCompatImageView(context, attrs) {
private var mGestureDetector: GestureDetector? = null
private var mScaleDetector: ScaleGestureDetector? = null
private var mRotateDetector: RotationGestureDetector? = null
private var mMidPntX = 0f
private var mMidPntY = 0f
private val mCurrentImageMatrix by lazy {
Matrix()
}
init {
setupGestureListeners()
}
private fun setupGestureListeners() {
mGestureDetector = GestureDetector(
context,
GestureListener(),
null,
true
)
mScaleDetector = ScaleGestureDetector(context, ScaleListener())
mRotateDetector =
RotationGestureDetector(RotateListener())
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
if (event!!.pointerCount > 1) {
mMidPntX = (event.getX(0) + event.getX(1)) / 2
mMidPntY = (event.getY(0) + event.getY(1)) / 2
}
mGestureDetector!!.onTouchEvent(event!!)
mScaleDetector!!.onTouchEvent(event)
mRotateDetector!!.onTouchEvent(event)
return true
}
private inner class GestureListener : SimpleOnGestureListener() {
override fun onScroll(
e1: MotionEvent,
e2: MotionEvent,
distanceX: Float,
distanceY: Float,
): Boolean {
postTranslate(-distanceX, -distanceY)
return true
}
}
private inner class ScaleListener : SimpleOnScaleGestureListener() {
override fun onScale(detector: ScaleGestureDetector): Boolean {
postScale(detector.scaleFactor, mMidPntX, mMidPntY)
return true
}
}
private inner class RotateListener : RotationGestureDetector.SimpleOnRotationGestureListener() {
override fun onRotation(rotationDetector: RotationGestureDetector): Boolean {
postRotate(rotationDetector.angle, mMidPntX, mMidPntY)
return true
}
}
/**
* This method rotates current image.
*
* @param deltaAngle - rotation angle
* @param px - rotation center X
* @param py - rotation center Y
*/
fun postRotate(deltaAngle: Float, px: Float, py: Float) {
if (deltaAngle != 0f) {
mCurrentImageMatrix.postRotate(deltaAngle, px, py)
imageMatrix = mCurrentImageMatrix
}
}
/**
* This method translates current image.
*
* @param deltaX - horizontal shift
* @param deltaY - vertical shift
*/
fun postTranslate(deltaX: Float, deltaY: Float) {
if (deltaX != 0f || deltaY != 0f) {
mCurrentImageMatrix.postTranslate(deltaX, deltaY)
imageMatrix = mCurrentImageMatrix
}
}
/**
* This method changes image scale for given value related to point (px, py) but only if
* resulting scale is in min/max bounds.
*
* @param deltaScale - scale value
* @param px - scale center X
* @param py - scale center Y
*/
fun postScale(deltaScale: Float, px: Float, py: Float) {
if (deltaScale != 0f) {
mCurrentImageMatrix.postScale(deltaScale, deltaScale, px, py)
imageMatrix = mCurrentImageMatrix
}
}
}
/**
* @author Kawa
* @date 2023/4/21
* @Description
*/
import android.view.MotionEvent;
import androidx.annotation.NonNull;
public class RotationGestureDetector {
private static final int INVALID_POINTER_INDEX = -1;
private float fX, fY, sX, sY;
private int mPointerIndex1, mPointerIndex2;
private float mAngle;
private boolean mIsFirstTouch;
private OnRotationGestureListener mListener;
public RotationGestureDetector(OnRotationGestureListener listener) {
mListener = listener;
mPointerIndex1 = INVALID_POINTER_INDEX;
mPointerIndex2 = INVALID_POINTER_INDEX;
}
public float getAngle() {
return mAngle;
}
public boolean onTouchEvent(@NonNull MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
sX = event.getX();
sY = event.getY();
mPointerIndex1 = event.findPointerIndex(event.getPointerId(0));
mAngle = 0;
mIsFirstTouch = true;
break;
case MotionEvent.ACTION_POINTER_DOWN:
fX = event.getX();
fY = event.getY();
mPointerIndex2 = event.findPointerIndex(event.getPointerId(event.getActionIndex()));
mAngle = 0;
mIsFirstTouch = true;
break;
case MotionEvent.ACTION_MOVE:
if (mPointerIndex1 != INVALID_POINTER_INDEX && mPointerIndex2 != INVALID_POINTER_INDEX && event.getPointerCount() > mPointerIndex2) {
float nfX, nfY, nsX, nsY;
nsX = event.getX(mPointerIndex1);
nsY = event.getY(mPointerIndex1);
nfX = event.getX(mPointerIndex2);
nfY = event.getY(mPointerIndex2);
if (mIsFirstTouch) {
mAngle = 0;
mIsFirstTouch = false;
} else {
calculateAngleBetweenLines(fX, fY, sX, sY, nfX, nfY, nsX, nsY);
}
if (mListener != null) {
mListener.onRotation(this);
}
fX = nfX;
fY = nfY;
sX = nsX;
sY = nsY;
}
break;
case MotionEvent.ACTION_UP:
mPointerIndex1 = INVALID_POINTER_INDEX;
break;
case MotionEvent.ACTION_POINTER_UP:
mPointerIndex2 = INVALID_POINTER_INDEX;
break;
}
return true;
}
private float calculateAngleBetweenLines(float fx1, float fy1, float fx2, float fy2,
float sx1, float sy1, float sx2, float sy2) {
return calculateAngleDelta(
(float) Math.toDegrees((float) Math.atan2((fy1 - fy2), (fx1 - fx2))),
(float) Math.toDegrees((float) Math.atan2((sy1 - sy2), (sx1 - sx2))));
}
private float calculateAngleDelta(float angleFrom, float angleTo) {
mAngle = angleTo % 360.0f - angleFrom % 360.0f;
if (mAngle < -180.0f) {
mAngle += 360.0f;
} else if (mAngle > 180.0f) {
mAngle -= 360.0f;
}
return mAngle;
}
public static class SimpleOnRotationGestureListener implements OnRotationGestureListener {
@Override
public boolean onRotation(RotationGestureDetector rotationDetector) {
return false;
}
}
public interface OnRotationGestureListener {
boolean onRotation(RotationGestureDetector rotationDetector);
}
}