Android – увеличение / уменьшение местоположения RelativeLayout с распространением / пинчем

У меня есть активность с RelativeLayout и частный класс, который расширяет SimpleOnScaleGestureListener . В методе onScale слушателя я хотел бы увеличить / уменьшить весь макет (все, что видит пользователь) с помощью пользователя, распространяющего / зажимающего его пальцы.

Я бы хотел, чтобы изменения в макете не были постоянными, т. Е. Когда жест распространения / пинча закончился, я хотел бы, чтобы макет возвращался к тому, что он был на первом месте (любой сброс может быть выполнен в методе onScaleEnd Например, SimpleOnScaleGestureListener ).

Я попытался реализовать его, вызвав setScaleX и setScaleY на RelativeLayout а также с помощью 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 Я нашел эти две связанные темы:
Расширение RelativeLayout и переопределение dispatchDraw () для создания масштабируемой ViewGroup
Увеличить содержимое в RelativeLayout

Однако я не получил реализацию. Какие другие методы я должен включить в расширенный класс для фактического масштабирования макета или его сброса?

Solutions Collecting From Web of "Android – увеличение / уменьшение местоположения RelativeLayout с распространением / пинчем"

Поэтому я создал подкласс 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 для ZoomableRelativelayout , используя ScaleGestureDetector :

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

И вам необходимо привязать сенсорный прослушиватель Zoomable layout с помощью сенсорного прослушивателя ScaleGestureDetector:

 mZoomableLayout.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // TODO Auto-generated method stub scaleGestureDetector.onTouchEvent(event); return true; } }); 

Думаю, мне удалось немного улучшить ответ 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(); } } 

Первоначальный ответ сбрасывал масштаб каждый раз, когда событие касания заканчивалось, но, как это, вы можете увеличивать и уменьшать несколько раз.

Создайте класс под названием Zoomlayout, который расширяет любой макет, который вы хотите увеличить в моем случае, это Relative Layout.

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

После этого добавьте ZoomLayout в xml, который имеет только одно дочернее. Например

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

Я hpoe это будет работать. Если этот код будет работать, пожалуйста, сделайте это как принято.

Решение Ашиша работало для меня. Мне пришлось сделать несколько улучшений в кодировке, поскольку в реализации MainActivity были некоторые ошибки форматирования.

Ниже приведены изменения:

 final ZoomLayout zoomlayout = (ZoomLayout) findViewById(R.id.zoomLayout); zoomlayout.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { zoomlayout.init(FullScreenVideoActivity.this); return false; } });