web-dev-qa-db-fra.com

Android - zoom avant / arrière RelativeLayout avec propagation / pincement

J'ai une activité avec un RelativeLayout et une classe privée, qui étend le SimpleOnScaleGestureListener. Dans la méthode onScale de l'auditeur, j'aimerais zoomer/dézoomer sur toute la mise en page (tout ce que l'utilisateur voit) avec l'utilisateur écartant/pinçant ses doigts.

J'aimerais que les modifications apportées à la mise en page ne soient PAS permanentes, c'est-à-dire que lorsque le geste de propagation/pincement est terminé, je voudrais que la mise en page revienne à ce qu'elle était en premier lieu (toute réinitialisation pourrait être effectuée dans le onScaleEnd méthode du SimpleOnScaleGestureListener par exemple).

J'ai essayé de l'implémenter en appelant setScaleX et setScaleY sur le RelativeLayout et également en utilisant un ScaleAnimation. Aucun des deux n'a entraîné un zoom fluide (ou quelque chose qui pourrait du tout être appelé zoom). Est-il même possible de zoomer/dézoomer sur un RelativeLayout?

La seule idée qui me reste serait de lire une capture d'écran du cache et de la mettre en tant que ImageView en haut de la mise en page entière et le zoom avant/arrière de cette image via setImageMatrix. Je n'ai cependant aucune idée de la façon de mettre cela en œuvre.

La disposition de mai contient également un conteneur pour un fragment, qui est vide au moment où le zoom est censé être possible. Dans le geste onScaleEnd, le fragment est placé dans son conteneur (déjà implémenté et fonctionne correctement). Voici ma mise en page:

<?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>

[~ # ~] modifier [~ # ~] J'ai trouvé ces deux sujets connexes:
Extension de RelativeLayout et remplacement de dispatchDraw () pour créer un ViewGroup zoomable
Zoom sur le contenu dans un RelativeLayout

Cependant, je n'ai pas obtenu la mise en œuvre. Quelles autres méthodes dois-je inclure dans la classe étendue pour réellement mettre à l'échelle la disposition ou la réinitialiser?

33
Schnodahipfe

J'ai donc créé une sous-classe de RelativeLayout comme décrit dans les rubriques mentionnées ci-dessus. Cela ressemble à ceci:

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

}

Mon implémentation de SimpleOnScaleGestureListener ressemble à ceci:

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

J'espère que cela t'aides!

Mise à jour:

Vous pouvez intégrer OnPinchListener pour votre ZoomableRelativelayout en utilisant ScaleGestureDetector:

ScaleGestureDetector scaleGestureDetector = new ScaleGestureDetector(this, new OnPinchListener());

Et vous devez lier l'écouteur tactile de la disposition Zoomable avec l'écouteur tactile de 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

Créez une classe appelée Zoomlayout qui étend toute mise en page que vous souhaitez zoomer dans mon cas, il s'agit de mise en page relative.

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

}

Après cela, ajoutez ZoomLayout en xml qui n'a qu'un seul enfant. Par exemple

<?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>

Maintenant, dans MainActivity, créez un objet de ZoomLayout et définissez 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;
 }

Je pense que cela fonctionnera. Si ce code fonctionne, veuillez le faire comme accepté.

13
Ashish Kumar Pal

Je pense que j'ai réussi à améliorer un peu la réponse de Schnodahipfe. J'ai ajouté deux méthodes à la classe 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);
    }
}

et réécrit la classe OnPinchListener comme ceci

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

La réponse d'origine réinitialisait l'échelle à chaque fois que l'événement tactile se terminait, mais comme cela, vous pouvez zoomer et dézoomer plusieurs fois.

6
Douglas Vanny

pour les fragments dont vous avez juste besoin de passer getActivity () au lieu du nom de l'activité

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