Android 는 ImageView 를 사용 하여 제스처 크기 조정 효 과 를 지원 합 니 다.

TouchImageView 는 ImageView 에서 ImageView 의 모든 기능 을 계승 합 니 다.그 밖 에 확대,드래그,더 블 클릭 확대 등 기능 도 있 고 viewpager 와 scaletype 을 지원 하 며 애니메이션 효 과 를 동반 합 니 다.

sharedConstructing
private void sharedConstructing(Context context) {
super.setClickable(true);
this.context = context;
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
mGestureDetector = new GestureDetector(context, new GestureListener());
matrix = new Matrix();
prevMatrix = new Matrix();
m = new float[9];
normalizedScale = 1;
if (mScaleType == null) {
mScaleType = ScaleType.FIT_CENTER;
}
minScale = 1;
maxScale = 3;
superMinScale = SUPER_MIN_MULTIPLIER * minScale;
superMaxScale = SUPER_MAX_MULTIPLIER * maxScale;
setImageMatrix(matrix);
setScaleType(ScaleType.MATRIX);
setState(State.NONE);
onDrawReady = false;
super.setOnTouchListener(new PrivateOnTouchListener());
}
초기 화,Scale Gesture Detector 를 설정 한 감청 기 는 Scale Listener 입 니 다.이것 은 크기 조정 제스처 를 처리 하 는 데 사 용 됩 니 다.Gesture Detector 를 설정 한 감청 기 는 Gesture Listener 입 니 다.이것 은 더 블 클릭 과 fling 제스처 를 처리 하 는 데 사 용 됩 니 다.앞의 두 제스처 는 그림 의 크기 조정 을 일 으 키 고 fling 은 그림 의 이동 을 일 으 킵 니 다.

mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
mGestureDetector = new GestureDetector(context, new GestureListener());
마지막 으로 사용자 정의 View 의 touch 이벤트 감청 기 를 PrivateOnTouch Listener 로 설정 합 니 다.이것 은 touch 이벤트 의 입구 입 니 다.

super.setOnTouchListener(new PrivateOnTouchListener());
PrivateOnTouchListener
private class PrivateOnTouchListener implements OnTouchListener {
//
// Remember last point position for dragging
//
private PointF last = new PointF();
@Override
public boolean onTouch(View v, MotionEvent event) {
mScaleDetector.onTouchEvent(event);
mGestureDetector.onTouchEvent(event);
PointF curr = new PointF(event.getX(), event.getY());
if (state == State.NONE || state == State.DRAG || state == State.FLING) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
last.set(curr);
if (fling != null)
fling.cancelFling();
setState(State.DRAG);
break;
case MotionEvent.ACTION_MOVE:
if (state == State.DRAG) {
float deltaX = curr.x - last.x;
float deltaY = curr.y - last.y;
float fixTransX = getFixDragTrans(deltaX, viewWidth, getImageWidth());
float fixTransY = getFixDragTrans(deltaY, viewHeight, getImageHeight());
matrix.postTranslate(fixTransX, fixTransY);
fixTrans();
last.set(curr.x, curr.y);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
setState(State.NONE);
break;
}
}
setImageMatrix(matrix);
//
// User-defined OnTouchListener
//
if(userTouchListener != null) {
userTouchListener.onTouch(v, event);
}
//
// OnTouchImageViewListener is set: TouchImageView dragged by user.
//
if (touchImageViewListener != null) {
touchImageViewListener.onMove();
}
//
// indicate event was handled
//
return true;
}
}
터치 할 때 PrivateOnTouch Listener 의 onTouch 에 도착 합 니 다.또한 포 착 된 MotionEvent 를 mScale Detector 와 mGesture Detector 에 맡 겨 사용자 의 제스처 를 처리 할 수 있 는 콜백 함수 가 있 는 지 분석 합 니 다.

mScaleDetector.onTouchEvent(event);
mGestureDetector.onTouchEvent(event);
동시에 현재 상태 가 DRAG 일 때 X,Y 이동 거 리 를 변환 행렬 에 할당 합 니 다.

matrix.postTranslate(fixTransX, fixTransY);
ImageView 에 행렬 을 설정 하여 X,Y 의 이동 을 완성 합 니 다.즉,한 손가락 드래그 동작 을 실현 합 니 다.

setImageMatrix(matrix);
ScaleListener

