Android - 用展开/捏合放大/缩小RelativeLayout

Posted

技术标签:

【中文标题】Android - 用展开/捏合放大/缩小RelativeLayout【英文标题】:Android - zoom in/out RelativeLayout with spread/pinch 【发布时间】:2012-04-18 07:48:48 【问题描述】:

我有一个带有RelativeLayout 的活动和一个私有类,它扩展了SimpleOnScaleGestureListener。在侦听器的onScale 方法中,我想放大/缩小整个布局(用户看到的所有内容),同时用户展开/捏手指。

我希望布局的更改不是永久性的,即当展开/捏合手势结束时,我希望布局恢复到最初的状态(任何重置都可以在以SimpleOnScaleGestureListeneronScaleEnd 方法为例)。

我尝试通过在RelativeLayout 上调用setScaleXsetScaleY 以及使用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();



我对@9​​87654323@ 的实现如下所示:

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的主要内容,如果未能解决你的问题,请参考以下文章

UIScrollView 放大固定点,并禁用捏合/拉出

捏合和点击手势无法正常工作

可捏合的网页视图

android中ImageView放大和缩小相关问题?

关于 android WebView字体的放大缩小

通过捏合手势缩放 UICollectionView