0

I am creating an app where multiple fragments have a video and a custom drawing view (let's call it canvas) where a user can draw a line with their finger. What I'm trying to figure out is: After the user draws on the canvas, how do I get the app to keep the drawing if the user goes to a different fragment or leaves the app?

Edit
Fragment With the same actions as previous, from my test app

import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

public class LineLHwFragment extends Fragment {

private PaintView paintView;


@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable 
ViewGroup container, @Nullable Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment_line_l_hw, container, 
false);
        paintView = v.findViewById(R.id.lineLPaintView);
    DisplayMetrics metrics = new DisplayMetrics();
    getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics);
    paintView.init(metrics);
    setHasOptionsMenu(true);
        return v;

}

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    inflater.inflate(R.menu.main, menu);
    super.onCreateOptionsMenu(menu, inflater);
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.normal:
            paintView.normal();
            return true;

        case R.id.clear:
            paintView.clear();
            return true;
    }

    return super.onOptionsItemSelected(item);
}


//putParcelableArray not working as getting ArrayList not Parcelable[]
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putParcelableArray("line test", paintView.getPaths());
}


}

Edit

Fragment that controls the drawing events now with a getter

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.EmbossMaskFilter;
import android.graphics.MaskFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;

import java.util.ArrayList;



public class PaintView extends View {

public static int BRUSH_SIZE = 10;
public static final int DEFAULT_COLOR = Color.WHITE;
public static int DEFAULT_BG_COLOR = Color.GRAY;
private static final float TOUCH_TOLERANCE = 4;
private float mX, mY;
private Path mPath;
private Paint mPaint;
private ArrayList<FingerPath> paths = new ArrayList<>();
private int currentColor;
private int backgroundColor = DEFAULT_BG_COLOR;
private int strokeWidth;
private boolean emboss;
private boolean blur;
private MaskFilter mEmboss;
private MaskFilter mBlur;
private Bitmap mBitmap;
private Canvas mCanvas;
private Paint mBitmapPaint = new Paint(Paint.DITHER_FLAG);



public PaintView(Context context) {
    this(context, null);

}

public PaintView(Context context, AttributeSet attrs) {
    super(context, attrs);
    mPaint = new Paint();
    mPaint.setAntiAlias(true);
    mPaint.setDither(true);

    mPaint.setColor(DEFAULT_COLOR);
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeJoin(Paint.Join.ROUND);
    mPaint.setStrokeCap(Paint.Cap.ROUND);
    mPaint.setXfermode(null);
    mPaint.setAlpha(0xff);

    mEmboss = new EmbossMaskFilter(new float[] {1, 1, 1}, 0.4f, 6, 3.5f);
    mBlur = new BlurMaskFilter(5, BlurMaskFilter.Blur.NORMAL);

}

//the getter in question

public ArrayList getPaths() {
    return paths;

}

public void init(DisplayMetrics metrics) {
    int height = metrics.heightPixels;
    int width = metrics.widthPixels;

    mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    mCanvas = new Canvas(mBitmap);

    currentColor = DEFAULT_COLOR;
    strokeWidth = BRUSH_SIZE;
}

public void normal() {
    emboss = false;
    blur = false;
}



public void clear() {
    backgroundColor = DEFAULT_BG_COLOR;
    paths.clear();
    normal();
    invalidate();
}

@Override
protected void onDraw(Canvas canvas) {
    canvas.save();
    mCanvas.drawColor(backgroundColor);

    for (FingerPath fp: paths) {
        mPaint.setColor(fp.color);
        mPaint.setStrokeWidth(fp.strokeWidth);
        mPaint.setMaskFilter(null);

        if (fp.emboss)
            mPaint.setMaskFilter(mEmboss);
        else if (fp.blur)
            mPaint.setMaskFilter(mBlur);

        mCanvas.drawPath(fp.path, mPaint);
    }

    canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);

    canvas.restore();
}

private void touchStart(float x, float y) {
    mPath = new Path();
    FingerPath fp = new FingerPath(currentColor, emboss, blur, strokeWidth, 
mPath);
    paths.add(fp);

    mPath.reset();
    mPath.moveTo(x, y);
    mX = x;
    mY = y;
}

