diff --git a/.circleci/config.yml b/.circleci/config.yml index a3cd7bfe..c5579475 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,7 +16,7 @@ jobs: # CircleCI maintains a library of pre-built images # documented at https://circleci.com/docs/2.0/circleci-images/ - - image: circleci/android:api-26-alpha + - image: circleci/android:api-28-alpha working_directory: ~/repo diff --git a/androidplot-core/src/main/java/com/androidplot/pie/PieRenderer.java b/androidplot-core/src/main/java/com/androidplot/pie/PieRenderer.java index dc83e620..caaddc06 100644 --- a/androidplot-core/src/main/java/com/androidplot/pie/PieRenderer.java +++ b/androidplot-core/src/main/java/com/androidplot/pie/PieRenderer.java @@ -170,7 +170,7 @@ protected void drawSegment(Canvas canvas, RectF bounds, Segment seg, SegmentForm canvas.drawLine(r1Inner.x, r1Inner.y, r1Outer.x, r1Outer.y, f.getRadialEdgePaint()); canvas.drawLine(r2Inner.x, r2Inner.y, r2Outer.x, r2Outer.y, f.getRadialEdgePaint()); } else { - canvas.save(Canvas.CLIP_SAVE_FLAG); + canvas.save(); Path chart = new Path(); chart.addCircle(cx, cy, outerRad, Path.Direction.CW); Path inside = new Path(); diff --git a/androidplot-core/src/main/java/com/androidplot/ui/LayoutManager.java b/androidplot-core/src/main/java/com/androidplot/ui/LayoutManager.java index e4c5cce5..0fb1a04c 100644 --- a/androidplot-core/src/main/java/com/androidplot/ui/LayoutManager.java +++ b/androidplot-core/src/main/java/com/androidplot/ui/LayoutManager.java @@ -92,7 +92,7 @@ public void draw(Canvas canvas) throws PlotRenderException { } for (Widget widget : elements()) { try { - canvas.save(Canvas.ALL_SAVE_FLAG); + canvas.save(); PositionMetrics metrics = widget.getPositionMetrics(); float elementWidth = widget.getWidthPix(displayDims.paddedRect.width()); float elementHeight = widget.getHeightPix(displayDims.paddedRect.height()); @@ -140,7 +140,7 @@ public void draw(Canvas canvas) throws PlotRenderException { private static void drawSpacing(Canvas canvas, RectF outer, RectF inner, Paint paint) { try { - canvas.save(Canvas.ALL_SAVE_FLAG); + canvas.save(); canvas.clipRect(inner, Region.Op.DIFFERENCE); canvas.drawRect(outer, paint); } finally { diff --git a/androidplot-core/src/main/java/com/androidplot/ui/SeriesRenderer.java b/androidplot-core/src/main/java/com/androidplot/ui/SeriesRenderer.java index 882bdcfc..afc1b142 100644 --- a/androidplot-core/src/main/java/com/androidplot/ui/SeriesRenderer.java +++ b/androidplot-core/src/main/java/com/androidplot/ui/SeriesRenderer.java @@ -81,7 +81,7 @@ protected abstract void onRender(Canvas canvas, RectF plotArea, SeriesType serie public void drawSeriesLegendIcon(Canvas canvas, RectF rect, SeriesFormatterType formatter) { try { - canvas.save(Canvas.ALL_SAVE_FLAG); + canvas.save(); canvas.clipRect(rect, Region.Op.INTERSECT); doDrawLegendIcon(canvas, rect, formatter); } finally { diff --git a/androidplot-core/src/main/java/com/androidplot/ui/widget/TextLabelWidget.java b/androidplot-core/src/main/java/com/androidplot/ui/widget/TextLabelWidget.java index 8c6e84cd..06bf54ff 100644 --- a/androidplot-core/src/main/java/com/androidplot/ui/widget/TextLabelWidget.java +++ b/androidplot-core/src/main/java/com/androidplot/ui/widget/TextLabelWidget.java @@ -101,7 +101,7 @@ public void doOnDraw(Canvas canvas, RectF widgetRect) { Anchor.CENTER); try { - canvas.save(Canvas.ALL_SAVE_FLAG); + canvas.save(); canvas.translate(start.x, start.y); switch (orientation) { case HORIZONTAL: diff --git a/androidplot-core/src/main/java/com/androidplot/xy/LineAndPointRenderer.java b/androidplot-core/src/main/java/com/androidplot/xy/LineAndPointRenderer.java index e8c0c23b..53e80dca 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/LineAndPointRenderer.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/LineAndPointRenderer.java @@ -309,7 +309,7 @@ protected void renderPath(Canvas canvas, RectF plotArea, Path path, PointF first RectF thisRegionRectF = thisRegionTransformed.asRectF(); if (thisRegionRectF != null) { try { - canvas.save(Canvas.ALL_SAVE_FLAG); + canvas.save(); canvas.clipPath(path); canvas.drawRect(thisRegionRectF, regionFormatter.getPaint()); } finally { diff --git a/androidplot-core/src/main/java/com/androidplot/xy/PanZoom.java b/androidplot-core/src/main/java/com/androidplot/xy/PanZoom.java index a8793264..33d61369 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/PanZoom.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/PanZoom.java @@ -2,11 +2,13 @@ import android.graphics.RectF; import android.graphics.PointF; +import android.support.annotation.NonNull; import android.view.*; import com.androidplot.*; import com.androidplot.util.*; +import java.io.Serializable; import java.util.*; /** @@ -35,6 +37,7 @@ public class PanZoom implements View.OnTouchListener { // rectangle created by the space between two fingers protected RectF fingersRect; private View.OnTouchListener delegate; + private State state = new State(); // Definition of the touch states protected enum DragState { @@ -95,7 +98,42 @@ public enum ZoomLimit { MIN_TICKS } - protected PanZoom(XYPlot plot, Pan pan, Zoom zoom) { + // TODO: consider making this immutable / threadsafe + public static class State implements Serializable { + private Number domainLowerBoundary; + private Number domainUpperBoundary; + private Number rangeLowerBoundary; + private Number rangeUpperBoundary; + private BoundaryMode domainBoundaryMode; + private BoundaryMode rangeBoundaryMode; + + public void setDomainBoundaries(Number lowerBoundary, Number upperBoundary, BoundaryMode mode) { + this.domainLowerBoundary = lowerBoundary; + this.domainUpperBoundary = upperBoundary; + this.domainBoundaryMode = mode; + } + + public void setRangeBoundaries(Number lowerBoundary, Number upperBoundary, BoundaryMode mode) { + this.rangeLowerBoundary = lowerBoundary; + this.rangeUpperBoundary = upperBoundary; + this.rangeBoundaryMode = mode; + } + + public void applyDomainBoundaries(@NonNull XYPlot plot) { + plot.setDomainBoundaries(domainLowerBoundary, domainUpperBoundary, domainBoundaryMode); + } + + public void applyRangeBoundaries(@NonNull XYPlot plot) { + plot.setRangeBoundaries(rangeLowerBoundary, rangeUpperBoundary, rangeBoundaryMode); + } + + public void apply(@NonNull XYPlot plot) { + applyDomainBoundaries(plot); + applyRangeBoundaries(plot); + } + } + + protected PanZoom(@NonNull XYPlot plot, Pan pan, Zoom zoom) { this.plot = plot; this.pan = pan; this.zoom = zoom; @@ -103,13 +141,32 @@ protected PanZoom(XYPlot plot, Pan pan, Zoom zoom) { } // additional constructor not to break api - protected PanZoom(XYPlot plot, Pan pan, Zoom zoom, ZoomLimit limit) { + protected PanZoom(@NonNull XYPlot plot, Pan pan, Zoom zoom, ZoomLimit limit) { this.plot = plot; this.pan = pan; this.zoom = zoom; this.zoomLimit = limit; } + public State getState() { + return this.state; + } + + public void setState(@NonNull State state) { + this.state = state; + state.apply(plot); + } + + protected void adjustRangeBoundary(Number lower, Number upper, BoundaryMode mode) { + state.setRangeBoundaries(lower, upper, mode); + state.applyRangeBoundaries(plot); + } + + protected void adjustDomainBoundary(Number lower, Number upper, BoundaryMode mode) { + state.setDomainBoundaries(lower, upper, mode); + state.applyDomainBoundaries(plot); + } + /** * 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}. @@ -117,7 +174,7 @@ protected PanZoom(XYPlot plot, Pan pan, Zoom zoom, ZoomLimit limit) { * @param plot * @return */ - public static PanZoom attach(XYPlot plot) { + public static PanZoom attach(@NonNull XYPlot plot) { return attach(plot, Pan.BOTH, Zoom.SCALE); } @@ -130,7 +187,7 @@ public static PanZoom attach(XYPlot plot) { * @param zoom * @return */ - public static PanZoom attach(XYPlot plot, Pan pan, Zoom zoom) { + public static PanZoom attach(@NonNull XYPlot plot, @NonNull Pan pan, @NonNull Zoom zoom) { return attach(plot,pan,zoom, ZoomLimit.OUTER); } @@ -142,7 +199,7 @@ public static PanZoom attach(XYPlot plot, Pan pan, Zoom zoom) { * @param limit * @return */ - public static PanZoom attach(XYPlot plot, Pan pan, Zoom zoom, ZoomLimit limit) { + public static PanZoom attach(@NonNull XYPlot plot, @NonNull Pan pan, @NonNull Zoom zoom, @NonNull ZoomLimit limit) { PanZoom pz = new PanZoom(plot, pan, zoom, limit); plot.setOnTouchListener(pz); return pz; @@ -237,12 +294,12 @@ protected void pan(final MotionEvent motionEvent) { if (EnumSet.of(Pan.HORIZONTAL, Pan.BOTH).contains(pan)) { Region newBounds = new Region(); calculatePan(oldFirstFinger, newBounds, true); - plot.setDomainBoundaries(newBounds.getMin(), newBounds.getMax(), BoundaryMode.FIXED); + adjustDomainBoundary(newBounds.getMin(), newBounds.getMax(), BoundaryMode.FIXED); } if (EnumSet.of(Pan.VERTICAL, Pan.BOTH).contains(pan)) { Region newBounds = new Region(); calculatePan(oldFirstFinger, newBounds, false); - plot.setRangeBoundaries(newBounds.getMin(), newBounds.getMax(), BoundaryMode.FIXED); + adjustRangeBoundary(newBounds.getMin(), newBounds.getMax(), BoundaryMode.FIXED); } plot.redraw(); @@ -291,10 +348,9 @@ protected void calculatePan(final PointF oldFirstFinger, Region bounds, final bo } protected boolean isValidScale(float scale) { - if (Float.isInfinite(scale) || Float.isNaN(scale) || scale > -0.001 && scale < 0.001) { - return false; - } - return true; + return !Float.isInfinite(scale) + && !Float.isNaN(scale) + && (!(scale > -0.001) || !(scale < 0.001)); } protected void zoom(final MotionEvent motionEvent) { @@ -349,14 +405,14 @@ protected void zoom(final MotionEvent motionEvent) { Zoom.STRETCH_BOTH, Zoom.SCALE).contains(zoom)) { calculateZoom(newRect, scaleX, true); - plot.setDomainBoundaries(newRect.left, newRect.right, BoundaryMode.FIXED); + adjustDomainBoundary(newRect.left, newRect.right, BoundaryMode.FIXED); } 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); + adjustRangeBoundary(newRect.top, newRect.bottom, BoundaryMode.FIXED); } plot.redraw(); } diff --git a/androidplot-core/src/main/java/com/androidplot/xy/XYGraphWidget.java b/androidplot-core/src/main/java/com/androidplot/xy/XYGraphWidget.java index 1ca3de77..22c551e9 100644 --- a/androidplot-core/src/main/java/com/androidplot/xy/XYGraphWidget.java +++ b/androidplot-core/src/main/java/com/androidplot/xy/XYGraphWidget.java @@ -847,7 +847,7 @@ protected void drawData(Canvas canvas) throws PlotRenderException { } try { if (isGridClippingEnabled) { - canvas.save(Canvas.ALL_SAVE_FLAG); + canvas.save(); canvas.clipRect(gridRect, android.graphics.Region.Op.INTERSECT); } diff --git a/build.gradle b/build.gradle index a7aa9c13..d45fb28b 100644 --- a/build.gradle +++ b/build.gradle @@ -23,10 +23,10 @@ allprojects { } ext { - theCompileSdkVersion = 26 - theTargetSdkVersion = 26 + theCompileSdkVersion = 28 + theTargetSdkVersion = 28 theMinSdkVersion = 5 - theVersionName = '1.5.5' + theVersionName = '1.5.6' theVersionCode = 0 } diff --git a/demoapp/src/main/java/com/androidplot/demos/SimpleXYPlotActivity.java b/demoapp/src/main/java/com/androidplot/demos/SimpleXYPlotActivity.java index 6238e4df..46118949 100644 --- a/demoapp/src/main/java/com/androidplot/demos/SimpleXYPlotActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/SimpleXYPlotActivity.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 AndroidPlot.com + * Copyright 2018 AndroidPlot.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,6 @@ import android.os.Bundle; import android.support.annotation.NonNull; -import com.androidplot.ui.Insets; import com.androidplot.util.PixelUtils; import com.androidplot.xy.CatmullRomInterpolator; import com.androidplot.xy.LineAndPointFormatter; @@ -49,7 +48,7 @@ public void onCreate(Bundle savedInstanceState) setContentView(R.layout.simple_xy_plot_example); // initialize our XYPlot reference: - plot = (XYPlot) findViewById(R.id.plot); + plot = findViewById(R.id.plot); // create a couple arrays of y-values to plot: final Number[] domainLabels = {1, 2, 3, 6, 7, 8, 9, 10, 13, 14}; @@ -101,19 +100,5 @@ public Object parseObject(String source, @NonNull ParsePosition pos) { return null; } }); - - new Thread(new Runnable() { - - @Override - public void run() { - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - plot.getGraph().setGridInsets(new Insets(120, 120, 120, 120)); - plot.redraw(); - } - }).start(); } } diff --git a/demoapp/src/main/java/com/androidplot/demos/TouchZoomExampleActivity.java b/demoapp/src/main/java/com/androidplot/demos/TouchZoomExampleActivity.java index c4105cde..dfae19d9 100644 --- a/demoapp/src/main/java/com/androidplot/demos/TouchZoomExampleActivity.java +++ b/demoapp/src/main/java/com/androidplot/demos/TouchZoomExampleActivity.java @@ -152,6 +152,7 @@ public void onNothingSelected(AdapterView parent) { } }); + zoomSpinner.setAdapter( new ArrayAdapter<>(this, R.layout.spinner_item, PanZoom.Zoom.values())); zoomSpinner.setSelection(panZoom.getZoom().ordinal()); @@ -167,5 +168,19 @@ public void onNothingSelected(AdapterView parent) { } }); } + + // (optional) save the current pan/zoom state + @Override + public void onSaveInstanceState(Bundle bundle) { + bundle.putSerializable("pan-zoom-state", panZoom.getState()); + } + + // (optional) restore the previously saved pan/zoom state + @Override + public void onRestoreInstanceState(Bundle bundle) { + PanZoom.State state = (PanZoom.State) bundle.getSerializable("pan-zoom-state"); + panZoom.setState(state); + plot.redraw(); + } } diff --git a/docs/quickstart.md b/docs/quickstart.md index e6ae7ff5..ffc3827d 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -14,7 +14,7 @@ To use the library in your gradle project add the following to your build.gradle ```groovy dependencies { - compile "com.androidplot:androidplot-core:1.5.5" + compile "com.androidplot:androidplot-core:1.5.6" } ``` diff --git a/docs/release_notes.md b/docs/release_notes.md index 762ac5a6..6bebeb8c 100644 --- a/docs/release_notes.md +++ b/docs/release_notes.md @@ -2,6 +2,11 @@ For details on what to expect in general when updating to a new version of Androiplot, check out the [versioning doc](versioning.md). +# 1.5.6 + +* Adds convenience methods for saving / restoring `PanZoom` state. +* (#80) Targets SDK 28, fixing compatibility issues. + # 1.5.5 * (#76) Fixed a bug that could cause a deadlock when grid steps are much larger than actual plot range. diff --git a/docs/xyplot.md b/docs/xyplot.md index 2dab08db..99128178 100644 --- a/docs/xyplot.md +++ b/docs/xyplot.md @@ -217,6 +217,25 @@ then the plot will pan and zoom on both axes infinitely. To set the plot's oute plot.getOuterLimits().set(0, 100, 0, 100); ``` +# Saving & Restoring PanZoom State +The `PanZoom` class provides convenience methods for saving and restoring state from your `Activity`: + +```java +// save the current pan/zoom state +@Override +public void onSaveInstanceState(Bundle bundle) { + bundle.putSerializable("pan-zoom-state", panZoom.getState()); +} + +// restore the previously saved pan/zoom state +@Override +public void onRestoreInstanceState(Bundle bundle) { + PanZoom.State state = (PanZoom.State) bundle.getSerializable("pan-zoom-state"); + panZoom.setState(state); + plot.redraw(); +} +``` + For a more detailed look at pan & zoom behavior, check out the [Touch Zoom Example source code](../demoapp/src/main/java/com/androidplot/demos/TouchZoomExampleActivity.java). # Series Renderers