Android - 用展开/捏合放大/缩小RelativeLayout
Posted
技术标签:
【中文标题】Android - 用展开/捏合放大/缩小RelativeLayout【英文标题】:Android - zoom in/out RelativeLayout with spread/pinch 【发布时间】:2012-04-18 07:48:48 【问题描述】:我有一个带有RelativeLayout
的活动和一个私有类,它扩展了SimpleOnScaleGestureListener
。在侦听器的onScale
方法中,我想放大/缩小整个布局(用户看到的所有内容),同时用户展开/捏手指。
我希望布局的更改不是永久性的,即当展开/捏合手势结束时,我希望布局恢复到最初的状态(任何重置都可以在以SimpleOnScaleGestureListener
的onScaleEnd
方法为例)。
我尝试通过在RelativeLayout
上调用setScaleX
和setScaleY
以及使用ScaleAnimation
来实现它。两者都没有导致平滑缩放(或任何可以称为缩放的东西)。甚至可以放大/缩小RelativeLayout
吗?
我剩下的唯一想法是从缓存中读取屏幕截图并将其作为ImageView
放在整个布局的顶部,并通过setImageMatrix
放大/缩小此图像。但是,我不知道如何实现它。
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_
android:layout_
android:background="#ffffff" >
<!-- Layout containing the thumbnail ImageViews -->
<LinearLayout
android:id="@+id/thumbnail_group_pui"
android:layout_
android:layout_
android:layout_centerVertical="true"
android:orientation="horizontal" >
<ImageView
android:layout_
android:layout_
android:background="@drawable/tn_c1"/>
<ImageView
android:layout_
android:layout_
android:background="@drawable/tn_c2"/>
<ImageView
android:layout_
android:layout_
android:background="@drawable/tn_c3"/>
<ImageView
android:layout_
android:layout_
android:background="@drawable/tn_c4"/>
<ImageView
android:layout_
android:layout_
android:background="@drawable/tn_c5"/>
<ImageView
android:layout_
android:layout_
android:background="@drawable/tn_c6"/>
<ImageView
android:layout_
android:layout_
android:background="@drawable/tn_c7"/>
</LinearLayout>
<!-- Layout containing the dashed boxes -->
<LinearLayout
android:layout_
android:layout_
android:layout_centerVertical="true"
android:orientation="horizontal" >
<ImageView
android:layout_
android:layout_
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:background="@drawable/dashed_box"/>
<ImageView
android:layout_
android:layout_
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:background="@drawable/dashed_box"/>
<ImageView
android:layout_
android:layout_
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:background="@drawable/dashed_box"/>
<ImageView
android:layout_
android:layout_
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:background="@drawable/dashed_box"/>
<ImageView
android:layout_
android:layout_
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:background="@drawable/dashed_box"/>
<ImageView
android:layout_
android:layout_
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:background="@drawable/dashed_box"/>
<ImageView
android:layout_
android:layout_
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_
android:layout_ />
</RelativeLayout>
编辑 我找到了这两个相关的话题:Extending RelativeLayout, and overriding dispatchDraw() to create a zoomable ViewGroupZoom Content in a RelativeLayout
但是,我没有得到实现。我必须在扩展类中包含哪些其他方法才能实际缩放布局或重置布局?
【问题讨论】:
缩放布局我们如何使用ZoomableRelativeLayout
?我需要将我的视图作为参数传递给它的构造函数吗?
【参考方案1】:
所以我创建了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();
我对@987654323@ 的实现如下所示:
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();
希望这会有所帮助!
更新:
您可以使用ScaleGestureDetector
为您的ZoomableRelativelayout
集成OnPinchListener
:
ScaleGestureDetector scaleGestureDetector = new ScaleGestureDetector(this, new OnPinchListener());
您需要将Zoomable布局的触摸监听器与ScaleGestureDetector的触摸监听器绑定:
mZoomableLayout.setOnTouchListener(new OnTouchListener()
@Override
public boolean onTouch(View v, MotionEvent event)
// TODO Auto-generated method stub
scaleGestureDetector.onTouchEvent(event);
return true;
);
【讨论】:
如何将此OnPinchListener
应用到RelativeLayout。
应用 onPinchListner 我认为你需要在需要指定 mOnPinchListener.onTouchEvent(ev) 的地方实现 onTouch;
似乎很多人都对为 ZoomableRelativeLayout 实现 OnPinchListener 有疑问,同样,我已经更新了代码!干杯
@Schnodahipfe :我实现了您的上述类,并且相对布局缩放完美。但我想在从屏幕上移开手指后保持缩小/缩小视图。所以为此我删除了“mZoomableRelativeLayout.restore();”来自“onScaleEnd”函数。这很完美。但问题是相对布局中的元素的触摸没有得到更新,因为当相对布局的“mScaleFactor”为1时它们仍然保持在它们存在的位置。请帮助....需要更新触摸相对布局内的按钮以及放大/缩小。
这不是从布局的相同大小(已经通过捏合缩放的缩放值)再次进行缩放,它的捏合工作完美。请告诉我我应该在这段代码中更新什么?
【参考方案2】:
创建名为 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);
在此之后在只有一个孩子的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_
android:id="@+id/zoomLayout"
android:background="#000000"
android:layout_>
<RelativeLayout
android:layout_
android:layout_>
<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_
android:layout_
android:id="@+id/tvSubtitle2"
android:layout_toLeftOf="@+id/ivOn"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<ImageView
android:id="@+id/ivOff"
android:layout_
android:layout_
android:src="@drawable/off_txt"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true" />
<ImageView
android:id="@+id/ivOn"
android:layout_
android:layout_
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_
android:layout_
android:src="@drawable/pin_off"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true" />
<ImageView
android:id="@+id/pinOn"
android:layout_
android:layout_
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_
android:layout_
android:layout_centerHorizontal="true"
android:layout_centerVertical="true">
<ImageView
android:src="@drawable/wait"
android:layout_
android:layout_
android:id="@+id/fullIVideo"/>
<ImageView
android:src="@drawable/wait"
android:layout_
android:layout_
android:id="@+id/colorCode"/>
<ImageView
android:src="@drawable/wait"
android:layout_
android:layout_
android:id="@+id/labelText"/>
<ImageView
android:src="@drawable/download"
android:layout_marginTop="91dp"
android:layout_
android:layout_
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_
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_>
<TextView
android:textSize="15sp"
android:textColor="#ffffff"
android:layout_gravity="center"
android:layout_
android:layout_
android:text="Medium Text"
android:id="@+id/tvLabel" />
<TextView
android:textColor="#ffffff"
android:layout_
android:layout_
android:layout_marginTop="5dp"
android:text="New Text"
android:layout_gravity="center"
android:id="@+id/tvLabelDescription" />
</LinearLayout>
<ImageView
android:layout_
android:layout_
android:src="@drawable/up"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:id="@+id/up" />
</RelativeLayout>
</com.focusmedica.digitalatlas.headandneck.ZoomLayout>
现在在 MainActivity 中创建 ZoomLayout 对象并定义 id.Like
ZoomLayout zoomlayout= findViewbyId(R.id.zoomLayout);
zoomlayout.setOnTouchListener(FullScreenVideoActivity.this);
public boolean onTouch(View v, MotionEvent event)
linear.init(FullScreenVideoActivity.this);
return false;
【讨论】:
这一行是什么线性的 linear.init(FullScreenVideoActivity.this); @AwaisMajeed ZoomLayout 我认为您不需要在触摸时在内部调用 init ,当它在 findViewById 外部时也可以工作 如何以编程方式暂时禁用此缩放功能而不将其从布局中删除?【参考方案3】:我想我设法稍微改进了 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);
并像这样重写 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();
原来的答案会在每次触摸事件结束时重置比例,但是像这样你可以放大和缩小多次。
【讨论】:
我应该如何定义 MAX_SCALE 和 MIN_SCALE? 我建议对 MIN_SCALE 使用 1.0f。我在 MAX_SCALE 上使用了 20.0f,但这取决于你想做什么。 这是一个视频编辑器的时间线,我希望用户能够垂直和水平放大和缩小。所以我应该通过实验找到 MIN_SCALE 和 MAX_SCALE 的写入数,对吗? 是的,这样最好。 如何将相对布局传递给 ZoomableRelativeLayout?【参考方案4】:对于片段,您只需要传递 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;
);
【讨论】:
不适合我。没有编译错误,也没有崩溃,但捏缩放什么都不做。我按照这里描述的方式在片段中使用它。有什么帮助吗? @Ashish Kumar Pal 捏缩放不起作用。@Diego Perez 你得到答案了吗?以上是关于Android - 用展开/捏合放大/缩小RelativeLayout的主要内容,如果未能解决你的问题,请参考以下文章