private void touchMove(float x, float y) {
    float dx = Math.abs(x-mX);
    float dy = Math.abs(y-mY);

    if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
        mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
        mX = x;
        mY = y;
    }
}

private void touchUp() {
    mPath.lineTo(mX, mY);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    float x = event.getX();
    float y = event.getY();

    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN :
            touchStart(x, y);
            invalidate();
            break;
        case MotionEvent.ACTION_MOVE :
            touchMove(x, y);
            invalidate();
            break;
        case MotionEvent.ACTION_UP :
            touchUp();
            invalidate();
            break;
    }

    return true;
}
}

Newly newly altered FingerPath Class

import android.graphics.Path;
import android.os.Parcel;
import android.os.Parcelable;

import java.util.ArrayList;


public class FingerPath implements Parcelable {

public int color;
public boolean emboss;
public boolean blur;
public int strokeWidth;
public Path path;

public FingerPath(int color, boolean emboss, boolean blur, int strokeWidth, 
Path path) {
    this.color = color;
    this.emboss = emboss;
    this.blur = blur;
    this.strokeWidth = strokeWidth;
    this.path = path;

}



protected FingerPath(Parcel in) {
    color = in.readInt();
    emboss = in.readByte() != 0;
    blur = in.readByte() != 0;
    strokeWidth = in.readInt();

}

public static final Creator<FingerPath> CREATOR = new Creator<FingerPath>() {
    @Override
    public FingerPath createFromParcel(Parcel in) {
        return new FingerPath(in);
    }

    @Override
    public FingerPath[] newArray(int size) {
        return new FingerPath[size];
    }
};

@Override
public int describeContents() {
    return 0;
}

@Override
public void writeToParcel(Parcel parcel, int i) {
    parcel.writeInt(color);
    parcel.writeByte((byte) (emboss ? 1 : 0));
    parcel.writeByte((byte) (blur ? 1 : 0));
    parcel.writeInt(strokeWidth);

}
dan
  • 53
  • 7

1 Answers1

0

easy way would be to convert View with painting to Bitmap and setting this image as background when restoring Fragment

but you probably want to keep editing already drawn paths option, so you have to store all your FingerPaths in onSaveInstanceState method and restore them in onCreateView (Bundle savedInstanceState will carry data). if you want to keep whole path history even when app will be restarted then instead use some persistent storage, e.g. SharedPreferences or SQLite database - store data in onDestroyView and restore in onCreateView

note that your custom FingerPath class must implements Parcelable for being stored in Bundle

snachmsm
  • 17,866
  • 3
  • 32
  • 74
  • many thanks for the answer. How would I go about storing the FingerPaths in onSaveInstanceState method? Would that be some sort of list? – dan Oct 09 '20 at 11:01
  • there is a method [putParcelableArrayList](https://developer.android.com/reference/android/os/Bundle#putParcelableArrayList(java.lang.String,%20java.util.ArrayList%3C?%20extends%20android.os.Parcelable%3E)) and you already have `ArrayList` with your `FingerPath`s. just make `FingerPath implements Parcelable`, implement properly this interface and make some setter and getter in `PaintView` – snachmsm Oct 09 '20 at 11:27
  • then inside `onSaveInstanceState` just obtain this array and put in `Bundle`. inside `onCreateView` (or in `onRestoreInstanceState`) check if `Bundle` isn't null and contains previously set data (key exists), if yes then restore it and set to `PaintView`. note that saving instance state works only during runtime (e.g. press Home and got back to app or rotated device - `Activity` is recreating with whole content, incl. `Fragment`s). when someone leave your app intentionally (e.g. back press, then remove from recents) and start it again then instance isn't saved/restored, this is new instance – snachmsm Oct 09 '20 at 11:27
  • thanks, I'll give that a try and get back to you about it – dan Oct 09 '20 at 11:40
  • sorry forgot to ask, what will I be using the setter and getters for? – dan Oct 09 '20 at 12:45
  • `paintView.getPaths` in `onSaveInstanceState` for getting already drawn lines from view and putting them into `Bundle` and `paintView.setPaths` for restoring them if `Bundle savedInstanceState` will contain any saved paths. currently `ArrayList paths` is (and should stay) `private`, so you don't have access to this table strictly from `Fragment` (in which saving instance is happening) – snachmsm Oct 09 '20 at 12:51
  • saving instance is for `Fragment`, but you must put some data in this `Bundle`, so you have to get this data (paths) from `PaintView` – snachmsm Oct 09 '20 at 12:54
  • I have implemented 'Parcelable' in the FingerPaths class and added it to the question. As I am new to java and coding in general, I don't know if I added the methods correctly or not – dan Oct 09 '20 at 14:42
  • everything seems properly implemented, now add `Bundle` instance state saving and restoring to `Fragment` and you are good to go :) – snachmsm Oct 10 '20 at 17:32
  • how do I obtain the array in 'onSaveInstanceState'? – dan Oct 11 '20 at 16:29
  • `paint.getPaths()` should return your `ArrayList paths` from `PaintView` instance (view found by id `acuteAnglePaintView`). make setter (for restoring/setting initial state) and getter (for saving), I've already described this few comments above... – snachmsm Oct 11 '20 at 20:00
  • I have edited the question with the code that I have now. I added the code to a test app that does the same thing as the app in question (code is essentially the same, just with fewer files). I've been trying to carry out what you described but the getter is causing issues (in 'onSavedInstanceState', 'putParcelableArray' is expecting Parcelable[] not ArrayList). Is there a way to make it Parcelable? Also, am I on the right track with what you are saying? Many thanks – dan Oct 15 '20 at 13:12
  • array is not an `ArrayList`. `String[]` isn't same as `ArrayList`. you should use `putParcelableArrayList` method. also you aren't storing into parcel `Path` in `FingerPath`, so it won't be restored (seems like most important variable in there)... besides that yes, you are on right track :) – snachmsm Oct 15 '20 at 13:55
  • so to restore it, I need to write parcel 'Path' into 'Bundle' then? If not in 'FingerPath' then would it be the fragment that a canvas, such as 'LineLHwFragment' class in the newly edited example? – dan Oct 15 '20 at 14:34
  • you need to write WHOLE `FingerPath` to `Bundle`, all its variables. you are ommiting saving `Path path` in `writeToParcel` method (and restoring in `FingerPath(Parcel in)`) – snachmsm Oct 15 '20 at 14:38
  • I tried saving 'Path path' with: 'parcel.writeParcelableArray(PaintView.getPaths(), 0);' but I get: _Non-static method 'getPaths()' cannot be referenced from a static context_ as an error message and if I change 'getPaths()' to static I get that expecting 'Parcelable[]' error as well. Is there a method that I'm missing? – dan Oct 15 '20 at 14:55
  • `PaintView.getPaths()` is a static call, because `PaintView` is class name... you should call this method on your instance of `PaintView` obtained by `findViewById(R.id.lineLPaintView)`, so `paintView.getPaths();` (small letter). `ArrayList paths` should be `static`, if you create two `PaintView`s then every should keep only own `paths`, `static` declaration makes this field/variable "shared" between all instances – snachmsm Oct 15 '20 at 18:40
  • so I should then do this: ' public PaintView paintView = findViewById(R.id.lineLPaintView) ' in the _LineLHwFragment_ fragment inside the 'onSaveInstanceState' method? sorry for all the questions, I'm pretty new to java – dan Oct 15 '20 at 19:03
  • no, you should use `paintView.getPaths();` in `putParcelableArrayList` in `onSaveInstanceState`. you don't have one question about saving instance, you have tons of questions about how Java and Android works... learn some more, read the docs and look for some tutorials, there are plenty on the web. comment section is for clarifying answer and I'm just learning you how to code... sadly I can't help you anymore, you need some basic training about coding, then some basic training about Java, then Android, and then start coding and ask questions. good luck – snachmsm Oct 16 '20 at 06:03