缩放 FrameLayout 时,如何在可缩放的 FrameLayout 上缩放所有 ImageView?
How to zoom all of the ImageView on a pinch zoomable FrameLayout when zooming the FrameLayout?
ZoomLayout 是自定义的 class,它扩展了 FrameLayout 并在其上实现了缩放功能。
PS: 单击将添加一个 ImageView 到 ZoomLayout
idea:缩放frameLayout,每个child一起缩放。
结果:无效。
还有其他方法吗?
<ZoomLayout
android:id="@+id/graphics_holder"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imgFloorPlan"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="matrix" />
</ZoomLayout>
import android.app.Activity;
import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import java.util.ArrayList;
public class ZoomLayout extends FrameLayout {
Matrix matrix;
// We can be in one of these 3 states
static final int NONE = 0;
static final int DRAG = 1;
static final int ZOOM = 2;
int mode = NONE;
// Remember some things for zooming
private PointF curr;
PointF last = new PointF();
PointF start = new PointF();
float minScale = 1f;
float maxScale = 3f;
float[] m;
int viewWidth, viewHeight;
static final int CLICK = 3;
float saveScale = 1f;
protected float origWidth, origHeight;
int oldMeasuredWidth, oldMeasuredHeight;
private ScaleGestureDetector mScaleDetector;
private GestureDetector gestureDetector;
private View currentView;
private ImageView imgFloorPlan;
private ArrayList<MyImageView> imgPointArray = new ArrayList<>();
public ZoomLayout(Context context) {
super(context);
init();
}
public ZoomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ZoomLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
super.setClickable(true);
Activity act = (Activity) getContext();
mScaleDetector = new ScaleGestureDetector(act, new ScaleListener());
gestureDetector = new GestureDetector(act, new ZoomGesture());
matrix = new Matrix();
m = new float[9];
imgFloorPlan = (ImageView) act.findViewById(R.id.imgFloorPlan);
setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
currentView = v;
mScaleDetector.onTouchEvent(event);
gestureDetector.onTouchEvent(event);
curr = new PointF(event.getX(), event.getY());
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
last.set(curr);
start.set(last);
mode = DRAG;
break;
case MotionEvent.ACTION_MOVE:
if (mode == DRAG) {
float deltaX = curr.x - last.x;
float deltaY = curr.y - last.y;
float fixTransX = getFixDragTrans(deltaX, viewWidth, origWidth * saveScale);
float fixTransY = getFixDragTrans(deltaY, viewHeight, origHeight * saveScale);
matrix.postTranslate(fixTransX, fixTransY);
fixTrans();
last.set(curr.x, curr.y);
}
break;
case MotionEvent.ACTION_UP:
mode = NONE;
int xDiff = (int) Math.abs(curr.x - start.x);
int yDiff = (int) Math.abs(curr.y - start.y);
if (xDiff < CLICK && yDiff < CLICK)
performClick();
break;
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
break;
default:
break;
}
imgFloorPlan.setImageMatrix(matrix);
for (ImageView imageView:imgPointArray) {
imageView.setImageMatrix(matrix);
}
invalidate();
return true; // indicate event was handled
}
});
}
public void setMaxZoom(float x) {
maxScale = x;
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
mode = ZOOM;
return true;
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
float mScaleFactor = detector.getScaleFactor();
float origScale = saveScale;
saveScale *= mScaleFactor;
if (saveScale > maxScale) {
saveScale = maxScale;
mScaleFactor = maxScale / origScale;
} else if (saveScale < minScale) {
saveScale = minScale;
mScaleFactor = minScale / origScale;
}
if (origWidth * saveScale <= viewWidth || origHeight * saveScale <= viewHeight)
matrix.postScale(mScaleFactor, mScaleFactor, viewWidth / 2, viewHeight / 2);
else
matrix.postScale(mScaleFactor, mScaleFactor, detector.getFocusX(), detector.getFocusY());
fixTrans();
return true;
}
}
void fixTrans() {
matrix.getValues(m);
float transX = m[Matrix.MTRANS_X];
float transY = m[Matrix.MTRANS_Y];
float fixTransX = getFixTrans(transX, viewWidth, origWidth * saveScale);
float fixTransY = getFixTrans(transY, viewHeight, origHeight * saveScale);
if (fixTransX != 0 || fixTransY != 0)
matrix.postTranslate(fixTransX, fixTransY);
}
float getFixTrans(float trans, float viewSize, float contentSize) {
float minTrans, maxTrans;
if (contentSize <= viewSize) {
minTrans = 0;
maxTrans = viewSize - contentSize;
} else {
minTrans = viewSize - contentSize;
maxTrans = 0;
}
if (trans < minTrans)
return -trans + minTrans;
if (trans > maxTrans)
return -trans + maxTrans;
return 0;
}
float getFixDragTrans(float delta, float viewSize, float contentSize) {
if (contentSize <= viewSize) {
return 0;
}
return delta;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.d("ZoomLayout", "onMeasure called");
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
viewWidth = MeasureSpec.getSize(widthMeasureSpec);
viewHeight = MeasureSpec.getSize(heightMeasureSpec);
// Rescales image on rotation
if (oldMeasuredHeight == viewWidth && oldMeasuredHeight == viewHeight
|| viewWidth == 0 || viewHeight == 0)
return;
oldMeasuredHeight = viewHeight;
oldMeasuredWidth = viewWidth;
if (saveScale == 1) {
//Fit to screen.
float scale;
Activity act = (Activity) getContext();
imgFloorPlan = (ImageView) act.findViewById(R.id.imgFloorPlan);
Drawable drawable = imgFloorPlan.getDrawable();
if (drawable == null || drawable.getIntrinsicWidth() == 0 || drawable.getIntrinsicHeight() == 0)
return;
int bmWidth = drawable.getIntrinsicWidth();
int bmHeight = drawable.getIntrinsicHeight();
Log.d("bmSize", "bmWidth: " + bmWidth + " bmHeight : " + bmHeight);
float scaleX = (float) viewWidth / (float) bmWidth;
float scaleY = (float) viewHeight / (float) bmHeight;
scale = Math.min(scaleX, scaleY);
matrix.setScale(scale, scale);
// Center the image
float redundantYSpace = (float) viewHeight - (scale * (float) bmHeight);
float redundantXSpace = (float) viewWidth - (scale * (float) bmWidth);
redundantYSpace /= (float) 2;
redundantXSpace /= (float) 2;
matrix.postTranslate(redundantXSpace, redundantYSpace);
origWidth = viewWidth - 2 * redundantXSpace;
origHeight = viewHeight - 2 * redundantYSpace;
imgFloorPlan.setImageMatrix(matrix);
for (ImageView imageView:imgPointArray) {
imageView.setImageMatrix(matrix);
}
}
fixTrans();
}
class ZoomGesture extends GestureDetector.SimpleOnGestureListener { //单手指操作
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
// TODO: Add on Touch event to place point
int x = (int) e.getX();
int y = (int) e.getY();
Log.d("PlayAreaView", "onSingleTapConfirmed" + x + "," + y);
int imageViewWH = (int) DimensionUtils.dpToPx(currentView, 60);
if (isTapOnFloorPlan(x, y, imageViewWH)) {
// Create a Pin on tapped location
MyImageView newPinView = new MyImageView(getContext());
newPinView.setLayoutParams(new FrameLayout.LayoutParams(imageViewWH, imageViewWH));
newPinView.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.ic_location_on_black_48dp));
newPinView.setScaleType(ImageView.ScaleType.MATRIX);
// Add to FrameLayout
addView(newPinView);
imgPointArray.add(newPinView);
// Move to mouse center
setAbsoluteLocationCentered(newPinView, x, y);
}Log.d("PlayAreaView", "onSingleTapConfirmed,getChildCount"+getChildCount());
return super.onSingleTapConfirmed(e);
}
@Override //双击
public boolean onDoubleTap(MotionEvent e) {
System.out.println("--onDoubleTap---");
return true;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
System.out.println("--onDoubleTapEvent---");
return super.onDoubleTapEvent(e);
}
}
private boolean isTapOnFloorPlan(int tapX, int tapY, int imageViewWH) {
Rect bounds = imgFloorPlan.getDrawable().getBounds();
int x = (imgFloorPlan.getMeasuredWidth() - bounds.width()) / 2;
int y = (imgFloorPlan.getMeasuredHeight() - bounds.height()) / 2;
int imageX1 = x + (imageViewWH / 2);
int imageX2 = x + bounds.width();
int imageY1 = y;
int imageY2 = y + bounds.height();
if (tapX >= imageX1 && tapX <= imageX2 && tapY >= imageY1 && tapY <= imageY2) {
Log.d("ZoomLayout", "isTapOnFloorPlan: " + true);
return true;
} else {
Log.d("ZoomLayout", "isTapOnFloorPlan: " + false);
return false;
}
}
private void setAbsoluteLocationCentered(View v, int x, int y) {
ViewGroup.LayoutParams params = v.getLayoutParams();
int viewRadiusX = params.width / 2;
int viewRadiusY = params.height;
x = x - viewRadiusX;
y = y - viewRadiusY;
setAbsoluteLocation(v, x, y);
}
private void setAbsoluteLocation(View v, int x, int y) {
FrameLayout.LayoutParams alp = (FrameLayout.LayoutParams) v.getLayoutParams();
alp.setMargins(x, y, 0, 0);
v.setLayoutParams(alp);
}
}
我找到了解决办法。
重点是矩阵
第一次,将图像缩放到imageview中并保存用于缩放图像的矩阵(原始宽度高度)。
请不要使用任何自动缩放方法,例如视图的 fitCenter 属性。
scaleType 必须是矩阵。
旧图像 + 矩阵 = 视图中的图像。
当 onMesure 和 onScale 时
视图中的图像 + 矩阵(Public 这个矩阵)= 缩放后的图像
其他视图 + public 矩阵 = 与图像一起缩放
也可以使用矩阵将点缩放到相同的比例。
有助于实施的关键字:
ZoomLayout、GestureDetector、ScaleGestureDetector
ZoomLayout 是自定义的 class,它扩展了 FrameLayout 并在其上实现了缩放功能。 PS: 单击将添加一个 ImageView 到 ZoomLayout
idea:缩放frameLayout,每个child一起缩放。
结果:无效。
还有其他方法吗?
<ZoomLayout
android:id="@+id/graphics_holder"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imgFloorPlan"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="matrix" />
</ZoomLayout>
import android.app.Activity;
import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import java.util.ArrayList;
public class ZoomLayout extends FrameLayout {
Matrix matrix;
// We can be in one of these 3 states
static final int NONE = 0;
static final int DRAG = 1;
static final int ZOOM = 2;
int mode = NONE;
// Remember some things for zooming
private PointF curr;
PointF last = new PointF();
PointF start = new PointF();
float minScale = 1f;
float maxScale = 3f;
float[] m;
int viewWidth, viewHeight;
static final int CLICK = 3;
float saveScale = 1f;
protected float origWidth, origHeight;
int oldMeasuredWidth, oldMeasuredHeight;
private ScaleGestureDetector mScaleDetector;
private GestureDetector gestureDetector;
private View currentView;
private ImageView imgFloorPlan;
private ArrayList<MyImageView> imgPointArray = new ArrayList<>();
public ZoomLayout(Context context) {
super(context);
init();
}
public ZoomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ZoomLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
super.setClickable(true);
Activity act = (Activity) getContext();
mScaleDetector = new ScaleGestureDetector(act, new ScaleListener());
gestureDetector = new GestureDetector(act, new ZoomGesture());
matrix = new Matrix();
m = new float[9];
imgFloorPlan = (ImageView) act.findViewById(R.id.imgFloorPlan);
setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
currentView = v;
mScaleDetector.onTouchEvent(event);
gestureDetector.onTouchEvent(event);
curr = new PointF(event.getX(), event.getY());
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
last.set(curr);
start.set(last);
mode = DRAG;
break;
case MotionEvent.ACTION_MOVE:
if (mode == DRAG) {
float deltaX = curr.x - last.x;
float deltaY = curr.y - last.y;
float fixTransX = getFixDragTrans(deltaX, viewWidth, origWidth * saveScale);
float fixTransY = getFixDragTrans(deltaY, viewHeight, origHeight * saveScale);
matrix.postTranslate(fixTransX, fixTransY);
fixTrans();
last.set(curr.x, curr.y);
}
break;
case MotionEvent.ACTION_UP:
mode = NONE;
int xDiff = (int) Math.abs(curr.x - start.x);
int yDiff = (int) Math.abs(curr.y - start.y);
if (xDiff < CLICK && yDiff < CLICK)
performClick();
break;
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
break;
default:
break;
}
imgFloorPlan.setImageMatrix(matrix);
for (ImageView imageView:imgPointArray) {
imageView.setImageMatrix(matrix);
}
invalidate();
return true; // indicate event was handled
}
});
}
public void setMaxZoom(float x) {
maxScale = x;
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
mode = ZOOM;
return true;
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
float mScaleFactor = detector.getScaleFactor();
float origScale = saveScale;
saveScale *= mScaleFactor;
if (saveScale > maxScale) {
saveScale = maxScale;
mScaleFactor = maxScale / origScale;
} else if (saveScale < minScale) {
saveScale = minScale;
mScaleFactor = minScale / origScale;
}
if (origWidth * saveScale <= viewWidth || origHeight * saveScale <= viewHeight)
matrix.postScale(mScaleFactor, mScaleFactor, viewWidth / 2, viewHeight / 2);
else
matrix.postScale(mScaleFactor, mScaleFactor, detector.getFocusX(), detector.getFocusY());
fixTrans();
return true;
}
}
void fixTrans() {
matrix.getValues(m);
float transX = m[Matrix.MTRANS_X];
float transY = m[Matrix.MTRANS_Y];
float fixTransX = getFixTrans(transX, viewWidth, origWidth * saveScale);
float fixTransY = getFixTrans(transY, viewHeight, origHeight * saveScale);
if (fixTransX != 0 || fixTransY != 0)
matrix.postTranslate(fixTransX, fixTransY);
}
float getFixTrans(float trans, float viewSize, float contentSize) {
float minTrans, maxTrans;
if (contentSize <= viewSize) {
minTrans = 0;
maxTrans = viewSize - contentSize;
} else {
minTrans = viewSize - contentSize;
maxTrans = 0;
}
if (trans < minTrans)
return -trans + minTrans;
if (trans > maxTrans)
return -trans + maxTrans;
return 0;
}
float getFixDragTrans(float delta, float viewSize, float contentSize) {
if (contentSize <= viewSize) {
return 0;
}
return delta;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.d("ZoomLayout", "onMeasure called");
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
viewWidth = MeasureSpec.getSize(widthMeasureSpec);
viewHeight = MeasureSpec.getSize(heightMeasureSpec);
// Rescales image on rotation
if (oldMeasuredHeight == viewWidth && oldMeasuredHeight == viewHeight
|| viewWidth == 0 || viewHeight == 0)
return;
oldMeasuredHeight = viewHeight;
oldMeasuredWidth = viewWidth;
if (saveScale == 1) {
//Fit to screen.
float scale;
Activity act = (Activity) getContext();
imgFloorPlan = (ImageView) act.findViewById(R.id.imgFloorPlan);
Drawable drawable = imgFloorPlan.getDrawable();
if (drawable == null || drawable.getIntrinsicWidth() == 0 || drawable.getIntrinsicHeight() == 0)
return;
int bmWidth = drawable.getIntrinsicWidth();
int bmHeight = drawable.getIntrinsicHeight();
Log.d("bmSize", "bmWidth: " + bmWidth + " bmHeight : " + bmHeight);
float scaleX = (float) viewWidth / (float) bmWidth;
float scaleY = (float) viewHeight / (float) bmHeight;
scale = Math.min(scaleX, scaleY);
matrix.setScale(scale, scale);
// Center the image
float redundantYSpace = (float) viewHeight - (scale * (float) bmHeight);
float redundantXSpace = (float) viewWidth - (scale * (float) bmWidth);
redundantYSpace /= (float) 2;
redundantXSpace /= (float) 2;
matrix.postTranslate(redundantXSpace, redundantYSpace);
origWidth = viewWidth - 2 * redundantXSpace;
origHeight = viewHeight - 2 * redundantYSpace;
imgFloorPlan.setImageMatrix(matrix);
for (ImageView imageView:imgPointArray) {
imageView.setImageMatrix(matrix);
}
}
fixTrans();
}
class ZoomGesture extends GestureDetector.SimpleOnGestureListener { //单手指操作
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
// TODO: Add on Touch event to place point
int x = (int) e.getX();
int y = (int) e.getY();
Log.d("PlayAreaView", "onSingleTapConfirmed" + x + "," + y);
int imageViewWH = (int) DimensionUtils.dpToPx(currentView, 60);
if (isTapOnFloorPlan(x, y, imageViewWH)) {
// Create a Pin on tapped location
MyImageView newPinView = new MyImageView(getContext());
newPinView.setLayoutParams(new FrameLayout.LayoutParams(imageViewWH, imageViewWH));
newPinView.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.ic_location_on_black_48dp));
newPinView.setScaleType(ImageView.ScaleType.MATRIX);
// Add to FrameLayout
addView(newPinView);
imgPointArray.add(newPinView);
// Move to mouse center
setAbsoluteLocationCentered(newPinView, x, y);
}Log.d("PlayAreaView", "onSingleTapConfirmed,getChildCount"+getChildCount());
return super.onSingleTapConfirmed(e);
}
@Override //双击
public boolean onDoubleTap(MotionEvent e) {
System.out.println("--onDoubleTap---");
return true;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
System.out.println("--onDoubleTapEvent---");
return super.onDoubleTapEvent(e);
}
}
private boolean isTapOnFloorPlan(int tapX, int tapY, int imageViewWH) {
Rect bounds = imgFloorPlan.getDrawable().getBounds();
int x = (imgFloorPlan.getMeasuredWidth() - bounds.width()) / 2;
int y = (imgFloorPlan.getMeasuredHeight() - bounds.height()) / 2;
int imageX1 = x + (imageViewWH / 2);
int imageX2 = x + bounds.width();
int imageY1 = y;
int imageY2 = y + bounds.height();
if (tapX >= imageX1 && tapX <= imageX2 && tapY >= imageY1 && tapY <= imageY2) {
Log.d("ZoomLayout", "isTapOnFloorPlan: " + true);
return true;
} else {
Log.d("ZoomLayout", "isTapOnFloorPlan: " + false);
return false;
}
}
private void setAbsoluteLocationCentered(View v, int x, int y) {
ViewGroup.LayoutParams params = v.getLayoutParams();
int viewRadiusX = params.width / 2;
int viewRadiusY = params.height;
x = x - viewRadiusX;
y = y - viewRadiusY;
setAbsoluteLocation(v, x, y);
}
private void setAbsoluteLocation(View v, int x, int y) {
FrameLayout.LayoutParams alp = (FrameLayout.LayoutParams) v.getLayoutParams();
alp.setMargins(x, y, 0, 0);
v.setLayoutParams(alp);
}
}
我找到了解决办法。
重点是矩阵
第一次,将图像缩放到imageview中并保存用于缩放图像的矩阵(原始宽度高度)。 请不要使用任何自动缩放方法,例如视图的 fitCenter 属性。 scaleType 必须是矩阵。
旧图像 + 矩阵 = 视图中的图像。
当 onMesure 和 onScale 时 视图中的图像 + 矩阵(Public 这个矩阵)= 缩放后的图像 其他视图 + public 矩阵 = 与图像一起缩放
也可以使用矩阵将点缩放到相同的比例。
有助于实施的关键字: ZoomLayout、GestureDetector、ScaleGestureDetector