Android - 使用spread / pinch放大/缩小RelativeLayout

时间:2012-04-04 15:06:22

标签: android android-layout android-animation

我的活动中包含RelativeLayout和私有类,扩展了SimpleOnScaleGestureListener。在侦听器的onScale方法中,我想放大/缩小整个布局(用户看到的一切),用户传播/捏住他的手指。

我希望布局的更改不是永久性的,即当展开/捏合手势结束时,我希望布局回到原来的位置(任何重置都可以在例如onScaleEnd的{​​{1}}方法。

我尝试通过调用SimpleOnScaleGestureListener上的setScaleXsetScaleY以及使用RelativeLayout来实现它。两者都没有导致平滑缩放(或者任何可以称为缩放的东西)。是否可以放大/缩小ScaleAnimation

我留下的唯一想法是从缓存中读取屏幕截图并将其作为RelativeLayout放在整个布局的顶部,并通过ImageView放大/缩小此图像。但是,我不知道如何实现这一点。

May布局还包含片段的容器,在可以进行缩放时它是空的。在setImageMatrix手势中,片段被放入其容器中(已经实现且工作正常)。这是我的布局:

onScaleEnd

修改 我找到了这两个相关的主题:
Extending RelativeLayout, and overriding dispatchDraw() to create a zoomable ViewGroup
Zoom Content in a RelativeLayout

但是,我没有得到实施。我还需要在扩展类中包含哪些其他方法才能实际扩展布局或重置布局?

4 个答案:

答案 0 :(得分:31)

所以我创建了一个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();
}

}

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();
    }
}

希望这有帮助!

更新

您可以使用OnPinchListenerZoomableRelativelayout整合ScaleGestureDetector

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;
                }
            });

答案 1 :(得分:13)

创建一个名为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_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>

现在在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;
 }

我知道它会起作用。如果这段代码有效,那么请接受。

答案 2 :(得分:6)

我想我设法改善了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();
    }
}

每次触摸事件结束时,原始答案都会重置比例,但是像这样你可以多次放大和缩小。

答案 3 :(得分:2)

对于片段,您只需要传递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;
        }
    });