private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
setState(State.ZOOM);
return true;
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
scaleImage(detector.getScaleFactor(), detector.getFocusX(), detector.getFocusY(), true);
//
// OnTouchImageViewListener is set: TouchImageView pinch zoomed by user.
//
if (touchImageViewListener != null) {
touchImageViewListener.onMove();
}
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
super.onScaleEnd(detector);
setState(State.NONE);
boolean animateToZoomBoundary = false;
float targetZoom = normalizedScale;
if (normalizedScale > maxScale) {
targetZoom = maxScale;
animateToZoomBoundary = true;
} else if (normalizedScale < minScale) {
targetZoom = minScale;
animateToZoomBoundary = true;
}
if (animateToZoomBoundary) {
DoubleTapZoom doubleTap = new DoubleTapZoom(targetZoom, viewWidth / 2, viewHeight / 2, true);
compatPostOnAnimation(doubleTap);
}
}
}
두 손가락 크기 조정 동작 은 Scale Listener 의 리 셋 으로 갑 니 다.onScale 리 셋 에서 그림 크기 조정 을 처리 합 니 다.

scaleImage(detector.getScaleFactor(), detector.getFocusX(), detector.getFocusY(), true);
scaleImage

private void scaleImage(double deltaScale, float focusX, float focusY, boolean stretchImageToSuper) {
float lowerScale, upperScale;
if (stretchImageToSuper) {
lowerScale = superMinScale;
upperScale = superMaxScale;
} else {
lowerScale = minScale;
upperScale = maxScale;
}
float origScale = normalizedScale;
normalizedScale *= deltaScale;
if (normalizedScale > upperScale) {
normalizedScale = upperScale;
deltaScale = upperScale / origScale;
} else if (normalizedScale < lowerScale) {
normalizedScale = lowerScale;
deltaScale = lowerScale / origScale;
}
matrix.postScale((float) deltaScale, (float) deltaScale, focusX, focusY);
fixScaleTrans();
}
여러 번 크기 조정 한 크기 조정 비 를 누적 하고 최대 크기 와 최소 크기 조정 비 를 설정 합 니 다.범 위 를 초과 할 수 없습니다.

normalizedScale *= deltaScale;
마지막 으로 X,Y 의 크기 조정 비 와 초점 을 변환 행렬 에 전달 하고 행렬 을 통 해 ImageView 에 연결 하여 크기 조정 동작 을 완성 합 니 다.

matrix.postScale((float) deltaScale, (float) deltaScale, focusX, focusY);
onScale End 리 셋 에서 현재 크기 조정 비율 이 최대 크기 조정 비율 을 초과 하거나 최소 크기 조정 비율 을 초과 하 는 지 판단 합 니 다.만약 그렇다면 최대 크기 조정 비율 이나 최소 크기 조정 비율 로 돌아 가 는 애니메이션 이 있 을 것 입 니 다.

DoubleTapZoom doubleTap = new DoubleTapZoom(targetZoom, viewWidth / 2, viewHeight / 2, true);
compatPostOnAnimation(doubleTap);
여기 있 는 애니메이션 DoubleTapZoom 은 애니메이션 을 더 블 클릭 하 는 것 입 니 다.DoubleTapZoom 에 대해 서 는 다음 과 같이 말씀 드 리 겠 습 니 다.이로써 두 손가락 크기 조정 동작 이 완료 되 었 습 니 다.아래 에 서 는 크기 조정 동작 을 계속 보 겠 습 니 다.
GestureListener

private class GestureListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onSingleTapConfirmed(MotionEvent e)
{
if(doubleTapListener != null) {
return doubleTapListener.onSingleTapConfirmed(e);
}
return performClick();
}
@Override
public void onLongPress(MotionEvent e)
{
performLongClick();
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
{
if (fling != null) {
//
// If a previous fling is still active, it should be cancelled so that two flings
// are not run simultaenously.
//
fling.cancelFling();
}
fling = new Fling((int) velocityX, (int) velocityY);
compatPostOnAnimation(fling);
return super.onFling(e1, e2, velocityX, velocityY);
}
@Override
public boolean onDoubleTap(MotionEvent e) {
boolean consumed = false;
if(doubleTapListener != null) {
consumed = doubleTapListener.onDoubleTap(e);
}
if (state == State.NONE) {
float targetZoom = (normalizedScale == minScale) ? maxScale : minScale;
DoubleTapZoom doubleTap = new DoubleTapZoom(targetZoom, e.getX(), e.getY(), false);
compatPostOnAnimation(doubleTap);
consumed = true;
}
return consumed;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
if(doubleTapListener != null) {
return doubleTapListener.onDoubleTapEvent(e);
}
return false;
}
}
onDoubleTap 리 셋 에서 두 번 눌 러 서 크기 조정 비 를 설정 합 니 다.현재 크기 조정 이 없 으 면 크기 조정 비 를 최대 값 으로 설정 합 니 다.최대 값 이 라면 크기 조정 이 없 는 것 으로 설정 합 니 다.

