mirror of
https://github.com/ankidroid/Anki-Android.git
synced 2024-09-20 12:02:16 +02:00
Add multitouch capabilities to whiteboard for interfacing a card under it (#4728)
This makes the whiteboard more ergonomic (in my subjective opinion). Related issue: https://github.com/ankidroid/Anki-Android/issues/4522 Summing up, when keeping one finger on the whiteboard while it is activated, it is now possible to use a second finger to scroll the card behind the whiteboard or tap onto one of its elements.
This commit is contained in:
parent
3e74c3d895
commit
8223d04a91
@ -2075,6 +2075,36 @@ public abstract class AbstractFlashcardViewer extends NavigationDrawerActivity {
|
||||
}
|
||||
|
||||
|
||||
/** Scroll the currently shown flashcard vertically
|
||||
*
|
||||
* @param dy amount to be scrolled
|
||||
*/
|
||||
public void scrollCurrentCardBy(int dy) {
|
||||
if (dy != 0 && mCard.canScrollVertically(dy)) {
|
||||
mCard.scrollBy(0, dy);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Tap onto the currently shown flashcard at position x and y
|
||||
*
|
||||
* @param x horizontal position of the event
|
||||
* @param y vertical position of the event
|
||||
*/
|
||||
public void tapOnCurrentCard(int x, int y) {
|
||||
// assemble suitable ACTION_DOWN and ACTION_UP events and forward them to the card's handler
|
||||
MotionEvent eDown = MotionEvent.obtain(SystemClock.uptimeMillis(),
|
||||
SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, x, y,
|
||||
1, 1, 0, 1, 1, 0, 0);
|
||||
mCard.dispatchTouchEvent(eDown);
|
||||
|
||||
MotionEvent eUp = MotionEvent.obtain(eDown.getDownTime(),
|
||||
SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, x, y,
|
||||
1, 1, 0, 1, 1, 0, 0);
|
||||
mCard.dispatchTouchEvent(eUp);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* getAnswerFormat returns the answer part of this card's template as entered by user, without any parsing
|
||||
*/
|
||||
|
@ -48,19 +48,27 @@ public class Whiteboard extends View {
|
||||
private Canvas mCanvas;
|
||||
private Path mPath;
|
||||
private Paint mBitmapPaint;
|
||||
private WeakReference<AnkiActivity> mActivity;
|
||||
private WeakReference<AbstractFlashcardViewer> mCardViewer;
|
||||
|
||||
private float mX;
|
||||
private float mY;
|
||||
private float mSecondFingerX0;
|
||||
private float mSecondFingerY0;
|
||||
private float mSecondFingerX;
|
||||
private float mSecondFingerY;
|
||||
|
||||
private int mSecondFingerPointerId;
|
||||
|
||||
private boolean mSecondFingerWithinTapTolerance;
|
||||
private boolean mCurrentlyDrawing = false;
|
||||
private boolean mInvertedColors = false;
|
||||
private boolean mMonochrome = true;
|
||||
private boolean mUndoModeActive = false;
|
||||
|
||||
|
||||
public Whiteboard(AnkiActivity context, boolean inverted, boolean monochrome) {
|
||||
super(context, null);
|
||||
mActivity = new WeakReference<>(context);
|
||||
public Whiteboard(AbstractFlashcardViewer cardViewer, boolean inverted, boolean monochrome) {
|
||||
super(cardViewer, null);
|
||||
mCardViewer = new WeakReference<>(cardViewer);
|
||||
mInvertedColors = inverted;
|
||||
mMonochrome = monochrome;
|
||||
|
||||
@ -69,13 +77,13 @@ public class Whiteboard extends View {
|
||||
if (mMonochrome) {
|
||||
foregroundColor = Color.BLACK;
|
||||
} else {
|
||||
foregroundColor = ContextCompat.getColor(context, R.color.wb_fg_color);
|
||||
foregroundColor = ContextCompat.getColor(cardViewer, R.color.wb_fg_color);
|
||||
}
|
||||
} else {
|
||||
if (mMonochrome) {
|
||||
foregroundColor = Color.WHITE;
|
||||
} else {
|
||||
foregroundColor = ContextCompat.getColor(context, R.color.wb_fg_color_inv);
|
||||
foregroundColor = ContextCompat.getColor(cardViewer, R.color.wb_fg_color_inv);
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,7 +94,7 @@ public class Whiteboard extends View {
|
||||
mPaint.setStyle(Paint.Style.STROKE);
|
||||
mPaint.setStrokeJoin(Paint.Join.ROUND);
|
||||
mPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
int wbStrokeWidth = AnkiDroidApp.getSharedPrefs(context).getInt("whiteBoardStrokeWidth", 6);
|
||||
int wbStrokeWidth = AnkiDroidApp.getSharedPrefs(cardViewer).getInt("whiteBoardStrokeWidth", 6);
|
||||
mPaint.setStrokeWidth((float) wbStrokeWidth);
|
||||
createBitmap();
|
||||
mPath = new Path();
|
||||
@ -103,37 +111,79 @@ public class Whiteboard extends View {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle touch screen motion events.
|
||||
*
|
||||
/** Handle motion events to draw using the touch screen or to interact with the flashcard behind
|
||||
* the whiteboard by using a second finger.
|
||||
*
|
||||
* @param event The motion event.
|
||||
* @return True if the event was handled, false otherwise.
|
||||
* @return True if the event was handled, false otherwise
|
||||
*/
|
||||
public boolean handleTouchEvent(MotionEvent event) {
|
||||
return handleDrawEvent(event) || handleMultiTouchEvent(event);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle motion events to draw using the touch screen. Only simple touch events are processed,
|
||||
* a multitouch event aborts to current stroke.
|
||||
*
|
||||
* @param event The motion event.
|
||||
* @return True if the event was handled, false otherwise or when drawing was aborted due to
|
||||
* detection of a multitouch event.
|
||||
*/
|
||||
private boolean handleDrawEvent(MotionEvent event) {
|
||||
float x = event.getX();
|
||||
float y = event.getY();
|
||||
|
||||
switch (event.getAction()) {
|
||||
switch (event.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
touchStart(x, y);
|
||||
drawStart(x, y);
|
||||
invalidate();
|
||||
return true;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
for (int i = 0; i < event.getHistorySize(); i++) {
|
||||
touchMove(event.getHistoricalX(i), event.getHistoricalY(i));
|
||||
if (mCurrentlyDrawing) {
|
||||
for (int i = 0; i < event.getHistorySize(); i++) {
|
||||
drawAlong(event.getHistoricalX(i), event.getHistoricalY(i));
|
||||
}
|
||||
drawAlong(x, y);
|
||||
invalidate();
|
||||
return true;
|
||||
}
|
||||
touchMove(x, y);
|
||||
invalidate();
|
||||
return true;
|
||||
return false;
|
||||
case MotionEvent.ACTION_UP:
|
||||
touchUp();
|
||||
invalidate();
|
||||
return true;
|
||||
if (mCurrentlyDrawing) {
|
||||
drawFinish();
|
||||
invalidate();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
if (mCurrentlyDrawing) {
|
||||
drawAbort();
|
||||
}
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse multitouch input to scroll the card behind the whiteboard or click on elements
|
||||
private boolean handleMultiTouchEvent(MotionEvent event) {
|
||||
if (event.getPointerCount() == 2) {
|
||||
switch (event.getActionMasked()) {
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
reinitializeSecondFinger(event);
|
||||
return true;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
return trySecondFingerScroll(event);
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
return trySecondFingerClick(event);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clear the whiteboard.
|
||||
@ -143,8 +193,8 @@ public class Whiteboard extends View {
|
||||
mBitmap.eraseColor(0);
|
||||
mUndo.clear();
|
||||
invalidate();
|
||||
if (mActivity.get() != null) {
|
||||
mActivity.get().supportInvalidateOptionsMenu();
|
||||
if (mCardViewer.get() != null) {
|
||||
mCardViewer.get().supportInvalidateOptionsMenu();
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,8 +205,8 @@ public class Whiteboard extends View {
|
||||
public void undo() {
|
||||
mUndo.pop();
|
||||
mUndo.apply();
|
||||
if (undoSize() == 0 && mActivity.get() != null) {
|
||||
mActivity.get().supportInvalidateOptionsMenu();
|
||||
if (undoSize() == 0 && mCardViewer.get() != null) {
|
||||
mCardViewer.get().supportInvalidateOptionsMenu();
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,8 +241,8 @@ public class Whiteboard extends View {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void touchStart(float x, float y) {
|
||||
private void drawStart(float x, float y) {
|
||||
mCurrentlyDrawing = true;
|
||||
mPath.reset();
|
||||
mPath.moveTo(x, y);
|
||||
mX = x;
|
||||
@ -200,7 +250,7 @@ public class Whiteboard extends View {
|
||||
}
|
||||
|
||||
|
||||
private void touchMove(float x, float y) {
|
||||
private void drawAlong(float x, float y) {
|
||||
float dx = Math.abs(x - mX);
|
||||
float dy = Math.abs(y - mY);
|
||||
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
|
||||
@ -211,10 +261,11 @@ public class Whiteboard extends View {
|
||||
}
|
||||
|
||||
|
||||
private void touchUp() {
|
||||
private void drawFinish() {
|
||||
mCurrentlyDrawing = false;
|
||||
PathMeasure pm = new PathMeasure(mPath, false);
|
||||
mPath.lineTo(mX, mY);
|
||||
if(pm.getLength() > 0){
|
||||
if (pm.getLength() > 0) {
|
||||
mCanvas.drawPath(mPath, mPaint);
|
||||
mUndo.add(mPath);
|
||||
} else {
|
||||
@ -224,12 +275,73 @@ public class Whiteboard extends View {
|
||||
mUndoModeActive = true;
|
||||
// kill the path so we don't double draw
|
||||
mPath.reset();
|
||||
if (mUndo.size() == 1 && mActivity.get() != null) {
|
||||
mActivity.get().supportInvalidateOptionsMenu();
|
||||
if (mUndo.size() == 1 && mCardViewer.get() != null) {
|
||||
mCardViewer.get().supportInvalidateOptionsMenu();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void drawAbort() {
|
||||
drawFinish();
|
||||
undo();
|
||||
}
|
||||
|
||||
|
||||
// call this with an ACTION_POINTER_DOWN event to start a new round of detecting drag or tap with
|
||||
// a second finger
|
||||
private void reinitializeSecondFinger(MotionEvent event) {
|
||||
mSecondFingerWithinTapTolerance = true;
|
||||
mSecondFingerPointerId = event.getPointerId(event.getActionIndex());
|
||||
mSecondFingerX0 = event.getX(event.findPointerIndex(mSecondFingerPointerId));
|
||||
mSecondFingerY0 = event.getY(event.findPointerIndex(mSecondFingerPointerId));
|
||||
}
|
||||
|
||||
private boolean updateSecondFinger(MotionEvent event) {
|
||||
int pointerIndex = event.findPointerIndex(mSecondFingerPointerId);
|
||||
if (pointerIndex > -1) {
|
||||
mSecondFingerX = event.getX(pointerIndex);
|
||||
mSecondFingerY = event.getY(pointerIndex);
|
||||
float dx = Math.abs(mSecondFingerX0 - mSecondFingerX);
|
||||
float dy = Math.abs(mSecondFingerY0 - mSecondFingerY);
|
||||
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
|
||||
mSecondFingerWithinTapTolerance = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// call this with an ACTION_POINTER_UP event to check whether it matches a tap of the second finger
|
||||
// if so, forward a click action and return true
|
||||
private boolean trySecondFingerClick(MotionEvent event) {
|
||||
if (mSecondFingerPointerId == event.getPointerId(event.getActionIndex())) {
|
||||
updateSecondFinger(event);
|
||||
AbstractFlashcardViewer cardViewer = mCardViewer.get();
|
||||
if (mSecondFingerWithinTapTolerance && cardViewer != null) {
|
||||
cardViewer.tapOnCurrentCard((int) mSecondFingerX, (int) mSecondFingerY);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// call this with an ACTION_MOVE event to check whether it is within the threshold for a tap of the second finger
|
||||
// in this case perform a scroll action
|
||||
private boolean trySecondFingerScroll(MotionEvent event) {
|
||||
if (updateSecondFinger(event) && !mSecondFingerWithinTapTolerance) {
|
||||
int dy = (int) (mSecondFingerY0 - mSecondFingerY);
|
||||
AbstractFlashcardViewer cardViewer = mCardViewer.get();
|
||||
if (dy != 0 && cardViewer != null) {
|
||||
cardViewer.scrollCurrentCardBy(dy);
|
||||
mSecondFingerX0 = mSecondFingerX;
|
||||
mSecondFingerY0 = mSecondFingerY;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private static int getDisplayHeight() {
|
||||
Display display = ((WindowManager) AnkiDroidApp.getInstance().getApplicationContext().
|
||||
getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
|
||||
|
Loading…
Reference in New Issue
Block a user