Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ public interface Delegate {

private Long activeClearTag = 0L;

private boolean activePathRecoveryEnabled = false;

private WeakReference<View> initializeLayoutTarget = new WeakReference<>(null);
private ViewTreeObserver.OnGlobalLayoutListener initializationGlobalLayoutListener;

Expand Down Expand Up @@ -443,4 +445,13 @@ public ScratchableLayoutDrawer setClearAnimationInterpolator(Interpolator clearA

return this;
}

@SuppressWarnings("WeakerAccess")
public ScratchableLayoutDrawer setActivePathRecoveryEnabled(boolean activePathRecoveryEnabled) {
this.activePathRecoveryEnabled = activePathRecoveryEnabled;

pathManager.setActivePathRecoveryEnabled(activePathRecoveryEnabled);

return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ public interface ThresholdChangedListener {
private final LinkedBlockingQueue<ScratchPathPoint> history = new LinkedBlockingQueue<ScratchPathPoint>();
private boolean stateRestorationEnabled;

private boolean activePathRecoveryEnabled = false;

/**
* Create a new {@link ScratchoffController} instance targeting a scratchable layout.
*/
Expand Down Expand Up @@ -152,7 +154,8 @@ public ScratchoffController attach() {
protected ScratchableLayoutDrawer createLayoutDrawer() {
return new ScratchableLayoutDrawer(this)
.setClearAnimationDurationMs(clearAnimationDurationMs)
.setClearAnimationInterpolator(clearAnimationInterpolator);
.setClearAnimationInterpolator(clearAnimationInterpolator)
.setActivePathRecoveryEnabled(activePathRecoveryEnabled);
}

protected ScratchoffThresholdProcessor createThresholdProcessor() {
Expand Down Expand Up @@ -480,6 +483,22 @@ public ScratchoffController setStateRestorationEnabled(boolean stateRestorationE
return this;
}

/**
* Set whether or not to recover from a null active {@link android.graphics.Path} during
* a touch-move event, instead of allowing a {@link NullPointerException} to be thrown.
* This can occur when a move event arrives without a preceding down event,
* e.g. due to race conditions during layout callbacks.
* The default for this value is false for the original (crashing) behavior.
*/
public ScratchoffController setActivePathRecoveryEnabled(
boolean activePathRecoveryEnabled
) {

this.activePathRecoveryEnabled = activePathRecoveryEnabled;

return this;
}

public View getViewBehind() {
return behindView.get();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public class ScratchPathManager implements ScratchPathPointsAggregator {
private final ArrayList<Path> paths = new ArrayList<>();
private float scale = 1f;

private boolean activePathRecoveryEnabled = false;

public ScratchPathManager() {
}

Expand All @@ -27,6 +29,12 @@ public ScratchPathManager setScale(float scale) {
return this;
}

public ScratchPathManager setActivePathRecoveryEnabled(boolean activePathRecoveryEnabled) {
this.activePathRecoveryEnabled = activePathRecoveryEnabled;

return this;
}

@Override
public void addScratchPathPoints(Collection<ScratchPathPoint> events) {
for (ScratchPathPoint event : events) {
Expand Down Expand Up @@ -82,6 +90,17 @@ protected void handleTouchMove(int pointerIndex, float x, float y) {

Path activePath = this.activePaths[pointerIndex];

// Recover from a null activePath caused by a move event arriving without
// a preceding down event (e.g. due to race conditions during layout callbacks)
if (activePath == null) {
if (!activePathRecoveryEnabled) {
return;
}

createPath(pointerIndex, x, y);
activePath = this.activePaths[pointerIndex];
}

// If the active Path has been drawn, it would have been reset to an empty state
if (activePath.isEmpty()) {
activePath.moveTo(x, y);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,29 @@ class ScratchPathManagerTests {
assertEquals(value, pathBounds.isEmpty)
}

@Test
fun testMoveWithNullActivePathIsIgnoredWhenRecoveryDisabled() {
val manager = ScratchPathManager()

assertEquals(0, manager.paths.size)

manager.handleTouchMove(0, 1f, 1f)

assertEquals(0, manager.paths.size)
}

@Test
fun testMoveWithNullActivePathCreatesPathWhenRecoveryEnabled() {
val manager = ScratchPathManager()
manager.setActivePathRecoveryEnabled(true)

assertEquals(0, manager.paths.size)

manager.handleTouchMove(0, 1f, 1f)

assertEquals(1, manager.paths.size)
}

@Test
fun testTouchDownAndMovePassthroughIncludesScalar() {
val manager = object: ScratchPathManager() {
Expand Down
Loading