我有一个扩展FrameLayout的自定义布局,几乎支持我需要的一切。滚动和缩放子视图或布局效果很好。但我遇到的一件事就是在缩放时保持焦点。捏指之间的中心点离开屏幕,但我希望它能像谷歌地图应用程序那样保持焦点(中心)点。
这是代码。
public class BetterZoomLayout extends FrameLayout
{
// State objects and values related to gesture tracking.
private ScaleGestureDetector mScaleGestureDetector;
private GestureDetectorCompat mGestureDetector;
private static final float MIN_ZOOM = 1.0f;
private static final float MAX_ZOOM = 4.0f;
private float scale = 1.0f;
private float lastScaleFactor = 0f;
// How much to translate the canvas
private float distanceX = 0f;
private float distanceY = 0f;
public BetterZoomLayout(Context context)
{
super(context);
init(context);
}
public BetterZoomLayout(Context context, AttributeSet attrs)
{
super(context, attrs);
init(context);
}
public BetterZoomLayout(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
init(context);
}
private void init(Context context)
{
// Sets up interactions
mScaleGestureDetector = new ScaleGestureDetector(context, mScaleGestureListener);
mGestureDetector = new GestureDetectorCompat(context, mGestureListener);
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
boolean retVal = mScaleGestureDetector.onTouchEvent(event);
retVal = mGestureDetector.onTouchEvent(event) || retVal;
applyScaleAndTranslation();
return retVal || super.onTouchEvent(event);
}
/**
* The scale listener, used for handling multi-finger scale gestures.
*/
private final ScaleGestureDetector.OnScaleGestureListener mScaleGestureListener
= new ScaleGestureDetector.SimpleOnScaleGestureListener()
{
/**
* This is the active focal point in terms of the viewport. Could be a local
* variable but kept here to minimize per-frame allocations.
*/
@Override
public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector)
{
lastScaleFactor = 0f;
return true;
}
@Override
public boolean onScale(ScaleGestureDetector scaleGestureDetector)
{
float scaleFactor = scaleGestureDetector.getScaleFactor();
if (lastScaleFactor == 0 || (Math.signum(scaleFactor) == Math.signum(lastScaleFactor)))
{
scale *= scaleFactor;
scale = Math.max(MIN_ZOOM, Math.min(scale, MAX_ZOOM));
lastScaleFactor = scaleFactor;
}
else
{
lastScaleFactor = 0;
}
return true;
}
};
/**
* The gesture listener, used for handling simple gestures such as double touches, scrolls,
* and flings.
*/
private final GestureDetector.SimpleOnGestureListener mGestureListener
= new GestureDetector.SimpleOnGestureListener()
{
@Override
public boolean onDown(MotionEvent e)
{
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float dX, float dY)
{
setupTranslation(dX, dY);
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
{
//fling((int) -velocityX, (int) -velocityY);
return true;
}
};
private void setupTranslation(float dX, float dY)
{
distanceX = -1 * dX + distanceX;
distanceY = -1 * dY + distanceY;
getParent().requestDisallowInterceptTouchEvent(true);
Rect viewableRect = new Rect();
BetterZoomLayout.this.getDrawingRect(viewableRect);
float offscreenWidth = child().getWidth() - (viewableRect.right - viewableRect.left);
float offscreenHeight = child().getHeight() - (viewableRect.bottom - viewableRect.top);
float maxDx = (child().getWidth() - (child().getWidth() / scale)) / 2 * scale;
float maxDy = (child().getHeight() - (child().getHeight() / scale)) / 2 * scale;
distanceX = Math.min(Math.max(distanceX, -(maxDx + offscreenWidth)), maxDx);
distanceY = Math.min(Math.max(distanceY, -(maxDy + offscreenHeight)), maxDy);
}
private void applyScaleAndTranslation()
{
child().setScaleX(scale);
child().setScaleY(scale);
child().setTranslationX(distanceX);
child().setTranslationY(distanceY);
}
private View child()
{
return getChildAt(0);
}
}