Dans le code ci-dessous, j'ai créé quelque chose appelé le RegionView
( git ), qui est un conteneur réutilisable chargé de gérer les opérations de glisser et de zoom pour chacun de ses enfants imbriqués.
Ici, nous manipulons les top
et left
coefficients d'un enfant View
est LayoutParams
de simuler le mouvement sur le diagramme. En découplant l'interprétation de la gestion de ce qui est compris comme une opération de traînée et de ce qui est déterminé comme une opération d'échelle, nous pouvons fournir une manipulation fiable d'un enfant View
.
package com.zonal.regionview;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.Vibrator;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.widget.RelativeLayout;
import java.util.HashMap;
import java.util.Map;
public class RegionView extends RelativeLayout implements View.OnTouchListener, GestureDetector.OnGestureListener, ScaleGestureDetector.OnScaleGestureListener {
private final GestureDetector mGestureDetector;
private final ScaleGestureDetector mScaleGestureDetector;
private final Map<Integer, View> mViewMap;
private boolean mScaling;
private float mScale;
private boolean mWrapContent;
private boolean mDropOnScale;
public RegionView(Context context) {
super(context);
this.mGestureDetector = new GestureDetector(context, this);
this.mViewMap = new HashMap<>();
this.mScaleGestureDetector = new ScaleGestureDetector(context, this);
this.mScaling = false;
this.mScale = Float.NaN;
this.mWrapContent = false;
this.mDropOnScale = false;
this.setOnTouchListener(this);
}
public RegionView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
this.mGestureDetector = new GestureDetector(context, this);
this.mViewMap = new HashMap<>();
this.mScaleGestureDetector = new ScaleGestureDetector(context, this);
this.mScaling = false;
this.mWrapContent = false;
this.mDropOnScale = false;
this.setOnTouchListener(this);
}
public RegionView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mGestureDetector = new GestureDetector(context, this);
this.mViewMap = new HashMap<>();
this.mScaleGestureDetector = new ScaleGestureDetector(context, this);
this.mScaling = false;
this.mWrapContent = false;
this.mDropOnScale = false;
this.setOnTouchListener(this);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public RegionView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
this.mGestureDetector = new GestureDetector(context, this);
this.mViewMap = new HashMap<>();
this.mScaleGestureDetector = new ScaleGestureDetector(context, this);
this.mScaling = false;
this.mWrapContent = false;
this.mDropOnScale = false;
this.setOnTouchListener(this);
}
@Override
public boolean onTouch(final View v, final MotionEvent event) {
final int lPointerId = event.getPointerId(event.getActionIndex());
this.getGestureDetector().onTouchEvent(event);
this.getScaleGestureDetector().onTouchEvent(event);
if(event.getAction() == MotionEvent.ACTION_UP) {
final View lView = this.getViewMap().get(lPointerId);
if(lView != null) {
this.getViewMap().remove(lPointerId);
}
}
return true;
}
@Override
public boolean onDown(MotionEvent e) {
final Integer lPointerId = Integer.valueOf(e.getPointerId(e.getActionIndex()));
final View lView = this.getViewFor(Math.round(e.getRawX()), Math.round(e.getRawY()));
if(lView != null) {
this.getViewMap().put(lPointerId, lView);
lView.setPivotX(0);
lView.setPivotY(0);
return true;
}
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if(!this.isScaling()) {
final Integer lPointerId = Integer.valueOf(e1.getPointerId(e1.getActionIndex()));
final View lView = this.getViewMap().get(lPointerId);
if(lView != null) {
final float lWidth = lView.getWidth() * lView.getScaleX();
final float lHeight = lView.getHeight() * lView.getScaleY();
final int[] lPosition = new int[] { (int)(e2.getX() - ((lWidth) / 2)), (int)(e2.getY() - ((lHeight) / 2)) };
if(this.isWrapContent()) {
this.onWrapContent(lPosition, lWidth, lHeight);
}
this.onUpdateDrag(lView, lPosition);
}
return true;
}
return false;
}
private final void onWrapContent(final int[] pPosition, final float pWidth, final float pHeight) {
pPosition[0] = Math.max(pPosition[0], 0);
pPosition[1] = Math.max(pPosition[1], 0);
pPosition[0] = Math.min(pPosition[0], (int)(this.getWidth() - pWidth));
pPosition[1] = Math.min(pPosition[1], (int)(this.getHeight() - pHeight));
}
private final void onUpdateDrag(final View pView, final int pLeft, final int pTop) {
final MarginLayoutParams lMarginLayoutParams = new MarginLayoutParams(pView.getLayoutParams());
lMarginLayoutParams.setMargins(pLeft, pTop, 0, 0);
pView.setLayoutParams(new RelativeLayout.LayoutParams(lMarginLayoutParams));
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
float lScaleFactor = detector.getScaleFactor() - 1;
final View lView = this.getViewMap().entrySet().iterator().next().getValue();
final float lScale = this.getScale() + lScaleFactor;
final int lWidth = Math.round(lView.getWidth() * lScale);
final int lHeight = Math.round(lView.getHeight() * lScale);
if(lWidth >= this.getWidth() || lHeight >= this.getHeight()) {
return false;
}
lView.setScaleX(lScale);
lView.setScaleY(lScale);
this.setScale(lScale);
final int[] lPosition = new int[] { Math.round(detector.getFocusX()) - (lWidth / 2), Math.round(detector.getFocusY()) - (lHeight / 2) };
if(this.isWrapContent()) {
this.onWrapContent(lPosition, lWidth, lHeight);
}
this.onUpdateDrag(lView, lPosition);
return true;
}
private final void onUpdateDrag(final View pView, final int[] pPosition) {
this.onUpdateDrag(pView, pPosition[0], pPosition[1]);
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
if(this.getViewMap().size() == 1) {
final View lView = this.getViewMap().entrySet().iterator().next().getValue();
this.setScale(lView.getScaleX());
this.setScaling(true);
return true;
}
return false;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
if(this.isScaling()) {
this.setScaling(false);
this.setScale(Float.NaN);
if(this.isDropOnScale()) {
this.getViewMap().clear();
}
}
}
private final View getViewFor(final int pX, final int pY) {
final int[] lLocationBuffer = new int[2];
for(int i = 0; i < this.getChildCount(); i++) {
final View lView = this.getChildAt(i);
lView.getLocationOnScreen(lLocationBuffer);
if(pX > lLocationBuffer[0] && pY > lLocationBuffer[1] && (pX < lLocationBuffer[0] + (lView.getWidth() * lView.getScaleX())) && (pY < lLocationBuffer[1] + (lView.getHeight() * lView.getScaleY()))) {
return lView;
}
}
return null;
}
@Override public void onShowPress(MotionEvent e) { }
@Override public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override public void onLongPress(MotionEvent e) { }
@Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return false; }
private final GestureDetector getGestureDetector() {
return this.mGestureDetector;
}
private final ScaleGestureDetector getScaleGestureDetector() {
return this.mScaleGestureDetector;
}
private final Map<Integer, View> getViewMap() {
return this.mViewMap;
}
private final void setScaling(final boolean pIsScaling) {
this.mScaling = pIsScaling;
}
private final boolean isScaling() {
return this.mScaling;
}
private final void setScale(final float pScale) {
this.mScale = pScale;
}
private final float getScale() {
return this.mScale;
}
public final void setWrapContent(final boolean pIsWrapContent) {
this.mWrapContent = pIsWrapContent;
}
public final boolean isWrapContent() {
return this.mWrapContent;
}
public final void setDropOnScale(final boolean pIsDropOnScale) {
this.mDropOnScale = pIsDropOnScale;
}
public final boolean isDropOnScale() {
return this.mDropOnScale;
}
}
Ici, je montre un exemple de cas d'utilisation:
package com.zonal.regionview;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.AnalogClock;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final RegionView lRegionView = new RegionView(this);
lRegionView.addView(new AnalogClock(this));
lRegionView.addView(new AnalogClock(this));
lRegionView.addView(new AnalogClock(this));
lRegionView.setWrapContent(true);
lRegionView.setDropOnScale(true);
this.setContentView(lRegionView);
}
}