在 XYPlot 上实现平移而不缩放 (androidplot 1.2.2)
Implementing Pan without Zooming on XYPlot (androidplot 1.2.2)
我想在不缩放的情况下在我的绘图上实现平移,方法是将原点值设置为小于域轴上的下边界。
到目前为止我的代码:
this.panZoom = PanZoom.attach(plot);
this.panZoom.setPan(PanZoom.Pan.HORIZONTAL);
this.panZoom.setZoom(null);
this.panZoom.setDelegate(this);
this.plot.setUserDomainOrigin(0);
this.plot.setDomainBoundaries(5, 20);
this.plot.setDomainStep(StepMode.INCREMENT_BY_VAL, 2);
通过这样做,我的情节从 5 开始很好,但在我滚动时不会移动..
我正在将我的项目迁移到 androidPlot 1.2.2,它正在使用 0.9.7
谢谢!
这似乎是 Androidplot 1.2.2 中的一个限制(将在下一个版本中修复 - tracked by this bug report。)
现在您可以在您的项目中添加此 PanZoom 实现:
package com.androidplot.xy;
import android.graphics.RectF;
import android.graphics.PointF;
import android.util.*;
import android.view.*;
import com.androidplot.*;
import java.util.*;
/**
* Enables basic pan/zoom touch behavior for an {@link XYPlot}.
* By default boundaries set on the associated plot will define the scroll/zoom extents as well as
* initial state of the plot's visible area. If you wish to specify a scrollable / zoomable area
* that is greater than or less than the plot's boundaries, use
* {@link #setDomainBoundaries(Number, Number)} and
* {@link #setRangeBoundaries(Number, Number)}
* TODO: zoom using dynamic center point
* TODO: stretch both mode
*/
public class PanZoom implements View.OnTouchListener {
protected static final float MIN_DIST_2_FING = 5f;
protected static final int FIRST_FINGER = 0;
protected static final int SECOND_FINGER = 1;
private XYPlot plot;
private Pan pan;
private Zoom zoom;
private boolean isEnabled = true;
private DragState dragState = DragState.NONE;
RectRegion limits = new RectRegion();
RectRegion previousLimits = new RectRegion();
private PointF firstFingerPos;
// rectangle created by the space between two fingers
private RectF fingersRect;
private View.OnTouchListener delegate;
// Definition of the touch states
protected enum DragState {
NONE,
ONE_FINGER,
TWO_FINGERS
}
public enum Pan {
NONE,
HORIZONTAL,
VERTICAL,
BOTH
}
public enum Zoom {
/**
* Comletely disable panning
*/
NONE,
/**
* Zoom on the horizontal axis only
*/
STRETCH_HORIZONTAL,
/**
* Zoom on the vertical axis only
*/
STRETCH_VERTICAL,
/**
* Zoom on the vertical axis by the vertical distance between each finger, while zooming
* on the horizontal axis by the horizantal distance between each finger.
*/
STRETCH_BOTH,
/**
* Zoom each axis by the same amount, specifically the total distance between each finger.
*/
SCALE
}
protected PanZoom(XYPlot plot, Pan pan, Zoom zoom) {
this.plot = plot;
this.pan = pan;
this.zoom = zoom;
}
/**
* Convenience method for enabling pan/zoom behavior on an instance of {@link XYPlot}, using
* a default behavior of {@link Pan#BOTH} and {@link Zoom#SCALE}.
* Use {@link PanZoom#attach(XYPlot, Pan, Zoom)} for finer grain control of this behavior.
* @param plot
* @return
*/
public static PanZoom attach(XYPlot plot) {
return attach(plot, Pan.BOTH, Zoom.SCALE);
}
public static PanZoom attach(XYPlot plot, Pan pan, Zoom zoom) {
PanZoom pz = new PanZoom(plot, pan, zoom);
plot.setOnTouchListener(pz);
return pz;
}
public boolean isEnabled() {
return isEnabled;
}
public void setEnabled(boolean enabled) {
isEnabled = enabled;
}
/**
* Set the boundaries by which domain pan/zoom calculations will abide; differs from an {@link XYPlot}'s boundaries
* in that those boundaries define the plot's starting state.
* @param lowerBoundary
* @param upperBoundary
*/
public void setDomainBoundaries(final Number lowerBoundary, final Number upperBoundary) {
limits.setMinX(lowerBoundary);
limits.setMaxX(upperBoundary);
}
/**
* Sets the range boundaries by which pan/zoom calculations will abide.
* @param lowerBoundary
* @param upperBoundary
*/
public void setRangeBoundaries(final Number lowerBoundary, final Number upperBoundary) {
limits.setMinY(lowerBoundary);
limits.setMaxY(upperBoundary);
}
@Override
public boolean onTouch(final View view, final MotionEvent event) {
boolean isConsumed = false;
if (delegate != null) {
isConsumed = delegate.onTouch(view, event);
}
if (isEnabled() && !isConsumed) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: // start gesture
firstFingerPos = new PointF(event.getX(), event.getY());
Log.d("PanZoom", "ONE_FINGER set");
dragState = DragState.ONE_FINGER;
break;
case MotionEvent.ACTION_POINTER_DOWN: // second finger
{
fingersRect = fingerDistance(event);
Log.d("PanZoom", "ACTION_POINTER_DOWN - distance: " + fingersRect.width());
// the distance check is done to avoid false alarms
if (fingersRect.width() > MIN_DIST_2_FING || fingersRect.width() < -MIN_DIST_2_FING) {
Log.d("PanZoom", "TWO_FINGERS set");
dragState = DragState.TWO_FINGERS;
}
break;
}
case MotionEvent.ACTION_POINTER_UP: // end zoom
dragState = DragState.NONE;
break;
case MotionEvent.ACTION_MOVE:
if (dragState == DragState.ONE_FINGER) {
Log.d("PanZoom", "ACTION_MOVE - one finger");
pan(event);
} else if (dragState == DragState.TWO_FINGERS) {
Log.d("PanZoom", "ACTION_MOVE - two fingers");
zoom(event);
}
break;
}
}
// we're forced to consume the event here as not consuming it will prevent future calls:
return true;
}
/**
* Calculates the distance between two finger motion events.
* @param firstFingerX
* @param firstFingerY
* @param secondFingerX
* @param secondFingerY
* @return
*/
protected RectF fingerDistance(float firstFingerX, float firstFingerY, float secondFingerX, float secondFingerY) {
final float left = firstFingerX > secondFingerX ? secondFingerX : firstFingerX;
final float right = firstFingerX > secondFingerX ? firstFingerX : secondFingerX;
final float top = firstFingerY > secondFingerY ? secondFingerY : firstFingerY;
final float bottom = firstFingerY > secondFingerY ? firstFingerY : secondFingerY;
return new RectF(left, top, right, bottom);
}
/**
* Calculates the distance between two finger motion events.
* @param evt
* @return
*/
protected RectF fingerDistance(final MotionEvent evt) {
return fingerDistance(
evt.getX(FIRST_FINGER),
evt.getY(FIRST_FINGER),
evt.getX(SECOND_FINGER),
evt.getY(SECOND_FINGER));
}
protected Number getMinXLimit() {
if (limits.getMinX() == null) {
limits.setMinX(plot.getBounds().getMinX().floatValue());
previousLimits.setMinX(limits.getMinX());
}
return limits.getMinX();
}
protected Number getMaxXLimit() {
if (limits.getMaxX() == null) {
limits.setMaxX(plot.getBounds().getMaxX().floatValue());
previousLimits.setMaxX(limits.getMaxX());
}
return limits.getMaxX();
}
protected Number getMinYLimit() {
if (limits.getMinY() == null) {
limits.setMinY(plot.getBounds().getMinY().floatValue());
previousLimits.setMinY(limits.getMinY());
}
return limits.getMinY();
}
protected Number getMaxYLimit() {
if (limits.getMaxY() == null) {
limits.setMaxY(plot.getBounds().getMaxY().floatValue());
previousLimits.setMaxY(limits.getMaxY());
}
return limits.getMaxY();
}
protected Number getLastMinX() {
if (previousLimits.getMinX() == null) {
previousLimits.setMinX(plot.getBounds().getMinX().floatValue());
}
return previousLimits.getMinX();
}
protected Number getLastMaxX() {
if (previousLimits.getMaxX() == null) {
previousLimits.setMaxX(plot.getBounds().getMaxX().floatValue());
}
return previousLimits.getMaxX();
}
protected Number getLastMinY() {
if (previousLimits.getMinY() == null) {
previousLimits.setMinY(plot.getBounds().getMinY().floatValue());
}
return previousLimits.getMinY();
}
private Number getLastMaxY() {
if (previousLimits.getMaxY() == null) {
previousLimits.setMaxY(plot.getBounds().getMaxY().floatValue());
}
return previousLimits.getMaxY();
}
protected void pan(final MotionEvent motionEvent) {
if (pan == Pan.NONE) {
return;
}
final PointF oldFirstFinger = firstFingerPos; //save old position of finger
firstFingerPos = new PointF(motionEvent.getX(), motionEvent.getY()); //update finger position
Region newBounds = new Region();
if (EnumSet.of(Pan.HORIZONTAL, Pan.BOTH).contains(pan)) {
calculatePan(oldFirstFinger, newBounds, true);
plot.setDomainBoundaries(newBounds.getMin(), newBounds.getMax(), BoundaryMode.FIXED);
previousLimits.setMinX(newBounds.getMin());
previousLimits.setMaxX(newBounds.getMax());
}
if (EnumSet.of(Pan.VERTICAL, Pan.BOTH).contains(pan)) {
calculatePan(oldFirstFinger, newBounds, false);
plot.setRangeBoundaries(newBounds.getMin(), newBounds.getMax(), BoundaryMode.FIXED);
previousLimits.setMinY(newBounds.getMin());
previousLimits.setMaxY(newBounds.getMax());
}
plot.redraw();
}
protected void calculatePan(final PointF oldFirstFinger, Region bounds, final boolean horizontal) {
final float offset;
// multiply the absolute finger movement for a factor.
// the factor is dependent on the calculated min and max
if (horizontal) {
bounds.setMin(getLastMinX());
bounds.setMax(getLastMaxX());
offset = (oldFirstFinger.x - firstFingerPos.x) *
((bounds.getMax().floatValue() - bounds.getMin().floatValue()) / plot.getWidth());
} else {
bounds.setMin(getLastMinY());
bounds.setMax(getLastMaxY());
offset = -(oldFirstFinger.y - firstFingerPos.y) *
((bounds.getMax().floatValue() - bounds.getMin().floatValue()) / plot.getHeight());
}
// move the calculated offset
bounds.setMin(bounds.getMin().floatValue() + offset);
bounds.setMax(bounds.getMax().floatValue() + offset);
//get the distance between max and min
final float diff = bounds.length().floatValue();
//check if we reached the limit of panning
if (horizontal) {
if (bounds.getMin().floatValue() < getMinXLimit().floatValue()) {
bounds.setMin(getMinXLimit());
bounds.setMax(bounds.getMin().floatValue() + diff);
}
if (bounds.getMax().floatValue() > getMaxXLimit().floatValue()) {
bounds.setMax(getMaxXLimit());
bounds.setMin(bounds.getMax().floatValue() - diff);
}
} else {
if (bounds.getMin().floatValue() < getMinYLimit().floatValue()) {
bounds.setMin(getMinYLimit());
bounds.setMax(bounds.getMin().floatValue() + diff);
}
if (bounds.getMax().floatValue() > getMaxYLimit().floatValue()) {
bounds.setMax(getMaxYLimit());
bounds.setMin(bounds.getMax().floatValue() - diff);
}
}
}
protected boolean isValidScale(float scale) {
if (Float.isInfinite(scale) || Float.isNaN(scale) || scale > -0.001 && scale < 0.001) {
return false;
}
return true;
}
protected void zoom(final MotionEvent motionEvent) {
if (zoom == Zoom.NONE) {
return;
}
final RectF oldFingersRect = fingersRect;
final RectF newFingersRect = fingerDistance(motionEvent);
fingersRect = newFingersRect;
RectF newRect = new RectF();
float scaleX = 1;
float scaleY = 1;
switch (zoom) {
case STRETCH_HORIZONTAL:
scaleX = oldFingersRect.width() / fingersRect.width();
if (!isValidScale(scaleX)) {
return;
}
break;
case STRETCH_VERTICAL:
scaleY = oldFingersRect.height() / fingersRect.height();
if (!isValidScale(scaleY)) {
return;
}
break;
case STRETCH_BOTH:
scaleX = oldFingersRect.width() / fingersRect.width();
scaleY = oldFingersRect.height() / fingersRect.height();
if (!isValidScale(scaleX) || !isValidScale(scaleY)) {
return;
}
break;
case SCALE:
float sc1 = (float) Math.hypot(oldFingersRect.height(), oldFingersRect.width());
float sc2 = (float) Math.hypot(fingersRect.height(), fingersRect.width());
float sc = sc1 / sc2;
scaleX = sc;
scaleY = sc;
if (!isValidScale(scaleX) || !isValidScale(scaleY)) {
return;
}
break;
}
if (EnumSet.of(
Zoom.STRETCH_HORIZONTAL,
Zoom.STRETCH_BOTH,
Zoom.SCALE).contains(zoom)) {
calculateZoom(newRect, scaleX, true);
plot.setDomainBoundaries(newRect.left, newRect.right, BoundaryMode.FIXED);
previousLimits.setMinX(newRect.left);
previousLimits.setMaxX(newRect.right);
}
if (EnumSet.of(
Zoom.STRETCH_VERTICAL,
Zoom.STRETCH_BOTH,
Zoom.SCALE).contains(zoom)) {
calculateZoom(newRect, scaleY, false);
plot.setRangeBoundaries(newRect.top, newRect.bottom, BoundaryMode.FIXED);
previousLimits.setMinY(newRect.top);
previousLimits.setMaxY(newRect.bottom);
}
plot.redraw();
}
protected void calculateZoom(RectF newRect, float scale, boolean isHorizontal) {
final float calcMax;
final float span;
if (isHorizontal) {
calcMax = getLastMaxX().floatValue();
span = calcMax - getLastMinX().floatValue();
} else {
calcMax = getLastMaxY().floatValue();
span = calcMax - getLastMinY().floatValue();
}
final float midPoint = calcMax - (span / 2.0f);
final float offset = span * scale / 2.0f;
if (isHorizontal) {
newRect.left = midPoint - offset;
newRect.right = midPoint + offset;
if (newRect.left < getMinXLimit().floatValue()) {
newRect.left = getMinXLimit().floatValue();
}
if (newRect.right > getMaxXLimit().floatValue()) {
newRect.right = getMaxXLimit().floatValue();
}
} else {
newRect.top = midPoint - offset;
newRect.bottom = midPoint + offset;
if (newRect.top < getMinYLimit().floatValue()) {
newRect.top = getMinYLimit().floatValue();
}
if (newRect.bottom > getMaxYLimit().floatValue()) {
newRect.bottom = getMaxYLimit().floatValue();
}
}
}
public Pan getPan() {
return pan;
}
public void setPan(Pan pan) {
this.pan = pan;
}
public Zoom getZoom() {
return zoom;
}
public void setZoom(Zoom zoom) {
this.zoom = zoom;
}
public View.OnTouchListener getDelegate() {
return delegate;
}
/**
* Set a delegate to receive onTouch calls before this class does. If the delegate wishes
* to consume the event, it should return true, otherwise it should return false. Returning
* false will not prevent future onTouch events from filtering through the delegate as it normally
* would when attaching directly to an instance of {@link View}.
* @param delegate
*/
public void setDelegate(View.OnTouchListener delegate) {
this.delegate = delegate;
}
public void reset() {
this.previousLimits = new RectRegion();
this.firstFingerPos = null;
this.fingersRect = null;
}
}
不要忘记更改 class 名称以避免与现有的 PanZoom 实现发生冲突。
如果您想将起始 window 边界限制到数据的一个子部分,请在 XYPlot 实例上正常设置这些边界。然后,使用平移和缩放的绝对边界配置 PanZoom 实例,通常是附加到绘图的系列的 min/max 值。
此外,如果您需要在 onCreate 中进行初始设置后更改可见 window(用户按下重置按钮等的结果),您还需要调用 PanZoom.reset()
消灭它的内部状态。正式版实现中可能不需要这一步。
我想在不缩放的情况下在我的绘图上实现平移,方法是将原点值设置为小于域轴上的下边界。
到目前为止我的代码:
this.panZoom = PanZoom.attach(plot);
this.panZoom.setPan(PanZoom.Pan.HORIZONTAL);
this.panZoom.setZoom(null);
this.panZoom.setDelegate(this);
this.plot.setUserDomainOrigin(0);
this.plot.setDomainBoundaries(5, 20);
this.plot.setDomainStep(StepMode.INCREMENT_BY_VAL, 2);
通过这样做,我的情节从 5 开始很好,但在我滚动时不会移动.. 我正在将我的项目迁移到 androidPlot 1.2.2,它正在使用 0.9.7
谢谢!
这似乎是 Androidplot 1.2.2 中的一个限制(将在下一个版本中修复 - tracked by this bug report。)
现在您可以在您的项目中添加此 PanZoom 实现:
package com.androidplot.xy;
import android.graphics.RectF;
import android.graphics.PointF;
import android.util.*;
import android.view.*;
import com.androidplot.*;
import java.util.*;
/**
* Enables basic pan/zoom touch behavior for an {@link XYPlot}.
* By default boundaries set on the associated plot will define the scroll/zoom extents as well as
* initial state of the plot's visible area. If you wish to specify a scrollable / zoomable area
* that is greater than or less than the plot's boundaries, use
* {@link #setDomainBoundaries(Number, Number)} and
* {@link #setRangeBoundaries(Number, Number)}
* TODO: zoom using dynamic center point
* TODO: stretch both mode
*/
public class PanZoom implements View.OnTouchListener {
protected static final float MIN_DIST_2_FING = 5f;
protected static final int FIRST_FINGER = 0;
protected static final int SECOND_FINGER = 1;
private XYPlot plot;
private Pan pan;
private Zoom zoom;
private boolean isEnabled = true;
private DragState dragState = DragState.NONE;
RectRegion limits = new RectRegion();
RectRegion previousLimits = new RectRegion();
private PointF firstFingerPos;
// rectangle created by the space between two fingers
private RectF fingersRect;
private View.OnTouchListener delegate;
// Definition of the touch states
protected enum DragState {
NONE,
ONE_FINGER,
TWO_FINGERS
}
public enum Pan {
NONE,
HORIZONTAL,
VERTICAL,
BOTH
}
public enum Zoom {
/**
* Comletely disable panning
*/
NONE,
/**
* Zoom on the horizontal axis only
*/
STRETCH_HORIZONTAL,
/**
* Zoom on the vertical axis only
*/
STRETCH_VERTICAL,
/**
* Zoom on the vertical axis by the vertical distance between each finger, while zooming
* on the horizontal axis by the horizantal distance between each finger.
*/
STRETCH_BOTH,
/**
* Zoom each axis by the same amount, specifically the total distance between each finger.
*/
SCALE
}
protected PanZoom(XYPlot plot, Pan pan, Zoom zoom) {
this.plot = plot;
this.pan = pan;
this.zoom = zoom;
}
/**
* Convenience method for enabling pan/zoom behavior on an instance of {@link XYPlot}, using
* a default behavior of {@link Pan#BOTH} and {@link Zoom#SCALE}.
* Use {@link PanZoom#attach(XYPlot, Pan, Zoom)} for finer grain control of this behavior.
* @param plot
* @return
*/
public static PanZoom attach(XYPlot plot) {
return attach(plot, Pan.BOTH, Zoom.SCALE);
}
public static PanZoom attach(XYPlot plot, Pan pan, Zoom zoom) {
PanZoom pz = new PanZoom(plot, pan, zoom);
plot.setOnTouchListener(pz);
return pz;
}
public boolean isEnabled() {
return isEnabled;
}
public void setEnabled(boolean enabled) {
isEnabled = enabled;
}
/**
* Set the boundaries by which domain pan/zoom calculations will abide; differs from an {@link XYPlot}'s boundaries
* in that those boundaries define the plot's starting state.
* @param lowerBoundary
* @param upperBoundary
*/
public void setDomainBoundaries(final Number lowerBoundary, final Number upperBoundary) {
limits.setMinX(lowerBoundary);
limits.setMaxX(upperBoundary);
}
/**
* Sets the range boundaries by which pan/zoom calculations will abide.
* @param lowerBoundary
* @param upperBoundary
*/
public void setRangeBoundaries(final Number lowerBoundary, final Number upperBoundary) {
limits.setMinY(lowerBoundary);
limits.setMaxY(upperBoundary);
}
@Override
public boolean onTouch(final View view, final MotionEvent event) {
boolean isConsumed = false;
if (delegate != null) {
isConsumed = delegate.onTouch(view, event);
}
if (isEnabled() && !isConsumed) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: // start gesture
firstFingerPos = new PointF(event.getX(), event.getY());
Log.d("PanZoom", "ONE_FINGER set");
dragState = DragState.ONE_FINGER;
break;
case MotionEvent.ACTION_POINTER_DOWN: // second finger
{
fingersRect = fingerDistance(event);
Log.d("PanZoom", "ACTION_POINTER_DOWN - distance: " + fingersRect.width());
// the distance check is done to avoid false alarms
if (fingersRect.width() > MIN_DIST_2_FING || fingersRect.width() < -MIN_DIST_2_FING) {
Log.d("PanZoom", "TWO_FINGERS set");
dragState = DragState.TWO_FINGERS;
}
break;
}
case MotionEvent.ACTION_POINTER_UP: // end zoom
dragState = DragState.NONE;
break;
case MotionEvent.ACTION_MOVE:
if (dragState == DragState.ONE_FINGER) {
Log.d("PanZoom", "ACTION_MOVE - one finger");
pan(event);
} else if (dragState == DragState.TWO_FINGERS) {
Log.d("PanZoom", "ACTION_MOVE - two fingers");
zoom(event);
}
break;
}
}
// we're forced to consume the event here as not consuming it will prevent future calls:
return true;
}
/**
* Calculates the distance between two finger motion events.
* @param firstFingerX
* @param firstFingerY
* @param secondFingerX
* @param secondFingerY
* @return
*/
protected RectF fingerDistance(float firstFingerX, float firstFingerY, float secondFingerX, float secondFingerY) {
final float left = firstFingerX > secondFingerX ? secondFingerX : firstFingerX;
final float right = firstFingerX > secondFingerX ? firstFingerX : secondFingerX;
final float top = firstFingerY > secondFingerY ? secondFingerY : firstFingerY;
final float bottom = firstFingerY > secondFingerY ? firstFingerY : secondFingerY;
return new RectF(left, top, right, bottom);
}
/**
* Calculates the distance between two finger motion events.
* @param evt
* @return
*/
protected RectF fingerDistance(final MotionEvent evt) {
return fingerDistance(
evt.getX(FIRST_FINGER),
evt.getY(FIRST_FINGER),
evt.getX(SECOND_FINGER),
evt.getY(SECOND_FINGER));
}
protected Number getMinXLimit() {
if (limits.getMinX() == null) {
limits.setMinX(plot.getBounds().getMinX().floatValue());
previousLimits.setMinX(limits.getMinX());
}
return limits.getMinX();
}
protected Number getMaxXLimit() {
if (limits.getMaxX() == null) {
limits.setMaxX(plot.getBounds().getMaxX().floatValue());
previousLimits.setMaxX(limits.getMaxX());
}
return limits.getMaxX();
}
protected Number getMinYLimit() {
if (limits.getMinY() == null) {
limits.setMinY(plot.getBounds().getMinY().floatValue());
previousLimits.setMinY(limits.getMinY());
}
return limits.getMinY();
}
protected Number getMaxYLimit() {
if (limits.getMaxY() == null) {
limits.setMaxY(plot.getBounds().getMaxY().floatValue());
previousLimits.setMaxY(limits.getMaxY());
}
return limits.getMaxY();
}
protected Number getLastMinX() {
if (previousLimits.getMinX() == null) {
previousLimits.setMinX(plot.getBounds().getMinX().floatValue());
}
return previousLimits.getMinX();
}
protected Number getLastMaxX() {
if (previousLimits.getMaxX() == null) {
previousLimits.setMaxX(plot.getBounds().getMaxX().floatValue());
}
return previousLimits.getMaxX();
}
protected Number getLastMinY() {
if (previousLimits.getMinY() == null) {
previousLimits.setMinY(plot.getBounds().getMinY().floatValue());
}
return previousLimits.getMinY();
}
private Number getLastMaxY() {
if (previousLimits.getMaxY() == null) {
previousLimits.setMaxY(plot.getBounds().getMaxY().floatValue());
}
return previousLimits.getMaxY();
}
protected void pan(final MotionEvent motionEvent) {
if (pan == Pan.NONE) {
return;
}
final PointF oldFirstFinger = firstFingerPos; //save old position of finger
firstFingerPos = new PointF(motionEvent.getX(), motionEvent.getY()); //update finger position
Region newBounds = new Region();
if (EnumSet.of(Pan.HORIZONTAL, Pan.BOTH).contains(pan)) {
calculatePan(oldFirstFinger, newBounds, true);
plot.setDomainBoundaries(newBounds.getMin(), newBounds.getMax(), BoundaryMode.FIXED);
previousLimits.setMinX(newBounds.getMin());
previousLimits.setMaxX(newBounds.getMax());
}
if (EnumSet.of(Pan.VERTICAL, Pan.BOTH).contains(pan)) {
calculatePan(oldFirstFinger, newBounds, false);
plot.setRangeBoundaries(newBounds.getMin(), newBounds.getMax(), BoundaryMode.FIXED);
previousLimits.setMinY(newBounds.getMin());
previousLimits.setMaxY(newBounds.getMax());
}
plot.redraw();
}
protected void calculatePan(final PointF oldFirstFinger, Region bounds, final boolean horizontal) {
final float offset;
// multiply the absolute finger movement for a factor.
// the factor is dependent on the calculated min and max
if (horizontal) {
bounds.setMin(getLastMinX());
bounds.setMax(getLastMaxX());
offset = (oldFirstFinger.x - firstFingerPos.x) *
((bounds.getMax().floatValue() - bounds.getMin().floatValue()) / plot.getWidth());
} else {
bounds.setMin(getLastMinY());
bounds.setMax(getLastMaxY());
offset = -(oldFirstFinger.y - firstFingerPos.y) *
((bounds.getMax().floatValue() - bounds.getMin().floatValue()) / plot.getHeight());
}
// move the calculated offset
bounds.setMin(bounds.getMin().floatValue() + offset);
bounds.setMax(bounds.getMax().floatValue() + offset);
//get the distance between max and min
final float diff = bounds.length().floatValue();
//check if we reached the limit of panning
if (horizontal) {
if (bounds.getMin().floatValue() < getMinXLimit().floatValue()) {
bounds.setMin(getMinXLimit());
bounds.setMax(bounds.getMin().floatValue() + diff);
}
if (bounds.getMax().floatValue() > getMaxXLimit().floatValue()) {
bounds.setMax(getMaxXLimit());
bounds.setMin(bounds.getMax().floatValue() - diff);
}
} else {
if (bounds.getMin().floatValue() < getMinYLimit().floatValue()) {
bounds.setMin(getMinYLimit());
bounds.setMax(bounds.getMin().floatValue() + diff);
}
if (bounds.getMax().floatValue() > getMaxYLimit().floatValue()) {
bounds.setMax(getMaxYLimit());
bounds.setMin(bounds.getMax().floatValue() - diff);
}
}
}
protected boolean isValidScale(float scale) {
if (Float.isInfinite(scale) || Float.isNaN(scale) || scale > -0.001 && scale < 0.001) {
return false;
}
return true;
}
protected void zoom(final MotionEvent motionEvent) {
if (zoom == Zoom.NONE) {
return;
}
final RectF oldFingersRect = fingersRect;
final RectF newFingersRect = fingerDistance(motionEvent);
fingersRect = newFingersRect;
RectF newRect = new RectF();
float scaleX = 1;
float scaleY = 1;
switch (zoom) {
case STRETCH_HORIZONTAL:
scaleX = oldFingersRect.width() / fingersRect.width();
if (!isValidScale(scaleX)) {
return;
}
break;
case STRETCH_VERTICAL:
scaleY = oldFingersRect.height() / fingersRect.height();
if (!isValidScale(scaleY)) {
return;
}
break;
case STRETCH_BOTH:
scaleX = oldFingersRect.width() / fingersRect.width();
scaleY = oldFingersRect.height() / fingersRect.height();
if (!isValidScale(scaleX) || !isValidScale(scaleY)) {
return;
}
break;
case SCALE:
float sc1 = (float) Math.hypot(oldFingersRect.height(), oldFingersRect.width());
float sc2 = (float) Math.hypot(fingersRect.height(), fingersRect.width());
float sc = sc1 / sc2;
scaleX = sc;
scaleY = sc;
if (!isValidScale(scaleX) || !isValidScale(scaleY)) {
return;
}
break;
}
if (EnumSet.of(
Zoom.STRETCH_HORIZONTAL,
Zoom.STRETCH_BOTH,
Zoom.SCALE).contains(zoom)) {
calculateZoom(newRect, scaleX, true);
plot.setDomainBoundaries(newRect.left, newRect.right, BoundaryMode.FIXED);
previousLimits.setMinX(newRect.left);
previousLimits.setMaxX(newRect.right);
}
if (EnumSet.of(
Zoom.STRETCH_VERTICAL,
Zoom.STRETCH_BOTH,
Zoom.SCALE).contains(zoom)) {
calculateZoom(newRect, scaleY, false);
plot.setRangeBoundaries(newRect.top, newRect.bottom, BoundaryMode.FIXED);
previousLimits.setMinY(newRect.top);
previousLimits.setMaxY(newRect.bottom);
}
plot.redraw();
}
protected void calculateZoom(RectF newRect, float scale, boolean isHorizontal) {
final float calcMax;
final float span;
if (isHorizontal) {
calcMax = getLastMaxX().floatValue();
span = calcMax - getLastMinX().floatValue();
} else {
calcMax = getLastMaxY().floatValue();
span = calcMax - getLastMinY().floatValue();
}
final float midPoint = calcMax - (span / 2.0f);
final float offset = span * scale / 2.0f;
if (isHorizontal) {
newRect.left = midPoint - offset;
newRect.right = midPoint + offset;
if (newRect.left < getMinXLimit().floatValue()) {
newRect.left = getMinXLimit().floatValue();
}
if (newRect.right > getMaxXLimit().floatValue()) {
newRect.right = getMaxXLimit().floatValue();
}
} else {
newRect.top = midPoint - offset;
newRect.bottom = midPoint + offset;
if (newRect.top < getMinYLimit().floatValue()) {
newRect.top = getMinYLimit().floatValue();
}
if (newRect.bottom > getMaxYLimit().floatValue()) {
newRect.bottom = getMaxYLimit().floatValue();
}
}
}
public Pan getPan() {
return pan;
}
public void setPan(Pan pan) {
this.pan = pan;
}
public Zoom getZoom() {
return zoom;
}
public void setZoom(Zoom zoom) {
this.zoom = zoom;
}
public View.OnTouchListener getDelegate() {
return delegate;
}
/**
* Set a delegate to receive onTouch calls before this class does. If the delegate wishes
* to consume the event, it should return true, otherwise it should return false. Returning
* false will not prevent future onTouch events from filtering through the delegate as it normally
* would when attaching directly to an instance of {@link View}.
* @param delegate
*/
public void setDelegate(View.OnTouchListener delegate) {
this.delegate = delegate;
}
public void reset() {
this.previousLimits = new RectRegion();
this.firstFingerPos = null;
this.fingersRect = null;
}
}
不要忘记更改 class 名称以避免与现有的 PanZoom 实现发生冲突。
如果您想将起始 window 边界限制到数据的一个子部分,请在 XYPlot 实例上正常设置这些边界。然后,使用平移和缩放的绝对边界配置 PanZoom 实例,通常是附加到绘图的系列的 min/max 值。
此外,如果您需要在 onCreate 中进行初始设置后更改可见 window(用户按下重置按钮等的结果),您还需要调用 PanZoom.reset()
消灭它的内部状态。正式版实现中可能不需要这一步。