Android 사용자 정의 ImageView: 줌 확대 및 축소 드래그 세트가 있는 ImageView
75020 단어 사용자 정의 View
첫 번째 단계는 사용자 ImageView 작성
이거 사용하실 때 Util 가방에 넣으시면 돼요.
/*
* Created by Ms on 2020-08-11
*
*/public class ZoomImageView extends AppCompatImageView implements ViewTreeObserver.OnGlobalLayoutListener {
private boolean mIsOneLoad = true;
// ,
private float mInitScale;
//
private float mMaxScale;
//
private float mMidScale;
private Matrix mScaleMatrix;
//
private ScaleGestureDetector mScaleGestureDetector;
//
private GestureDetector gestureDetector;
//
private boolean isEnlarge = false;//
private ValueAnimator mAnimator; //
//
private OverScroller scroller;
private int mCurrentX, mCurrentY;
private ValueAnimator translationAnimation; //
//
private OnClickListener onClickListener;//
public ZoomImageView(Context context) {
this(context, null);
}
public ZoomImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ZoomImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// , ScaleType ScaleType.MATRIX,
setScaleType(ScaleType.MATRIX);
scroller = new OverScroller(context);
mScaleMatrix = new Matrix();
//
mScaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.SimpleOnScaleGestureListener() {
@Override
public boolean onScale(ScaleGestureDetector detector) {
scale(detector);
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
scaleEnd(detector);
}
});
//
gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, final float distanceX, final float distanceY) {
//
onTranslationImage(-distanceX, -distanceY);
return true;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
//
onDoubleDrowScale(e.getX(), e.getY());
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
//
mCurrentX = (int) e2.getX();
mCurrentY = (int) e2.getY();
RectF rectF = getMatrixRectF();
if (rectF == null) {
return false;
}
//startX x
int startX = mCurrentX;
int startY = mCurrentY;
int minX = 0, maxX = 0, minY = 0, maxY = 0;
int vX = Math.round(velocityX);
int vY = Math.round(velocityY);
maxX = Math.round(rectF.width());
maxY = Math.round(rectF.height());
if (startX != maxX || startY != maxY) {
// fling , getCurX getCurY x y
// , x y
//
scroller.fling(startX, startY, vX, vY, 0, maxX, 0, maxY, maxX, maxY);
}
if (translationAnimation != null && translationAnimation.isStarted())
translationAnimation.end();
translationAnimation = ObjectAnimator.ofFloat(0, 1);
translationAnimation.setDuration(500);
translationAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if (scroller.computeScrollOffset()) {
// x
int newX = scroller.getCurrX();
int dx = newX - mCurrentX;
mCurrentX = newX;
// y
int newY = scroller.getCurrY();
int dy = newY - mCurrentY;
mCurrentY = newY;
//
if (dx != 0 && dy != 0)
onTranslationImage(dx, dy);
}
}
});
translationAnimation.start();
return super.onFling(e1, e2, velocityX, velocityY);
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
//
if(onClickListener != null)
onClickListener.onClick(ZoomImageView.this);
return true;
}
});
}
@Override
public void setOnClickListener(OnClickListener onClickListener) {
this.onClickListener = onClickListener;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getViewTreeObserver().addOnGlobalLayoutListener(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
/**
* imageView , imageView
*/
@Override
public void onGlobalLayout() {
if (mIsOneLoad) {
//
int width = getWidth();
int height = getHeight();
// ,
Drawable d = getDrawable();
if (d == null)
return;
//
int dw = d.getIntrinsicWidth();
int dh = d.getIntrinsicHeight();
float scale = 1.0f;
if (dw > width && dh <= height) {
scale = width * 1.0f / dw;
}
if (dw <= width && dh > height) {
scale = height * 1.0f / dh;
}
if ((dw <= width && dh <= height) || (dw >= width && dh >= height)) {
scale = Math.min(width * 1.0f / dw, height * 1.0f / dh);
}
// ,
mInitScale = scale;
//
mMidScale = mInitScale * 2;
//
mMaxScale = mInitScale * 4;
// ,
float translationX = width * 1.0f / 2 - dw / 2;
float translationY = height * 1.0f / 2 - dh / 2;
mScaleMatrix.postTranslate(translationX, translationY);
mScaleMatrix.postScale(mInitScale, mInitScale, width * 1.0f / 2, height * 1.0f / 2);
setImageMatrix(mScaleMatrix);
mIsOneLoad = false;
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return mScaleGestureDetector.onTouchEvent(event)|
gestureDetector.onTouchEvent(event);
}
// ( )
public void scale(ScaleGestureDetector detector) {
Drawable drawable = getDrawable();
if (drawable == null)
return;
float scale = getScale();
// ,scaleFactor>1 ,<1
float scaleFactor = detector.getScaleFactor();
// , [mInitScale,mMaxScale]
mScaleMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());
setImageMatrix(mScaleMatrix);
removeBorderAndTranslationCenter();
}
//
public void scaleEnd(ScaleGestureDetector detector) {
float scale = getScale();
scale = detector.getScaleFactor() * scale;
if (scale < mInitScale) {
scaleAnimation(mInitScale, getWidth() / 2, getHeight() / 2);
} else if (scale > mMaxScale) {
scaleAnimation(mMaxScale, getWidth() / 2, getHeight() / 2);
}
}
// ( )
private void onTranslationImage(float dx, float dy) {
if (getDrawable() == null)
return;
RectF rect = getMatrixRectF();
//
if (rect.width() <= getWidth())
dx = 0.0f;
// ,
if (rect.height() <= getHeight())
dy = 0.0f;
// 0,
if (dx == 0.0f && dy == 0.0f)
return;
mScaleMatrix.postTranslate(dx, dy);
setImageMatrix(mScaleMatrix);
//
removeBorderAndTranslationCenter();
}
//
private void removeBorderAndTranslationCenter() {
RectF rectF = getMatrixRectF();
if (rectF == null)
return;
int width = getWidth();
int height = getHeight();
float widthF = rectF.width();
float heightF = rectF.height();
float left = rectF.left;
float right = rectF.right;
float top = rectF.top;
float bottom = rectF.bottom;
float translationX = 0.0f, translationY = 0.0f;
if (left > 0) {
//
if (widthF > width) {
// ,
translationX = -left;
} else {
// ,
translationX = width * 1.0f / 2f - (widthF * 1.0f / 2f + left);
}
} else if (right < width) {
//
if (widthF > width) {
// ,
translationX = width - right;
} else {
// ,
translationX = width * 1.0f / 2f - (widthF * 1.0f / 2f + left);
}
}
if (top > 0) {
//
if (heightF > height) {
// ,
translationY = -top;
} else {
// ,
translationY = height * 1.0f / 2f - (top + heightF * 1.0f / 2f);
}
} else if (bottom < height) {
//
if (heightF > height) {
// ,
translationY = height - bottom;
} else {
// ,
translationY = height * 1.0f / 2f - (top + heightF * 1.0f / 2f);
}
}
mScaleMatrix.postTranslate(translationX, translationY);
setImageMatrix(mScaleMatrix);
}
/**
*
*
* @param x
* @param y
*/
private void onDoubleDrowScale(float x, float y) {
// ,
if (mAnimator != null && mAnimator.isRunning())
return;
float drowScale = getDoubleDrowScale();
// ,
scaleAnimation(drowScale, x, y);
}
/**
*
*
* @param drowScale
* @param x
* @param y
*/
private void scaleAnimation(final float drowScale, final float x, final float y) {
if (mAnimator != null && mAnimator.isRunning())
return;
mAnimator = ObjectAnimator.ofFloat(getScale(), drowScale);
mAnimator.setDuration(300);
mAnimator.setInterpolator(new AccelerateInterpolator());
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = ((float) animation.getAnimatedValue()) / getScale();
mScaleMatrix.postScale(value, value, x, y);
setImageMatrix(mScaleMatrix);
removeBorderAndTranslationCenter();
}
});
mAnimator.start();
}
// ( deviation )
private float getDoubleDrowScale() {
float deviation = 0.05f;
float drowScale = 1.0f;
float scale = getScale();
if (Math.abs(mInitScale - scale) < deviation)
scale = mInitScale;
if (Math.abs(mMidScale - scale) < deviation)
scale = mMidScale;
if (Math.abs(mMaxScale - scale) < deviation)
scale = mMaxScale;
if (scale != mMidScale) {
// mMidScale, mMidScale
drowScale = mMidScale;
isEnlarge = scale < mMidScale;
} else {
// mMidScale,
// , , ,
if (isEnlarge) {
//
drowScale = mMaxScale;
} else {
//
drowScale = mInitScale;
}
}
return drowScale;
}
//
private RectF getMatrixRectF() {
Drawable drawable = getDrawable();
if (drawable == null) {
return null;
}
RectF rectF = new RectF(0, 0, drawable.getMinimumWidth(), drawable.getMinimumHeight());
Matrix matrix = getImageMatrix();
matrix.mapRect(rectF);
return rectF;
}
/**
*
*
* @return
*/
private float getScale() {
float[] values = new float[9];
mScaleMatrix.getValues(values);
return values[Matrix.MSCALE_X];
}
/**
* , true
*
* @param direction
* @return true
*/
@Override
public boolean canScrollHorizontally(int direction) {
RectF rect = getMatrixRectF();
if (rect == null || rect.isEmpty())
return false;
if (direction > 0) {
return rect.right >= getWidth() + 1;
} else {
return rect.left <= 0 - 1;
}
}
/**
*
*
* @param direction
* @return
*/
@Override
public boolean canScrollVertically(int direction) {
RectF rect = getMatrixRectF();
if (rect == null || rect.isEmpty())
return false;
if (direction > 0) {
return rect.bottom >= getHeight() + 1;
} else {
return rect.top <= 0 - 1;
}
}
}
2단계, XML 레이아웃 추가
<com.test.utils.ZoomImageView
android:id="@+id/ima_photo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY" />
이렇게 하면 돼요. 저희가 공간에 자원을 넣으면 정상적으로 사용할 수 있어요.