float targetZoom = (normalizedScale == minScale) ? maxScale : minScale;
그리고 현재 클릭 한 좌 표를 크기 조정 센터 로 하고 크기 조정 비 와 함께 DoubleTapZoom 에 건 네 주 며 크기 조정 애니메이션 을 완성 합 니 다.

DoubleTapZoom doubleTap = new DoubleTapZoom(targetZoom, e.getX(), e.getY(), false);
compatPostOnAnimation(doubleTap);
DoubleTapZoom

private class DoubleTapZoom implements Runnable {
private long startTime;
private static final float ZOOM_TIME = 500;
private float startZoom, targetZoom;
private float bitmapX, bitmapY;
private boolean stretchImageToSuper;
private AccelerateDecelerateInterpolator interpolator = new AccelerateDecelerateInterpolator();
private PointF startTouch;
private PointF endTouch;
DoubleTapZoom(float targetZoom, float focusX, float focusY, boolean stretchImageToSuper) {
setState(State.ANIMATE_ZOOM);
startTime = System.currentTimeMillis();
this.startZoom = normalizedScale;
this.targetZoom = targetZoom;
this.stretchImageToSuper = stretchImageToSuper;
PointF bitmapPoint = transformCoordTouchToBitmap(focusX, focusY, false);
this.bitmapX = bitmapPoint.x;
this.bitmapY = bitmapPoint.y;
//
// Used for translating image during scaling
//
startTouch = transformCoordBitmapToTouch(bitmapX, bitmapY);
endTouch = new PointF(viewWidth / 2, viewHeight / 2);
}
@Override
public void run() {
float t = interpolate();
double deltaScale = calculateDeltaScale(t);
scaleImage(deltaScale, bitmapX, bitmapY, stretchImageToSuper);
translateImageToCenterTouchPosition(t);
fixScaleTrans();
setImageMatrix(matrix);
//
// OnTouchImageViewListener is set: double tap runnable updates listener
// with every frame.
//
if (touchImageViewListener != null) {
touchImageViewListener.onMove();
}
if (t < 1f) {
//
// We haven't finished zooming
//
compatPostOnAnimation(this);
} else {
//
// Finished zooming
//
setState(State.NONE);
}
}
/**
* Interpolate between where the image should start and end in order to translate
* the image so that the point that is touched is what ends up centered at the end
* of the zoom.
* @param t
*/
private void translateImageToCenterTouchPosition(float t) {
float targetX = startTouch.x + t * (endTouch.x - startTouch.x);
float targetY = startTouch.y + t * (endTouch.y - startTouch.y);
PointF curr = transformCoordBitmapToTouch(bitmapX, bitmapY);
matrix.postTranslate(targetX - curr.x, targetY - curr.y);
}
/**
* Use interpolator to get t
* @return
*/
private float interpolate() {
long currTime = System.currentTimeMillis();
float elapsed = (currTime - startTime) / ZOOM_TIME;
elapsed = Math.min(1f, elapsed);
return interpolator.getInterpolation(elapsed);
}
/**
* Interpolate the current targeted zoom and get the delta
* from the current zoom.
* @param t
* @return
*/
private double calculateDeltaScale(float t) {
double zoom = startZoom + t * (targetZoom - startZoom);
return zoom / normalizedScale;
}
}
DoubleTapZoom 은 사실 하나의 라인 으로 Runnable 을 실 현 했 습 니 다.Run 방법 을 직접 봅 시다.여기 서 시간 t 를 정 의 했 습 니 다.

float t = interpolate();
사실 t 는 500 ms 내 에 가속 차 이 를 통 해 0 에서 1 로 성장 을 가속 화 시킨다.

private float interpolate() {
long currTime = System.currentTimeMillis();
float elapsed = (currTime - startTime) / ZOOM_TIME;
elapsed = Math.min(1f, elapsed);
return interpolator.getInterpolation(elapsed);
}
t 를 통 해 현재 크기 조정 비 를 계산 합 니 다.

double deltaScale = calculateDeltaScale(t);
크기 조정 실현

scaleImage(deltaScale, bitmapX, bitmapY, stretchImageToSuper);
그 다음 에 현재 t 의 값 에 따라 애니메이션 이 끝 났 는 지 여 부 를 판단 합 니 다.만약 에 t 가 1 보다 작 으 면 애니메이션 이 아직 끝나 지 않 았 음 을 나타 내 고 이 스 레 드 를 다시 실행 합 니 다.그렇지 않 으 면 설정 상태 가 완 료 됩 니 다.이 500 ms 에서 여러 번 스 레 드 를 실행 하고 ImageView 를 여러 번 다시 그 려 애니메이션 효 과 를 실현 합 니 다.

