Android - 用展开/捏合放大/缩小RelativeLayout
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10013906/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Android - zoom in/out RelativeLayout with spread/pinch
提问by Schnodahipfe
I have an activity with a RelativeLayout
and a private class in it, which extends the SimpleOnScaleGestureListener
. In the onScale
method of the listener I'd like to zoom in/out of the whole layout (everything the user sees) with the user spreading/pinching his fingers.
我有一个带有 aRelativeLayout
和一个私有类的活动,它扩展了SimpleOnScaleGestureListener
. 在onScale
侦听器的方法中,我想放大/缩小整个布局(用户看到的所有内容),同时用户展开/捏住他的手指。
I'd like the changes to the layout NOT to be permanent, i.e. when the spread/pinch gesture is over I'd like the layout to go back to what it was in first place (any resetting could be done in the onScaleEnd
method of the SimpleOnScaleGestureListener
for example).
我想改变的布局不被永久的,即当病毒/捏合手势是在我想的布局回去这是什么在首位(任何复位可能在做onScaleEnd
的方法SimpleOnScaleGestureListener
例如)。
I've tried to implement it via calling setScaleX
and setScaleY
on the RelativeLayout
and also by using a ScaleAnimation
. Neither resulted in smooth zooming (or anything that could at all be called zooming). Is it even possible to zoom in/out a RelativeLayout
?
我尝试通过调用setScaleX
和setScaleY
onRelativeLayout
以及使用ScaleAnimation
. 两者都没有导致平滑缩放(或任何可以称为缩放的东西)。甚至可以放大/缩小 aRelativeLayout
吗?
The only idea I have left would be reading a screenshot from the cache and putting it as an ImageView
on top of the whole layout and the zoom in/out of this image via setImageMatrix
. I have no clue, however, how to implement this.
我剩下的唯一想法是从缓存中读取屏幕截图并将其作为ImageView
整个布局的顶部,并通过setImageMatrix
. 但是,我不知道如何实现这一点。
May layout also contains a container for a fragment, which is empty at the time the zooming is supposed to be possible. In the onScaleEnd
gesture, the fragment is put into it's container (already implemented and working fine). Here is my layout:
May 布局还包含一个片段容器,在应该可以进行缩放时该容器是空的。在onScaleEnd
手势中,片段被放入它的容器中(已经实现并且工作正常)。这是我的布局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout_pinch"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff" >
<!-- Layout containing the thumbnail ImageViews -->
<LinearLayout
android:id="@+id/thumbnail_group_pui"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:orientation="horizontal" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tn_c1"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tn_c2"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tn_c3"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tn_c4"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tn_c5"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tn_c6"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tn_c7"/>
</LinearLayout>
<!-- Layout containing the dashed boxes -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="152dp"
android:layout_centerVertical="true"
android:orientation="horizontal" >
<ImageView
android:layout_width="177dp"
android:layout_height="match_parent"
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:background="@drawable/dashed_box"/>
<ImageView
android:layout_width="177dp"
android:layout_height="match_parent"
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:background="@drawable/dashed_box"/>
<ImageView
android:layout_width="177dp"
android:layout_height="match_parent"
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:background="@drawable/dashed_box"/>
<ImageView
android:layout_width="177dp"
android:layout_height="match_parent"
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:background="@drawable/dashed_box"/>
<ImageView
android:layout_width="177dp"
android:layout_height="match_parent"
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:background="@drawable/dashed_box"/>
<ImageView
android:layout_width="177dp"
android:layout_height="match_parent"
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:background="@drawable/dashed_box"/>
<ImageView
android:layout_width="177dp"
android:layout_height="match_parent"
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:background="@drawable/dashed_box"/>
</LinearLayout>
<!-- Container for the fragments -->
<FrameLayout
android:id="@+id/fragment_container_pui"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
EDITI found these two related topics:
Extending RelativeLayout, and overriding dispatchDraw() to create a zoomable ViewGroup
Zoom Content in a RelativeLayout
编辑我发现这两个相关主题:
扩展RelativeLayout,并覆盖dispatchDraw()以在RelativeLayout中创建可缩放的ViewGroup
缩放内容
I did not get the implementation, however. What other methods do I have to include in the extended class to actually scale the layout or reset it?
然而,我没有得到实现。我必须在扩展类中包含哪些其他方法才能实际缩放布局或重置它?
采纳答案by Schnodahipfe
So I created a subclass of RelativeLayout
as described in the above mentioned topics. It looks like this:
所以我创建了一个子类,RelativeLayout
如上述主题中所述。它看起来像这样:
public class ZoomableRelativeLayout extends RelativeLayout {
float mScaleFactor = 1;
float mPivotX;
float mPivotY;
public ZoomableRelativeLayout(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
public ZoomableRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public ZoomableRelativeLayout(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
protected void dispatchDraw(Canvas canvas) {
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.scale(mScaleFactor, mScaleFactor, mPivotX, mPivotY);
super.dispatchDraw(canvas);
canvas.restore();
}
public void scale(float scaleFactor, float pivotX, float pivotY) {
mScaleFactor = scaleFactor;
mPivotX = pivotX;
mPivotY = pivotY;
this.invalidate();
}
public void restore() {
mScaleFactor = 1;
this.invalidate();
}
}
My implementation of the SimpleOnScaleGestureListener
looks like this:
我的实现SimpleOnScaleGestureListener
看起来像这样:
private class OnPinchListener extends SimpleOnScaleGestureListener {
float startingSpan;
float endSpan;
float startFocusX;
float startFocusY;
public boolean onScaleBegin(ScaleGestureDetector detector) {
startingSpan = detector.getCurrentSpan();
startFocusX = detector.getFocusX();
startFocusY = detector.getFocusY();
return true;
}
public boolean onScale(ScaleGestureDetector detector) {
mZoomableRelativeLayout.scale(detector.getCurrentSpan()/startingSpan, startFocusX, startFocusY);
return true;
}
public void onScaleEnd(ScaleGestureDetector detector) {
mZoomableRelativeLayout.restore();
}
}
Hope this helps!
希望这可以帮助!
Update:
更新:
You can integrate OnPinchListener
for your ZoomableRelativelayout
by using ScaleGestureDetector
:
您可以使用以下方法OnPinchListener
为您集成:ZoomableRelativelayout
ScaleGestureDetector
ScaleGestureDetector scaleGestureDetector = new ScaleGestureDetector(this, new OnPinchListener());
And you are required to bind touch listener of Zoomable layout with the touch listener of ScaleGestureDetector:
并且您需要将 Zoomable 布局的触摸侦听器与 ScaleGestureDetector 的触摸侦听器绑定:
mZoomableLayout.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
scaleGestureDetector.onTouchEvent(event);
return true;
}
});
回答by Ashish Kumar Pal
Create class called Zoomlayout which extends any layout that you want to zoom in my case it is Relative Layout.
创建名为 Zoomlayout 的类,它扩展了您想要放大的任何布局,在我的情况下它是相对布局。
public class ZoomLayout extends RelativeLayout implements ScaleGestureDetector.OnScaleGestureListener {
private enum Mode {
NONE,
DRAG,
ZOOM
}
private static final String TAG = "ZoomLayout";
private static final float MIN_ZOOM = 1.0f;
private static final float MAX_ZOOM = 4.0f;
private Mode mode = Mode.NONE;
private float scale = 1.0f;
private float lastScaleFactor = 0f;
// Where the finger first touches the screen
private float startX = 0f;
private float startY = 0f;
// How much to translate the canvas
private float dx = 0f;
private float dy = 0f;
private float prevDx = 0f;
private float prevDy = 0f;
public ZoomLayout(Context context) {
super(context);
init(context);
}
public ZoomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ZoomLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
public void init(Context context) {
final ScaleGestureDetector scaleDetector = new ScaleGestureDetector(context, this);
this.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "DOWN");
if (scale > MIN_ZOOM) {
mode = Mode.DRAG;
startX = motionEvent.getX() - prevDx;
startY = motionEvent.getY() - prevDy;
}
break;
case MotionEvent.ACTION_MOVE:
if (mode == Mode.DRAG) {
dx = motionEvent.getX() - startX;
dy = motionEvent.getY() - startY;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode = Mode.ZOOM;
break;
case MotionEvent.ACTION_POINTER_UP:
mode = Mode.DRAG;
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "UP");
mode = Mode.NONE;
prevDx = dx;
prevDy = dy;
break;
}
scaleDetector.onTouchEvent(motionEvent);
if ((mode == Mode.DRAG && scale >= MIN_ZOOM) || mode == Mode.ZOOM) {
getParent().requestDisallowInterceptTouchEvent(true);
float maxDx = (child().getWidth() - (child().getWidth() / scale)) / 2 * scale;
float maxDy = (child().getHeight() - (child().getHeight() / scale))/ 2 * scale;
dx = Math.min(Math.max(dx, -maxDx), maxDx);
dy = Math.min(Math.max(dy, -maxDy), maxDy);
Log.i(TAG, "Width: " + child().getWidth() + ", scale " + scale + ", dx " + dx
+ ", max " + maxDx);
applyScaleAndTranslation();
}
return true;
}
});
}
// ScaleGestureDetector
@Override
public boolean onScaleBegin(ScaleGestureDetector scaleDetector) {
Log.i(TAG, "onScaleBegin");
return true;
}
@Override
public boolean onScale(ScaleGestureDetector scaleDetector) {
float scaleFactor = scaleDetector.getScaleFactor();
Log.i(TAG, "onScale" + scaleFactor);
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;
}
@Override
public void onScaleEnd(ScaleGestureDetector scaleDetector) {
Log.i(TAG, "onScaleEnd");
}
private void applyScaleAndTranslation() {
child().setScaleX(scale);
child().setScaleY(scale);
child().setTranslationX(dx);
child().setTranslationY(dy);
}
private View child() {
return getChildAt(0);
}
}
After this add ZoomLayout in xml that has only one child.For example
在此之后在只有一个孩子的 xml 中添加 ZoomLayout。例如
<?xml version="1.0" encoding="utf-8"?>
<com.focusmedica.digitalatlas.headandneck.ZoomLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:id="@+id/zoomLayout"
android:background="#000000"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:paddingTop="5dp"
android:textColor="#ffffff"
android:text="Heading"
android:gravity="center"
android:textAlignment="textStart"
android:paddingLeft="5dp"
android:textSize="20sp"
android:textStyle="bold"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/tvSubtitle2"
android:layout_toLeftOf="@+id/ivOn"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<ImageView
android:id="@+id/ivOff"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/off_txt"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true" />
<ImageView
android:id="@+id/ivOn"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/on_txt"
android:layout_alignParentTop="true"
android:layout_alignLeft="@+id/pinOn"
android:layout_alignStart="@+id/pinOn" />
<ImageView
android:id="@+id/pinOff"
android:visibility="invisible"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/pin_off"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true" />
<ImageView
android:id="@+id/pinOn"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/pin_on"
android:layout_alignParentTop="true"
android:layout_toLeftOf="@+id/ivOff"
android:layout_toStartOf="@+id/ivOff" />
<RelativeLayout
android:id="@+id/linear"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true">
<ImageView
android:src="@drawable/wait"
android:layout_width="match_parent"
android:layout_height="300dp"
android:id="@+id/fullIVideo"/>
<ImageView
android:src="@drawable/wait"
android:layout_width="match_parent"
android:layout_height="300dp"
android:id="@+id/colorCode"/>
<ImageView
android:src="@drawable/wait"
android:layout_width="match_parent"
android:layout_height="300dp"
android:id="@+id/labelText"/>
<ImageView
android:src="@drawable/download"
android:layout_marginTop="91dp"
android:layout_width="100dp"
android:layout_height="100dp"
android:id="@+id/label_play"
android:layout_alignTop="@+id/fullIVideo"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>
<LinearLayout
android:orientation="vertical"
android:id="@+id/custom_toast_layout"
android:layout_width="300dp"
android:layout_above="@+id/up"
android:background="@drawable/rectangle_frame"
android:paddingLeft="10dp"
android:paddingBottom="10dp"
android:paddingTop="10dp"
android:paddingRight="10dp"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_height="wrap_content">
<TextView
android:textSize="15sp"
android:textColor="#ffffff"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Medium Text"
android:id="@+id/tvLabel" />
<TextView
android:textColor="#ffffff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="New Text"
android:layout_gravity="center"
android:id="@+id/tvLabelDescription" />
</LinearLayout>
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/up"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:id="@+id/up" />
</RelativeLayout>
</com.focusmedica.digitalatlas.headandneck.ZoomLayout>
Now in MainActivity create object of ZoomLayout and define id.Like
现在在 MainActivity 创建 ZoomLayout 对象并定义 id.Like
ZoomLayout zoomlayout=(ZoomLayout)findviewbyid(R.id.zoomLayout);
zoomlayout.setOnTouchListener(FullScreenVideoActivity.this);
public boolean onTouch(View v, MotionEvent event) {
linear.init(FullScreenVideoActivity.this);
return false;
}
I hpoe it will work.if this code will work then please make as accepted.
我 hpoe 它将起作用。如果此代码起作用,那么请按接受进行。
回答by Douglas Vanny
I think I managed to improve Schnodahipfe's answer a little bit. I added two methods to the ZoomableRelativeLayout class.
我想我设法改善了 Schnodahipfe 的答案。我向 ZoomableRelativeLayout 类添加了两个方法。
public void relativeScale(float scaleFactor, float pivotX, float pivotY)
{
mScaleFactor *= scaleFactor;
if(scaleFactor >= 1)
{
mPivotX = mPivotX + (pivotX - mPivotX) * (1 - 1 / scaleFactor);
mPivotY = mPivotY + (pivotY - mPivotY) * (1 - 1 / scaleFactor);
}
else
{
pivotX = getWidth()/2;
pivotY = getHeight()/2;
mPivotX = mPivotX + (pivotX - mPivotX) * (1 - scaleFactor);
mPivotY = mPivotY + (pivotY - mPivotY) * (1 - scaleFactor);
}
this.invalidate();
}
public void release()
{
if(mScaleFactor < MIN_SCALE)
{
final float startScaleFactor = mScaleFactor;
Animation a = new Animation()
{
@Override
protected void applyTransformation(float interpolatedTime, Transformation t)
{
scale(startScaleFactor + (MIN_SCALE - startScaleFactor)*interpolatedTime,mPivotX,mPivotY);
}
};
a.setDuration(300);
startAnimation(a);
}
else if(mScaleFactor > MAX_SCALE)
{
final float startScaleFactor = mScaleFactor;
Animation a = new Animation()
{
@Override
protected void applyTransformation(float interpolatedTime, Transformation t)
{
scale(startScaleFactor + (MAX_SCALE - startScaleFactor)*interpolatedTime,mPivotX,mPivotY);
}
};
a.setDuration(300);
startAnimation(a);
}
}
and rewrote the OnPinchListener class like this
并像这样重写 OnPinchListener 类
private class OnPinchListener extends ScaleGestureDetector.SimpleOnScaleGestureListener
{
float currentSpan;
float startFocusX;
float startFocusY;
public boolean onScaleBegin(ScaleGestureDetector detector)
{
currentSpan = detector.getCurrentSpan();
startFocusX = detector.getFocusX();
startFocusY = detector.getFocusY();
return true;
}
public boolean onScale(ScaleGestureDetector detector)
{
ZoomableRelativeLayout zoomableRelativeLayout= (ZoomableRelativeLayout) ImageFullScreenActivity.this.findViewById(R.id.imageWrapper);
zoomableRelativeLayout.relativeScale(detector.getCurrentSpan() / currentSpan, startFocusX, startFocusY);
currentSpan = detector.getCurrentSpan();
return true;
}
public void onScaleEnd(ScaleGestureDetector detector)
{
ZoomableRelativeLayout zoomableRelativeLayout= (ZoomableRelativeLayout) ImageFullScreenActivity.this.findViewById(R.id.imageWrapper);
zoomableRelativeLayout.release();
}
}
The original answer would reset the scale every time the touch event ended, but like this you can zoom in and out multiple times.
每次触摸事件结束时,原始答案都会重置比例,但像这样您可以多次放大和缩小。
回答by Brian Begun
for fragments you just need to pass getActivity() instead of Activity Name
对于片段,您只需要传递 getActivity() 而不是 Activity Name
final ZoomLayout zoomlayout = (ZoomLayout) findViewById(R.id.zoomLayout);
zoomlayout.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
zoomlayout.init(getActivity());
return false;
}
});