diff --git a/README.md b/README.md
index c4cc357..111d4db 100644
--- a/README.md
+++ b/README.md
@@ -16,27 +16,15 @@ Updraft is built by App Agencies [Apps with love](https://appswithlove.com/) and
## Requirements
-- minSdkVersion >=19
+- minSdkVersion >=21
## Installation
-Add the repository into your top-level build.gradle file:
-
-```
-allproject {
- repositories {
- mavenCentral()
- // needed for an old dependency, we will need to fix in later release
- maven { url 'https://maven.scijava.org/content/repositories/public/' }
- }
-}
-```
-
Add this line to your dependencies in app-level build.gradle file:
```
dependencies {
- implementation 'com.appswithlove.updraft:updraft-sdk:1.0.14'
+ implementation 'com.appswithlove.updraft:updraft-sdk:1.0.15'
}
```
diff --git a/app/build.gradle b/app/build.gradle
index 8ceada2..9b69693 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -10,7 +10,7 @@ android {
compileSdk 34
defaultConfig {
applicationId "com.appswithlove.updraftsdk"
- minSdkVersion 19
+ minSdkVersion 21
targetSdkVersion 34
versionCode 5
versionName "1.4"
diff --git a/freedrawview/.gitignore b/freedrawview/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/freedrawview/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/freedrawview/README.md b/freedrawview/README.md
new file mode 100644
index 0000000..af09da0
--- /dev/null
+++ b/freedrawview/README.md
@@ -0,0 +1,199 @@
+FreeDrawView
+======
+
+
+A View that let you draw freely on it. You can customize paint width, alpha and color. Can be useful for notes app, signatures or hands-free writing
+
+This View works flawlessly inside Scrolling parents like NestedScrollView. Be careful with lists, you need to restore manually the draw state!
+
+Also supports state-restore on rotation, with custom behaviours like "clear, crop or fitXY" and you can take a screenshot (given to you as a Bitmap Object) of the View drawn content
+
+[Changelog](CHANGELOG.md)
+
+You can try the demo app on google play store.
+coming soon
+Or see the full video demo on YouTube.
+https://youtu.be/ejEdq4lnPjc
+
+Download
+------
+#### Gradle:
+```groovy
+compile 'com.rm:freedrawview:1.1.2'
+```
+
+
+Min SDK version: 9 (Android 2.3)
+
+
+## Usage
+
+To use this library, just add this inside your layout file
+
+```xml
+
+```
+
+... if you need to use this View's custom xml attributes (shown in a table below or in the example above) do not forget to add this to your root layout
+```
+xmlns:app="http://schemas.android.com/apk/res-auto"
+```
+
+And this in your Activity
+```java
+public class MainActivity extends AppCompatActivity {
+ FreeDrawView mSignatureView;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ mSignatureView = (FreeDrawView) findViewById(R.id.your_id);
+
+ // Setup the View
+ mSignatureView.setPaintColor(Color.BLACK);
+ mSignatureView.setPaintWidthPx(getResources.getDimensionPixelSize(R.dimen.paint_width));
+ //mSignatureView.setPaintWidthPx(12);
+ mSignatureView.setPaintWidthDp(getResources.getDimension(R.dimen.paint_width));
+ //mSignatureView.setPaintWidthDp(6);
+ mSignatureView.setPaintAlpha(255);// from 0 to 255
+ mSignatureView.setResizeBehaviour(ResizeBehaviour.CROP);// Must be one of ResizeBehaviour
+ // values;
+
+ // This listener will be notified every time the path done and undone count changes
+ mSignatureView.setPathRedoUndoCountChangeListener(new PathRedoUndoCountChangeListener() {
+ @Override
+ public void onUndoCountChanged(int undoCount) {
+ // The undoCount is the number of the paths that can be undone
+ }
+
+ @Override
+ public void onRedoCountChanged(int redoCount) {
+ // The redoCount is the number of path removed that can be redrawn
+ }
+ });
+ // This listener will be notified every time a new path has been drawn
+ mSignatureView.setOnPathDrawnListener(new PathDrawnListener() {
+ @Override
+ public void onNewPathDrawn() {
+ // The user has finished drawing a path
+ }
+
+ @Override
+ public void onPathStart() {
+ // The user has started drawing a path
+ }
+ });
+
+ // This will take a screenshot of the current drawn content of the view
+ mSignatureView.getDrawScreenshot(new FreeDrawView.DrawCreatorListener() {
+ @Override
+ public void onDrawCreated(Bitmap draw) {
+ // The draw Bitmap is the drawn content of the View
+ }
+
+ @Override
+ public void onDrawCreationError() {
+ // Something went wrong creating the bitmap, should never
+ // happen unless the async task has been canceled
+ }
+ });
+ }
+}
+```
+
+#### Save and restore manually the Draw content
+From v1.1.0 you can get the current state of the Draw (as a Serializable object) and than restore it:
+```java
+ FreeDrawSerializableState state = mSignatureView.getCurrentViewStateAsSerializable();// This returns a FreeDrawSerializableState (which implements Serializable)
+
+ // Save this "state" object into a file or keep it where you want
+
+ mSignatureView.restoreStateFromSerializable(state);// Restore the state of the view
+
+ // Now all the previous paths and points have been restored (including the history)
+```
+
+To save this Serializable Object inside a file you can take a look at the class [FileHelper](app/src/main/java/com/rm/freedrawsample/FileHelper.java)
+
+
+
+#### Supported Attributes
+FreeDrawView
+------
+| XML Attribute | Java method | Description | Default value |
+|------------------------- |----------------------------------------------------------------- |----------------------------------------------------------------------------------------------------------------- |--------------------------------------------------------------------------------------------- |
+| paintColor | setPaintColor(@ColorInt int checked) | Set the color of the paint | Color.BLACK |
+| paintWidth | setPaintWidthPx(@FloatRange(from = 0) float widthPx) | Set the width of the paint in pixels | 4dp |
+| | setPaintWidthDp(float dp) | Set the width of the paint in dp | 4dp |
+| paintAlpha | setPaintAlpha(@IntRange(from = 0, to = 255) int alpha) | Set the alpha of the paint | 255 |
+| resizeBehaviour | setResizeBehaviour(ResizeBehaviour newBehaviour) | The behaviour of the view every time it is resized (on rotation for example) one of [clear, fitXY, crop] | ResizeBehaviour.CROP |
+
+
+
+#### Limitations and TODO
+* Multitouch drawing is currently not supported
+* Eraser is not yet implemented
+* ~~Manually restore state is not supported~~
+* Get the draw screenshot from a FreeDrawSerializableState without adding the view
+
+
+
+Also, the FreeDrawView class gives some utility methods to handle path history:
+* ```public void undoLast()```
+ This method undo the last drawn segment
+* ```public void redoLast()```
+ This method redraw the last undone segment
+* ```public void undoAll()```
+ This method undo all the drawn segments, they can be redone one by one or all in one
+* ```public void redoAll()```
+ This method redraw all the undone segments
+* ```public void clearHistory()```
+ This method removes all the history segments (The one that could be redone)
+* ```public void clearDraw()```
+ This method removes all the current drawn segments, without adding them to the history
+* ```public void clearDrawAndHistory()```
+ This method removes all the current drawn segments and clears the history
+* ```public int getPathCount(boolean includeCurrentlyDrawingPath)```
+ This method returns the current number of segments drawn
+
+
+
+
+You can use:
+* ```setOnPathDrawnListener(PathDrawnListener listener)```
+to be notified every time the user starts or finishes drawing a line.
+* ```setPathRedoUndoCountChangeListener(PathRedoUndoCountChangeListener listener)```
+to be notified when the undo or redo count changes.
+
+And remove them with:
+* ```removePathDrawnListener()```
+* ```removePathRedoUndoCountChangeListener()```
+
+
+License
+--------
+
+ Copyright 2017 Riccardo Moro.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/freedrawview/build.gradle b/freedrawview/build.gradle
new file mode 100644
index 0000000..d43e337
--- /dev/null
+++ b/freedrawview/build.gradle
@@ -0,0 +1,45 @@
+apply plugin: 'com.android.library'
+
+ext {
+ bintrayRepo = 'maven'
+ bintrayName = 'freedrawview'
+
+ publishedGroupId = 'com.rm'
+ libraryName = 'freedrawview'
+ artifact = 'freedrawview'
+
+ libraryDescription = 'A View on which you can freely draw, customizing paint width, alpha and color, and take a screenshot of the content.'
+
+ siteUrl = 'https://github.com/RiccardoMoro/FreeDrawView'
+ gitUrl = 'https://github.com/RiccardoMoro/FreeDrawView.git'
+
+ libraryVersion = '1.1.2'
+
+ developerId = 'RiccardoMoro'
+ developerName = 'Riccardo Moro'
+ developerEmail = 'riccardomoro.rm@gmail.com'
+
+ licenseName = 'The Apache Software License, Version 2.0'
+ licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+ allLicenses = ["Apache-2.0"]
+}
+
+android {
+ namespace "com.rm.freedrawview"
+ compileSdk 34
+
+ defaultConfig {
+ minSdkVersion 9
+ targetSdkVersion 34
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation libs.androidx.annotation
+}
diff --git a/freedrawview/proguard-rules.pro b/freedrawview/proguard-rules.pro
new file mode 100644
index 0000000..49b4af1
--- /dev/null
+++ b/freedrawview/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in C:\Users\riky9\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/freedrawview/sample.gif b/freedrawview/sample.gif
new file mode 100644
index 0000000..11ce3a6
Binary files /dev/null and b/freedrawview/sample.gif differ
diff --git a/freedrawview/sample_rotation.gif b/freedrawview/sample_rotation.gif
new file mode 100644
index 0000000..7ab0bc5
Binary files /dev/null and b/freedrawview/sample_rotation.gif differ
diff --git a/freedrawview/sample_scrollable.gif b/freedrawview/sample_scrollable.gif
new file mode 100644
index 0000000..57f8cc6
Binary files /dev/null and b/freedrawview/sample_scrollable.gif differ
diff --git a/freedrawview/screen.png b/freedrawview/screen.png
new file mode 100644
index 0000000..b740dc1
Binary files /dev/null and b/freedrawview/screen.png differ
diff --git a/freedrawview/src/main/AndroidManifest.xml b/freedrawview/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..737b447
--- /dev/null
+++ b/freedrawview/src/main/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/freedrawview/src/main/java/com/rm/freedrawview/FreeDrawHelper.java b/freedrawview/src/main/java/com/rm/freedrawview/FreeDrawHelper.java
new file mode 100644
index 0000000..ec98f4d
--- /dev/null
+++ b/freedrawview/src/main/java/com/rm/freedrawview/FreeDrawHelper.java
@@ -0,0 +1,114 @@
+package com.rm.freedrawview;
+
+import android.content.res.Resources;
+import android.graphics.ComposePathEffect;
+import android.graphics.CornerPathEffect;
+import android.graphics.Paint;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+
+/**
+ * Created by Riccardo Moro on 10/23/2016.
+ */
+
+public class FreeDrawHelper {
+
+ /**
+ * Function used to check whenever a list of points is a line or a path to draw
+ */
+ static boolean isAPoint(@NonNull List points) {
+ if (points.size() == 0)
+ return false;
+
+ if (points.size() == 1)
+ return true;
+
+ for (int i = 1; i < points.size(); i++) {
+ if (points.get(i - 1).x != points.get(i).x || points.get(i - 1).y != points.get(i).y)
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Create, initialize and setup a paint
+ */
+ static Paint createPaintAndInitialize(int paintColor, int paintAlpha,
+ float paintWidth, boolean fill) {
+
+ Paint paint = createPaint();
+
+ initializePaint(paint, paintColor, paintAlpha, paintWidth, fill);
+
+ return paint;
+ }
+
+ static Paint createPaint() {
+ return new Paint(Paint.ANTI_ALIAS_FLAG);
+ }
+
+ static void initializePaint(Paint paint, int paintColor, int paintAlpha, float paintWidth,
+ boolean fill) {
+
+ if (fill) {
+ setupFillPaint(paint);
+ } else {
+ setupStrokePaint(paint);
+ }
+
+ paint.setStrokeWidth(paintWidth);
+ paint.setColor(paintColor);
+ paint.setAlpha(paintAlpha);
+ }
+
+ static void setupFillPaint(Paint paint) {
+ paint.setStyle(Paint.Style.FILL);
+ }
+
+ static void setupStrokePaint(Paint paint) {
+ paint.setStrokeJoin(Paint.Join.ROUND);
+ paint.setStrokeCap(Paint.Cap.ROUND);
+ paint.setPathEffect(new ComposePathEffect(
+ new CornerPathEffect(100f),
+ new CornerPathEffect(100f)));
+ paint.setStyle(Paint.Style.STROKE);
+ }
+
+ static void copyFromPaint(Paint from, Paint to, boolean copyWidth) {
+
+ to.setColor(from.getColor());
+ to.setAlpha(from.getAlpha());
+
+ if (copyWidth) {
+ to.setStrokeWidth(from.getStrokeWidth());
+ }
+ }
+
+ static void copyFromValues(Paint to, int color, int alpha, float strokeWidth,
+ boolean copyWidth) {
+
+ to.setColor(color);
+ to.setAlpha(alpha);
+
+ if (copyWidth) {
+ to.setStrokeWidth(strokeWidth);
+ }
+ }
+
+ /**
+ * Converts a given dp number to it's pixel corresponding number
+ */
+ public static float convertDpToPixels(float dp) {
+ return (dp * Resources.getSystem().getDisplayMetrics().density);
+ }
+
+ /**
+ * Converts a given pixel number to it's dp corresponding number
+ */
+ public static float convertPixelsToDp(float px) {
+ return px / Resources.getSystem().getDisplayMetrics().density;
+ }
+}
diff --git a/freedrawview/src/main/java/com/rm/freedrawview/FreeDrawSavedState.java b/freedrawview/src/main/java/com/rm/freedrawview/FreeDrawSavedState.java
new file mode 100644
index 0000000..632fbf8
--- /dev/null
+++ b/freedrawview/src/main/java/com/rm/freedrawview/FreeDrawSavedState.java
@@ -0,0 +1,137 @@
+package com.rm.freedrawview;
+
+import android.graphics.Paint;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.View;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.IntRange;
+
+import java.util.ArrayList;
+
+/**
+ * Created by Riccardo Moro on 11/4/2016.
+ */
+
+class FreeDrawSavedState extends View.BaseSavedState {
+
+ private ArrayList mPaths = new ArrayList<>();
+ private ArrayList mCanceledPaths = new ArrayList<>();
+
+ private int mPaintColor;
+ private int mPaintAlpha;
+ private float mPaintWidth;
+
+ private ResizeBehaviour mResizeBehaviour;
+
+ private int mLastDimensionW;
+ private int mLastDimensionH;
+
+ FreeDrawSavedState(Parcelable superState, ArrayList paths,
+ ArrayList canceledPaths, float paintWidth,
+ int paintColor, int paintAlpha, ResizeBehaviour resizeBehaviour,
+ int lastDimensionW, int lastDimensionH) {
+ super(superState);
+
+ mPaths = paths;
+ mCanceledPaths = canceledPaths;
+ mPaintWidth = paintWidth;
+
+ mPaintColor = paintColor;
+ mPaintAlpha = paintAlpha;
+
+ mResizeBehaviour = resizeBehaviour;
+
+ mLastDimensionW = lastDimensionW;
+ mLastDimensionH = lastDimensionH;
+ }
+
+ ArrayList getPaths() {
+ return mPaths;
+ }
+
+ ArrayList getCanceledPaths() {
+ return mCanceledPaths;
+ }
+
+ @ColorInt
+ int getPaintColor() {
+ return mPaintColor;
+ }
+
+ @IntRange(from = 0, to = 255)
+ int getPaintAlpha() {
+ return mPaintAlpha;
+ }
+
+ float getCurrentPaintWidth() {
+ return mPaintWidth;
+ }
+
+ Paint getCurrentPaint() {
+
+ Paint paint = FreeDrawHelper.createPaint();
+ FreeDrawHelper.setupStrokePaint(paint);
+ FreeDrawHelper.copyFromValues(paint, mPaintColor, mPaintAlpha, mPaintWidth, true);
+ return paint;
+ }
+
+ ResizeBehaviour getResizeBehaviour() {
+ return mResizeBehaviour;
+ }
+
+ int getLastDimensionW() {
+ return mLastDimensionW;
+ }
+
+ int getLastDimensionH() {
+ return mLastDimensionH;
+ }
+
+ // Parcelable stuff
+ private FreeDrawSavedState(Parcel in) {
+ super(in);
+
+ in.readTypedList(mPaths, HistoryPath.CREATOR);
+ in.readTypedList(mCanceledPaths, HistoryPath.CREATOR);
+
+ mPaintColor = in.readInt();
+ mPaintAlpha = in.readInt();
+ mPaintWidth = in.readFloat();
+
+ mResizeBehaviour = (ResizeBehaviour) in.readSerializable();
+
+ mLastDimensionW = in.readInt();
+ mLastDimensionH = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+
+ out.writeTypedList(mPaths);
+ out.writeTypedList(mCanceledPaths);
+
+ out.writeInt(mPaintColor);
+ out.writeInt(mPaintAlpha);
+ out.writeFloat(mPaintWidth);
+
+ out.writeSerializable(mResizeBehaviour);
+
+ out.writeInt(mLastDimensionW);
+ out.writeInt(mLastDimensionH);
+ }
+
+ // Parcelable CREATOR class, needed for parcelable to work
+ public static final Parcelable.Creator CREATOR =
+ new Parcelable.Creator() {
+ public FreeDrawSavedState createFromParcel(Parcel in) {
+ return new FreeDrawSavedState(in);
+ }
+
+ public FreeDrawSavedState[] newArray(int size) {
+ return new FreeDrawSavedState[size];
+ }
+ };
+}
diff --git a/freedrawview/src/main/java/com/rm/freedrawview/FreeDrawSerializableState.java b/freedrawview/src/main/java/com/rm/freedrawview/FreeDrawSerializableState.java
new file mode 100644
index 0000000..c6fe72c
--- /dev/null
+++ b/freedrawview/src/main/java/com/rm/freedrawview/FreeDrawSerializableState.java
@@ -0,0 +1,104 @@
+package com.rm.freedrawview;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+/**
+ * Created by Riccardo on 23/05/2017.
+ */
+
+public class FreeDrawSerializableState implements Serializable {
+
+ static final long serialVersionUID = 40L;
+
+ private ArrayList mCanceledPaths;
+ private ArrayList mPaths;
+
+ private int mPaintColor;
+ private int mPaintAlpha;
+ private float mPaintWidth;
+
+ private ResizeBehaviour mResizeBehaviour;
+
+ private int mLastDimensionW;
+ private int mLastDimensionH;
+
+ public FreeDrawSerializableState(ArrayList canceledPaths,
+ ArrayList paths, int paintColor, int paintAlpha,
+ float paintWidth, ResizeBehaviour resizeBehaviour,
+ int lastW, int lastH) {
+
+ setCanceledPaths(canceledPaths != null ? canceledPaths : new ArrayList());
+ setPaths(paths != null ? paths : new ArrayList());
+ setPaintWidth(paintWidth >= 0 ? paintWidth : 0);
+ setPaintColor(paintColor);
+ setPaintAlpha(paintAlpha);
+ setResizeBehaviour(resizeBehaviour);
+ setLastDimensionW(lastW >= 0 ? lastW : 0);
+ setLastDimensionH(lastH >= 0 ? lastH : 0);
+ }
+
+ public ArrayList getCanceledPaths() {
+ return mCanceledPaths;
+ }
+
+ public void setCanceledPaths(ArrayList canceledPaths) {
+ this.mCanceledPaths = canceledPaths;
+ }
+
+ public ArrayList getPaths() {
+ return mPaths;
+ }
+
+ public void setPaths(ArrayList paths) {
+ this.mPaths = paths;
+ }
+
+ public float getPaintWidth() {
+ return mPaintWidth;
+ }
+
+ public void setPaintWidth(float paintWidth) {
+ this.mPaintWidth = paintWidth;
+ }
+
+ public int getPaintColor() {
+ return mPaintColor;
+ }
+
+ public void setPaintColor(int paintColor) {
+ this.mPaintColor = paintColor;
+ }
+
+ public int getPaintAlpha() {
+ return mPaintAlpha;
+ }
+
+ public void setPaintAlpha(int paintAlpha) {
+ this.mPaintAlpha = paintAlpha;
+ }
+
+ public ResizeBehaviour getResizeBehaviour() {
+ return mResizeBehaviour;
+ }
+
+ public void setResizeBehaviour(ResizeBehaviour resizeBehaviour) {
+ this.mResizeBehaviour = resizeBehaviour;
+ }
+
+ public int getLastDimensionW() {
+ return mLastDimensionW;
+ }
+
+ public void setLastDimensionW(int lastDimensionW) {
+ this.mLastDimensionW = lastDimensionW;
+ }
+
+ public int getLastDimensionH() {
+ return mLastDimensionH;
+ }
+
+ public void setLastDimensionH(int lastDimensionH) {
+ this.mLastDimensionH = lastDimensionH;
+ }
+}
diff --git a/freedrawview/src/main/java/com/rm/freedrawview/FreeDrawView.java b/freedrawview/src/main/java/com/rm/freedrawview/FreeDrawView.java
new file mode 100644
index 0000000..2ffa849
--- /dev/null
+++ b/freedrawview/src/main/java/com/rm/freedrawview/FreeDrawView.java
@@ -0,0 +1,775 @@
+package com.rm.freedrawview;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.os.AsyncTask;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.FloatRange;
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * Created by Riccardo Moro on 9/10/2016.
+ */
+public class FreeDrawView extends View implements View.OnTouchListener {
+ private static final String TAG = FreeDrawView.class.getSimpleName();
+
+ private static final float DEFAULT_STROKE_WIDTH = 4;
+ private static final int DEFAULT_COLOR = Color.BLACK;
+ private static final int DEFAULT_ALPHA = 255;
+
+ private Paint mCurrentPaint;
+ private Path mCurrentPath;
+
+ private ResizeBehaviour mResizeBehaviour;
+
+ private ArrayList mPoints = new ArrayList<>();
+ private ArrayList mPaths = new ArrayList<>();
+ private ArrayList mCanceledPaths = new ArrayList<>();
+
+ @ColorInt
+ private int mPaintColor = DEFAULT_COLOR;
+ @IntRange(from = 0, to = 255)
+ private int mPaintAlpha = DEFAULT_ALPHA;
+
+ private int mLastDimensionW = -1;
+ private int mLastDimensionH = -1;
+
+ private boolean mFinishPath = false;
+
+ private PathDrawnListener mPathDrawnListener;
+ private PathRedoUndoCountChangeListener mPathRedoUndoCountChangeListener;
+
+ public FreeDrawView(Context context) {
+ this(context, null);
+ }
+
+ public FreeDrawView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public FreeDrawView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
+ setOnTouchListener(this);
+
+ TypedArray a = null;
+ try {
+
+ a = context.getTheme().obtainStyledAttributes(
+ attrs,
+ R.styleable.FreeDrawView,
+ defStyleAttr, 0);
+
+ initPaints(a);
+ } finally {
+ if (a != null) {
+ a.recycle();
+ }
+ }
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+
+ // Get the superclass parcelable state
+ Parcelable superState = super.onSaveInstanceState();
+
+ if (mPoints.size() > 0) {// Currently doing a line, save it's current path
+ createHistoryPathFromPoints();
+ }
+
+ return new FreeDrawSavedState(superState, mPaths, mCanceledPaths,
+ getPaintWidth(), getPaintColor(), getPaintAlpha(),
+ getResizeBehaviour(), mLastDimensionW, mLastDimensionH);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+
+ // If not instance of my state, let the superclass handle it
+ if (!(state instanceof FreeDrawSavedState)) {
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ FreeDrawSavedState savedState = (FreeDrawSavedState) state;
+ // Superclass restore state
+ super.onRestoreInstanceState(savedState.getSuperState());
+
+ // My state restore
+ mPaths = savedState.getPaths();
+ mCanceledPaths = savedState.getCanceledPaths();
+ mCurrentPaint = savedState.getCurrentPaint();
+
+ setPaintWidthPx(savedState.getCurrentPaintWidth());
+ setPaintColor(savedState.getPaintColor());
+ setPaintAlpha(savedState.getPaintAlpha());
+
+ setResizeBehaviour(savedState.getResizeBehaviour());
+
+ // Restore the last dimensions, so that in onSizeChanged i can calculate the
+ // height and width change factor and multiply every point x or y to it, so that if the
+ // View is resized, it adapt automatically it's points to the new width/height
+ mLastDimensionW = savedState.getLastDimensionW();
+ mLastDimensionH = savedState.getLastDimensionH();
+
+ notifyRedoUndoCountChanged();
+ }
+
+ /**
+ * Set the paint color
+ *
+ * @param color The now color to be applied to the
+ */
+ public void setPaintColor(@ColorInt int color) {
+
+ invalidate();
+
+ mPaintColor = color;
+
+ mCurrentPaint.setColor(mPaintColor);
+ mCurrentPaint.setAlpha(mPaintAlpha);// Restore the previous alpha
+ }
+
+ /**
+ * Get the current paint color without it's alpha
+ */
+ @ColorInt
+ public int getPaintColor() {
+ return mPaintColor;
+ }
+
+ /**
+ * Get the current color with the current alpha
+ */
+ @ColorInt
+ public int getPaintColorWithAlpha() {
+ return mCurrentPaint.getColor();
+ }
+
+
+ /**
+ * Set the paint width in px
+ *
+ * @param widthPx The new weight in px, must be > 0
+ */
+ public void setPaintWidthPx(@FloatRange(from = 0) float widthPx) {
+ if (widthPx > 0) {
+
+ invalidate();
+
+ mCurrentPaint.setStrokeWidth(widthPx);
+ }
+ }
+
+ /**
+ * Set the paint width in dp
+ *
+ * @param dp The new weight in dp, must be > 0
+ */
+ public void setPaintWidthDp(float dp) {
+ setPaintWidthPx(FreeDrawHelper.convertDpToPixels(dp));
+ }
+
+ /**
+ * {@link #getPaintWidth(boolean)}
+ */
+ @FloatRange(from = 0)
+ public float getPaintWidth() {
+ return getPaintWidth(false);
+ }
+
+ /**
+ * Get the current paint with in dp or pixel
+ */
+ @FloatRange(from = 0)
+ public float getPaintWidth(boolean inDp) {
+ if (inDp) {
+ return FreeDrawHelper.convertPixelsToDp(mCurrentPaint.getStrokeWidth());
+ } else {
+ return mCurrentPaint.getStrokeWidth();
+ }
+ }
+
+
+ /**
+ * Set the paint opacity, must be between 0 and 1
+ *
+ * @param alpha The alpha to apply to the paint
+ */
+ public void setPaintAlpha(@IntRange(from = 0, to = 255) int alpha) {
+
+ // Finish current path and redraw, so that the new setting is applied only to the next path
+ invalidate();
+
+ mPaintAlpha = alpha;
+ mCurrentPaint.setAlpha(mPaintAlpha);
+ }
+
+ /**
+ * Get the current paint alpha
+ */
+ @IntRange(from = 0, to = 255)
+ public int getPaintAlpha() {
+ return mPaintAlpha;
+ }
+
+
+ /**
+ * Set what to do when the view is resized (on rotation if its dimensions are not fixed)
+ * {@link ResizeBehaviour}
+ */
+ public void setResizeBehaviour(ResizeBehaviour newBehaviour) {
+ mResizeBehaviour = newBehaviour;
+ }
+
+ /**
+ * Get the current behaviour on view resize
+ */
+ public ResizeBehaviour getResizeBehaviour() {
+ return mResizeBehaviour;
+ }
+
+
+ /**
+ * Cancel the last drawn segment
+ */
+ public void undoLast() {
+
+ if (mPaths.size() > 0) {
+ // End current path
+ mFinishPath = true;
+ invalidate();
+
+ // Cancel the last one and redraw
+ mCanceledPaths.add(mPaths.get(mPaths.size() - 1));
+ mPaths.remove(mPaths.size() - 1);
+ invalidate();
+
+ notifyRedoUndoCountChanged();
+ }
+ }
+
+ /**
+ * Re-add the first removed path and redraw
+ */
+ public void redoLast() {
+
+ if (mCanceledPaths.size() > 0) {
+ mPaths.add(mCanceledPaths.get(mCanceledPaths.size() - 1));
+ mCanceledPaths.remove(mCanceledPaths.size() - 1);
+ invalidate();
+
+ notifyRedoUndoCountChanged();
+ }
+ }
+
+ /**
+ * Remove all the paths and redraw (can be undone with {@link #redoLast()})
+ */
+ public void undoAll() {
+ Collections.reverse(mPaths);
+ mCanceledPaths.addAll(mPaths);
+ mPaths = new ArrayList<>();
+ invalidate();
+
+ notifyRedoUndoCountChanged();
+ }
+
+ /**
+ * Re-add all the removed paths and redraw
+ */
+ public void redoAll() {
+
+ if (mCanceledPaths.size() > 0) {
+ mPaths.addAll(mCanceledPaths);
+ mCanceledPaths = new ArrayList<>();
+ invalidate();
+
+ notifyRedoUndoCountChanged();
+ }
+ }
+
+ /**
+ * Get how many undo operations are available
+ */
+ public int getUndoCount() {
+ return mPaths.size();
+ }
+
+ /**
+ * Get how many redo operations are available
+ */
+ public int getRedoCount() {
+ return mCanceledPaths.size();
+ }
+
+ /**
+ * Get how many paths are drawn on this FreeDrawView
+ *
+ * @param includeCurrentlyDrawingPath Include the path that is currently been drawn
+ * @return The number of paths drawn
+ */
+ public int getPathCount(boolean includeCurrentlyDrawingPath) {
+ int size = mPaths.size();
+
+ if (includeCurrentlyDrawingPath && mPoints.size() > 0) {
+ size++;
+ }
+ return size;
+ }
+
+ /**
+ * Set a path drawn listener, will be called every time a new path is drawn
+ */
+ public void setOnPathDrawnListener(PathDrawnListener listener) {
+ mPathDrawnListener = listener;
+ }
+
+ /**
+ * Remove the path drawn listener
+ */
+ public void removePathDrawnListener() {
+ mPathDrawnListener = null;
+ }
+
+ /**
+ * Clear the current draw and the history
+ */
+ public void clearDrawAndHistory() {
+
+ clearDraw(false);
+ clearHistory(true);
+ }
+
+ /**
+ * Clear the current draw
+ */
+ public void clearDraw() {
+
+ clearDraw(true);
+ }
+
+ private void clearDraw(boolean invalidate) {
+ mPoints = new ArrayList<>();
+ mPaths = new ArrayList<>();
+
+ notifyRedoUndoCountChanged();
+
+ if (invalidate) {
+ invalidate();
+ }
+ }
+
+ /**
+ * Clear the history (paths that can be redone)
+ */
+ public void clearHistory() {
+ clearHistory(true);
+ }
+
+ private void clearHistory(boolean invalidate) {
+ mCanceledPaths = new ArrayList<>();
+
+ notifyRedoUndoCountChanged();
+
+ if (invalidate) {
+ invalidate();
+ }
+ }
+
+ /**
+ * Set a redo-undo count change listener, this will be called every time undo or redo count
+ * changes
+ */
+ public void setPathRedoUndoCountChangeListener(PathRedoUndoCountChangeListener listener) {
+ mPathRedoUndoCountChangeListener = listener;
+ }
+
+ /**
+ * Remove the redo-undo count listener
+ */
+ public void removePathRedoUndoCountChangeListener() {
+ mPathRedoUndoCountChangeListener = null;
+ }
+
+ /**
+ * Get a serializable object with all the needed info about the current draw and state
+ *
+ * @return A {@link FreeDrawSerializableState} containing all the needed data
+ */
+ public FreeDrawSerializableState getCurrentViewStateAsSerializable() {
+
+ return new FreeDrawSerializableState(mCanceledPaths, mPaths, getPaintColor(),
+ getPaintAlpha(), getPaintWidth(), getResizeBehaviour(),
+ mLastDimensionW, mLastDimensionH);
+ }
+
+ /**
+ * Restore the state of the draw from the given serializable state
+ *
+ * @param state A {@link FreeDrawSerializableState} containing all the draw and paint info,
+ * if null, nothing will be restored. Null sub fields will be ignored
+ */
+ public void restoreStateFromSerializable(FreeDrawSerializableState state) {
+
+ if (state != null) {
+
+ if (state.getCanceledPaths() != null) {
+ mCanceledPaths = state.getCanceledPaths();
+ }
+
+ if (state.getPaths() != null) {
+ mPaths = state.getPaths();
+ }
+
+ mPaintColor = state.getPaintColor();
+ mPaintAlpha = state.getPaintAlpha();
+
+ mCurrentPaint.setColor(state.getPaintColor());
+ mCurrentPaint.setAlpha(state.getPaintAlpha());
+ setPaintWidthPx(state.getPaintWidth());
+
+ mResizeBehaviour = state.getResizeBehaviour();
+
+ if (state.getLastDimensionW() >= 0) {
+ mLastDimensionW = state.getLastDimensionW();
+ }
+
+ if (state.getLastDimensionH() >= 0) {
+ mLastDimensionH = state.getLastDimensionH();
+ }
+
+ notifyRedoUndoCountChanged();
+ invalidate();
+ }
+ }
+
+ /**
+ * Create a Bitmap with the content drawn inside the view
+ */
+ public void getDrawScreenshot(@NonNull final DrawCreatorListener listener) {
+ new TakeScreenShotAsyncTask(listener).execute();
+ }
+
+
+ // Internal methods
+ private void notifyPathStart() {
+ if (mPathDrawnListener != null) {
+ mPathDrawnListener.onPathStart();
+ }
+ }
+
+ private void notifyPathDrawn() {
+ if (mPathDrawnListener != null) {
+ mPathDrawnListener.onNewPathDrawn();
+ }
+ }
+
+ private void notifyRedoUndoCountChanged() {
+ if (mPathRedoUndoCountChangeListener != null) {
+ mPathRedoUndoCountChangeListener.onRedoCountChanged(getRedoCount());
+ mPathRedoUndoCountChangeListener.onUndoCountChanged(getUndoCount());
+ }
+ }
+
+ private void initPaints(TypedArray a) {
+ mCurrentPaint = FreeDrawHelper.createPaint();
+
+ mCurrentPaint.setColor(a != null ? a.getColor(R.styleable.FreeDrawView_paintColor,
+ mPaintColor) : mPaintColor);
+ mCurrentPaint.setAlpha(a != null ?
+ a.getInt(R.styleable.FreeDrawView_paintAlpha, mPaintAlpha)
+ : mPaintAlpha);
+ mCurrentPaint.setStrokeWidth(a != null ?
+ a.getDimensionPixelSize(R.styleable.FreeDrawView_paintWidth,
+ (int) FreeDrawHelper.convertDpToPixels(DEFAULT_STROKE_WIDTH))
+ : FreeDrawHelper.convertDpToPixels(DEFAULT_STROKE_WIDTH));
+
+ FreeDrawHelper.setupStrokePaint(mCurrentPaint);
+
+ if (a != null) {
+ int resizeBehaviour = a.getInt(R.styleable.FreeDrawView_resizeBehaviour, -1);
+ mResizeBehaviour =
+ resizeBehaviour == 0 ? ResizeBehaviour.CLEAR :
+ resizeBehaviour == 1 ? ResizeBehaviour.FIT_XY :
+ resizeBehaviour == 2 ? ResizeBehaviour.CROP :
+ ResizeBehaviour.CROP;
+ }
+ }
+
+ private Paint createAndCopyColorAndAlphaForFillPaint(Paint from, boolean copyWidth) {
+ Paint paint = FreeDrawHelper.createPaint();
+ FreeDrawHelper.setupFillPaint(paint);
+ paint.setColor(from.getColor());
+ paint.setAlpha(from.getAlpha());
+ if (copyWidth) {
+ paint.setStrokeWidth(from.getStrokeWidth());
+ }
+ return paint;
+ }
+
+ @Override
+ protected synchronized void onDraw(Canvas canvas) {
+ if (mPaths.size() == 0 && mPoints.size() == 0) {
+ return;
+ }
+
+ // Avoid concurrency errors by first setting the finished path variable to false
+ final boolean finishedPath = mFinishPath;
+ mFinishPath = false;
+
+ for (HistoryPath currentPath : mPaths) {
+
+ // If the path is just a single point, draw as a point
+ if (currentPath.isPoint()) {
+
+ canvas.drawCircle(currentPath.getOriginX(), currentPath.getOriginY(),
+ currentPath.getPaint().getStrokeWidth() / 2, currentPath.getPaint());
+ } else {// Else draw the complete path
+
+ canvas.drawPath(currentPath.getPath(), currentPath.getPaint());
+ }
+ }
+
+ // Initialize the current path
+ if (mCurrentPath == null)
+ mCurrentPath = new Path();
+ else
+ mCurrentPath.rewind();
+
+ // If a single point, add a circle to the path
+ if (mPoints.size() == 1 || FreeDrawHelper.isAPoint(mPoints)) {
+
+ canvas.drawCircle(mPoints.get(0).x, mPoints.get(0).y,
+ mCurrentPaint.getStrokeWidth() / 2,
+ createAndCopyColorAndAlphaForFillPaint(mCurrentPaint, false));
+ } else if (mPoints.size() != 0) {// Else draw the complete series of points
+
+ boolean first = true;
+
+ for (Point point : mPoints) {
+
+ if (first) {
+ mCurrentPath.moveTo(point.x, point.y);
+ first = false;
+ } else {
+ mCurrentPath.lineTo(point.x, point.y);
+ }
+ }
+
+ canvas.drawPath(mCurrentPath, mCurrentPaint);
+ }
+
+ // If the path is finished, add it to the history
+ if (finishedPath && mPoints.size() > 0) {
+ createHistoryPathFromPoints();
+ }
+ }
+
+ // Create a path from the current points
+ private void createHistoryPathFromPoints() {
+ mPaths.add(new HistoryPath(mPoints, new Paint(mCurrentPaint)));
+
+ mPoints = new ArrayList<>();
+
+ notifyPathDrawn();
+ notifyRedoUndoCountChanged();
+ }
+
+ @Override
+ public boolean onTouch(View view, MotionEvent motionEvent) {
+
+ if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
+ notifyPathStart();
+ }
+ if (getParent() != null) {
+ getParent().requestDisallowInterceptTouchEvent(true);
+ }
+
+ // Clear all the history when restarting to draw
+ mCanceledPaths = new ArrayList<>();
+
+ if ((motionEvent.getAction() != MotionEvent.ACTION_UP) &&
+ (motionEvent.getAction() != MotionEvent.ACTION_CANCEL)) {
+ Point point;
+ for (int i = 0; i < motionEvent.getHistorySize(); i++) {
+ point = new Point();
+ point.x = motionEvent.getHistoricalX(i);
+ point.y = motionEvent.getHistoricalY(i);
+ mPoints.add(point);
+ }
+ point = new Point();
+ point.x = motionEvent.getX();
+ point.y = motionEvent.getY();
+ mPoints.add(point);
+ mFinishPath = false;
+ } else
+ mFinishPath = true;
+
+ invalidate();
+ return true;
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ float xMultiplyFactor = 1;
+ float yMultiplyFactor = 1;
+
+
+ if (mLastDimensionW == -1) {
+ mLastDimensionW = w;
+ }
+
+ if (mLastDimensionH == -1) {
+ mLastDimensionH = h;
+ }
+
+ if (w >= 0 && w != oldw && w != mLastDimensionW) {
+ xMultiplyFactor = (float) w / mLastDimensionW;
+ mLastDimensionW = w;
+ }
+
+ if (h >= 0 && h != oldh && h != mLastDimensionH) {
+ yMultiplyFactor = (float) h / mLastDimensionH;
+ mLastDimensionH = h;
+ }
+
+ multiplyPathsAndPoints(xMultiplyFactor, yMultiplyFactor);
+ }
+
+ // Translate all the paths, used every time that this view size is changed
+ @SuppressWarnings("SuspiciousNameCombination")
+ private void multiplyPathsAndPoints(float xMultiplyFactor, float yMultiplyFactor) {
+
+ // If both factors == 1 or <= 0 or no paths/points to apply things, just return
+ if ((xMultiplyFactor == 1 && yMultiplyFactor == 1)
+ || (xMultiplyFactor <= 0 || yMultiplyFactor <= 0) ||
+ (mPaths.size() == 0 && mCanceledPaths.size() == 0 && mPoints.size() == 0)) {
+ return;
+ }
+
+ if (mResizeBehaviour == ResizeBehaviour.CLEAR) {// If clear, clear all and return
+ mPaths = new ArrayList<>();
+ mCanceledPaths = new ArrayList<>();
+ mPoints = new ArrayList<>();
+ return;
+ } else if (mResizeBehaviour == ResizeBehaviour.CROP) {
+ xMultiplyFactor = yMultiplyFactor = 1;
+ }
+
+ // Adapt drawn paths
+ for (HistoryPath historyPath : mPaths) {
+
+ if (historyPath.isPoint()) {
+ historyPath.setOriginX(historyPath.getOriginX() * xMultiplyFactor);
+ historyPath.setOriginY(historyPath.getOriginY() * yMultiplyFactor);
+ } else {
+ for (Point point : historyPath.getPoints()) {
+ point.x *= xMultiplyFactor;
+ point.y *= yMultiplyFactor;
+ }
+ }
+
+ historyPath.generatePath();
+ }
+
+ // Adapt canceled paths
+ for (HistoryPath historyPath : mCanceledPaths) {
+
+ if (historyPath.isPoint()) {
+ historyPath.setOriginX(historyPath.getOriginX() * xMultiplyFactor);
+ historyPath.setOriginY(historyPath.getOriginY() * yMultiplyFactor);
+ } else {
+ for (Point point : historyPath.getPoints()) {
+ point.x *= xMultiplyFactor;
+ point.y *= yMultiplyFactor;
+ }
+ }
+
+ historyPath.generatePath();
+ }
+
+ // Adapt drawn points
+ for (Point point : mPoints) {
+ point.x *= xMultiplyFactor;
+ point.y *= yMultiplyFactor;
+ }
+ }
+
+ public interface DrawCreatorListener {
+ void onDrawCreated(Bitmap draw);
+
+ void onDrawCreationError();
+ }
+
+
+ private class TakeScreenShotAsyncTask extends AsyncTask {
+ private int mWidth, mHeight;
+ private Canvas mCanvas;
+ private Bitmap mBitmap;
+ private DrawCreatorListener mListener;
+
+ public TakeScreenShotAsyncTask(@NonNull DrawCreatorListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ mWidth = getWidth();
+ mHeight = getHeight();
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+
+ try {
+ mBitmap = Bitmap.createBitmap(
+ mWidth, mHeight, Bitmap.Config.ARGB_8888);
+ mCanvas = new Canvas(mBitmap);
+ } catch (Exception e) {
+ e.printStackTrace();
+ cancel(true);
+ }
+
+ return null;
+ }
+
+ @Override
+ protected void onCancelled() {
+ super.onCancelled();
+
+ if (mListener != null) {
+ mListener.onDrawCreationError();
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ super.onPostExecute(aVoid);
+
+ draw(mCanvas);
+
+ if (mListener != null) {
+ mListener.onDrawCreated(mBitmap);
+ }
+ }
+ }
+}
diff --git a/freedrawview/src/main/java/com/rm/freedrawview/HistoryPath.java b/freedrawview/src/main/java/com/rm/freedrawview/HistoryPath.java
new file mode 100644
index 0000000..066c8df
--- /dev/null
+++ b/freedrawview/src/main/java/com/rm/freedrawview/HistoryPath.java
@@ -0,0 +1,205 @@
+package com.rm.freedrawview;
+
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+/**
+ * Created by Riccardo Moro on 9/27/2016.
+ */
+
+class HistoryPath implements Parcelable, Serializable {
+
+ static final long serialVersionUID = 41L;
+
+ private static final String TAG = HistoryPath.class.getSimpleName();
+
+ private ArrayList points = new ArrayList<>();
+ private int paintColor;
+ private int paintAlpha;
+ private float paintWidth;
+ private float originX, originY;
+ private boolean isPoint;
+
+ private transient Path path = null;
+ private transient Paint paint = null;
+
+ HistoryPath(@NonNull ArrayList points, @NonNull Paint paint) {
+ this.points = new ArrayList<>(points);
+ this.paintColor = paint.getColor();
+ this.paintAlpha = paint.getAlpha();
+ this.paintWidth = paint.getStrokeWidth();
+ this.originX = points.get(0).x;
+ this.originY = points.get(0).y;
+ this.isPoint = FreeDrawHelper.isAPoint(points);
+
+ generatePath();
+ generatePaint();
+ }
+
+ public void generatePath() {
+
+ path = new Path();
+
+ if (points != null) {
+ boolean first = true;
+
+ for (int i = 0; i < points.size(); i++) {
+
+ Point point = points.get(i);
+
+ if (first) {
+ path.moveTo(point.x, point.y);
+ first = false;
+ } else {
+ path.lineTo(point.x, point.y);
+ }
+ }
+ }
+ }
+
+ private void generatePaint() {
+
+ paint = FreeDrawHelper.createPaintAndInitialize(paintColor, paintAlpha, paintWidth,
+ isPoint);
+ }
+
+ public Path getPath() {
+
+ if (path == null) {
+
+ generatePath();
+ }
+
+ return path;
+ }
+
+ public boolean isPoint() {
+ return isPoint;
+ }
+
+ public void setPoint(boolean point) {
+ isPoint = point;
+ }
+
+ public float getOriginX() {
+ return originX;
+ }
+
+ public void setOriginX(float originX) {
+ this.originX = originX;
+ }
+
+ public float getOriginY() {
+ return originY;
+ }
+
+ public void setOriginY(float originY) {
+ this.originY = originY;
+ }
+
+ public int getPaintColor() {
+ return paintColor;
+ }
+
+ public void setPaintColor(int paintColor) {
+ this.paintColor = paintColor;
+ }
+
+ public int getPaintAlpha() {
+ return paintAlpha;
+ }
+
+ public void setPaintAlpha(int paintAlpha) {
+ this.paintAlpha = paintAlpha;
+ }
+
+ public float getPaintWidth() {
+ return paintWidth;
+ }
+
+ public void setPaintWidth(float paintWidth) {
+ this.paintWidth = paintWidth;
+ }
+
+ public Paint getPaint() {
+
+ if (paint == null) {
+ generatePaint();
+ }
+
+ return paint;
+ }
+
+ public ArrayList getPoints() {
+ return points;
+ }
+
+ public void setPoints(ArrayList points) {
+ this.points = points;
+ }
+
+ // Parcelable stuff
+ private HistoryPath(Parcel in) {
+ in.readTypedList(points, Point.CREATOR);
+
+ paintColor = in.readInt();
+ paintAlpha = in.readInt();
+ paintWidth = in.readFloat();
+
+ originX = in.readFloat();
+ originY = in.readFloat();
+
+ isPoint = in.readByte() != 0;
+
+ generatePath();
+ generatePaint();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeTypedList(points);
+
+ dest.writeInt(paintColor);
+ dest.writeInt(paintAlpha);
+ dest.writeFloat(paintWidth);
+
+ dest.writeFloat(originX);
+ dest.writeFloat(originY);
+
+ dest.writeByte((byte) (isPoint ? 1 : 0));
+ }
+
+ // Parcelable CREATOR class
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public HistoryPath createFromParcel(Parcel in) {
+ return new HistoryPath(in);
+ }
+
+ @Override
+ public HistoryPath[] newArray(int size) {
+ return new HistoryPath[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "Point: " + isPoint + "\n" +
+ "Points: " + points + "\n" +
+ "Color: " + paintColor + "\n" +
+ "Alpha: " + paintAlpha + "\n" +
+ "Width: " + paintWidth;
+ }
+}
diff --git a/freedrawview/src/main/java/com/rm/freedrawview/PathDrawnListener.java b/freedrawview/src/main/java/com/rm/freedrawview/PathDrawnListener.java
new file mode 100644
index 0000000..909737d
--- /dev/null
+++ b/freedrawview/src/main/java/com/rm/freedrawview/PathDrawnListener.java
@@ -0,0 +1,12 @@
+package com.rm.freedrawview;
+
+/**
+ * Created by Riccardo on 22/11/16.
+ */
+
+public interface PathDrawnListener {
+
+ void onPathStart();
+
+ void onNewPathDrawn();
+}
diff --git a/freedrawview/src/main/java/com/rm/freedrawview/PathRedoUndoCountChangeListener.java b/freedrawview/src/main/java/com/rm/freedrawview/PathRedoUndoCountChangeListener.java
new file mode 100644
index 0000000..14659e6
--- /dev/null
+++ b/freedrawview/src/main/java/com/rm/freedrawview/PathRedoUndoCountChangeListener.java
@@ -0,0 +1,11 @@
+package com.rm.freedrawview;
+
+/**
+ * Created by Riccardo on 22/11/16.
+ */
+
+public interface PathRedoUndoCountChangeListener {
+ void onUndoCountChanged(int undoCount);
+
+ void onRedoCountChanged(int redoCount);
+}
diff --git a/freedrawview/src/main/java/com/rm/freedrawview/Point.java b/freedrawview/src/main/java/com/rm/freedrawview/Point.java
new file mode 100644
index 0000000..34a91e0
--- /dev/null
+++ b/freedrawview/src/main/java/com/rm/freedrawview/Point.java
@@ -0,0 +1,57 @@
+package com.rm.freedrawview;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.io.Serializable;
+
+/**
+ * Created by Riccardo Moro on 9/25/2016.
+ */
+
+class Point implements Parcelable, Serializable {
+
+ static final long serialVersionUID = 42L;
+
+ float x, y;
+
+ Point() {
+ x = y = -1;
+ }
+
+ @Override
+ public String toString() {
+ return "" + x + " : " + y + " - ";
+ }
+
+
+ // Parcelable stuff
+ private Point(Parcel in) {
+ x = in.readFloat();
+ y = in.readFloat();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeFloat(x);
+ dest.writeFloat(y);
+ }
+
+ // Parcelable CREATOR class
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public Point createFromParcel(Parcel in) {
+ return new Point(in);
+ }
+
+ @Override
+ public Point[] newArray(int size) {
+ return new Point[size];
+ }
+ };
+}
diff --git a/freedrawview/src/main/java/com/rm/freedrawview/ResizeBehaviour.java b/freedrawview/src/main/java/com/rm/freedrawview/ResizeBehaviour.java
new file mode 100644
index 0000000..2afcc88
--- /dev/null
+++ b/freedrawview/src/main/java/com/rm/freedrawview/ResizeBehaviour.java
@@ -0,0 +1,18 @@
+package com.rm.freedrawview;
+
+/**
+ * Created by Riccardo Moro on 11/6/2016.
+ */
+
+/**
+ * When RMFreeDrawView dimensions are changed, you can apply one of the following behaviours
+ * {@link #CLEAR} - It just clear the View from every previous paint
+ * {@link #FIT_XY} - It stretch the content to fit the new dimensions
+ * {@link #CROP} - Keep the exact position of the previous point, if the dimensions changes, there
+ * may be points outside the view and not visible
+ */
+public enum ResizeBehaviour {
+ CLEAR,
+ FIT_XY,
+ CROP
+}
diff --git a/freedrawview/src/main/res/values/attrs.xml b/freedrawview/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..7ef2e3d
--- /dev/null
+++ b/freedrawview/src/main/res/values/attrs.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index cfedeff..41c78d7 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,21 +1,20 @@
[versions]
androidMavenGradlePlugin = "2.1"
-appcompat = "1.6.1"
+appcompat = "1.7.0"
constraintlayout = "2.1.4"
-coreKtx = "1.7.0"
+coreKtx = "1.13.1"
dokkaGradlePlugin = "1.7.20"
-freedrawview = "1.1.2"
-gradle = "8.2.2"
+gradle = "8.3.2"
ink = "1.0.0"
-lifecycleExtensions = "2.0.0"
-lifecycleViewmodelKtx = "2.6.1"
+lifecycleExtensions = "2.2.0"
+lifecycleViewmodelKtx = "2.8.2"
loco = "0.3.1"
-loggingInterceptor = "3.10.0"
-material = "1.9.0"
+loggingInterceptor = "4.12.0"
+material = "1.12.0"
multidex = "1.0.3"
-retrofit = "2.4.0"
-rxandroid = "2.0.2"
-rxjava = "2.1.12"
+retrofit = "2.9.0"
+rxandroid = "2.1.1"
+rxjava = "2.2.21"
updraft = "2.2.4"
kotlin = "1.9.23"
vanniktechMavenPublish = "0.24.0"
@@ -29,7 +28,6 @@ constraintlayout = { module = "androidx.constraintlayout:constraintlayout", vers
converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }
core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
dokka-gradle-plugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokkaGradlePlugin" }
-freedrawview = { module = "com.rm:freedrawview", version.ref = "freedrawview" }
ink = { module = "com.simplify:ink", version.ref = "ink" }
kotlin-stdlib-jdk7 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk7", version.ref = "kotlin" }
lifecycle-compiler = { module = "androidx.lifecycle:lifecycle-compiler", version.ref = "lifecycleExtensions" }
@@ -46,6 +44,8 @@ tools-build-gradle = { module = "com.android.tools.build:gradle", version.ref =
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
loco = { module = "com.appswithlove.loco:loco", version = "0.3.1" }
updraft = { module = "com.appswithlove.updraft:updraft", version.ref = "updraft" }
+androidx-startup = { module = "androidx.startup:startup-runtime", version = "1.1.1" }
+androidx-annotation = { module = "androidx.annotation:annotation", version = "1.8.0" }
[plugins]
androidApplication = { id = "com.android.application", version.ref = "gradle" }
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 0f56903..b8e2ce0 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Tue Nov 23 11:31:29 CET 2021
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
diff --git a/settings.gradle b/settings.gradle
index cf5b7e1..1e677b8 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -3,7 +3,6 @@ pluginManagement {
google()
gradlePluginPortal()
mavenCentral()
- maven { url 'https://maven.scijava.org/content/repositories/public/' }
}
}
@@ -11,10 +10,9 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
- maven { url 'https://maven.scijava.org/content/repositories/public/' }
}
}
rootProject.name = "updraft-sdk-android"
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
-include ':app', ':updraft-sdk'
+include ':app', ':updraft-sdk', ':freedrawview'
diff --git a/updraft-sdk/build.gradle b/updraft-sdk/build.gradle
index b2b631d..3a018bc 100644
--- a/updraft-sdk/build.gradle
+++ b/updraft-sdk/build.gradle
@@ -45,6 +45,7 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation project(':freedrawview')
implementation libs.appcompat
implementation libs.material
@@ -61,8 +62,8 @@ dependencies {
implementation libs.lifecycle.extensions
implementation libs.lifecycle.runtime
annotationProcessor libs.lifecycle.compiler
+ implementation libs.androidx.startup
- implementation libs.freedrawview
implementation libs.core.ktx
implementation libs.lifecycle.viewmodel.ktx
implementation libs.kotlin.stdlib.jdk7
diff --git a/updraft-sdk/src/main/AndroidManifest.xml b/updraft-sdk/src/main/AndroidManifest.xml
index fef41cb..d401c87 100644
--- a/updraft-sdk/src/main/AndroidManifest.xml
+++ b/updraft-sdk/src/main/AndroidManifest.xml
@@ -1,5 +1,6 @@
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools">
@@ -8,5 +9,15 @@
android:name="com.appswithlove.updraft.feedback.FeedbackActivity"
android:theme="@style/Theme.Transparent"
android:exported="true" />
+
+
+
+
+
diff --git a/updraft-sdk/src/main/java/com/appswithlove/updraft/Updraft.kt b/updraft-sdk/src/main/java/com/appswithlove/updraft/Updraft.kt
index faac116..7d3d275 100644
--- a/updraft-sdk/src/main/java/com/appswithlove/updraft/Updraft.kt
+++ b/updraft-sdk/src/main/java/com/appswithlove/updraft/Updraft.kt
@@ -6,6 +6,7 @@ import com.appswithlove.updraft.interactor.CheckFeedbackEnabledInteractor
import com.appswithlove.updraft.interactor.CheckUpdateInteractor
import com.appswithlove.updraft.manager.AppUpdateManager
import com.appswithlove.updraft.manager.CheckFeedbackEnabledManager
+import com.appswithlove.updraft.manager.CurrentActivityManger
import com.appswithlove.updraft.manager.ShakeDetectorManager
import com.appswithlove.updraft.presentation.UpdraftSdkUi
@@ -28,7 +29,7 @@ class Updraft private constructor(application: Application, private val settings
init {
val checkUpdateInteractor = CheckUpdateInteractor(apiWrapper)
- val updraftSdkUi = UpdraftSdkUi(application, settings)
+ val updraftSdkUi = UpdraftSdkUi(CurrentActivityManger.INSTANCE, settings)
mAppUpdateManager = AppUpdateManager(checkUpdateInteractor, updraftSdkUi)
val checkFeedbackEnabledInteractor = CheckFeedbackEnabledInteractor(
apiWrapper, application
diff --git a/updraft-sdk/src/main/java/com/appswithlove/updraft/manager/CurrentActivityManger.kt b/updraft-sdk/src/main/java/com/appswithlove/updraft/manager/CurrentActivityManger.kt
new file mode 100644
index 0000000..0218b67
--- /dev/null
+++ b/updraft-sdk/src/main/java/com/appswithlove/updraft/manager/CurrentActivityManger.kt
@@ -0,0 +1,75 @@
+package com.appswithlove.updraft.manager
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.app.Application
+import android.os.Bundle
+
+@SuppressLint("StaticFieldLeak")
+class CurrentActivityManger private constructor() : Application.ActivityLifecycleCallbacks {
+
+ companion object {
+ val INSTANCE: CurrentActivityManger by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
+ CurrentActivityManger()
+ }
+ }
+
+ private var currentActivity: Activity? = null
+ private var currentLifecycle: LifecycleState? = null
+
+ private val listeners: MutableSet = mutableSetOf()
+
+ fun addListener(listener: CurrentActivityListener) {
+ listeners.add(listener)
+ dispatchCurrentActivityState()
+ }
+
+ fun removeListener(listener: CurrentActivityListener) {
+ listeners.remove(listener)
+ }
+
+ override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
+ currentActivity = activity
+ }
+
+ override fun onActivityStarted(activity: Activity) {}
+ override fun onActivityResumed(activity: Activity) {
+ currentLifecycle = LifecycleState.Resumed
+ dispatchCurrentActivityState()
+ }
+
+ override fun onActivityPaused(activity: Activity) {
+ currentLifecycle = LifecycleState.Paused
+ dispatchCurrentActivityState()
+ }
+
+ override fun onActivityStopped(activity: Activity) {}
+ override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
+ override fun onActivityDestroyed(activity: Activity) {
+ currentActivity = null
+ currentLifecycle = null
+ }
+
+ private fun dispatchCurrentActivityState() {
+ val activity = currentActivity
+ if (activity != null) {
+ listeners.forEach { listener ->
+ if (currentLifecycle == LifecycleState.Resumed) {
+ listener.onActivityResumed(activity)
+ } else if (currentLifecycle == LifecycleState.Paused) {
+ listener.onActivityPaused(activity)
+ }
+ }
+ }
+ }
+
+ interface CurrentActivityListener {
+ fun onActivityResumed(activity: Activity)
+ fun onActivityPaused(activity: Activity)
+ }
+
+ private enum class LifecycleState {
+ Resumed,
+ Paused
+ }
+}
\ No newline at end of file
diff --git a/updraft-sdk/src/main/java/com/appswithlove/updraft/manager/SdkInitializer.kt b/updraft-sdk/src/main/java/com/appswithlove/updraft/manager/SdkInitializer.kt
new file mode 100644
index 0000000..0f27ee0
--- /dev/null
+++ b/updraft-sdk/src/main/java/com/appswithlove/updraft/manager/SdkInitializer.kt
@@ -0,0 +1,17 @@
+package com.appswithlove.updraft.manager
+
+import android.annotation.SuppressLint
+import android.app.Application
+import android.content.Context
+import androidx.startup.Initializer
+
+@SuppressLint("Unused")
+class SdkInitializer : Initializer {
+ override fun create(context: Context): CurrentActivityManger {
+ val activityManger = CurrentActivityManger.INSTANCE
+ (context.applicationContext as? Application)?.registerActivityLifecycleCallbacks(activityManger)
+ return activityManger
+ }
+
+ override fun dependencies(): List>> = emptyList()
+}
diff --git a/updraft-sdk/src/main/java/com/appswithlove/updraft/presentation/UpdraftSdkUi.kt b/updraft-sdk/src/main/java/com/appswithlove/updraft/presentation/UpdraftSdkUi.kt
index c46eb69..f72403c 100644
--- a/updraft-sdk/src/main/java/com/appswithlove/updraft/presentation/UpdraftSdkUi.kt
+++ b/updraft-sdk/src/main/java/com/appswithlove/updraft/presentation/UpdraftSdkUi.kt
@@ -2,27 +2,27 @@ package com.appswithlove.updraft.presentation
import android.app.Activity
import android.app.AlertDialog
-import android.app.Application
-import android.app.Application.ActivityLifecycleCallbacks
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.graphics.Bitmap
import android.net.Uri
-import android.os.Bundle
import android.widget.Toast
import com.appswithlove.updraft.BuildConfig
import com.appswithlove.updraft.R
import com.appswithlove.updraft.Settings
import com.appswithlove.updraft.Updraft.Companion.getInstance
import com.appswithlove.updraft.feedback.FeedbackActivity
+import com.appswithlove.updraft.manager.CurrentActivityManger
import java.io.IOException
/**
* Created by satori on 3/27/18.
*/
-class UpdraftSdkUi(application: Application, private val mSettings: Settings) :
- ActivityLifecycleCallbacks {
+class UpdraftSdkUi(
+ currentActivityManger: CurrentActivityManger,
+ private val mSettings: Settings,
+) : CurrentActivityManger.CurrentActivityListener {
private var mCurrentActivity: Activity? = null
private var mShowDialogPending = false
private var mPendingUrl: String? = null
@@ -36,6 +36,9 @@ class UpdraftSdkUi(application: Application, private val mSettings: Settings) :
var isCurrentlyShowingFeedback = false
private set
+ init {
+ currentActivityManger.addListener(this)
+ }
fun setListener(listener: Listener?) {
mListener = listener
@@ -167,8 +170,15 @@ class UpdraftSdkUi(application: Application, private val mSettings: Settings) :
bitmap.recycle()
}
- override fun onActivityCreated(activity: Activity, bundle: Bundle?) {}
- override fun onActivityStarted(activity: Activity) {}
+
+ fun closeFeedback() {
+ mCurrentActivity.apply {
+ if (this is FeedbackActivity && !this.isFinishing()) {
+ this.finish()
+ }
+ }
+ }
+
override fun onActivityResumed(activity: Activity) {
mCurrentActivity = activity
if (mShowStartAlertDialogPending) {
@@ -195,27 +205,11 @@ class UpdraftSdkUi(application: Application, private val mSettings: Settings) :
mCurrentActivity = null
}
- fun closeFeedback() {
- mCurrentActivity.apply {
- if (this is FeedbackActivity && !this.isFinishing()) {
- this.finish()
- }
- }
- }
-
- override fun onActivityStopped(activity: Activity) {}
- override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
- override fun onActivityDestroyed(activity: Activity) {}
-
companion object {
private const val TAG = "UpdraftSdkUi"
private const val FILENAME = "UPDRAFT_SCREENSHOT.png"
}
- init {
- application.registerActivityLifecycleCallbacks(this)
- }
-
interface Listener {
fun onOkClicked(url: String)
}