Skip to content
This repository was archived by the owner on Feb 9, 2023. It is now read-only.
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
5 changes: 3 additions & 2 deletions hover/src/main/java/io/mattcarroll/hover/ExitView.java
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ private void initExitIconViewStatus() {
public boolean isInExitZone(@NonNull Point position, @NonNull Point screenSize) {
int exitXExcludeThresholdLeft = screenSize.x / 10;
int exitXExcludeThresholdRight = screenSize.x * 9 / 10;
int safeMargin = 1; // safe from the decimal point calculation

Rect exitArea = new Rect(
0 - mExitIcon.getWidth(),
Expand All @@ -144,14 +145,14 @@ public boolean isInExitZone(@NonNull Point position, @NonNull Point screenSize)
0 - mExitIcon.getWidth(),
screenSize.y * 4 / 6,
exitXExcludeThresholdLeft,
screenSize.y - mExitIcon.getHeight() / 2
screenSize.y - (mExitIcon.getHeight() / 2 + safeMargin)
);

Rect excludedXExitAreaRight = new Rect(
exitXExcludeThresholdRight,
screenSize.y * 4 / 6,
screenSize.x + mExitIcon.getWidth(),
screenSize.y - mExitIcon.getHeight() / 2
screenSize.y - (mExitIcon.getHeight() / 2 + safeMargin)
);

return exitArea.contains(position.x, position.y)
Expand Down
44 changes: 33 additions & 11 deletions hover/src/main/java/io/mattcarroll/hover/FloatingTab.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ class FloatingTab extends HoverFrameLayout {
private int mTabSize;
private View mTabView;
private Dock mDock;
private AnimatorSet mAnimatorSetDisappear;
private AnimatorSet mAnimatorSetAppear;

public FloatingTab(@NonNull Context context, @NonNull String tabId) {
super(context);
Expand Down Expand Up @@ -100,15 +102,16 @@ public void enableDebugMode(boolean debugMode) {
}

public void appear(@Nullable final Runnable onAppeared) {
AnimatorSet animatorSet = new AnimatorSet();
cancelAnimatorSetAppearIfNeeded();
mAnimatorSetAppear = new AnimatorSet();
ObjectAnimator scaleX = ObjectAnimator.ofFloat(this, "scaleX", 0.0f, 1.0f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(this, "scaleY", 0.0f, 1.0f);
animatorSet.setDuration(APPEARING_ANIMATION_DURATION);
animatorSet.setInterpolator(new OvershootInterpolator());
animatorSet.playTogether(scaleX, scaleY);
animatorSet.start();
mAnimatorSetAppear.setDuration(APPEARING_ANIMATION_DURATION);
mAnimatorSetAppear.setInterpolator(new OvershootInterpolator());
mAnimatorSetAppear.playTogether(scaleX, scaleY);
mAnimatorSetAppear.start();

animatorSet.addListener(new Animator.AnimatorListener() {
mAnimatorSetAppear.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
Expand All @@ -133,18 +136,22 @@ public void onAnimationRepeat(Animator animation) {
}

public void appearImmediate() {
cancelAnimatorSetDisappearIfNeeded();
setVisibility(VISIBLE);
setScaleX(1.0f);
setScaleY(1.0f);
}

public void disappear(@Nullable final Runnable onDisappeared) {
AnimatorSet animatorSet = new AnimatorSet();
cancelAnimatorSetDisappearIfNeeded();
mAnimatorSetDisappear = new AnimatorSet();
ObjectAnimator scaleX = ObjectAnimator.ofFloat(this, "scaleX", 0.0f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(this, "scaleY", 0.0f);
animatorSet.setDuration(APPEARING_ANIMATION_DURATION);
animatorSet.playTogether(scaleX, scaleY);
animatorSet.start();
mAnimatorSetDisappear.setDuration(APPEARING_ANIMATION_DURATION);
mAnimatorSetDisappear.playTogether(scaleX, scaleY);
mAnimatorSetDisappear.start();

animatorSet.addListener(new Animator.AnimatorListener() {
mAnimatorSetDisappear.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
Expand All @@ -169,9 +176,24 @@ public void onAnimationRepeat(Animator animation) {
}

public void disappearImmediate() {
cancelAnimatorSetAppearIfNeeded();
setVisibility(GONE);
}

private void cancelAnimatorSetAppearIfNeeded() {
if (mAnimatorSetAppear != null && mAnimatorSetAppear.isRunning()) {
mAnimatorSetAppear.cancel();
mAnimatorSetAppear = null;
}
}

private void cancelAnimatorSetDisappearIfNeeded() {
if (mAnimatorSetDisappear != null && mAnimatorSetDisappear.isRunning()) {
mAnimatorSetDisappear.cancel();
mAnimatorSetDisappear = null;
}
}

public void shrink() {
mTabSize = getResources().getDimensionPixelSize(R.dimen.hover_tab_size_shrunk);
updateSize();
Expand Down
185 changes: 133 additions & 52 deletions hover/src/main/java/io/mattcarroll/hover/HoverViewStateCollapsed.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class HoverViewStateCollapsed extends BaseHoverViewState {
private static final float MIN_TAB_VERTICAL_POSITION = 0.0f;
private static final float MAX_TAB_VERTICAL_POSITION = 1.0f;
private static final long DEFAULT_IDLE_MILLIS = 5000;
private static final int POP_THROWING_THRESHOLD = 20;
private static final float POP_THROWING_SPEED_THRESHOLD = 0.3f;
private static final int NEGATIVE = -1;
private static final int POSITIVE = 1;

Expand All @@ -55,8 +55,7 @@ class HoverViewStateCollapsed extends BaseHoverViewState {
private Handler mHandler = new Handler();
private Runnable mIdleActionRunnable;
private Runnable mOnStateChanged;
private Point mPrevPoint = null;
private Point mCurrPoint = new Point(0, 0);
private GestureBlackBox mGestureBlackBox = new GestureBlackBox();

@Override
public void takeControl(@NonNull HoverView floatingTab, final Runnable onStateChanged) {
Expand Down Expand Up @@ -92,6 +91,7 @@ public void run() {
return;
}
if (wasFloatingTabVisible) {
mFloatingTab.appearImmediate();
sendToDock();
} else {
moveToDock();
Expand Down Expand Up @@ -196,72 +196,35 @@ protected void onPickedUpByUser() {
mHoverView.notifyOnDragStart(this);
}

private double calculateDistance(@NonNull Point point1, @NonNull Point point2) {
return Math.sqrt(
Math.pow(point2.x - point1.x, 2) + Math.pow(point2.y - point1.y, 2)
);
}

/**
* Get target Y position from 2 points
* @param point1 first point
* @param point2 second point
* @return targetPoint
*/
private float getTargetYPosition(@NonNull Point point1, @NonNull Point point2) {
// STEP 1: get liner line equation from 2 points (ax + by = c)
float a = point2.y - point1.y;
float b = point1.x - point2.x;
float c = a * (point1.x) + b * (point1.y);

// STEP 2: get x direction of the line
int xDirection = POSITIVE;
if (point1.x - point2.x >= 0) {
xDirection = NEGATIVE;
}

// To avoid divide by zero exception
if (b == 0) {
b = 1;
}

// STEP 3: return target Y position ( y = (c - ax) / b)
if (xDirection == NEGATIVE) {
return c / b;
} else {
return (c - a * mHoverView.getScreenSize().x) / b;
}
}

private void onDroppedByUser() {
if (!hasControl()) {
return;
}
mHoverView.mScreen.getExitView().hide();
mGestureBlackBox.addGesturePoint(mFloatingTab.getPosition());


if (mPrevPoint == null) {
mPrevPoint = mFloatingTab.getPosition();
}
Point screenSize = mHoverView.getScreenSize();
boolean droppedOnExit = mHoverView.mScreen.getExitView().isInExitZone(mFloatingTab.getPosition(), screenSize);
if (droppedOnExit) {
onClose(true);
} else {
handleDrop(screenSize);
}
mGestureBlackBox.clear();
}

private void handleDrop(Point screenSize) {
float distance = (float) calculateDistance(mPrevPoint, mFloatingTab.getPosition());
int tabSize = mHoverView.getResources().getDimensionPixelSize(R.dimen.hover_tab_size);
float tabHorizontalPositionPercent = (float) mFloatingTab.getPosition().x / screenSize.x;
final float viewHeightPercent = mFloatingTab.getHeight() / 2f / screenSize.y;
float tabVerticalPositionPercent;
if (distance > POP_THROWING_THRESHOLD) {
float positionY = getTargetYPosition(mPrevPoint, mFloatingTab.getPosition());

if (mGestureBlackBox.getSpeed() > POP_THROWING_SPEED_THRESHOLD) {
float positionY = mGestureBlackBox.getTargetYPosition();
tabVerticalPositionPercent = positionY / screenSize.y;

int diffPositionX = mPrevPoint.x - mFloatingTab.getPosition().x;
int diffPositionX = mGestureBlackBox.getDiffX();
if (diffPositionX > 0) {
tabHorizontalPositionPercent = 0f;
} else {
Expand Down Expand Up @@ -409,14 +372,15 @@ void moveFloatingTabTo(View floatingTab, @NonNull Point position) {
mHoverView.mScreen.getExitView().showExitAnimation();
}
mFloatingTab.moveCenterTo(position);
mPrevPoint = mCurrPoint;
mCurrPoint = position;
mGestureBlackBox.addGesturePoint(position);
}

protected void activateDragger() {
ArrayList<Pair<? extends HoverFrameLayout, ? extends BaseTouchController.TouchListener>> list = new ArrayList<>();
list.add(new Pair<>(mFloatingTab, mFloatingTabDragListener));
mHoverView.mDragger.activate(list);
if (mHoverView != null && mHoverView.mDragger != null) {
ArrayList<Pair<? extends HoverFrameLayout, ? extends BaseTouchController.TouchListener>> list = new ArrayList<>();
list.add(new Pair<>(mFloatingTab, mFloatingTabDragListener));
mHoverView.mDragger.activate(list);
}
}

protected void deactivateDragger() {
Expand Down Expand Up @@ -501,4 +465,121 @@ public void onTouchDown(FloatingTab floatingTab) {
public void onTouchUp(FloatingTab floatingTab) {
}
}

class GestureBlackBox {
private final long mMaxMeasureTimeGap = 100L;
private final int mMaxArraySize = 100;
GesturePoint mFirstPoint = null;
GesturePoint mSecondPoint = null;

ArrayList<GesturePoint> mGesturePoints = new ArrayList<>();

private void addGesturePoint(Point point) {
if (mGesturePoints.size() >= mMaxArraySize) {
mGesturePoints.remove(0);
}
mGesturePoints.add(new GesturePoint(point, System.currentTimeMillis()));
}

private boolean updatePoints() {
if (mGesturePoints.size() < 2) {
return false;
}

mFirstPoint = mGesturePoints.get(mGesturePoints.size() - 1);
mSecondPoint = mGesturePoints.get(mGesturePoints.size() - 2);
for (int i = mGesturePoints.size() - 2; i >= 0; i--) {
GesturePoint tempPoint = mGesturePoints.get(i);
if (mFirstPoint.mPointMillis - tempPoint.mPointMillis <= mMaxMeasureTimeGap) {
mSecondPoint = tempPoint;
} else {
break;
}
}
return true;
}

private double getDistance() {
if (!updatePoints()) {
return 0;
}
return calculateDistance(mFirstPoint.mPoint, mSecondPoint.mPoint);
}

private int getDiffX() {
if (!updatePoints()) {
return 0;
}
return mSecondPoint.mPoint.x - mFirstPoint.mPoint.x;
}

private float getTargetYPosition() {
if (!updatePoints()) {
return 0;
}

return getTargetYPosition(mSecondPoint.mPoint, mFirstPoint.mPoint);
}

/**
* Get target Y position from 2 points
*
* @param point1 first mPoint
* @param point2 second mPoint
* @return targetPoint
*/
private float getTargetYPosition(@NonNull Point point1, @NonNull Point point2) {
// STEP 1: get liner line equation from 2 points (ax + by = c)
float a = point2.y - point1.y;
float b = point1.x - point2.x;
float c = a * (point1.x) + b * (point1.y);

// STEP 2: get x direction of the line
int xDirection = POSITIVE;
if (point1.x - point2.x >= 0) {
xDirection = NEGATIVE;
}

// To avoid divide by zero exception
if (b == 0) {
b = 1;
}

// STEP 3: return target Y position ( y = (c - ax) / b)
if (xDirection == NEGATIVE) {
return c / b;
} else {
return (c - a * mHoverView.getScreenSize().x) / b;
}
}

private double getSpeed() {
if (!updatePoints()) {
return 0;
}
return getDistance() / (mFirstPoint.mPointMillis - mSecondPoint.mPointMillis);
}

private void clear() {
mGesturePoints.clear();
mFirstPoint = null;
mSecondPoint = null;
}

private double calculateDistance(@NonNull Point point1, @NonNull Point point2) {
return Math.sqrt(
Math.pow(point2.x - point1.x, 2) + Math.pow(point2.y - point1.y, 2)
);
}

class GesturePoint {
private Point mPoint;
private long mPointMillis;

GesturePoint(Point point, long pointMillis) {
this.mPoint = point;
this.mPointMillis = pointMillis;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,12 @@ protected void onClose(final boolean userDropped) {

@Override
protected void activateDragger() {
ArrayList<Pair<? extends HoverFrameLayout, ? extends BaseTouchController.TouchListener>> list = new ArrayList<>();
list.add(new Pair<>(mFloatingTab, mFloatingTabDragListener));
list.add(new Pair<>(mMessageView, mDefaultMessageViewDragListener));
mHoverView.mDragger.activate(list);
if (mHoverView != null && mHoverView.mDragger != null) {
ArrayList<Pair<? extends HoverFrameLayout, ? extends BaseTouchController.TouchListener>> list = new ArrayList<>();
list.add(new Pair<>(mFloatingTab, mFloatingTabDragListener));
list.add(new Pair<>(mMessageView, mDefaultMessageViewDragListener));
mHoverView.mDragger.activate(list);
}
}

@Override
Expand Down