RelativeLayout でズームとパンを実行する関数を実装していますが、ほぼ完了です。ただし、レイアウト内にビューがあり、変換を適用すると、これらのビューのタッチが更新されず、追跡できなくなります。
変換を実行することで、ズームとパンに応じて機能するビューのタッチが必要です。
以下のコードを共有し、ヘルプをリクエストして、完成させます。
public class ZoomLayout extends RelativeLayout implements OnTouchListener, OnDoubleTapListener, OnGestureListener {
// ===================================================================================
// ==================================== ATRIBUTES ====================================
// ===================================================================================
private float currentScaleFactor = 1.0f;
private float previousScaleFactor = 1.0f;
private ScaleGestureDetector scaleGestureDetector;
private GestureDetector gestureDetector;
private float xPrevious;
private float yPrevious;
private float xActual;
private float yActual;
private float leftStage;
private float topStage;
private float xInitial;
private float yInitial;
private float maxScale = 5f;
private float minScale = 0.3f;
private int widthStage;
private int heightStage;
private boolean firstTimeOnEvent = true;
private boolean zoomEnable = true;
private boolean oneFingerMoveEnable = false;
private boolean twoFingersMoveEnable = true;
private boolean doubleTapZoomEnable = true;
private Matrix savedMatrix = null;
private boolean onScale = false;
private float diferenceFromScaleX;
private float diferenceFromScaleY;
private List<ZoomListener> listeners = new CopyOnWriteArrayList<ZoomListener>();
// ===================================================================================
// =================================== CONTRUCTORS ==================================
// ===================================================================================
public ZoomLayout(Context context)
{
super(context);
setWillNotDraw(false);
savedMatrix = new Matrix();
savedMatrix.reset();
scaleGestureDetector = new ScaleGestureDetector(getContext(), new ZoomScale());
gestureDetector = new GestureDetector(context, this);
gestureDetector.setOnDoubleTapListener(this);
this.setOnTouchListener(this);
}
public ZoomLayout(Context context, int width, int height)
{
this(context);
this.widthStage = width;
this.heightStage = height;
}
// ===================================================================================
// ===================================== METHODS =====================================
// ===================================================================================
public void setZoomScale(float scale)
{
currentScaleFactor = scale;
previousScaleFactor = scale;
savedMatrix.reset();
xActual = 0;
yActual = 0;
xPrevious = 0;
yPrevious = 0;
float maxLeft = ((float) (widthStage * 0.5) - (float) (widthStage * currentScaleFactor * 0.5));
float maxTop = ((float) (heightStage * 0.5) - (float) (heightStage * currentScaleFactor * 0.5));
savedMatrix.postScale(currentScaleFactor, currentScaleFactor);
savedMatrix.postTranslate(maxLeft, maxTop);
invalidate();
requestLayout();
leftStage = 0;
topStage = 0;
}
public void addZoomListener(ZoomListener listener)
{
listeners.add(listener);
}
public void removeZoomListener(ZoomListener listener)
{
listeners.remove(listener);
}
// ===================================================================================
// ===================================== EVENTS =====================================
// ===================================================================================
@Override
public boolean onTouch(View v, MotionEvent event)
{
if (zoomEnable)
{
switch (event.getAction() & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_POINTER_DOWN:
firstTimeOnEvent = true;
break;
case MotionEvent.ACTION_UP:
firstTimeOnEvent = true;
break;
case MotionEvent.ACTION_POINTER_UP:
break;
case MotionEvent.ACTION_CANCEL:
firstTimeOnEvent = true;
break;
case MotionEvent.ACTION_MOVE:
break;
}
scaleGestureDetector.onTouchEvent(event);
gestureDetector.onTouchEvent(event);
}
else
{
return false;
}
for (int i = 0; i < listeners.size(); i++)
{
listeners.get(i).onTouch(v, event);
}
return true;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev)
{
onTouchEvent(ev);
return super.onInterceptTouchEvent(ev);
}
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
return super.invalidateChildInParent(location, dirty);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
int count = getChildCount();
for(int i=0;i<count;i++){
View child = getChildAt(i);
if(child.getVisibility()!=GONE){
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)child.getLayoutParams();
child.layout(
(int)(params.leftMargin*currentScaleFactor),
(int)(params.topMargin*currentScaleFactor),
(int)((params.leftMargin + child.getMeasuredWidth())*currentScaleFactor ),
(int)((params.topMargin + child.getMeasuredHeight())*currentScaleFactor)
);
child.setLayoutParams(params);
}
}
}
@Override
protected void dispatchDraw(Canvas canvas)
{
canvas.setMatrix(savedMatrix);
super.dispatchDraw(canvas);
}
private class ZoomScale extends ScaleGestureDetector.SimpleOnScaleGestureListener
{
@Override
public boolean onScaleBegin(ScaleGestureDetector detector)
{
onScale = true;
for (int i = 0; i < listeners.size(); i++)
{
listeners.get(i).onScaleBegin(detector);
}
return super.onScaleBegin(detector);
}
@Override
public void onScaleEnd(ScaleGestureDetector detector)
{
onScale = false;
diferenceFromScaleX = detector.getFocusX() - xActual;
diferenceFromScaleY = detector.getFocusY() - yActual;
for (int i = 0; i < listeners.size(); i++)
{
listeners.get(i).onScaleEnd(detector);
}
super.onScaleEnd(detector);
}
@Override
public boolean onScale(ScaleGestureDetector detector)
{
xPrevious = xActual;
yPrevious = yActual;
xActual = scaleGestureDetector.getFocusX();
yActual = scaleGestureDetector.getFocusY();
if (!firstTimeOnEvent)
{
currentScaleFactor = (currentScaleFactor * detector.getScaleFactor());
currentScaleFactor = (Math.max(Math.min(currentScaleFactor, maxScale), minScale));
float diferencaScale = (currentScaleFactor / previousScaleFactor);
float x = xActual - xPrevious;
float y = yActual - yPrevious;
savedMatrix.postScale(diferencaScale, diferencaScale, xInitial - x, yInitial - y);
savedMatrix.postTranslate(x, y);
float[] values = new float[9];
savedMatrix.getValues(values);
float leftAux = (float) values[Matrix.MTRANS_X];
float topAux = (float) values[Matrix.MTRANS_Y];
float maxLeft = 0;
float maxTop = 0;
if (widthStage * currentScaleFactor >= 1280)
{
maxLeft = -((float) (widthStage * currentScaleFactor) - (float) 1280);
if (leftAux < 0)
{
if (leftAux < maxLeft)
{
maxLeft -= leftAux;
}
else
{
maxLeft = 0;
}
}
else
{
maxLeft = -leftAux;
}
}
else
{
maxLeft = ((float) (widthStage * 0.5) - (float) (widthStage * currentScaleFactor * 0.5));
maxLeft -= leftAux;
}
if (heightStage * currentScaleFactor >= 752)
{
maxTop = -((float) (heightStage * currentScaleFactor) - (float) 752);
if (topAux < 0)
{
if (topAux < maxTop)
{
maxTop -= topAux;
}
else
{
maxTop = 0;
}
}
else
{
maxTop = -topAux;
}
}
else
{
maxTop = ((float) (752 * 0.5) - (float) (heightStage * currentScaleFactor * 0.5));
maxTop -= topAux;
}
savedMatrix.postTranslate(maxLeft, maxTop);
previousScaleFactor = currentScaleFactor;
invalidate();
requestLayout();
}
else
{
firstTimeOnEvent = false;
xInitial = detector.getFocusX();
yInitial = detector.getFocusY();
previousScaleFactor = currentScaleFactor;
}
float[] values = new float[9];
savedMatrix.getValues(values);
leftStage = values[Matrix.MTRANS_X];
topStage = values[Matrix.MTRANS_Y];
for (int i = 0; i < listeners.size(); i++)
{
listeners.get(i).onScale(detector);
}
atualizaParametrosView();
return true;
}
}
public boolean onDoubleTap(MotionEvent e)
{
if (doubleTapZoomEnable)
{
currentScaleFactor = 1.0f;
previousScaleFactor = 1.0f;
savedMatrix.reset();
xActual = 0;
yActual = 0;
xPrevious = 0;
yPrevious = 0;
savedMatrix.postTranslate(0, 0);
invalidate();
requestLayout();
leftStage = 0;
topStage = 0;
for (int i = 0; i < listeners.size(); i++)
{
listeners.get(i).onDoubleTap(e);
}
}
return true;
}
public boolean onDoubleTapEvent(MotionEvent e)
{
for (int i = 0; i < listeners.size(); i++)
{
listeners.get(i).onDoubleTapEvent(e);
}
return false;
}
public boolean onSingleTapConfirmed(MotionEvent e)
{
for (int i = 0; i < listeners.size(); i++)
{
listeners.get(i).onSingleTapConfirmed(e);
}
return false;
}
public boolean onDown(MotionEvent e)
{
for (int i = 0; i < listeners.size(); i++)
{
listeners.get(i).onDown(e);
}
return false;
}
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
{
for (int i = 0; i < listeners.size(); i++)
{
listeners.get(i).onFling(e1, e2, velocityX, velocityY);
}
return false;
}
public void onLongPress(MotionEvent e)
{
for (int i = 0; i < listeners.size(); i++)
{
listeners.get(i).onLongPress(e);
}
}
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
{
if (oneFingerMoveEnable && !onScale)
{
xPrevious = xActual;
yPrevious = yActual;
xActual = e2.getX() - diferenceFromScaleX;
yActual = e2.getY() - diferenceFromScaleY;
if (!firstTimeOnEvent)
{
float x = xActual - xPrevious;
float y = yActual - yPrevious;
savedMatrix.postTranslate(x, y);
float[] values = new float[9];
savedMatrix.getValues(values);
float leftAux = (float) values[Matrix.MTRANS_X];
float topAux = (float) values[Matrix.MTRANS_Y];
float maxTop = 0;
float maxLeft = 0;
if (widthStage * currentScaleFactor >= 1280)
{
maxLeft = -((float) (widthStage * currentScaleFactor) - (float) 1280);
if (leftAux < 0)
{
if (leftAux < maxLeft)
{
maxLeft -= leftAux;
}
else
{
maxLeft = 0;
}
}
else
{
maxLeft = -leftAux;
}
}
else
{
maxLeft = ((float) (widthStage * 0.5) - (float) (widthStage * currentScaleFactor * 0.5));
maxLeft -= leftAux;
}
if (heightStage * currentScaleFactor >= 752)
{
maxTop = -((float) (heightStage * currentScaleFactor) - (float) 752);
if (topAux < 0)
{
if (topAux < maxTop)
{
maxTop -= topAux;
}
else
{
maxTop = 0;
}
}
else
{
maxTop = -topAux;
}
}
else
{
maxTop = ((float) (752 * 0.5) - (float) (heightStage * currentScaleFactor * 0.5));
maxTop -= topAux;
}
savedMatrix.postTranslate(maxLeft, maxTop);
invalidate();
requestLayout();
}
else
{
firstTimeOnEvent = false;
xInitial = e2.getX() - diferenceFromScaleX;
yInitial = e2.getY() - diferenceFromScaleY;
}
float[] values = new float[9];
savedMatrix.getValues(values);
leftStage = values[Matrix.MTRANS_X];
topStage = values[Matrix.MTRANS_Y];
}
for (int i = 0; i < listeners.size(); i++)
{
listeners.get(i).onScroll(e1, e2, distanceX, distanceY);
}
atualizaParametrosView();
return true;
}
private void atualizaParametrosView()
{
RelativeLayout.LayoutParams params = (LayoutParams) this.getChildAt(0).getLayoutParams();
params.width = widthStage;
params.height = heightStage;
this.getChildAt(0).setLayoutParams(params);
params = (LayoutParams) this.getLayoutParams();
params.width = widthStage;
params.height = heightStage;
this.setLayoutParams(params);
((View) this.getParent()).getLayoutParams().width = widthStage;
((View) this.getParent()).getLayoutParams().height = heightStage;
}
public void onShowPress(MotionEvent e)
{
for (int i = 0; i < listeners.size(); i++)
{
listeners.get(i).onShowPress(e);
}
}
public boolean onSingleTapUp(MotionEvent e)
{
for (int i = 0; i < listeners.size(); i++)
{
listeners.get(i).onSingleTapUp(e);
}
return false;
}
// ===================================================================================
// ================================= GETTERS & SETTERS ===============================
// ===================================================================================
public float getScaleFactorActual()
{
return currentScaleFactor;
}
public float getScaleFactorPrevious()
{
return previousScaleFactor;
}
public float getxPrevious()
{
return xPrevious;
}
public float getyPrevious()
{
return yPrevious;
}
public float getxActual()
{
return xActual;
}
public float getyActual()
{
return yActual;
}
public float getLeftStage()
{
return leftStage;
}
public float getTopStage()
{
return topStage;
}
public float getxInitial()
{
return xInitial;
}
public float getyInitial()
{
return yInitial;
}
public float getMaxScale()
{
return maxScale;
}
public void setMaxScale(float maxScale)
{
this.maxScale = maxScale;
}
public float getMinScale()
{
return minScale;
}
public void setMinScale(float minScale)
{
this.minScale = minScale;
}
public int getWidthStage()
{
return widthStage;
}
public void setWidthStage(int widthStage)
{
this.widthStage = widthStage;
}
public int getHeightStage()
{
return heightStage;
}
public void setHeightStage(int heightStage)
{
this.heightStage = heightStage;
}
public boolean isZoomEnable()
{
return zoomEnable;
}
public void setZoomEnable(boolean zoomEnable)
{
this.zoomEnable = zoomEnable;
}
public boolean isOneFingerMoveEnable()
{
return oneFingerMoveEnable;
}
public void setOneFingerMoveEnable(boolean oneFingerMoveEnable)
{
this.oneFingerMoveEnable = oneFingerMoveEnable;
}
public boolean isTwoFingersMoveEnable()
{
return twoFingersMoveEnable;
}
public void setTwoFingersMoveEnable(boolean twoFingersMoveEnable)
{
this.twoFingersMoveEnable = twoFingersMoveEnable;
}
public boolean isDoubleTapZoomEnable()
{
return doubleTapZoomEnable;
}
public void setDoubleTapZoomEnable(boolean doubleTapZoomEnable)
{
this.doubleTapZoomEnable = doubleTapZoomEnable;
}
}