Android - увеличение/уменьшение изображения RelativeLayout с распространением/пинчем
У меня есть активность с RelativeLayout
и частный класс, который расширяет SimpleOnScaleGestureListener
. В методе onScale
слушателя я хотел бы увеличить/уменьшить весь макет (все, что видит пользователь) с помощью пользователя, распространяющего/зажимающего его пальцы.
Я бы хотел, чтобы изменения в макете не были постоянными, т.е. когда жест распространения/пинча закончился, я бы хотел, чтобы макет возвращался к тому, на чем он был на первом месте (любой сброс может быть выполнен в onScaleEnd
метод SimpleOnScaleGestureListener
, например).
Я попытался реализовать его, вызвав setScaleX
и setScaleY
на RelativeLayout
, а также с помощью ScaleAnimation
. Не было также и плавного масштабирования (или всего, что можно было бы назвать масштабированием). Возможно ли даже увеличение/уменьшение a 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>
ИЗМЕНИТЬ
Я нашел эти две связанные темы:
Расширение RelativeLayout и переопределение dispatchDraw() для создания масштабируемой ViewGroup
Увеличить содержимое в RelativeLayout
Однако я не реализовал реализацию. Какие другие методы я должен включить в расширенный класс для фактического масштабирования макета или reset it?
Ответы
Ответ 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();
}
}
Моя реализация 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;
}
});
Ответ 2
Создайте класс под названием 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;
}
Я надеюсь, что это сработает. Если этот код будет работать, пожалуйста, сделайте как принято.
Ответ 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();
}
}
Первоначальный ответ будет reset масштаб каждый раз, когда событие касания закончится, но, как это, вы можете увеличивать и уменьшать несколько раз.
Ответ 4
для фрагментов вам просто нужно передать getActivity() вместо имени Activity
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;
}
});