if (t < 1f) {
compatPostOnAnimation(this);
} else {
setState(State.NONE);
}
동시에 Gesture Listener 의 onFling 리 셋 에서 Fling 의 X,Y 속 도 를 설정 한 다음 에 Fling 의 변위 애니메이션 을 실행 합 니 다.

fling = new Fling((int) velocityX, (int) velocityY);
compatPostOnAnimation(fling);
Fling

private class Fling implements Runnable {
CompatScroller scroller;
int currX, currY;
Fling(int velocityX, int velocityY) {
setState(State.FLING);
scroller = new CompatScroller(context);
matrix.getValues(m);
int startX = (int) m[Matrix.MTRANS_X];
int startY = (int) m[Matrix.MTRANS_Y];
int minX, maxX, minY, maxY;
if (getImageWidth() > viewWidth) {
minX = viewWidth - (int) getImageWidth();
maxX = 0;
} else {
minX = maxX = startX;
}
if (getImageHeight() > viewHeight) {
minY = viewHeight - (int) getImageHeight();
maxY = 0;
} else {
minY = maxY = startY;
}
scroller.fling(startX, startY, (int) velocityX, (int) velocityY, minX,
maxX, minY, maxY);
currX = startX;
currY = startY;
}
public void cancelFling() {
if (scroller != null) {
setState(State.NONE);
scroller.forceFinished(true);
}
}
@Override
public void run() {
//
// OnTouchImageViewListener is set: TouchImageView listener has been flung by user.
// Listener runnable updated with each frame of fling animation.
//
if (touchImageViewListener != null) {
touchImageViewListener.onMove();
}
if (scroller.isFinished()) {
scroller = null;
return;
}
if (scroller.computeScrollOffset()) {
int newX = scroller.getCurrX();
int newY = scroller.getCurrY();
int transX = newX - currX;
int transY = newY - currY;
currX = newX;
currY = newY;
matrix.postTranslate(transX, transY);
fixTrans();
setImageMatrix(matrix);
compatPostOnAnimation(this);
}
}
}
Fling 도 하나의 스 레 드 로 Runnable 을 실현 합 니 다.Fling 제스처 의 X,Y 속도 에 따라 저 희 는 Scroller 의 fling 함 수 를 실행 하고 현재 위 치 를 시작 위치 로 설정 합 니 다.

scroller.fling(startX, startY, (int) velocityX, (int) velocityY, minX,maxX, minY, maxY);
currX = startX;
currY = startY;
다시 Run 함 수 를 살 펴 보고 scroller 의 현재 스크롤 위치 에 따라 새로운 위치 정 보 를 계산 하여 이전 위치 와 상쇄 하여 X,Y 축 에서 이동 거 리 를 얻 고 이동 을 실현 합 니 다.

if (scroller.computeScrollOffset()) {
int newX = scroller.getCurrX();
int newY = scroller.getCurrY();
int transX = newX - currX;
int transY = newY - currY;
currX = newX;
currY = newY;
matrix.postTranslate(transX, transY);
fixTrans();
setImageMatrix(matrix);
compatPostOnAnimation(this);
}
마지막 으로 일정 시간 동안 스 레 드 를 다시 호출 하여 새로운 이동 그림 을 완성 합 니 다.스크롤 러 가 스크롤 을 멈 출 때 까지 ImageView 를 여러 번 다시 그 려 서 fling 애니메이션 효 과 를 실현 합 니 다.

private void compatPostOnAnimation(Runnable runnable) {
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
postOnAnimation(runnable);
} else {
postDelayed(runnable, 1000/60);
}
}
아래 에 표시 효 과 를 살 펴 보 겠 습 니 다.
단일 그림
这里写图片描述
그림 을 ViewPager 에 불 러 오기
这里写图片描述
미 러 이미지
这里写图片描述
클릭 하여 그림 변경 가능
这里写图片描述
클릭 하여 ScaleType 변경 가능
这里写图片描述
위 에서 말 한 것 은 소 편 이 소개 한 안 드 로 이 드 가 ImageView 를 사용 하여 제스처 크기 조정 효 과 를 실현 하 는 것 입 니 다.여러분 에 게 도움 이 되 기 를 바 랍 니 다.궁금 한 점 이 있 으 시 면 메 시 지 를 남 겨 주세요.소 편 은 바로 답 해 드 리 겠 습 니 다.여기 서도 저희 사이트 에 대한 여러분 의 지지 에 감 사 드 립 니 다!

좋은 웹페이지 즐겨찾기