web-dev-qa-db-ja.com

Android-スプレッド/ピンチでの相対レイアウトのズームイン/アウト

RelativeLayoutとその中にSimpleOnScaleGestureListenerを拡張するプライベートクラスを持つアクティビティがあります。リスナーのonScaleメソッドで、ユーザーが指を広げる/ピンチすることで、レイアウト全体(ユーザーが見るものすべて)をズームイン/アウトしたいと思います。

レイアウトの変更が永続的ではないようにしたい、つまり、スプレッド/ピンチジェスチャが終了したときに、レイアウトを元の場所に戻したい(onScaleEndたとえばSimpleOnScaleGestureListenerのメソッド)。

setScaleXsetScaleYRelativeLayoutを呼び出し、さらにScaleAnimationを使用して実装しようとしました。どちらもスムーズなズーム(またはズームと呼ばれるもの)をもたらしませんでした。 RelativeLayoutを拡大/縮小することも可能ですか?

私が残した唯一のアイデアは、キャッシュからスクリーンショットを読み取り、レイアウト全体の上にImageViewとして配置し、setImageMatrixを介してこの画像をズームイン/アウトすることです。ただし、これをどのように実装するのかはわかりません。

レイアウトには、フラグメントのコンテナも含まれている場合があります。これは、ズームが可能なはずの時点では空です。 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>

[〜#〜] edit [〜#〜]次の2つの関連トピックが見つかりました。
RelativeLayoutを拡張し、dispatchDraw()をオーバーライドしてズーム可能なViewGroupを作成します
RelativeLayoutのズームコンテンツ

ただし、実装は取得できませんでした。レイアウトを実際にスケーリングまたはリセットするには、拡張クラスに他にどのメソッドを含める必要がありますか?

33
Schnodahipfe

上記のトピックで説明したように、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();
    }
}

お役に立てれば!

更新:

OnPinchListenerを使用して、ZoomableRelativelayoutScaleGestureDetectorを統合できます。

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

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

}

この後、子が1つしかない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;
 }

このコードが機能する場合は、受け入れられたとおりに作成してください。

13

シュノダヒップの答えを少し改善できたと思います。 ZoomableRelativeLayoutクラスに2つのメソッドを追加しました。

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

元の答えでは、タッチイベントが終了するたびにスケールがリセットされますが、このように複数回ズームインおよびズームアウトできます。

6
Douglas Vanny

フラグメントの場合、アクティビティ名の代わりにgetActivity()を渡すだけです

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;
        }
    });
2
Brian Begun