diff --git a/build.gradle b/build.gradle index a8a3253..b607207 100644 --- a/build.gradle +++ b/build.gradle @@ -2,23 +2,21 @@ buildscript { repositories { + google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.0-alpha4' - - // For Bintray publishing - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.2' - classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' + classpath 'com.android.tools.build:gradle:3.2.1' } } +plugins { + id "com.jfrog.bintray" version "1.8.0" +} + allprojects { repositories { + google() jcenter() } } - -task clean(type: Delete) { - delete rootProject.buildDir -} diff --git a/gradle.properties b/gradle.properties index aaf2362..4bced23 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,6 +17,5 @@ # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true compileSdkVersion = 26 -targetSdkVersion = 25 -buildToolsVersion = 25.0.3 -supportLibVersion = 24.2.0 \ No newline at end of file +targetSdkVersion = 26 +supportLibVersion = 26.1.0 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5027f0d..f09db74 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Jun 24 21:38:23 PDT 2017 +#Tue Nov 13 00:07:04 KST 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-rc-1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip diff --git a/hover/build.gradle b/hover/build.gradle index 988f291..86b8441 100644 --- a/hover/build.gradle +++ b/hover/build.gradle @@ -1,11 +1,11 @@ apply plugin: 'com.android.library' apply plugin: 'checkstyle' -version = '0.9.8' +project.group = 'com.buzzvil' +project.version = '1.0.1' android { compileSdkVersion project.compileSdkVersion.toInteger() - buildToolsVersion project.buildToolsVersion defaultConfig { minSdkVersion 15 @@ -21,52 +21,13 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - compile "com.android.support:appcompat-v7:${supportLibVersion}" - compile "com.android.support:recyclerview-v7:${supportLibVersion}" // Used to get DiffUtil + implementation fileTree(dir: 'libs', include: ['*.jar']) + api ("androidx.appcompat:appcompat:$androidXAppcompatVersion") + api ("androidx.recyclerview:recyclerview:$androidXRecyclerviewVersion") - testCompile 'junit:junit:4.12' + testImplementation "junit:junit:$junitVersion" } -// For Bintray publishing -ext { - bintrayRepo = 'maven' - bintrayName = 'hover' - - publishedGroupId = 'io.mattcarroll.hover' - artifact = 'hover' - libraryName = 'Hover' - - libraryDescription = 'An Android implementation of a floating menu.' - - siteUrl = 'http://google.github.io/hover/' - gitUrl = 'https://github.com/google/hover.git' - - libraryVersion = version - - developerId = 'matthew-carroll' - developerName = 'Matt Carroll' - developerEmail = 'me@mattcarroll.io' - - licenseName = 'The Apache Software License, Version 2.0' - licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' - allLicenses = ["Apache-2.0"] -} - -apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle' -apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle' - -// The following is used to add our dependencies to the classpath for Javadoc linking. -afterEvaluate { - javadoc.classpath += files(android.libraryVariants.collect { variant -> - variant.javaCompile.classpath.files - }) -} - -// This disabled Javadoc tasks because its breaking the release build. Since Javadocs are -// not mission critical, they will remain disabled until the issue can be debugged. -tasks.withType(Javadoc).all { enabled = false } - //------ Checkstyle ------- task checkstyle(type: Checkstyle) { showViolations = true @@ -86,4 +47,14 @@ project.afterEvaluate { preBuild.dependsOn('checkstyle') assemble.dependsOn('lint') check.dependsOn('checkstyle') -} \ No newline at end of file +} + +ext { + pName = 'hover' + pDescription = 'Buzzvil hover Android SDK' + pPublisherVcsUrl = 'https://github.com/Buzzvil/hover.git' + pGroup = project.group + pVersion = project.version +} + +apply from: "$rootDir/gradle/publish.gradle" \ No newline at end of file diff --git a/hover/src/main/java/io/mattcarroll/hover/BaseHoverViewState.java b/hover/src/main/java/io/mattcarroll/hover/BaseHoverViewState.java index 770d4d1..625b5fc 100644 --- a/hover/src/main/java/io/mattcarroll/hover/BaseHoverViewState.java +++ b/hover/src/main/java/io/mattcarroll/hover/BaseHoverViewState.java @@ -15,64 +15,43 @@ */ package io.mattcarroll.hover; -import android.support.annotation.NonNull; -import android.view.WindowManager; +import androidx.annotation.CallSuper; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; /** * {@link HoverViewState} that includes behavior common to all implementations. */ abstract class BaseHoverViewState implements HoverViewState { - private HoverView mHoverView; + private boolean mHasControl = false; + protected HoverView mHoverView; + @CallSuper @Override - public void takeControl(@NonNull HoverView hoverView) { - mHoverView = hoverView; - } - - // Only call this if using HoverMenuView directly in a window. - @Override - public void addToWindow() { - if (!mHoverView.mIsAddedToWindow) { - mHoverView.mWindowViewController.addView( - WindowManager.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.MATCH_PARENT, - false, - mHoverView - ); - - mHoverView.mIsAddedToWindow = true; - - if (mHoverView.mIsTouchableInWindow) { - mHoverView.makeTouchableInWindow(); - } else { - mHoverView.makeUntouchableInWindow(); - } + public void takeControl(@NonNull HoverView hoverView, Runnable onStateChanged) { + if (mHasControl) { + throw new RuntimeException("Cannot take control of a FloatingTab when we already control one."); } + mHasControl = true; + mHoverView = hoverView; } - // Only call this if using HoverMenuView directly in a window. + @CallSuper @Override - public void removeFromWindow() { - if (mHoverView.mIsAddedToWindow) { - mHoverView.mWindowViewController.removeView(mHoverView); - mHoverView.mIsAddedToWindow = false; + public void giveUpControl(@NonNull HoverViewState nextState) { + if (!mHasControl) { + throw new RuntimeException("Cannot give up control of a FloatingTab when we don't have the control"); } + mHasControl = false; + mHoverView = null; } - @Override - public void makeTouchableInWindow() { - mHoverView.mIsTouchableInWindow = true; - if (mHoverView.mIsAddedToWindow) { - mHoverView.mWindowViewController.makeTouchable(mHoverView); - } + protected final boolean hasControl() { + return mHasControl; } @Override - public void makeUntouchableInWindow() { - mHoverView.mIsTouchableInWindow = false; - if (mHoverView.mIsAddedToWindow) { - mHoverView.mWindowViewController.makeUntouchable(mHoverView); - } + public void setMenu(@Nullable HoverMenu menu) { } } diff --git a/hover/src/main/java/io/mattcarroll/hover/BaseTouchController.java b/hover/src/main/java/io/mattcarroll/hover/BaseTouchController.java new file mode 100644 index 0000000..1263983 --- /dev/null +++ b/hover/src/main/java/io/mattcarroll/hover/BaseTouchController.java @@ -0,0 +1,166 @@ +package io.mattcarroll.hover; + +import android.graphics.PointF; +import android.graphics.Rect; +import androidx.annotation.NonNull; +import androidx.core.util.Pair; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public abstract class BaseTouchController { + private static final String TAG = "BaseTouchController"; + + protected Map mTouchViewMap = new HashMap<>(); + protected boolean mIsActivated; + private boolean mIsDebugMode; + + private final HoverFrameLayout.OnPositionChangeListener mOnPositionChangeListener = new HoverFrameLayout.OnPositionChangeListener() { + @Override + public void onPositionChange(@NonNull View view) { + moveTouchViewTo(mTouchViewMap.get(view.getTag()).mTouchView, new PointF(view.getX(), view.getY())); + } + }; + + public abstract View createTouchView(@NonNull Rect rect); + + public abstract void destroyTouchView(@NonNull View touchView); + + public abstract void moveTouchViewTo(@NonNull View touchView, @NonNull PointF position); + + public void activate(final List> viewList) { + if (!mIsActivated) { + Log.d(TAG, "Activating."); + mIsActivated = true; + + clearTouchViewMap(); + for (int i = 0; i < viewList.size(); i++) { + final Pair viewItem = viewList.get(i); + final String tag = "view" + i; + final TouchViewItem touchViewItem = createTouchViewItem(viewItem.first, viewItem.second, tag); + mTouchViewMap.put(tag, touchViewItem); + } + updateTouchControlViewAppearance(); + } + } + + public void deactivate() { + if (mIsActivated) { + Log.d(TAG, "Deactivating."); + clearTouchViewMap(); + mIsActivated = false; + } + } + + public void enableDebugMode(boolean isDebugMode) { + mIsDebugMode = isDebugMode; + updateTouchControlViewAppearance(); + } + + private , V extends HoverFrameLayout> TouchViewItem createTouchViewItem(final V originalView, final T listener, final String tag) { + return new TouchViewItem<>(originalView, createTouchViewFrom(originalView), listener, tag); + } + + protected , V extends View> TouchDetector createTouchDetector(final V originalView, final T touchListener) { + return new TouchDetector<>(originalView, touchListener); + } + + private void clearTouchViewMap() { + for (final TouchViewItem touchViewItem : mTouchViewMap.values()) { + touchViewItem.destroy(); + } + mTouchViewMap.clear(); + } + + private void updateTouchControlViewAppearance() { + for (final TouchViewItem touchViewItemItem : mTouchViewMap.values()) { + final View touchView = touchViewItemItem.mTouchView; + if (null != touchView) { + if (mIsDebugMode) { + touchView.setBackgroundColor(0x44FF0000); + } else { + touchView.setBackgroundColor(0x00000000); + } + } + } + } + + private Rect getRectFrom(final View view) { + final Rect rect = new Rect(); + view.getDrawingRect(rect); + return rect; + + } + + private View createTouchViewFrom(final View originalView) { + final View touchView = createTouchView(getRectFrom(originalView)); + moveTouchViewTo(touchView, new PointF(originalView.getX(), originalView.getY())); + return touchView; + } + + public interface TouchListener { + void onTap(V view); + + void onTouchDown(V view); + + void onTouchUp(V view); + } + + protected class TouchDetector, V extends View> implements View.OnTouchListener { + + @NonNull + protected final V mOriginalView; + @NonNull + protected final T mEventListener; + + TouchDetector(@NonNull final V originalView, @NonNull final T touchListener) { + this.mOriginalView = originalView; + this.mEventListener = touchListener; + } + + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + switch (motionEvent.getAction()) { + case MotionEvent.ACTION_DOWN: + Log.d(TAG, "ACTION_DOWN"); + mEventListener.onTouchDown(mOriginalView); + return true; + case MotionEvent.ACTION_UP: + Log.d(TAG, "ACTION_UP"); + mEventListener.onTouchUp(mOriginalView); + mEventListener.onTap(mOriginalView); + return true; + default: + return false; + } + } + } + + protected class TouchViewItem> { + final V mOriginalView; + final View mTouchView; + final T mTouchListener; + + TouchViewItem(final V originalView, final View touchView, final T touchListener, final String tag) { + this.mOriginalView = originalView; + this.mTouchView = touchView; + this.mTouchListener = touchListener; + + mOriginalView.setTag(tag); + mTouchView.setTag(tag); + + mTouchView.setOnTouchListener(createTouchDetector(mOriginalView, mTouchListener)); + mOriginalView.addOnPositionChangeListener(mOnPositionChangeListener); + } + + void destroy() { + mTouchView.setOnTouchListener(null); + mOriginalView.removeOnPositionChangeListener(mOnPositionChangeListener); + destroyTouchView(mTouchView); + } + } +} diff --git a/hover/src/main/java/io/mattcarroll/hover/Content.java b/hover/src/main/java/io/mattcarroll/hover/Content.java index 68a2d32..d9a9836 100755 --- a/hover/src/main/java/io/mattcarroll/hover/Content.java +++ b/hover/src/main/java/io/mattcarroll/hover/Content.java @@ -15,7 +15,7 @@ */ package io.mattcarroll.hover; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.view.View; /** diff --git a/hover/src/main/java/io/mattcarroll/hover/ContentDisplay.java b/hover/src/main/java/io/mattcarroll/hover/ContentDisplay.java index 222396b..997434c 100644 --- a/hover/src/main/java/io/mattcarroll/hover/ContentDisplay.java +++ b/hover/src/main/java/io/mattcarroll/hover/ContentDisplay.java @@ -19,9 +19,9 @@ import android.graphics.Color; import android.graphics.Point; import android.graphics.drawable.Drawable; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.content.ContextCompat; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -62,7 +62,8 @@ public void onGlobalLayout() { private final FloatingTab.OnPositionChangeListener mOnTabPositionChangeListener = new FloatingTab.OnPositionChangeListener() { @Override - public void onPositionChange(@NonNull Point position) { + public void onPositionChange(@NonNull View view) { + final Point position = new Point((int) view.getX() + (view.getWidth() / 2), (int) view.getY() + (view.getHeight() / 2)); Log.d(TAG, mSelectedTab + " tab moved to " + position); updateTabSelectorPosition(); @@ -71,11 +72,6 @@ public void onPositionChange(@NonNull Point position) { // We have received an affirmative position for the selected tab. Show tab selector. mTabSelectorView.setVisibility(VISIBLE); } - - @Override - public void onDockChange(@NonNull Point dock) { - // No-op. - } }; public ContentDisplay(@NonNull Context context) { diff --git a/hover/src/main/java/io/mattcarroll/hover/Dock.java b/hover/src/main/java/io/mattcarroll/hover/Dock.java index 24ca0bc..33ee584 100644 --- a/hover/src/main/java/io/mattcarroll/hover/Dock.java +++ b/hover/src/main/java/io/mattcarroll/hover/Dock.java @@ -16,7 +16,7 @@ package io.mattcarroll.hover; import android.graphics.Point; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; diff --git a/hover/src/main/java/io/mattcarroll/hover/Dragger.java b/hover/src/main/java/io/mattcarroll/hover/Dragger.java index cf36ae3..fdabc7b 100644 --- a/hover/src/main/java/io/mattcarroll/hover/Dragger.java +++ b/hover/src/main/java/io/mattcarroll/hover/Dragger.java @@ -16,66 +16,173 @@ package io.mattcarroll.hover; import android.graphics.Point; -import android.support.annotation.NonNull; +import android.graphics.PointF; +import androidx.annotation.NonNull; +import android.util.Log; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; /** * Reports user drag behavior on the screen to a {@link DragListener}. */ -public interface Dragger { +public abstract class Dragger extends BaseTouchController { + private static final String TAG = "Dragger"; - /** - * Starts reporting user drag behavior given a drag area represented by {@code controlBounds}. - * @param dragListener listener that receives information about drag behavior - * @param dragStartCenterPosition initial touch point to start dragging - */ - void activate(@NonNull DragListener dragListener, @NonNull Point dragStartCenterPosition); + private final int mTapTouchSlop; - /** - * Stops monitoring and reporting user drag behavior. - */ - void deactivate(); + public Dragger(int mTapTouchSlop) { + this.mTapTouchSlop = mTapTouchSlop; + } - /** - * Enable/Disable debug mode. In debug mode this Dragger will paint its touch area with a - * translucent color. - * @param debugMode true for debug mode, false otherwise - */ - void enableDebugMode(boolean debugMode); + public abstract PointF getTouchViewPosition(@NonNull View touchView); - interface DragListener { + public abstract Point getContainerSize(); - /** - * The user has pressed within the draggable area at the given position. - * @param x x-coordinate of the user's press (in the parent View's coordinate space) - * @param y y-coordiante of the user's press (in the parent View's coordinate space) - */ - void onPress(float x, float y); + @Override + protected , V extends View> TouchDetector createTouchDetector(final V originalView, final T touchListener) { + if (touchListener instanceof DragListener) { + return new DragDetector<>(originalView, (DragListener) touchListener); + } else { + return super.createTouchDetector(originalView, touchListener); + } + } + + private boolean isTouchWithinSlopOfOriginalTouch(float dx, float dy) { + double distance = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)); + Log.d(TAG, "Drag distance " + distance + " vs slop allowance " + mTapTouchSlop); + return distance < mTapTouchSlop; + } + + private PointF convertCornerToCenter(View touchView, @NonNull PointF cornerPosition) { + return new PointF( + cornerPosition.x + (touchView.getWidth() / 2f), + cornerPosition.y + (touchView.getHeight() / 2f) + ); + } + + private PointF convertCenterToCorner(View touchView, @NonNull PointF centerPosition) { + return new PointF( + centerPosition.x - (touchView.getWidth() / 2f), + centerPosition.y - (touchView.getHeight() / 2f) + ); + } + public interface DragListener extends TouchListener { /** * The user has begun dragging. - * @param x x-coordinate of the user's drag start (in the parent View's coordinate space) - * @param y y-coordiante of the user's drag start (in the parent View's coordinate space) + * + * @param view the view that is being dragged + * @param x x-coordinate of the user's drag start (in the parent View's coordinate space) + * @param y y-coordiante of the user's drag start (in the parent View's coordinate space) */ - void onDragStart(float x, float y); + void onDragStart(V view, float x, float y); /** * The user has dragged to the given coordinates. - * @param x x-coordinate of the user's drag (in the parent View's coordinate space) - * @param y y-coordiante of the user's drag (in the parent View's coordinate space) + * + * @param view the view that is being dragged + * @param x x-coordinate of the user's drag (in the parent View's coordinate space) + * @param y y-coordiante of the user's drag (in the parent View's coordinate space) */ - void onDragTo(float x, float y); + void onDragTo(V view, float x, float y); /** * The user has stopped touching the drag area. - * @param x x-coordinate of the user's release (in the parent View's coordinate space) - * @param y y-coordiante of the user's release (in the parent View's coordinate space) + * + * @param view the view that is being dragged + * @param x x-coordinate of the user's release (in the parent View's coordinate space) + * @param y y-coordiante of the user's release (in the parent View's coordinate space) */ - void onReleasedAt(float x, float y); + void onReleasedAt(V view, float x, float y); /** - * The user tapped the drag area (instead of dragging it). + * The drag is cancelled (ex: due to screen off). + * + * @param view the view that is being dragged */ - void onTap(); + void onDragCancel(V view); + } + + private class DragDetector, V extends View> extends TouchDetector { + + private final GestureDetector mGestureDetector; + private boolean mIsDragging; + private PointF mOriginalViewPosition = new PointF(); + private PointF mCurrentViewPosition = new PointF(); + private PointF mOriginalTouchPosition = new PointF(); + + public DragDetector(final V originalView, final T dragListener) { + super(originalView, dragListener); + mGestureDetector = new GestureDetector(null, new GestureDetector.SimpleOnGestureListener() { + public void onLongPress(final MotionEvent e) { + tryDragStart("LONG_PRESS"); + } + }); + } + + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + mGestureDetector.onTouchEvent(motionEvent); + switch (motionEvent.getAction()) { + case MotionEvent.ACTION_DOWN: + Log.d(TAG, "ACTION_DOWN"); + mIsDragging = false; + + mOriginalViewPosition = convertCornerToCenter(view, getTouchViewPosition(view)); + mCurrentViewPosition = new PointF(mOriginalViewPosition.x, mOriginalViewPosition.y); + mOriginalTouchPosition.set(motionEvent.getRawX(), motionEvent.getRawY()); + mEventListener.onTouchDown(mOriginalView); + return true; + case MotionEvent.ACTION_MOVE: + Log.d(TAG, "ACTION_MOVE. motionX: " + motionEvent.getRawX() + ", motionY: " + motionEvent.getRawY()); + float dragDeltaX = motionEvent.getRawX() - mOriginalTouchPosition.x; + float dragDeltaY = motionEvent.getRawY() - mOriginalTouchPosition.y; + mCurrentViewPosition = new PointF( + mOriginalViewPosition.x + dragDeltaX, + mOriginalViewPosition.y + dragDeltaY + ); + + if (mIsDragging || !isTouchWithinSlopOfOriginalTouch(dragDeltaX, dragDeltaY)) { + if (!tryDragStart("ACTION_MOVE")) { + mEventListener.onDragTo(mOriginalView, mCurrentViewPosition.x, mCurrentViewPosition.y); + } + } + + return true; + case MotionEvent.ACTION_UP: + Log.d(TAG, "ACTION_UP"); + mEventListener.onTouchUp(mOriginalView); + if (!mIsDragging) { + Log.d(TAG, "Reporting as a tap."); + mEventListener.onTap(mOriginalView); + } else { + Log.d(TAG, "Reporting as a drag release at: " + mCurrentViewPosition); + mEventListener.onReleasedAt(mOriginalView, mCurrentViewPosition.x, mCurrentViewPosition.y); + mIsDragging = false; + } + return true; + case MotionEvent.ACTION_CANCEL: + Log.d(TAG, "ACTION_CANCEL"); + if (mIsDragging) { + mEventListener.onDragCancel(mOriginalView); + } + mIsDragging = false; + return true; + default: + return false; + } + } + private boolean tryDragStart(final String reason) { + if (mIsDragging) { + return false; + } + // Dragging is just started by reason + Log.d(TAG, "" + reason + " starts drag."); + mIsDragging = true; + mEventListener.onDragStart(mOriginalView, mCurrentViewPosition.x, mCurrentViewPosition.y); + return true; + } } } diff --git a/hover/src/main/java/io/mattcarroll/hover/ExitView.java b/hover/src/main/java/io/mattcarroll/hover/ExitView.java index 6e79170..5032302 100644 --- a/hover/src/main/java/io/mattcarroll/hover/ExitView.java +++ b/hover/src/main/java/io/mattcarroll/hover/ExitView.java @@ -15,16 +15,23 @@ */ package io.mattcarroll.hover; +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; import android.content.Context; import android.graphics.Point; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import android.graphics.Rect; import android.util.AttributeSet; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Interpolator; import android.widget.RelativeLayout; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.view.animation.PathInterpolatorCompat; + /** * Fullscreen View that provides an exit "drop zone" for users to exit the Hover Menu. */ @@ -32,8 +39,28 @@ class ExitView extends RelativeLayout { private static final String TAG = "ExitView"; + private static final int FADE_DURATION = 250; + private static final int SHOW_HIDE_DURATION = 250; + private static final float EXIT_ICON_DEFAULT_SCALE_X = 1.0f; + private static final float EXIT_ICON_DEFAULT_SCALE_Y = 1.0f; + private static final float EXIT_ICON_TARGET_SCALE_X = 1.2f; + private static final float EXIT_ICON_TARGET_SCALE_Y = 1.2f; + private static final float EXIT_ICON_DEFAULT_ROTATION = 0f; + private static final float EXIT_ICON_TARGET_ROTATION = 90f; + private static final float EXIT_ICON_DEFAULT_ALPHA = 0.6f; + private static final float EXIT_ICON_TARGET_ALPHA = 0.75f; + private static final float EXIT_VIEW_DEFAULT_ALPHA = 0f; + private static final float EXIT_VIEW_TARGET_ALPHA = 1.0f; + private static final float EXIT_VIEW_DEFAULT_Y = 800f; + private static final float EXIT_VIEW_TARGET_Y = 0f; + private int mExitRadiusInPx; private View mExitIcon; + private View mExitGradient; + private ViewGroup mVgExit; + private ObjectAnimator mShowEnterAnimation = null; + private ObjectAnimator mShowExitAnimation = null; + private boolean mIsShowing = false; public ExitView(@NonNull Context context) { this(context, null); @@ -48,27 +75,155 @@ private void init() { LayoutInflater.from(getContext()).inflate(R.layout.view_hover_menu_exit, this, true); mExitIcon = findViewById(R.id.view_exit); - + mVgExit = findViewById(R.id.vg_exit); + mExitGradient = findViewById(R.id.view_exit_gradient); mExitRadiusInPx = getResources().getDimensionPixelSize(R.dimen.hover_exit_radius); + mExitIcon.setAlpha(EXIT_ICON_DEFAULT_ALPHA); + + setAnimations(); } - public boolean isInExitZone(@NonNull Point position) { - Point exitCenter = getExitZoneCenter(); - double distanceToExit = calculateDistance(position, exitCenter); - Log.d(TAG, "Drop point: " + position + ", Exit center: " + exitCenter + ", Distance: " + distanceToExit); - return distanceToExit <= mExitRadiusInPx; + private Interpolator getExitViewInterpolator() { + return PathInterpolatorCompat.create(0.75f, 0f, 0.25f, 1f); } - private Point getExitZoneCenter() { - return new Point( - (int) (mExitIcon.getX() + (mExitIcon.getWidth() / 2)), - (int) (mExitIcon.getY() + (mExitIcon.getHeight() / 2)) - ); + private void setAnimations() { + PropertyValuesHolder showEnterAnimationScaleX = PropertyValuesHolder.ofFloat("scaleX", EXIT_ICON_DEFAULT_SCALE_X, EXIT_ICON_TARGET_SCALE_X); + PropertyValuesHolder showEnterAnimationScaleY = PropertyValuesHolder.ofFloat("scaleY", EXIT_ICON_DEFAULT_SCALE_Y, EXIT_ICON_TARGET_SCALE_Y); + PropertyValuesHolder showEnterAnimationRotate = PropertyValuesHolder.ofFloat("rotation", EXIT_ICON_DEFAULT_ROTATION, EXIT_ICON_TARGET_ROTATION); + PropertyValuesHolder showEnterAnimationAlpha = PropertyValuesHolder.ofFloat("alpha", EXIT_ICON_DEFAULT_ALPHA, EXIT_ICON_TARGET_ALPHA); + mShowEnterAnimation = ObjectAnimator.ofPropertyValuesHolder(mExitIcon, showEnterAnimationScaleX, showEnterAnimationScaleY, showEnterAnimationRotate, showEnterAnimationAlpha); + mShowEnterAnimation.setDuration(SHOW_HIDE_DURATION); + mShowEnterAnimation.setInterpolator(getExitViewInterpolator()); + mShowEnterAnimation.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animator) { + initExitIconViewStatus(); + } + + @Override + public void onAnimationEnd(Animator animator) { + } + + @Override + public void onAnimationCancel(Animator animator) { + initExitIconViewStatus(); + } + + @Override + public void onAnimationRepeat(Animator animator) { + } + }); + + PropertyValuesHolder showExitAnimationScaleX = PropertyValuesHolder.ofFloat("scaleX", EXIT_ICON_TARGET_SCALE_X, EXIT_ICON_DEFAULT_SCALE_X); + PropertyValuesHolder showExitAnimationScaleY = PropertyValuesHolder.ofFloat("scaleY", EXIT_ICON_TARGET_SCALE_Y, EXIT_ICON_DEFAULT_SCALE_Y); + PropertyValuesHolder showExitAnimationRotate = PropertyValuesHolder.ofFloat("rotation", EXIT_ICON_TARGET_ROTATION, EXIT_ICON_DEFAULT_ROTATION); + PropertyValuesHolder showExitAnimationAlpha = PropertyValuesHolder.ofFloat("alpha", EXIT_ICON_TARGET_ALPHA, EXIT_ICON_DEFAULT_ALPHA); + mShowExitAnimation = ObjectAnimator.ofPropertyValuesHolder(mExitIcon, showExitAnimationScaleX, showExitAnimationScaleY, showExitAnimationRotate, showExitAnimationAlpha); + mShowExitAnimation.setDuration(SHOW_HIDE_DURATION); + mShowExitAnimation.setInterpolator(getExitViewInterpolator()); } - private double calculateDistance(@NonNull Point p1, @NonNull Point p2) { - return Math.sqrt( - Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2) + private void initExitIconViewStatus() { + mExitIcon.setScaleY(EXIT_ICON_DEFAULT_SCALE_Y); + mExitIcon.setScaleX(EXIT_ICON_DEFAULT_SCALE_X); + mExitIcon.setRotation(EXIT_ICON_DEFAULT_ROTATION); + } + + 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(), + screenSize.y * 4 / 6, + screenSize.x + mExitIcon.getWidth(), + screenSize.y + mExitIcon.getHeight() + ); + + Rect excludedXExitAreaLeft = new Rect( + 0 - mExitIcon.getWidth(), + screenSize.y * 4 / 6, + exitXExcludeThresholdLeft, + screenSize.y - (mExitIcon.getHeight() / 2 + safeMargin) ); + + Rect excludedXExitAreaRight = new Rect( + exitXExcludeThresholdRight, + screenSize.y * 4 / 6, + screenSize.x + mExitIcon.getWidth(), + screenSize.y - (mExitIcon.getHeight() / 2 + safeMargin) + ); + + return exitArea.contains(position.x, position.y) + && !excludedXExitAreaLeft.contains(position.x, position.y) + && !excludedXExitAreaRight.contains(position.x, position.y); + } + + public void showEnterAnimation() { + if (mShowEnterAnimation != null && !mShowEnterAnimation.isRunning() && !mIsShowing) { + mShowEnterAnimation.start(); + mIsShowing = true; + } + } + + public void showExitAnimation() { + if (mShowExitAnimation != null && !mShowExitAnimation.isRunning() && mIsShowing) { + mShowExitAnimation.start(); + mIsShowing = false; + } + } + + public void show() { + resetExitButtonAnimation(); + + ObjectAnimator exitGradientAnimator = ObjectAnimator.ofFloat(mExitGradient, "alpha", EXIT_VIEW_TARGET_ALPHA); + exitGradientAnimator.setDuration(FADE_DURATION); + exitGradientAnimator.setInterpolator(getExitViewInterpolator()); + exitGradientAnimator.start(); + + ObjectAnimator vgExitAnimator = ObjectAnimator.ofFloat(mVgExit, "y", EXIT_VIEW_DEFAULT_Y, EXIT_VIEW_TARGET_Y); + vgExitAnimator.setDuration(FADE_DURATION); + vgExitAnimator.setInterpolator(getExitViewInterpolator()); + vgExitAnimator.start(); + + setVisibility(VISIBLE); + } + + public void resetExitButtonAnimation() { + mIsShowing = false; + initExitIconViewStatus(); + } + + public void hide() { + ObjectAnimator vgExitAnimator = ObjectAnimator.ofFloat(mVgExit, "y", EXIT_VIEW_TARGET_Y, EXIT_VIEW_DEFAULT_Y); + vgExitAnimator.setDuration(FADE_DURATION); + vgExitAnimator.setInterpolator(getExitViewInterpolator()); + vgExitAnimator.start(); + + ObjectAnimator exitGradientAnimator = ObjectAnimator.ofFloat(mExitGradient, "alpha", EXIT_VIEW_DEFAULT_ALPHA); + exitGradientAnimator.setDuration(FADE_DURATION); + exitGradientAnimator.setInterpolator(getExitViewInterpolator()); + exitGradientAnimator.start(); + + exitGradientAnimator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + setVisibility(GONE); + } + + @Override + public void onAnimationCancel(Animator animation) { + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + }); } } diff --git a/hover/src/main/java/io/mattcarroll/hover/FloatingTab.java b/hover/src/main/java/io/mattcarroll/hover/FloatingTab.java index 53a8502..628499c 100644 --- a/hover/src/main/java/io/mattcarroll/hover/FloatingTab.java +++ b/hover/src/main/java/io/mattcarroll/hover/FloatingTab.java @@ -20,52 +20,47 @@ import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; +import android.content.res.Configuration; import android.graphics.Color; import android.graphics.Point; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.view.animation.OvershootInterpolator; import android.widget.FrameLayout; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArraySet; - /** * {@code FloatingTab} is the cornerstone of a {@link HoverView}. When a {@code HoverView} is * collapsed, it is reduced to a single {@code FloatingTab} that the user can drag and drop. When * a {@code HoverView} is expanded, that one {@code FloatingTab} slides to a row of tabs that appear * and offer a menu system. - * + *

* A {@code FloatingTab} can move around the screen in various ways. A {@code FloatingTab} can place * itself at a "dock position", or slide from its current position to its "dock position", or * position itself at an arbitrary location on screen. - * + *

* {@code FloatingTab}s position themselves based on their center. */ -class FloatingTab extends FrameLayout { +class FloatingTab extends HoverFrameLayout { private static final String TAG = "FloatingTab"; + private static final int APPEARING_ANIMATION_DURATION = 300; private final String mId; private int mTabSize; private View mTabView; private Dock mDock; - private final Set mOnPositionChangeListeners = new CopyOnWriteArraySet<>(); - - private final OnLayoutChangeListener mOnLayoutChangeListener = new OnLayoutChangeListener() { - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { - notifyListenersOfPositionChange(); - } - }; + private AnimatorSet mAnimatorSetDisappear; + private AnimatorSet mAnimatorSetAppear; public FloatingTab(@NonNull Context context, @NonNull String tabId) { super(context); mId = tabId; mTabSize = getResources().getDimensionPixelSize(R.dimen.hover_tab_size); + setClipChildren(false); + setClipToPadding(false); int padding = getResources().getDimensionPixelSize(R.dimen.hover_tab_margin); setPadding(padding, padding, padding, padding); @@ -74,20 +69,28 @@ public FloatingTab(@NonNull Context context, @NonNull String tabId) { @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); + updateSize(); + } + private void updateSize() { // Make this View the desired size. - ViewGroup.LayoutParams layoutParams = getLayoutParams(); + final ViewGroup.LayoutParams layoutParams = getLayoutParams(); layoutParams.width = mTabSize; layoutParams.height = mTabSize; setLayoutParams(layoutParams); + } - addOnLayoutChangeListener(mOnLayoutChangeListener); + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + if (mDock != null) { + moveCenterTo(mDock.position()); + } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - removeOnLayoutChangeListener(mOnLayoutChangeListener); } public void enableDebugMode(boolean debugMode) { @@ -99,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); - scaleX.setDuration(250); ObjectAnimator scaleY = ObjectAnimator.ofFloat(this, "scaleY", 0.0f, 1.0f); - scaleY.setDuration(250); - 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) { } @@ -132,21 +136,25 @@ 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); - scaleX.setDuration(250); ObjectAnimator scaleY = ObjectAnimator.ofFloat(this, "scaleY", 0.0f); - scaleY.setDuration(250); - 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) { } + public void onAnimationStart(Animator animation) { + } @Override public void onAnimationEnd(Animator animation) { @@ -158,17 +166,47 @@ public void onAnimationEnd(Animator animation) { } @Override - public void onAnimationCancel(Animator animation) { } + public void onAnimationCancel(Animator animation) { + } @Override - public void onAnimationRepeat(Animator animation) { } + 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(); + setPadding(0, 0, 0, 0); + } + + public void expand() { + mTabSize = getResources().getDimensionPixelSize(R.dimen.hover_tab_size); + updateSize(); + int padding = getResources().getDimensionPixelSize(R.dimen.hover_tab_margin); + setPadding(padding, padding, padding, padding); + } + @NonNull public String getTabId() { return mId; @@ -235,39 +273,79 @@ public void dock(@Nullable final Runnable onDocked) { animatorSet.addListener(new Animator.AnimatorListener() { @Override - public void onAnimationStart(Animator animation) { } + public void onAnimationStart(Animator animation) { + } @Override public void onAnimationEnd(Animator animation) { if (null != onDocked) { onDocked.run(); } - notifyListenersOfPositionChange(); + notifyListenersOfPositionChange(FloatingTab.this); } @Override - public void onAnimationCancel(Animator animation) { } + public void onAnimationCancel(Animator animation) { + } @Override - public void onAnimationRepeat(Animator animation) { } + public void onAnimationRepeat(Animator animation) { + } }); xAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { - notifyListenersOfPositionChange(); + notifyListenersOfPositionChange(FloatingTab.this); + } + }); + } + + public void closeAnimation(Point targetPosition, @Nullable final Runnable onDocked) { + Point destinationCornerPosition = convertCenterToCorner(targetPosition); + Log.d(TAG, "Docking to destination point: " + destinationCornerPosition); + + ObjectAnimator xAnimation = ObjectAnimator.ofFloat(this, "x", targetPosition.x); + xAnimation.setDuration(500); + xAnimation.setInterpolator(new OvershootInterpolator()); + ObjectAnimator yAnimation = ObjectAnimator.ofFloat(this, "y", targetPosition.y); + yAnimation.setDuration(500); + yAnimation.setInterpolator(new OvershootInterpolator()); + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.play(xAnimation).with(yAnimation); + animatorSet.start(); + + animatorSet.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + if (null != onDocked) { + onDocked.run(); + } + } + + @Override + public void onAnimationCancel(Animator animation) { + } + + @Override + public void onAnimationRepeat(Animator animation) { } }); } public void dockImmediately() { - moveTo(mDock.position()); + moveCenterTo(mDock.position()); } - public void moveTo(@NonNull Point floatPosition) { - Point cornerPosition = convertCenterToCorner(floatPosition); + public void moveCenterTo(@NonNull Point centerPosition) { + Point cornerPosition = convertCenterToCorner(centerPosition); setX(cornerPosition.x); setY(cornerPosition.y); + notifyListenersOfPositionChange(this); } private Point convertCenterToCorner(@NonNull Point centerPosition) { @@ -277,24 +355,11 @@ private Point convertCenterToCorner(@NonNull Point centerPosition) { ); } - public void addOnPositionChangeListener(@Nullable OnPositionChangeListener listener) { - mOnPositionChangeListeners.add(listener); - } - - public void removeOnPositionChangeListener(@NonNull OnPositionChangeListener listener) { - mOnPositionChangeListeners.remove(listener); - } - - private void notifyListenersOfPositionChange() { - Point position = getPosition(); - for (OnPositionChangeListener listener : mOnPositionChangeListeners) { - listener.onPositionChange(position); - } - } - private void notifyListenersOfDockChange() { for (OnPositionChangeListener listener : mOnPositionChangeListeners) { - listener.onDockChange(mDock.position()); + if (listener instanceof OnFloatingTabChangeListener) { + ((OnFloatingTabChangeListener) listener).onDockChange(mDock); + } } } @@ -304,9 +369,7 @@ public void setOnClickListener(@Nullable View.OnClickListener onClickListener) { super.setOnClickListener(onClickListener); } - public interface OnPositionChangeListener { - void onPositionChange(@NonNull Point tabPosition); - - void onDockChange(@NonNull Point dockPosition); + public interface OnFloatingTabChangeListener extends OnPositionChangeListener { + void onDockChange(@NonNull Dock dock); } } diff --git a/hover/src/main/java/io/mattcarroll/hover/HoverFrameLayout.java b/hover/src/main/java/io/mattcarroll/hover/HoverFrameLayout.java new file mode 100644 index 0000000..e7662ac --- /dev/null +++ b/hover/src/main/java/io/mattcarroll/hover/HoverFrameLayout.java @@ -0,0 +1,72 @@ +package io.mattcarroll.hover; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; + +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +class HoverFrameLayout extends FrameLayout { + + protected final Set mOnPositionChangeListeners = new CopyOnWriteArraySet<>(); + private final OnLayoutChangeListener mOnLayoutChangeListener = new OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { + notifyListenersOfPositionChange(v); + } + }; + + public HoverFrameLayout(@NonNull Context context) { + super(context); + } + + public HoverFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public HoverFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public HoverFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public void addOnPositionChangeListener(@Nullable OnPositionChangeListener listener) { + mOnPositionChangeListeners.add(listener); + } + + public void removeOnPositionChangeListener(@NonNull OnPositionChangeListener listener) { + mOnPositionChangeListeners.remove(listener); + } + + protected void notifyListenersOfPositionChange(final View view) { + for (OnPositionChangeListener listener : mOnPositionChangeListeners) { + listener.onPositionChange(view); + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + addOnLayoutChangeListener(mOnLayoutChangeListener); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + removeOnLayoutChangeListener(mOnLayoutChangeListener); + } + + interface OnPositionChangeListener { + void onPositionChange(@NonNull View view); + } +} + diff --git a/hover/src/main/java/io/mattcarroll/hover/HoverMenu.java b/hover/src/main/java/io/mattcarroll/hover/HoverMenu.java index aa51f38..54ce906 100644 --- a/hover/src/main/java/io/mattcarroll/hover/HoverMenu.java +++ b/hover/src/main/java/io/mattcarroll/hover/HoverMenu.java @@ -15,10 +15,10 @@ */ package io.mattcarroll.hover; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.util.DiffUtil; -import android.support.v7.util.ListUpdateCallback; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.ListUpdateCallback; import android.view.View; import java.util.ArrayList; @@ -26,7 +26,7 @@ /** * A {@code HoverMenu} models the structure of a menu that appears within a {@link HoverView}. - * + *

* A {@code HoverMenu} includes an ordered list of {@link Section}s. Each {@code Section} has a tab * {@code View} that represents the section, and the {@link Content} of the given section. */ @@ -112,11 +112,17 @@ public static class Section { private final SectionId mId; private final View mTabView; private final Content mContent; + private final View mTabMessageView; public Section(@NonNull SectionId id, @NonNull View tabView, @NonNull Content content) { + this(id, tabView, content, null); + } + + public Section(@NonNull SectionId id, @NonNull View tabView, @NonNull Content content, @Nullable View tabMessageView) { mId = id; mTabView = tabView; mContent = content; + mTabMessageView = tabMessageView; } @NonNull @@ -129,6 +135,11 @@ public View getTabView() { return mTabView; } + @Nullable + public View getTabMessageView() { + return mTabMessageView; + } + @NonNull public Content getContent() { return mContent; diff --git a/hover/src/main/java/io/mattcarroll/hover/HoverView.java b/hover/src/main/java/io/mattcarroll/hover/HoverView.java index b5d837d..8da4957 100644 --- a/hover/src/main/java/io/mattcarroll/hover/HoverView.java +++ b/hover/src/main/java/io/mattcarroll/hover/HoverView.java @@ -18,24 +18,27 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.res.TypedArray; +import android.graphics.Point; import android.os.Parcel; import android.os.Parcelable; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import android.util.AttributeSet; import android.util.Log; import android.view.KeyEvent; +import android.view.View; import android.view.ViewConfiguration; +import android.view.WindowManager; import android.widget.RelativeLayout; - -import java.util.Set; -import java.util.concurrent.CopyOnWriteArraySet; - import io.mattcarroll.hover.view.InViewDragger; import io.mattcarroll.hover.window.InWindowDragger; import io.mattcarroll.hover.window.WindowViewController; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + import static io.mattcarroll.hover.SideDock.SidePosition.LEFT; +import static io.mattcarroll.hover.SideDock.SidePosition.RIGHT; /** * {@code HoverMenuView} is a floating menu implementation. This implementation displays tabs along @@ -70,12 +73,10 @@ public static HoverView createForWindow(@NonNull Context context, @NonNull private static Dragger createWindowDragger(@NonNull Context context, @NonNull WindowViewController windowViewController) { - int touchDiameter = context.getResources().getDimensionPixelSize(R.dimen.hover_exit_radius); int slop = ViewConfiguration.get(context).getScaledTouchSlop(); return new InWindowDragger( context, windowViewController, - touchDiameter, slop ); } @@ -85,22 +86,28 @@ public static HoverView createForView(@NonNull Context context) { return new HoverView(context, null); } - final HoverViewState mClosed = new HoverViewStateClosed(); - final HoverViewState mCollapsed = new HoverViewStateCollapsed(); - final HoverViewState mExpanded = new HoverViewStateExpanded(); + private final HoverViewState mClosed = new HoverViewStateClosed(); + private final HoverViewState mCollapsed = new HoverViewStateCollapsed(); + private final HoverViewState mPreviewed = new HoverViewStatePreviewed(); + private final HoverViewState mExpanded = new HoverViewStateExpanded(); + private final HoverViewState mHidden = new HoverViewStateHidden(); final WindowViewController mWindowViewController; final Dragger mDragger; final Screen mScreen; - HoverViewState mState; + private HoverViewState mState; HoverMenu mMenu; HoverMenu.SectionId mSelectedSectionId; SideDock mCollapsedDock; + SideDock.SidePosition mInitialDockPosition; boolean mIsAddedToWindow; boolean mIsTouchableInWindow; boolean mIsDebugMode = false; int mTabSize; + private PositionDock mPositionToHide; OnExitListener mOnExitListener; - final Set mListeners = new CopyOnWriteArraySet<>(); + private final Set mOnStateChangeListeners = new CopyOnWriteArraySet<>(); + private final Set mOnFloatingTabInteractionListeners = new CopyOnWriteArraySet<>(); + private HoverViewIdleAction mIdleAction; // Public for use with XML inflation. Clients should use static methods for construction. public HoverView(@NonNull Context context, @Nullable AttributeSet attrs) { @@ -118,11 +125,9 @@ public HoverView(@NonNull Context context, @Nullable AttributeSet attrs) { @NonNull private Dragger createInViewDragger(@NonNull Context context) { - int touchDiameter = context.getResources().getDimensionPixelSize(R.dimen.hover_exit_radius); int slop = ViewConfiguration.get(context).getScaledTouchSlop(); return new InViewDragger( this, - touchDiameter, slop ); } @@ -135,6 +140,7 @@ private HoverView(@NonNull Context context, mDragger = dragger; mScreen = new Screen(this); mWindowViewController = windowViewController; + mInitialDockPosition = initialDockPosition; init(); @@ -173,7 +179,7 @@ private void init() { mTabSize = getResources().getDimensionPixelSize(R.dimen.hover_tab_size); restoreVisualState(); setFocusableInTouchMode(true); // For handling hardware back button presses. - setState(new HoverViewStateClosed()); + close(); } @Override @@ -232,11 +238,9 @@ void restoreVisualState() { persistentState.restore(this, mMenu); } - // TODO: when to call this? public void release() { Log.d(TAG, "Released."); mDragger.deactivate(); - // TODO: should we also release the screen? } public void enableDebugMode(boolean debugMode) { @@ -246,9 +250,35 @@ public void enableDebugMode(boolean debugMode) { mScreen.enableDrugMode(debugMode); } - void setState(@NonNull HoverViewState state) { - mState = state; - mState.takeControl(this); + public void setPositionToHide(Point position) { + if (position == null) { + this.mPositionToHide = null; + } else { + this.mPositionToHide = new PositionDock(position); + } + } + + @Nullable + public PositionDock getPositionToHide() { + return mPositionToHide; + } + + void setState(@NonNull HoverViewState newState, Runnable onStateChanged) { + if (mState != newState) { + if (mState != null) { + mState.giveUpControl(newState); + } + mState = newState; + mState.takeControl(this, onStateChanged); + } + } + + public HoverViewState getState() { + return mState; + } + + public void setTabMessageViewInteractionListener(@Nullable final OnTabMessageViewInteractionListener messageViewDragListener) { + ((HoverViewStatePreviewed) mPreviewed).setMessageViewDragListener(messageViewDragListener); } private void onBackPressed() { @@ -256,91 +286,184 @@ private void onBackPressed() { } public void setMenu(@Nullable HoverMenu menu) { + mMenu = menu; + // If the menu is null or empty then close the menu. + if (null == menu || menu.getSectionCount() == 0) { + close(); + return; + } + restoreVisualState(); + + if (null == mSelectedSectionId || null == mMenu.getSection(mSelectedSectionId)) { + mSelectedSectionId = mMenu.getSection(0).getId(); + } + final FloatingTab selectedTab = mScreen.getChainedTab(mSelectedSectionId); + if (selectedTab != null) { + selectedTab.setTabView(mMenu.getSection(mSelectedSectionId).getTabView()); + } mState.setMenu(menu); } + public void preview() { + setState(mPreviewed, new Runnable() { + @Override + public void run() { + for (OnStateChangeListener onStateChangeListener : mOnStateChangeListeners) { + onStateChangeListener.onPreviewed(); + } + } + }); + } + public void expand() { - mState.expand(); + setState(mExpanded, new Runnable() { + @Override + public void run() { + for (OnStateChangeListener onStateChangeListener : mOnStateChangeListeners) { + onStateChangeListener.onExpanded(); + } + } + }); } public void collapse() { - mState.collapse(); + setState(mCollapsed, new Runnable() { + @Override + public void run() { + for (OnStateChangeListener onStateChangeListener : mOnStateChangeListeners) { + onStateChangeListener.onCollapsed(); + } + } + }); } public void close() { - mState.close(); + setState(mClosed, new Runnable() { + @Override + public void run() { + for (OnStateChangeListener onStateChangeListener : mOnStateChangeListeners) { + onStateChangeListener.onClosed(); + } + } + }); } - public void setOnExitListener(@Nullable OnExitListener listener) { - mOnExitListener = listener; + public void hide() { + setState(mHidden, new Runnable() { + @Override + public void run() { + for (OnStateChangeListener onStateChangeListener : mOnStateChangeListeners) { + onStateChangeListener.onHidden(); + } + } + }); } - public void addOnExpandAndCollapseListener(@NonNull Listener listener) { - mListeners.add(listener); + public void setIdleAction(HoverViewIdleAction idleAction) { + this.mIdleAction = idleAction; } - public void removeOnExpandAndCollapseListener(@NonNull Listener listener) { - mListeners.remove(listener); + public HoverViewIdleAction getIdleAction() { + return mIdleAction; } - void notifyListenersExpanding() { - Log.d(TAG, "Notifying listeners that Hover is expanding."); - for (Listener listener : mListeners) { - listener.onExpanding(); - } + public void setOnExitListener(@Nullable OnExitListener listener) { + mOnExitListener = listener; } - void notifyListenersExpanded() { - Log.d(TAG, "Notifying listeners that Hover is now expanded."); - for (Listener listener : mListeners) { - listener.onExpanded(); - } + public void addOnStateChangeListener(@NonNull OnStateChangeListener onStateChangeListener) { + mOnStateChangeListeners.add(onStateChangeListener); } - void notifyListenersCollapsing() { - Log.d(TAG, "Notifying listeners that Hover is collapsing."); - for (Listener listener : mListeners) { - listener.onCollapsing(); - } + public void removeOnStateChangeListener(@NonNull OnStateChangeListener onStateChangeListener) { + mOnStateChangeListeners.remove(onStateChangeListener); + } + + public void addOnFloatingTabInteractionListener(@NonNull OnFloatingTabInteractionListener onFloatingTabInteractionListener) { + mOnFloatingTabInteractionListeners.add(onFloatingTabInteractionListener); } - void notifyListenersCollapsed() { - Log.d(TAG, "Notifying listeners that Hover is now collapsed."); - for (Listener listener : mListeners) { - listener.onCollapsed(); + public void removeOnFloatingTabInteractionListener(@NonNull OnFloatingTabInteractionListener onFloatingTabInteractionListener) { + mOnFloatingTabInteractionListeners.remove(onFloatingTabInteractionListener); + } + + void notifyOnTap(HoverViewState state) { + for (OnFloatingTabInteractionListener onFloatingTabInteractionListener : mOnFloatingTabInteractionListeners) { + onFloatingTabInteractionListener.onTap(state.getStateType()); } } - void notifyListenersClosing() { - Log.d(TAG, "Notifying listeners that Hover is closing."); - for (Listener listener : mListeners) { - listener.onClosing(); + void notifyOnDragStart(HoverViewState state) { + for (OnFloatingTabInteractionListener onFloatingTabInteractionListener : mOnFloatingTabInteractionListeners) { + onFloatingTabInteractionListener.onDragStart(state.getStateType()); } } - void notifyListenersClosed() { - Log.d(TAG, "Notifying listeners that Hover is closed."); - for (Listener listener : mListeners) { - listener.onClosed(); + void notifyOnDocked(HoverViewState state) { + for (OnFloatingTabInteractionListener onFloatingTabInteractionListener : mOnFloatingTabInteractionListeners) { + onFloatingTabInteractionListener.onDocked(state.getStateType()); } } // Only call this if using HoverMenuView directly in a window. public void addToWindow() { - mState.addToWindow(); + if (!mIsAddedToWindow) { + mWindowViewController.addView( + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.MATCH_PARENT, + false, + this + ); + + mIsAddedToWindow = true; + + if (mIsTouchableInWindow) { + makeTouchableInWindow(); + } else { + makeUntouchableInWindow(); + } + } } // Only call this if using HoverMenuView directly in a window. public void removeFromWindow() { - mState.removeFromWindow(); + if (mIsAddedToWindow) { + mWindowViewController.removeView(this); + mIsAddedToWindow = false; + release(); + } + } + + @Nullable + public TabMessageView getTabMessageView() { + if (mScreen == null) { + return null; + } + return mScreen.getTabMessageView(mSelectedSectionId); } void makeTouchableInWindow() { - mState.makeTouchableInWindow(); + mIsTouchableInWindow = true; + if (mIsAddedToWindow) { + mWindowViewController.makeTouchable(this); + } } void makeUntouchableInWindow() { - mState.makeUntouchableInWindow(); + mIsTouchableInWindow = false; + if (mIsAddedToWindow) { + mWindowViewController.makeUntouchable(this); + } + } + + public Point getScreenSize() { + if (mDragger == null) { + final Point screenSize = new Point(); + ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getSize(screenSize); + return screenSize; + } else { + return mDragger.getContainerSize(); + } } // State of the HoverMenuView that is persisted across configuration change and other brief OS @@ -453,7 +576,7 @@ private static class PersistentState { } public void restore(@NonNull HoverView hoverView, @NonNull HoverMenu menu) { - SideDock.SidePosition sidePosition = getSidePosition(menu.getId()); + SideDock.SidePosition sidePosition = getSidePosition(menu.getId(), hoverView.mInitialDockPosition); hoverView.mCollapsedDock = new SideDock( hoverView, hoverView.mTabSize, @@ -469,10 +592,10 @@ public void restore(@NonNull HoverView hoverView, @NonNull HoverMenu menu) { + ", Section ID: " + selectedSectionId); } - private SideDock.SidePosition getSidePosition(@NonNull String menuId) { + private SideDock.SidePosition getSidePosition(@NonNull String menuId, @Nullable SideDock.SidePosition initialDockPosition) { return new SideDock.SidePosition( - mPrefs.getInt(menuId + SAVED_STATE_DOCKS_SIDE, LEFT), - mPrefs.getFloat(menuId + SAVED_STATE_DOCK_POSITION, 0.5f) + mPrefs.getInt(menuId + SAVED_STATE_DOCKS_SIDE, initialDockPosition != null ? initialDockPosition.getSide() : RIGHT), + mPrefs.getFloat(menuId + SAVED_STATE_DOCK_POSITION, initialDockPosition != null ? initialDockPosition.getVerticalDockPositionPercentage() : 0.6f) ); } @@ -503,19 +626,54 @@ public void save(@NonNull HoverMenu menu, /** * Listener invoked when the corresponding transitions occur within a given {@link HoverView}. */ - public interface Listener { - - void onExpanding(); - + public interface OnStateChangeListener { void onExpanded(); - void onCollapsing(); - void onCollapsed(); - void onClosing(); + void onPreviewed(); void onClosed(); + void onHidden(); + } + + public static class DefaultOnStateChangeListener implements OnStateChangeListener { + @Override + public void onExpanded() { + } + + @Override + public void onCollapsed() { + } + + @Override + public void onPreviewed() { + } + + @Override + public void onClosed() { + } + + @Override + public void onHidden() { + } + } + + public interface OnFloatingTabInteractionListener { + void onTap(HoverViewStateType stateType); + + void onDragStart(HoverViewStateType stateType); + + void onDocked(HoverViewStateType stateType); + } + + public abstract static class OnTabMessageViewInteractionListener implements Dragger.DragListener { + } + + public interface HoverViewIdleAction { + void changeState(View iconView); + + void restoreState(View iconView); } } diff --git a/hover/src/main/java/io/mattcarroll/hover/HoverViewState.java b/hover/src/main/java/io/mattcarroll/hover/HoverViewState.java index e46ec7a..6edbb76 100644 --- a/hover/src/main/java/io/mattcarroll/hover/HoverViewState.java +++ b/hover/src/main/java/io/mattcarroll/hover/HoverViewState.java @@ -15,34 +15,22 @@ */ package io.mattcarroll.hover; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; /** * A state of a {@link HoverView}. {@code HoverView} is implemented with a state pattern and this * is the interface that is implemented by all such states. */ -interface HoverViewState { +public interface HoverViewState { /** * Activates this state. * @param hoverView hoverView + * @param onStateChanged Runnable to be run after state has changed */ - void takeControl(@NonNull HoverView hoverView); + void takeControl(@NonNull HoverView hoverView, Runnable onStateChanged); - /** - * Expands the HoverView. - */ - void expand(); - - /** - * Collapses the HoverView. - */ - void collapse(); - - /** - * Closes the HoverView (no menu or tabs are visible). - */ - void close(); + void giveUpControl(@NonNull HoverViewState nextState); /** * Displays the given {@code menu} within the HoverView. @@ -62,25 +50,5 @@ interface HoverViewState { */ void onBackPressed(); - /** - * Adds the HoverView to the Android device's Window. - */ - void addToWindow(); - - /** - * Removes the HoverView from the Android device's Window. - */ - void removeFromWindow(); - - /** - * Assuming that the HoverView is added to the Android device's Window, makes the HoverView - * touchable. - */ - void makeTouchableInWindow(); - - /** - * Assuming that the HoverView is added to the Android device's Window, makes the HoverView - * untouchable (touch events pass through the overlay to whatever is beneath). - */ - void makeUntouchableInWindow(); + HoverViewStateType getStateType(); } diff --git a/hover/src/main/java/io/mattcarroll/hover/HoverViewStateClosed.java b/hover/src/main/java/io/mattcarroll/hover/HoverViewStateClosed.java index 53cb899..1966d53 100644 --- a/hover/src/main/java/io/mattcarroll/hover/HoverViewStateClosed.java +++ b/hover/src/main/java/io/mattcarroll/hover/HoverViewStateClosed.java @@ -15,12 +15,9 @@ */ package io.mattcarroll.hover; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; import android.util.Log; -import static android.view.View.GONE; - /** * {@link HoverViewState} that operates the {@link HoverView} when it is closed. Closed means that * nothing is visible - no tabs, no content. From the user's perspective, there is no @@ -28,89 +25,50 @@ */ class HoverViewStateClosed extends BaseHoverViewState { - private static final String TAG = "HoverMenuViewStateClosed"; - - private HoverView mHoverView; + private static final String TAG = "HoverViewStateClosed"; @Override - public void takeControl(@NonNull HoverView hoverView) { + public void takeControl(@NonNull HoverView hoverView, final Runnable onStateChanged) { + super.takeControl(hoverView, onStateChanged); Log.d(TAG, "Taking control."); - super.takeControl(hoverView); - mHoverView = hoverView; - mHoverView.notifyListenersClosing(); - mHoverView.mState = this; + mHoverView.makeUntouchableInWindow(); mHoverView.clearFocus(); - mHoverView.mScreen.getContentDisplay().setVisibility(GONE); final FloatingTab selectedTab = mHoverView.mScreen.getChainedTab(mHoverView.mSelectedSectionId); if (null != selectedTab) { selectedTab.disappear(new Runnable() { @Override public void run() { + if (!hasControl()) { + return; + } mHoverView.mScreen.destroyChainedTab(selectedTab); - mHoverView.notifyListenersClosed(); + onStateChanged.run(); } }); } else { - mHoverView.notifyListenersClosed(); - } - - mHoverView.makeUntouchableInWindow(); - } - - private void changeState(@NonNull HoverViewState nextState) { - mHoverView.setState(nextState); - mHoverView = null; - } - - @Override - public void expand() { - if (null != mHoverView.mMenu) { - Log.d(TAG, "Expanding."); - changeState(mHoverView.mExpanded); - } else { - Log.d(TAG, "Asked to expand, but there is no menu set. Can't expand until a menu is available."); - } - } - - @Override - public void collapse() { - if (null != mHoverView.mMenu) { - Log.d(TAG, "Collapsing."); - changeState(mHoverView.mCollapsed); - } else { - Log.d(TAG, "Asked to collapse, but there is no menu set. Can't collapse until a menu is available."); + onStateChanged.run(); } } @Override - public void close() { - Log.d(TAG, "Instructed to close, but Hover is already closed."); + public boolean respondsToBackButton() { + return false; } @Override - public void setMenu(@Nullable final HoverMenu menu) { - mHoverView.mMenu = menu; - - // If the menu is null then there is nothing to restore. - if (null == menu) { - return; - } - - mHoverView.restoreVisualState(); - - if (null == mHoverView.mSelectedSectionId || null == mHoverView.mMenu.getSection(mHoverView.mSelectedSectionId)) { - mHoverView.mSelectedSectionId = mHoverView.mMenu.getSection(0).getId(); - } + public void onBackPressed() { + // No-op } @Override - public boolean respondsToBackButton() { - return false; + public void giveUpControl(@NonNull HoverViewState nextState) { + Log.d(TAG, "Giving up control."); + super.giveUpControl(nextState); } @Override - public void onBackPressed() { - // No-op + public HoverViewStateType getStateType() { + return HoverViewStateType.CLOSED; } } diff --git a/hover/src/main/java/io/mattcarroll/hover/HoverViewStateCollapsed.java b/hover/src/main/java/io/mattcarroll/hover/HoverViewStateCollapsed.java index 8092f21..e982dc9 100644 --- a/hover/src/main/java/io/mattcarroll/hover/HoverViewStateCollapsed.java +++ b/hover/src/main/java/io/mattcarroll/hover/HoverViewStateCollapsed.java @@ -16,70 +16,54 @@ package io.mattcarroll.hover; import android.graphics.Point; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.util.ListUpdateCallback; +import android.os.Handler; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.util.Pair; +import androidx.recyclerview.widget.ListUpdateCallback; import android.util.Log; import android.view.View; -import static android.view.View.GONE; +import java.util.ArrayList; + import static android.view.View.INVISIBLE; -import static android.view.View.VISIBLE; /** * {@link HoverViewState} that operates the {@link HoverView} when it is collapsed. Collapsed means * that the only thing visible is the selected {@link FloatingTab}. This tab docks itself against * the left or right sides of the screen. The user can drag the tab around and drop it. - * + *

* If the tab is tapped, the {@code HoverView} is transitioned to its expanded state. - * + *

* If the tab is dropped on the exit region, the {@code HoverView} is transitioned to its closed state. */ class HoverViewStateCollapsed extends BaseHoverViewState { - private static final String TAG = "HoverMenuViewStateCollapsed"; - - private HoverView mHoverView; - private FloatingTab mFloatingTab; - private HoverMenu.Section mSelectedSection; + private static final String TAG = "HoverViewStateCollapsed"; + 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 float POP_THROWING_SPEED_THRESHOLD = 0.3f; + private static final int NEGATIVE = -1; + private static final int POSITIVE = 1; + + protected FloatingTab mFloatingTab; + protected final FloatingTabDragListener mFloatingTabDragListener = new FloatingTabDragListener(this); + protected HoverMenu.Section mSelectedSection; private int mSelectedSectionIndex = -1; - private boolean mHasControl = false; private boolean mIsCollapsed = false; - private boolean mIsDocked = false; - private Dragger.DragListener mDragListener; - private Listener mListener; - - private final View.OnLayoutChangeListener mOnLayoutChangeListener = new View.OnLayoutChangeListener() { - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { - if (mHasControl && mIsDocked) { - // We're docked. Adjust the tab position in case the screen was rotated. This should - // only be a concern when displaying as a window overlay, but not when displaying - // within a view hierarchy. - moveToDock(); - } - } - }; - - HoverViewStateCollapsed() { } + private Handler mHandler = new Handler(); + private Runnable mIdleActionRunnable; + private Runnable mOnStateChanged; + private GestureBlackBox mGestureBlackBox = new GestureBlackBox(); @Override - public void takeControl(@NonNull HoverView hoverView) { + public void takeControl(@NonNull HoverView floatingTab, final Runnable onStateChanged) { + super.takeControl(floatingTab, onStateChanged); Log.d(TAG, "Taking control."); - super.takeControl(hoverView); - - if (mHasControl) { - Log.w(TAG, "Already has control."); - return; - } - - Log.d(TAG, "Instructing tab to dock itself."); - mHasControl = true; - mHoverView = hoverView; - mHoverView.mState = this; - mHoverView.clearFocus(); // For handling hardware back button presses. - mHoverView.mScreen.getContentDisplay().setVisibility(GONE); + mOnStateChanged = onStateChanged; mHoverView.makeUntouchableInWindow(); + mHoverView.clearFocus(); // For handling hardware back button presses. Log.d(TAG, "Taking control with selected section: " + mHoverView.mSelectedSectionId); mSelectedSection = mHoverView.mMenu.getSection(mHoverView.mSelectedSectionId); @@ -89,16 +73,11 @@ public void takeControl(@NonNull HoverView hoverView) { final boolean wasFloatingTabVisible; if (null == mFloatingTab) { wasFloatingTabVisible = false; - mFloatingTab = mHoverView.mScreen.createChainedTab(mHoverView.mSelectedSectionId, mSelectedSection.getTabView()); + mFloatingTab = mHoverView.mScreen.createChainedTab(mSelectedSection); } else { wasFloatingTabVisible = true; } - mDragListener = new FloatingTabDragListener(this); mIsCollapsed = false; // We're collapsing, not yet collapsed. - if (null != mListener) { - mHoverView.notifyListenersCollapsing(); - mListener.onCollapsing(); - } initDockPosition(); // post() animation to dock in case the container hasn't measured itself yet. @@ -108,76 +87,52 @@ public void takeControl(@NonNull HoverView hoverView) { mHoverView.post(new Runnable() { @Override public void run() { + if (!hasControl()) { + return; + } if (wasFloatingTabVisible) { + mFloatingTab.appearImmediate(); sendToDock(); } else { - mFloatingTab.setVisibility(VISIBLE); moveToDock(); - onDocked(); + mFloatingTab.appear(new Runnable() { + @Override + public void run() { + if (!hasControl()) { + return; + } + onDocked(); + } + }); } } }); - mFloatingTab.addOnLayoutChangeListener(mOnLayoutChangeListener); - if (null != mHoverView.mMenu) { listenForMenuChanges(); } - } - @Override - public void expand() { - changeState(mHoverView.mExpanded); + initIdleActionRunnable(); } @Override - public void collapse() { - Log.d(TAG, "Instructed to collapse, but already collapsed."); - } - - @Override - public void close() { - changeState(mHoverView.mClosed); - } - - private void changeState(@NonNull HoverViewState nextState) { + public void giveUpControl(@NonNull HoverViewState nextState) { Log.d(TAG, "Giving up control."); - if (!mHasControl) { - throw new RuntimeException("Cannot give control to another HoverMenuController when we don't have the HoverTab."); - } - - mFloatingTab.removeOnLayoutChangeListener(mOnLayoutChangeListener); + restoreHoverViewIdleAction(); if (null != mHoverView.mMenu) { mHoverView.mMenu.setUpdatedCallback(null); } - mHasControl = false; - mIsDocked = false; + mHoverView.mScreen.getExitView().hide(); + deactivateDragger(); - mDragListener = null; mFloatingTab = null; - - mHoverView.setState(nextState); - mHoverView = null; + super.giveUpControl(nextState); } @Override public void setMenu(@Nullable final HoverMenu menu) { - mHoverView.mMenu = menu; - - // If the menu is null or empty then we can't be collapsed, close the menu. - if (null == menu || menu.getSectionCount() == 0) { - close(); - return; - } - - mHoverView.restoreVisualState(); - - if (null == mHoverView.mSelectedSectionId || null == mHoverView.mMenu.getSection(mHoverView.mSelectedSectionId)) { - mHoverView.mSelectedSectionId = mHoverView.mMenu.getSection(0).getId(); - } - listenForMenuChanges(); } @@ -194,18 +149,12 @@ public void onRemoved(int position, int count) { if (mSelectedSectionIndex == position) { Log.d(TAG, "Selected tab removed. Displaying a new tab."); // TODO: externalize a selection strategy for when the selected section disappears - mFloatingTab.removeOnLayoutChangeListener(mOnLayoutChangeListener); mHoverView.mScreen.destroyChainedTab(mFloatingTab); mSelectedSectionIndex = mSelectedSectionIndex > 0 ? mSelectedSectionIndex - 1 : 0; mSelectedSection = mHoverView.mMenu.getSection(mSelectedSectionIndex); mHoverView.mSelectedSectionId = mSelectedSection.getId(); - mFloatingTab = mHoverView.mScreen.createChainedTab( - mSelectedSection.getId(), - mSelectedSection.getTabView() - ); - - mFloatingTab.addOnLayoutChangeListener(mOnLayoutChangeListener); + mFloatingTab = mHoverView.mScreen.createChainedTab(mSelectedSection); } } @@ -237,44 +186,75 @@ public void onBackPressed() { // No-op } - public void setListener(@Nullable Listener listener) { - mListener = listener; - } - - private void onPickedUpByUser() { - mIsDocked = false; - mHoverView.mScreen.getExitView().setVisibility(VISIBLE); - if (null != mListener) { - mListener.onDragStart(); + protected void onPickedUpByUser() { + if (!hasControl()) { + return; } + + mHoverView.mScreen.getExitView().show(); + restoreHoverViewIdleAction(); + mHoverView.notifyOnDragStart(this); } private void onDroppedByUser() { - mHoverView.mScreen.getExitView().setVisibility(GONE); - if (null != mListener) { - mListener.onDragEnd(); + if (!hasControl()) { + return; } + mHoverView.mScreen.getExitView().hide(); + mGestureBlackBox.addGesturePoint(mFloatingTab.getPosition()); - boolean droppedOnExit = mHoverView.mScreen.getExitView().isInExitZone(mFloatingTab.getPosition()); + + Point screenSize = mHoverView.getScreenSize(); + boolean droppedOnExit = mHoverView.mScreen.getExitView().isInExitZone(mFloatingTab.getPosition(), screenSize); if (droppedOnExit) { - Log.d(TAG, "User dropped floating tab on exit."); - closeMenu(new Runnable() { - @Override - public void run() { - if (null != mHoverView.mOnExitListener) { - mHoverView.mOnExitListener.onExit(); - } - } - }); + onClose(true); } else { - int tabSize = mHoverView.getResources().getDimensionPixelSize(R.dimen.hover_tab_size); - Point screenSize = new Point(mHoverView.mScreen.getWidth(), mHoverView.mScreen.getHeight()); - float tabHorizontalPositionPercent = (float) mFloatingTab.getPosition().x / screenSize.x; - float tabVerticalPosition = (float) mFloatingTab.getPosition().y / screenSize.y; - Log.d(TAG, "Dropped at horizontal " + tabHorizontalPositionPercent + ", vertical " + tabVerticalPosition); + handleDrop(screenSize); + } + mGestureBlackBox.clear(); + } + + private void handleDrop(Point screenSize) { + 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 (mGestureBlackBox.getSpeed() > POP_THROWING_SPEED_THRESHOLD) { + float positionY = mGestureBlackBox.getTargetYPosition(); + tabVerticalPositionPercent = positionY / screenSize.y; + + int diffPositionX = mGestureBlackBox.getDiffX(); + if (diffPositionX > 0) { + tabHorizontalPositionPercent = 0f; + } else { + tabHorizontalPositionPercent = 1f; + } + } else { + tabVerticalPositionPercent = (float) mFloatingTab.getPosition().y / screenSize.y; + } + + tabVerticalPositionPercent = computeVerticalPositionPercent(viewHeightPercent, tabVerticalPositionPercent); + + Point throwTargetPosition = new Point( + (int) (tabHorizontalPositionPercent * (float) mHoverView.getScreenSize().x), + (int) (tabVerticalPositionPercent * (float) mHoverView.getScreenSize().y)); + boolean throwOnExit = mHoverView.mScreen.getExitView().isInExitZone(throwTargetPosition, screenSize); + if (throwOnExit) { + Point closeTargetPosition = new Point( + screenSize.x / 2 - tabSize / 2, + (int) (screenSize.y * tabVerticalPositionPercent) - tabSize / 2); + closeWithThrowingAnimation(closeTargetPosition); + } else { + int sideDockHorizontalPosition = SideDock.SidePosition.RIGHT; + if (tabHorizontalPositionPercent <= 0.5) { + sideDockHorizontalPosition = SideDock.SidePosition.LEFT; + } + + Log.d(TAG, "Dropped at horizontal " + tabHorizontalPositionPercent + ", vertical " + tabVerticalPositionPercent); SideDock.SidePosition sidePosition = new SideDock.SidePosition( - tabHorizontalPositionPercent <= 0.5 ? SideDock.SidePosition.LEFT : SideDock.SidePosition.RIGHT, - tabVerticalPosition + sideDockHorizontalPosition, + tabVerticalPositionPercent ); mHoverView.mCollapsedDock = new SideDock( mHoverView, @@ -282,20 +262,54 @@ public void run() { sidePosition ); mHoverView.saveVisualState(); - Log.d(TAG, "User dropped tab. Sending to new dock: " + mHoverView.mCollapsedDock); - sendToDock(); } } - private void onTap() { + private float computeVerticalPositionPercent(float viewHeightPercent, float tabVerticalPositionPercent) { + if (tabVerticalPositionPercent < MIN_TAB_VERTICAL_POSITION + viewHeightPercent) { + tabVerticalPositionPercent = MIN_TAB_VERTICAL_POSITION + viewHeightPercent; + } else if (tabVerticalPositionPercent > MAX_TAB_VERTICAL_POSITION - viewHeightPercent) { + tabVerticalPositionPercent = MAX_TAB_VERTICAL_POSITION - viewHeightPercent; + } + return tabVerticalPositionPercent; + } + + protected void onClose(final boolean userDropped) { + if (!hasControl()) { + return; + } + + if (userDropped) { + Log.d(TAG, "User dropped floating tab on exit."); + if (null != mHoverView.mOnExitListener) { + mHoverView.mOnExitListener.onExit(); + } + } else { + Log.d(TAG, "Auto dropped."); + } + mHoverView.close(); + } + + protected void onTap() { Log.d(TAG, "Floating tab was tapped."); - expand(); - if (null != mListener) { - mListener.onTap(); + if (mHoverView != null) { + mHoverView.notifyOnTap(this); } } + private void closeWithThrowingAnimation(Point targetPoint) { + Log.d(TAG, "closeWithThrowingAnimation"); + deactivateDragger(); + mFloatingTab.closeAnimation(targetPoint, new Runnable() { + @Override + public void run() { + activateDragger(); + onClose(true); + } + }); + } + private void sendToDock() { Log.d(TAG, "Sending floating tab to dock."); deactivateDragger(); @@ -303,6 +317,9 @@ private void sendToDock() { mFloatingTab.dock(new Runnable() { @Override public void run() { + if (!hasControl()) { + return; + } onDocked(); } }); @@ -311,10 +328,10 @@ public void run() { private void moveToDock() { Log.d(TAG, "Moving floating tag to dock."); Point dockPosition = mHoverView.mCollapsedDock.sidePosition().calculateDockPosition( - new Point(mHoverView.mScreen.getWidth(), mHoverView.mScreen.getHeight()), + mHoverView.getScreenSize(), mFloatingTab.getTabSize() ); - mFloatingTab.moveTo(dockPosition); + mFloatingTab.moveCenterTo(dockPosition); } private void initDockPosition() { @@ -328,97 +345,241 @@ private void initDockPosition() { } } - private void onDocked() { + protected void onDocked() { Log.d(TAG, "Docked. Activating dragger."); - mIsDocked = true; + if (!hasControl() || !mHoverView.mIsAddedToWindow) { + return; + } activateDragger(); + scheduleHoverViewIdleAction(); // We consider ourselves having gone from "collapsing" to "collapsed" upon the very first dock. boolean didJustCollapse = !mIsCollapsed; mIsCollapsed = true; mHoverView.saveVisualState(); - if (null != mListener) { - if (didJustCollapse) { - mHoverView.notifyListenersCollapsed(); - mListener.onCollapsed(); + if (didJustCollapse) { + if (mOnStateChanged != null) { + mOnStateChanged.run(); } - mListener.onDocked(); + } + mHoverView.notifyOnDocked(this); + } + + void moveFloatingTabTo(View floatingTab, @NonNull Point position) { + if (mHoverView.mScreen.getExitView().isInExitZone(position, mHoverView.getScreenSize())) { + mHoverView.mScreen.getExitView().showEnterAnimation(); + } else { + mHoverView.mScreen.getExitView().showExitAnimation(); + } + mFloatingTab.moveCenterTo(position); + mGestureBlackBox.addGesturePoint(position); + } + + protected void activateDragger() { + if (mHoverView != null && mHoverView.mDragger != null) { + ArrayList> list = new ArrayList<>(); + list.add(new Pair<>(mFloatingTab, mFloatingTabDragListener)); + mHoverView.mDragger.activate(list); } } - private void moveTabTo(@NonNull Point position) { - mFloatingTab.moveTo(position); + protected void deactivateDragger() { + mHoverView.mDragger.deactivate(); } - private void closeMenu(final @Nullable Runnable onClosed) { - mFloatingTab.disappear(new Runnable() { + private void initIdleActionRunnable() { + this.mIdleActionRunnable = new Runnable() { @Override public void run() { - mHoverView.mScreen.destroyChainedTab(mFloatingTab); - - if (null != onClosed) { - onClosed.run(); + if (mHoverView == null) { + return; } - close(); + final HoverViewState state = mHoverView.getState(); + if (!(state instanceof HoverViewStatePreviewed) && state instanceof HoverViewStateCollapsed) { + final HoverView.HoverViewIdleAction idleAction = mHoverView.getIdleAction(); + if (idleAction != null) { + idleAction.changeState(mFloatingTab); + } + + } } - }); + }; } - private void activateDragger() { - mHoverView.mDragger.activate(mDragListener, mFloatingTab.getPosition()); + private void scheduleHoverViewIdleAction() { + mHandler.postDelayed(mIdleActionRunnable, DEFAULT_IDLE_MILLIS); } - private void deactivateDragger() { - mHoverView.mDragger.deactivate(); + protected void restoreHoverViewIdleAction() { + mHandler.removeCallbacks(mIdleActionRunnable); + final HoverView.HoverViewIdleAction idleAction = mHoverView.getIdleAction(); + if (idleAction != null) { + idleAction.restoreState(mFloatingTab); + } } - public interface Listener { - void onCollapsing(); - - void onCollapsed(); - - void onDragStart(); - - void onDragEnd(); - - void onDocked(); - - void onTap(); - - // TODO: do we need this? - void onExited(); + @Override + public HoverViewStateType getStateType() { + return HoverViewStateType.COLLAPSED; } - private static final class FloatingTabDragListener implements Dragger.DragListener { + protected static final class FloatingTabDragListener implements Dragger.DragListener { private final HoverViewStateCollapsed mOwner; - private FloatingTabDragListener(@NonNull HoverViewStateCollapsed owner) { + protected FloatingTabDragListener(@NonNull HoverViewStateCollapsed owner) { mOwner = owner; } @Override - public void onPress(float x, float y) { } + public void onDragStart(FloatingTab floatingTab, float x, float y) { + mOwner.onPickedUpByUser(); + } @Override - public void onDragStart(float x, float y) { - mOwner.onPickedUpByUser(); + public void onDragTo(FloatingTab floatingTab, float x, float y) { + mOwner.moveFloatingTabTo(floatingTab, new Point((int) x, (int) y)); } @Override - public void onDragTo(float x, float y) { - mOwner.moveTabTo(new Point((int) x, (int) y)); + public void onReleasedAt(FloatingTab floatingTab, float x, float y) { + mOwner.onDroppedByUser(); } @Override - public void onReleasedAt(float x, float y) { + public void onDragCancel(FloatingTab floatingTab) { mOwner.onDroppedByUser(); } @Override - public void onTap() { + public void onTap(FloatingTab floatingTab) { mOwner.onTap(); } + + @Override + public void onTouchDown(FloatingTab floatingTab) { + } + + @Override + public void onTouchUp(FloatingTab floatingTab) { + } + } + + class GestureBlackBox { + private final long mMaxMeasureTimeGap = 100L; + private final int mMaxArraySize = 100; + GesturePoint mFirstPoint = null; + GesturePoint mSecondPoint = null; + + ArrayList 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; + } + } } } diff --git a/hover/src/main/java/io/mattcarroll/hover/HoverViewStateExpanded.java b/hover/src/main/java/io/mattcarroll/hover/HoverViewStateExpanded.java index 577c043..0c1bc38 100644 --- a/hover/src/main/java/io/mattcarroll/hover/HoverViewStateExpanded.java +++ b/hover/src/main/java/io/mattcarroll/hover/HoverViewStateExpanded.java @@ -16,9 +16,9 @@ package io.mattcarroll.hover; import android.graphics.Point; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.util.ListUpdateCallback; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.ListUpdateCallback; import android.util.Log; import android.view.View; @@ -38,25 +38,27 @@ */ class HoverViewStateExpanded extends BaseHoverViewState { - private static final String TAG = "HoverMenuViewStateExpanded"; + private static final String TAG = "HoverViewStateExpanded"; private static final int ANCHOR_TAB_X_OFFSET_IN_PX = 100; private static final int ANCHOR_TAB_Y_OFFSET_IN_PX = 100; private static final int TAB_SPACING_IN_PX = 200; private static final int TAB_APPEARANCE_DELAY_IN_MS = 100; - private boolean mHasControl = false; - private HoverView mHoverView; private boolean mHasMenu = false; private FloatingTab mSelectedTab; private final List mChainedTabs = new ArrayList<>(); private final List mTabChains = new ArrayList<>(); private final Map mSections = new HashMap<>(); private Point mDock; - private Listener mListener; + private int mTabsToUnchainCount; + private Runnable mOnStateChanged; private final Runnable mShowTabsRunnable = new Runnable() { @Override public void run() { + if (!hasControl()) { + return; + } mHoverView.mScreen.getShadeView().show(); mHoverView.mScreen.getContentDisplay().selectedTabIs(mSelectedTab); @@ -64,41 +66,26 @@ public void run() { ? mHoverView.mMenu.getSection(mHoverView.mSelectedSectionId) : mHoverView.mMenu.getSection(0); mHoverView.mScreen.getContentDisplay().displayContent(selectedSection.getContent()); - mHoverView.mScreen.getContentDisplay().setVisibility(View.VISIBLE); - - mHoverView.notifyListenersExpanded(); - if (null != mListener) { - mListener.onExpanded(); - } + mOnStateChanged.run(); } }; - HoverViewStateExpanded() { } - @Override - public void takeControl(@NonNull HoverView hoverView) { + public void takeControl(@NonNull HoverView hoverView, Runnable onStateChanged) { + super.takeControl(hoverView, onStateChanged); Log.d(TAG, "Taking control."); - super.takeControl(hoverView); - if (mHasControl) { - throw new RuntimeException("Cannot take control of a FloatingTab when we already control one."); - } - - mHasControl = true; - mHoverView = hoverView; - mHoverView.mState = this; + mOnStateChanged = onStateChanged; mHoverView.makeTouchableInWindow(); mHoverView.requestFocus(); // For handling hardware back button presses. mDock = new Point( - mHoverView.mScreen.getWidth() - ANCHOR_TAB_X_OFFSET_IN_PX, + mHoverView.getScreenSize().x - ANCHOR_TAB_X_OFFSET_IN_PX, ANCHOR_TAB_Y_OFFSET_IN_PX ); if (null != mHoverView.mMenu) { Log.d(TAG, "Already has menu. Expanding."); setMenu(mHoverView.mMenu); } - - mHoverView.makeTouchableInWindow(); } private void expandMenu() { @@ -115,11 +102,6 @@ private void expandMenu() { } else { mSelectedTab.dock(mShowTabsRunnable); } - - mHoverView.notifyListenersExpanding(); - if (null != mListener) { - mListener.onExpanding(); - } } private void createChainedTabs() { @@ -128,10 +110,7 @@ private void createChainedTabs() { for (int i = 0; i < mHoverView.mMenu.getSectionCount(); ++i) { HoverMenu.Section section = mHoverView.mMenu.getSection(i); Log.d(TAG, "Creating tab view for: " + section.getId()); - final FloatingTab chainedTab = mHoverView.mScreen.createChainedTab( - section.getId(), - section.getTabView() - ); + final FloatingTab chainedTab = mHoverView.mScreen.createChainedTab(section); Log.d(TAG, "Created FloatingTab for ID " + section.getId()); if (!mHoverView.mSelectedSectionId.equals(section.getId())) { @@ -196,51 +175,20 @@ public void run() { } @Override - public void expand() { - Log.d(TAG, "Instructed to expand, but already expanded."); - } - - @Override - public void collapse() { - Log.d(TAG, "Collapsing."); - changeState(mHoverView.mCollapsed); - } - - @Override - public void close() { - Log.d(TAG, "Closing."); - changeState(mHoverView.mClosed); - } - - private void changeState(@NonNull final HoverViewState nextState) { + public void giveUpControl(@NonNull final HoverViewState nextState) { Log.d(TAG, "Giving up control."); - if (!mHasControl) { - throw new RuntimeException("Cannot give control to another HoverMenuController when we don't have the HoverTab."); - } - if (null != mHoverView.mMenu) { mHoverView.mMenu.setUpdatedCallback(null); } - - mHasControl = false; mHasMenu = false; mHoverView.mScreen.getContentDisplay().selectedTabIs(null); mHoverView.mScreen.getContentDisplay().displayContent(null); mHoverView.mScreen.getContentDisplay().setVisibility(View.GONE); mHoverView.mScreen.getShadeView().hide(); - mHoverView.setState(nextState); - unchainTabs(new Runnable() { - @Override - public void run() { - Log.d(TAG, "Running unchained runnable."); - // We wait to nullify our HoverMenuView because some final animations need it. - // TODO: maybe the answer is for the collapse state to handle what happens to the tabs and content display and shade? - mHoverView = null; - } - }); + unchainTabs(null); + super.giveUpControl(nextState); } - private int mTabsToUnchainCount; private void unchainTabs(@Nullable final Runnable onUnChained) { int selectedTabIndex = 0; for (int i = 0; i < mChainedTabs.size(); ++i) { @@ -292,20 +240,6 @@ public void run() { @Override public void setMenu(@Nullable HoverMenu menu) { Log.d(TAG, "Setting menu."); - mHoverView.mMenu = menu; - - // Expanded menus can't be null/empty. If it is then go to closed state. - if (null == mHoverView.mMenu || mHoverView.mMenu.getSectionCount() == 0) { - close(); - return; - } - - mHoverView.restoreVisualState(); - - if (null == mHoverView.mSelectedSectionId || null == mHoverView.mMenu.getSection(mHoverView.mSelectedSectionId)) { - mHoverView.mSelectedSectionId = mHoverView.mMenu.getSection(0).getId(); - } - mHoverView.mMenu.setUpdatedCallback(new ListUpdateCallback() { @Override public void onInserted(int position, int count) { @@ -344,10 +278,10 @@ public void onChanged(int position, int count, Object payload) { } }); - if (mHasControl && !mHasMenu) { + if (hasControl() && !mHasMenu) { Log.d(TAG, "Has control. Received initial menu. Expanding menu."); expandMenu(); - } else if (mHasControl) { + } else if (hasControl()) { Log.d(TAG, "Has control. Already had menu. Switching menu."); transitionDisplayFromOldMenuToNew(); } @@ -381,28 +315,23 @@ public boolean respondsToBackButton() { @Override public void onBackPressed() { - collapse(); + mHoverView.collapse(); } - private void createTabsForIndices(int ... sectionIndices) { + private void createTabsForIndices(int... sectionIndices) { for (int sectionIndex : sectionIndices) { Log.d(TAG, "Creating tab for section at index " + sectionIndex); HoverMenu.Section section = mHoverView.mMenu.getSection(sectionIndex); Log.d(TAG, "Adding new tab. Section: " + sectionIndex + ", ID: " + section.getId()); - FloatingTab newTab = addTab(section.getId(), section.getTabView(), sectionIndex); + FloatingTab newTab = addTab(sectionIndex, section); mSections.put(newTab, section); } updateChainedPositions(); } - private FloatingTab addTab(@NonNull HoverMenu.SectionId sectionId, - @NonNull View tabView, - int position) { - final FloatingTab newTab = mHoverView.mScreen.createChainedTab( - sectionId, - tabView - ); + private FloatingTab addTab(int position, HoverMenu.Section section) { + final FloatingTab newTab = mHoverView.mScreen.createChainedTab(section); newTab.disappearImmediate(); if (mChainedTabs.size() <= position) { // This section was appended to the end. @@ -433,7 +362,7 @@ private void reorderSection(int fromPosition, int toPosition) { updateChainedPositions(); } - private void updateSections(int ... sectionIndices) { + private void updateSections(int... sectionIndices) { Log.d(TAG, "Tab(s) changed: " + Arrays.toString(sectionIndices)); for (int sectionIndex : sectionIndices) { updateSection(sectionIndex); @@ -457,7 +386,7 @@ private void updateSection(int sectionIndex) { } } - private void removeSections(int ... sectionIndices) { + private void removeSections(int... sectionIndices) { Log.d(TAG, "Tab(s) removed: " + Arrays.toString(sectionIndices)); // Sort the indices so that they appear from lowest to highest. Then process // in reverse order so that we don't remove sections out from under us. @@ -519,7 +448,7 @@ private void onTabSelected(@NonNull FloatingTab selectedTab) { if (!section.getId().equals(mHoverView.mSelectedSectionId)) { selectSection(section); } else { - collapse(); + mHoverView.collapse(); } } @@ -531,17 +460,8 @@ private void selectSection(@NonNull HoverMenu.Section section) { contentDisplay.displayContent(section.getContent()); } - // TODO: do we need this? - public void setListener(@NonNull Listener listener) { - mListener = listener; - } - - public interface Listener { - void onExpanding(); - - void onExpanded(); - - // TODO: do we need this? - void onCollapseRequested(); + @Override + public HoverViewStateType getStateType() { + return HoverViewStateType.EXPANDED; } } diff --git a/hover/src/main/java/io/mattcarroll/hover/HoverViewStateHidden.java b/hover/src/main/java/io/mattcarroll/hover/HoverViewStateHidden.java new file mode 100644 index 0000000..06ef01e --- /dev/null +++ b/hover/src/main/java/io/mattcarroll/hover/HoverViewStateHidden.java @@ -0,0 +1,84 @@ +package io.mattcarroll.hover; + +import android.os.Handler; +import android.os.Looper; +import androidx.annotation.NonNull; +import android.util.Log; +import android.view.View; + +class HoverViewStateHidden extends BaseHoverViewState { + + private static final String TAG = "HoverViewStateHidden"; + + private FloatingTab mSelectedTab; + + @Override + public void takeControl(@NonNull final HoverView hoverView, final Runnable onStateChanged) { + super.takeControl(hoverView, onStateChanged); + Log.d(TAG, "Taking control."); + mHoverView.makeUntouchableInWindow(); + mHoverView.clearFocus(); + + HoverMenu.Section mSelectedSection = mHoverView.mMenu.getSection(mHoverView.mSelectedSectionId); + if (mSelectedSection == null) { + mSelectedSection = mHoverView.mMenu.getSection(0); + } + + mSelectedTab = mHoverView.mScreen.getChainedTab(mSelectedSection.getId()); + if (mSelectedTab == null) { + mSelectedTab = mHoverView.mScreen.createChainedTab(mSelectedSection); + } + + mSelectedTab.shrink(); + mSelectedTab.setSelected(true); + + final PositionDock positionToHide = mHoverView.getPositionToHide(); + if (positionToHide == null) { + mHoverView.setVisibility(View.GONE); + onStateChanged.run(); + return; + } + + mSelectedTab.setDock(positionToHide); + mSelectedTab.dock(new Runnable() { + @Override + public void run() { + if (!hasControl() || !mHoverView.mIsAddedToWindow) { + return; + } + onStateChanged.run(); + new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { + @Override + public void run() { + if (mHoverView != null) { + mHoverView.setVisibility(View.GONE); + } + } + }, 50); + } + }); + } + + @Override + public void giveUpControl(@NonNull HoverViewState nextState) { + Log.d(TAG, "Giving up control."); + mSelectedTab.setSelected(false); + mSelectedTab.expand(); + mHoverView.setVisibility(View.VISIBLE); + super.giveUpControl(nextState); + } + + @Override + public boolean respondsToBackButton() { + return false; + } + + @Override + public void onBackPressed() { + } + + @Override + public HoverViewStateType getStateType() { + return HoverViewStateType.HIDDEN; + } +} diff --git a/hover/src/main/java/io/mattcarroll/hover/HoverViewStatePreviewed.java b/hover/src/main/java/io/mattcarroll/hover/HoverViewStatePreviewed.java new file mode 100644 index 0000000..c924ae9 --- /dev/null +++ b/hover/src/main/java/io/mattcarroll/hover/HoverViewStatePreviewed.java @@ -0,0 +1,162 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * 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. + */ +package io.mattcarroll.hover; + +import androidx.annotation.NonNull; +import androidx.core.util.Pair; +import android.util.Log; + +import java.util.ArrayList; + +/** + * {@link HoverViewState} that operates the {@link HoverView} when it is closed. Closed means that + * nothing is visible - no tabs, no content. From the user's perspective, there is no + * {@code HoverView}. + */ +class HoverViewStatePreviewed extends HoverViewStateCollapsed { + + private static final String TAG = "HoverViewStatePreviewed"; + private TabMessageView mMessageView; + private Dragger.DragListener mDefaultMessageViewDragListener; + private Dragger.DragListener mCustomMessageViewDragListener; + + HoverViewStatePreviewed() { + mDefaultMessageViewDragListener = new DefaultMessageViewDragListener(); + } + + @Override + public void takeControl(@NonNull HoverView hoverView, final Runnable onStateChanged) { + super.takeControl(hoverView, null); + Log.d(TAG, "Taking control."); + mMessageView = mHoverView.mScreen.getTabMessageView(mHoverView.mSelectedSectionId); + mMessageView.setMessageView(mSelectedSection.getTabMessageView()); + mMessageView.appear(mHoverView.mCollapsedDock, new Runnable() { + @Override + public void run() { + if (!hasControl()) { + return; + } + onStateChanged.run(); + activateDragger(); + } + }); + } + + @Override + public void giveUpControl(@NonNull final HoverViewState nextState) { + Log.d(TAG, "Giving up control."); + if (nextState instanceof HoverViewStateCollapsed) { + mMessageView.disappear(true); + } else { + mMessageView.disappear(false); + } + super.giveUpControl(nextState); + } + + @Override + protected void onPickedUpByUser() { + super.onPickedUpByUser(); + } + + @Override + protected void onClose(final boolean userDropped) { + super.onClose(userDropped); + } + + @Override + protected void activateDragger() { + if (mHoverView != null && mHoverView.mDragger != null) { + ArrayList> list = new ArrayList<>(); + list.add(new Pair<>(mFloatingTab, mFloatingTabDragListener)); + list.add(new Pair<>(mMessageView, mDefaultMessageViewDragListener)); + mHoverView.mDragger.activate(list); + } + } + + @Override + protected void onDocked() { + super.onDocked(); + } + + @Override + public HoverViewStateType getStateType() { + return HoverViewStateType.PREVIEWED; + } + + public void setMessageViewDragListener(final Dragger.DragListener messageViewDragListener) { + this.mCustomMessageViewDragListener = messageViewDragListener; + } + + private class DefaultMessageViewDragListener implements Dragger.DragListener { + + @Override + public void onDragStart(TabMessageView view, float x, float y) { + if (mCustomMessageViewDragListener == null) { + return; + } + mCustomMessageViewDragListener.onDragStart(view, x, y); + + } + + @Override + public void onDragTo(TabMessageView view, float x, float y) { + if (mCustomMessageViewDragListener == null) { + return; + } + mCustomMessageViewDragListener.onDragTo(view, x, y); + } + + @Override + public void onReleasedAt(TabMessageView view, float x, float y) { + if (mCustomMessageViewDragListener == null) { + return; + } + mCustomMessageViewDragListener.onReleasedAt(view, x, y); + } + @Override + public void onDragCancel(TabMessageView view) { + if (mCustomMessageViewDragListener == null) { + return; + } + mCustomMessageViewDragListener.onDragCancel(view); + } + + @Override + public void onTap(TabMessageView view) { + if (mCustomMessageViewDragListener == null) { + return; + } + mCustomMessageViewDragListener.onTap(view); + } + + @Override + public void onTouchDown(TabMessageView view) { + if (mCustomMessageViewDragListener == null) { + return; + } + mCustomMessageViewDragListener.onTouchDown(view); + } + + @Override + public void onTouchUp(TabMessageView view) { + if (mCustomMessageViewDragListener == null) { + return; + } + mCustomMessageViewDragListener.onTouchUp(view); + } + } + +} diff --git a/hover/src/main/java/io/mattcarroll/hover/HoverViewStateType.java b/hover/src/main/java/io/mattcarroll/hover/HoverViewStateType.java new file mode 100644 index 0000000..a1ae900 --- /dev/null +++ b/hover/src/main/java/io/mattcarroll/hover/HoverViewStateType.java @@ -0,0 +1,9 @@ +package io.mattcarroll.hover; + +public enum HoverViewStateType { + CLOSED, + COLLAPSED, + PREVIEWED, + EXPANDED, + HIDDEN, +} diff --git a/hover/src/main/java/io/mattcarroll/hover/PositionDock.java b/hover/src/main/java/io/mattcarroll/hover/PositionDock.java index be789c3..e49f98a 100644 --- a/hover/src/main/java/io/mattcarroll/hover/PositionDock.java +++ b/hover/src/main/java/io/mattcarroll/hover/PositionDock.java @@ -16,7 +16,7 @@ package io.mattcarroll.hover; import android.graphics.Point; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; /** * {@link Dock} that has a static position as defined by a provided {@link Point}. A diff --git a/hover/src/main/java/io/mattcarroll/hover/Screen.java b/hover/src/main/java/io/mattcarroll/hover/Screen.java index 441f976..b67c124 100644 --- a/hover/src/main/java/io/mattcarroll/hover/Screen.java +++ b/hover/src/main/java/io/mattcarroll/hover/Screen.java @@ -15,8 +15,8 @@ */ package io.mattcarroll.hover; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -40,10 +40,13 @@ class Screen { private ExitView mExitView; private ShadeView mShadeView; private Map mTabs = new HashMap<>(); + private Map mTabMessageViews = new HashMap<>(); private boolean mIsDebugMode = false; Screen(@NonNull ViewGroup hoverMenuContainer) { mContainer = hoverMenuContainer; + mContainer.setClipChildren(false); + mContainer.setClipToPadding(false); mShadeView = new ShadeView(mContainer.getContext()); mContainer.addView(mShadeView, new WindowManager.LayoutParams( @@ -73,18 +76,10 @@ public void enableDrugMode(boolean debugMode) { } } - public int getWidth() { - return mContainer.getWidth(); - } - - public int getHeight() { - return mContainer.getHeight(); - } - @NonNull - public FloatingTab createChainedTab(@NonNull HoverMenu.SectionId sectionId, @NonNull View tabView) { - String tabId = sectionId.toString(); - return createChainedTab(tabId, tabView); + public FloatingTab createChainedTab(@NonNull HoverMenu.Section section) { + String tabId = section.getId().toString(); + return createChainedTab(tabId, section.getTabView()); } @NonNull @@ -100,8 +95,11 @@ public FloatingTab createChainedTab(@NonNull String tabId, @NonNull View tabView FloatingTab chainedTab = new FloatingTab(mContainer.getContext(), tabId); chainedTab.setTabView(tabView); chainedTab.enableDebugMode(mIsDebugMode); - mContainer.addView(chainedTab); mTabs.put(tabId, chainedTab); + final TabMessageView messageView = new TabMessageView(tabView.getContext(), chainedTab); + mContainer.addView(messageView); + mContainer.addView(chainedTab); + mTabMessageViews.put(tabId, messageView); return chainedTab; } } @@ -119,6 +117,7 @@ public FloatingTab getChainedTab(@Nullable String tabId) { public void destroyChainedTab(@NonNull FloatingTab chainedTab) { mTabs.remove(chainedTab.getTabId()); + mTabMessageViews.remove(chainedTab.getTabId()); chainedTab.setTabView(null); mContainer.removeView(chainedTab); } @@ -134,4 +133,8 @@ public ExitView getExitView() { public ShadeView getShadeView() { return mShadeView; } + + public TabMessageView getTabMessageView(final HoverMenu.SectionId sectionId) { + return mTabMessageViews.get(sectionId.toString()); + } } diff --git a/hover/src/main/java/io/mattcarroll/hover/ShadeView.java b/hover/src/main/java/io/mattcarroll/hover/ShadeView.java index 8ba309f..89829bf 100644 --- a/hover/src/main/java/io/mattcarroll/hover/ShadeView.java +++ b/hover/src/main/java/io/mattcarroll/hover/ShadeView.java @@ -18,8 +18,8 @@ import android.animation.Animator; import android.animation.ObjectAnimator; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import android.util.AttributeSet; import android.view.LayoutInflater; import android.widget.FrameLayout; diff --git a/hover/src/main/java/io/mattcarroll/hover/SideDock.java b/hover/src/main/java/io/mattcarroll/hover/SideDock.java index 0d55ac3..770725e 100644 --- a/hover/src/main/java/io/mattcarroll/hover/SideDock.java +++ b/hover/src/main/java/io/mattcarroll/hover/SideDock.java @@ -16,10 +16,9 @@ package io.mattcarroll.hover; import android.graphics.Point; -import android.support.annotation.IntDef; -import android.support.annotation.NonNull; +import androidx.annotation.IntDef; +import androidx.annotation.NonNull; import android.util.Log; -import android.view.ViewGroup; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -33,12 +32,12 @@ public class SideDock extends Dock { private static final String TAG = "SideDock"; - private ViewGroup mContainerView; + private HoverView mHoverView; private int mTabSize; private SidePosition mSidePosition; - SideDock(@NonNull ViewGroup containerView, int tabSize, @NonNull SidePosition sidePosition) { - mContainerView = containerView; + SideDock(@NonNull HoverView hoverView, int tabSize, @NonNull SidePosition sidePosition) { + mHoverView = hoverView; mTabSize = tabSize; mSidePosition = sidePosition; } @@ -46,8 +45,7 @@ public class SideDock extends Dock { @NonNull @Override public Point position() { - Point screenSize = new Point(mContainerView.getWidth(), mContainerView.getHeight()); - return mSidePosition.calculateDockPosition(screenSize, mTabSize); + return mSidePosition.calculateDockPosition(mHoverView.getScreenSize(), mTabSize); } @NonNull @@ -67,7 +65,6 @@ public static class SidePosition { public @interface Side { } public static final int LEFT = 0; public static final int RIGHT = 1; - @Side private int mSide; private float mVerticalDockPositionPercentage; diff --git a/hover/src/main/java/io/mattcarroll/hover/TabChain.java b/hover/src/main/java/io/mattcarroll/hover/TabChain.java index 16147c8..992cc9b 100644 --- a/hover/src/main/java/io/mattcarroll/hover/TabChain.java +++ b/hover/src/main/java/io/mattcarroll/hover/TabChain.java @@ -16,14 +16,11 @@ package io.mattcarroll.hover; import android.graphics.Point; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import android.util.Log; import android.view.View; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArraySet; - /** * Connects one {@link FloatingTab}s position to that of another {@link FloatingTab}. The space * between the tabs can be configured at construction time. @@ -36,16 +33,15 @@ class TabChain { private final int mTabSpacingInPx; private Point mLockedPosition; private FloatingTab mPredecessorTab; - private final Set mOnPositionChangeListeners = new CopyOnWriteArraySet(); - private final FloatingTab.OnPositionChangeListener mOnPredecessorPositionChange = new FloatingTab.OnPositionChangeListener() { + private final FloatingTab.OnFloatingTabChangeListener mOnPredecessorPositionChange = new FloatingTab.OnFloatingTabChangeListener() { @Override - public void onPositionChange(@NonNull Point position) { + public void onPositionChange(@NonNull View view) { // No-op. We only care when our predecessor's dock changes. } @Override - public void onDockChange(@NonNull Point dock) { + public void onDockChange(@NonNull Dock dock) { Log.d(TAG, hashCode() + "'s predecessor dock moved to: " + dock); moveToChainedPosition(false); } @@ -113,7 +109,7 @@ private void moveToChainedPosition(boolean immediate) { mTab.dock(); } } else { - mTab.moveTo(mTab.getDockPosition()); + mTab.dockImmediately(); mTab.appear(null); } } diff --git a/hover/src/main/java/io/mattcarroll/hover/TabMessageView.java b/hover/src/main/java/io/mattcarroll/hover/TabMessageView.java new file mode 100644 index 0000000..a60787b --- /dev/null +++ b/hover/src/main/java/io/mattcarroll/hover/TabMessageView.java @@ -0,0 +1,160 @@ +package io.mattcarroll.hover; + +import android.content.Context; +import android.graphics.Point; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.interpolator.view.animation.LinearOutSlowInInterpolator; +import android.util.Log; +import android.view.View; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.AnimationSet; +import android.view.animation.TranslateAnimation; + +public class TabMessageView extends HoverFrameLayout { + private static final String TAG = "TabMessageView"; + + private final FloatingTab mFloatingTab; + private SideDock mSideDock; + private View mMessageView; + + private final FloatingTab.OnFloatingTabChangeListener mOnFloatingTabChangeListener = new FloatingTab.OnFloatingTabChangeListener() { + private static final int DEFAULT_SIDE = SideDock.SidePosition.LEFT; + + private Point mLastPosition; + private int mLastSide; + + @Override + public void onPositionChange(View view) { + if (!(view instanceof FloatingTab)) { + return; + } + final Point position = ((FloatingTab) view).getPosition(); + final Integer side = getSide(); + if (side.equals(mLastSide) && position.equals(mLastPosition) || getWidth() == 0) { + return; + } + Log.d(TAG, mFloatingTab + " tab moved to " + position); + final float tabSizeHalf = mFloatingTab.getTabSize() / 2f; + if (side == SideDock.SidePosition.RIGHT) { + setX(position.x - tabSizeHalf - getWidth()); + } else { + setX(position.x + tabSizeHalf); + } + setY(position.y - tabSizeHalf); + mLastPosition = position; + mLastSide = side; + } + + @Override + public void onDockChange(@NonNull Dock dock) { + if (dock instanceof SideDock) { + final SideDock sideDock = (SideDock) dock; + if (sideDock.sidePosition() != mSideDock.sidePosition()) { + appear(sideDock, null); + } + } + } + + private int getSide() { + if (mSideDock != null) { + return mSideDock.sidePosition().getSide(); + } + return DEFAULT_SIDE; + } + }; + + public TabMessageView(@NonNull Context context, @NonNull FloatingTab floatingTab) { + super(context); + mFloatingTab = floatingTab; + setVisibility(GONE); + + // To prevent child's shadow clipping + setClipToPadding(false); + setClipChildren(false); + setPadding(10, 20, 10, 20); + } + + public void setMessageView(@Nullable View view) { + if (view == mMessageView) { + return; + } + removeAllViews(); + mMessageView = view; + if (mMessageView != null) { + addView(mMessageView); + } + } + + @Nullable + public View getMessageView() { + return mMessageView; + } + + public void appear(final SideDock dock, @Nullable final Runnable onAppeared) { + mSideDock = dock; + mFloatingTab.addOnPositionChangeListener(mOnFloatingTabChangeListener); + if (getVisibility() != View.VISIBLE) { + final AnimationSet animation = new AnimationSet(true); + final AlphaAnimation alpha = new AlphaAnimation(0, 1); + final float fromXDelta = getResources().getDimensionPixelSize(R.dimen.hover_message_animate_translation_x) + * (dock.sidePosition().getSide() == SideDock.SidePosition.LEFT ? -1 : 1); + final float fromYDelta = getResources().getDimensionPixelSize(R.dimen.hover_message_animate_translation_y); + TranslateAnimation translate = new TranslateAnimation(fromXDelta, 0, fromYDelta, 0); + animation.setDuration(300); + animation.setInterpolator(new LinearOutSlowInInterpolator()); + animation.addAnimation(alpha); + animation.addAnimation(translate); + animation.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + } + + @Override + public void onAnimationEnd(Animation animation) { + if (onAppeared != null) { + onAppeared.run(); + } + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + }); + startAnimation(animation); + setVisibility(VISIBLE); + } + } + + public void disappear(final boolean withAnimation) { + disappear(withAnimation, 1); + } + + public void disappear(final boolean withAnimation, float startAlpha) { + mFloatingTab.removeOnPositionChangeListener(mOnFloatingTabChangeListener); + mSideDock = null; + if (withAnimation && getVisibility() == View.VISIBLE) { + final AnimationSet animation = new AnimationSet(true); + final AlphaAnimation alpha = new AlphaAnimation(startAlpha, 0); + alpha.setDuration(300); + animation.addAnimation(alpha); + startAnimation(animation); + } + setVisibility(GONE); + } + + public void moveCenterTo(@NonNull Point floatPosition) { + Point cornerPosition = convertCenterToCorner(floatPosition); + setX(cornerPosition.x); + setY(cornerPosition.y); + notifyListenersOfPositionChange(this); + } + + private Point convertCenterToCorner(@NonNull Point centerPosition) { + return new Point( + (int) (centerPosition.x - (getWidth() / 2)), + (int) (centerPosition.y - (getHeight() / 2)) + ); + } +} diff --git a/hover/src/main/java/io/mattcarroll/hover/TabSelectorView.java b/hover/src/main/java/io/mattcarroll/hover/TabSelectorView.java index 997f5ee..f6d4fa2 100755 --- a/hover/src/main/java/io/mattcarroll/hover/TabSelectorView.java +++ b/hover/src/main/java/io/mattcarroll/hover/TabSelectorView.java @@ -19,7 +19,7 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; -import android.support.annotation.ColorInt; +import androidx.annotation.ColorInt; import android.util.AttributeSet; import android.util.TypedValue; import android.view.View; diff --git a/hover/src/main/java/io/mattcarroll/hover/content/Navigator.java b/hover/src/main/java/io/mattcarroll/hover/content/Navigator.java index 3129026..ca398b0 100644 --- a/hover/src/main/java/io/mattcarroll/hover/content/Navigator.java +++ b/hover/src/main/java/io/mattcarroll/hover/content/Navigator.java @@ -16,7 +16,7 @@ package io.mattcarroll.hover.content; import android.content.Context; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.util.AttributeSet; import android.view.ViewGroup; import android.widget.FrameLayout; diff --git a/hover/src/main/java/io/mattcarroll/hover/content/NavigatorContent.java b/hover/src/main/java/io/mattcarroll/hover/content/NavigatorContent.java index 87058ac..99413c6 100644 --- a/hover/src/main/java/io/mattcarroll/hover/content/NavigatorContent.java +++ b/hover/src/main/java/io/mattcarroll/hover/content/NavigatorContent.java @@ -15,7 +15,7 @@ */ package io.mattcarroll.hover.content; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.view.View; /** diff --git a/hover/src/main/java/io/mattcarroll/hover/content/menus/DoNothingMenuAction.java b/hover/src/main/java/io/mattcarroll/hover/content/menus/DoNothingMenuAction.java index be8cb17..c22687b 100644 --- a/hover/src/main/java/io/mattcarroll/hover/content/menus/DoNothingMenuAction.java +++ b/hover/src/main/java/io/mattcarroll/hover/content/menus/DoNothingMenuAction.java @@ -16,7 +16,7 @@ package io.mattcarroll.hover.content.menus; import android.content.Context; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import io.mattcarroll.hover.content.Navigator; diff --git a/hover/src/main/java/io/mattcarroll/hover/content/menus/Menu.java b/hover/src/main/java/io/mattcarroll/hover/content/menus/Menu.java index 7167808..50660ad 100644 --- a/hover/src/main/java/io/mattcarroll/hover/content/menus/Menu.java +++ b/hover/src/main/java/io/mattcarroll/hover/content/menus/Menu.java @@ -15,7 +15,7 @@ */ package io.mattcarroll.hover.content.menus; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import java.util.List; diff --git a/hover/src/main/java/io/mattcarroll/hover/content/menus/MenuAction.java b/hover/src/main/java/io/mattcarroll/hover/content/menus/MenuAction.java index 588173c..18e46a5 100755 --- a/hover/src/main/java/io/mattcarroll/hover/content/menus/MenuAction.java +++ b/hover/src/main/java/io/mattcarroll/hover/content/menus/MenuAction.java @@ -16,7 +16,7 @@ package io.mattcarroll.hover.content.menus; import android.content.Context; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import io.mattcarroll.hover.content.Navigator; diff --git a/hover/src/main/java/io/mattcarroll/hover/content/menus/MenuItem.java b/hover/src/main/java/io/mattcarroll/hover/content/menus/MenuItem.java index b44d061..b2930ff 100755 --- a/hover/src/main/java/io/mattcarroll/hover/content/menus/MenuItem.java +++ b/hover/src/main/java/io/mattcarroll/hover/content/menus/MenuItem.java @@ -15,7 +15,7 @@ */ package io.mattcarroll.hover.content.menus; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; /** * Represents a menu item that can act as a composite with submenu items. diff --git a/hover/src/main/java/io/mattcarroll/hover/content/menus/MenuListAdapter.java b/hover/src/main/java/io/mattcarroll/hover/content/menus/MenuListAdapter.java index 9573718..db294d9 100755 --- a/hover/src/main/java/io/mattcarroll/hover/content/menus/MenuListAdapter.java +++ b/hover/src/main/java/io/mattcarroll/hover/content/menus/MenuListAdapter.java @@ -15,7 +15,7 @@ */ package io.mattcarroll.hover.content.menus; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; diff --git a/hover/src/main/java/io/mattcarroll/hover/content/menus/MenuListContent.java b/hover/src/main/java/io/mattcarroll/hover/content/menus/MenuListContent.java index 602a798..bbeb1d3 100755 --- a/hover/src/main/java/io/mattcarroll/hover/content/menus/MenuListContent.java +++ b/hover/src/main/java/io/mattcarroll/hover/content/menus/MenuListContent.java @@ -16,8 +16,8 @@ package io.mattcarroll.hover.content.menus; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import android.view.View; import io.mattcarroll.hover.Content; diff --git a/hover/src/main/java/io/mattcarroll/hover/content/menus/MenuListView.java b/hover/src/main/java/io/mattcarroll/hover/content/menus/MenuListView.java index fd914e1..65fec3c 100755 --- a/hover/src/main/java/io/mattcarroll/hover/content/menus/MenuListView.java +++ b/hover/src/main/java/io/mattcarroll/hover/content/menus/MenuListView.java @@ -16,8 +16,8 @@ package io.mattcarroll.hover.content.menus; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; diff --git a/hover/src/main/java/io/mattcarroll/hover/content/menus/ShowSubmenuMenuAction.java b/hover/src/main/java/io/mattcarroll/hover/content/menus/ShowSubmenuMenuAction.java index 63bf15d..cc5c0fc 100644 --- a/hover/src/main/java/io/mattcarroll/hover/content/menus/ShowSubmenuMenuAction.java +++ b/hover/src/main/java/io/mattcarroll/hover/content/menus/ShowSubmenuMenuAction.java @@ -16,8 +16,8 @@ package io.mattcarroll.hover.content.menus; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import android.view.View; import io.mattcarroll.hover.content.Navigator; diff --git a/hover/src/main/java/io/mattcarroll/hover/content/menus/serialization/MenuActionFactory.java b/hover/src/main/java/io/mattcarroll/hover/content/menus/serialization/MenuActionFactory.java index 01d6243..f4aab66 100644 --- a/hover/src/main/java/io/mattcarroll/hover/content/menus/serialization/MenuActionFactory.java +++ b/hover/src/main/java/io/mattcarroll/hover/content/menus/serialization/MenuActionFactory.java @@ -15,7 +15,7 @@ */ package io.mattcarroll.hover.content.menus.serialization; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import io.mattcarroll.hover.content.menus.Menu; import io.mattcarroll.hover.content.menus.MenuAction; diff --git a/hover/src/main/java/io/mattcarroll/hover/content/menus/serialization/MenuDeserializer.java b/hover/src/main/java/io/mattcarroll/hover/content/menus/serialization/MenuDeserializer.java index 157397c..05b9b96 100644 --- a/hover/src/main/java/io/mattcarroll/hover/content/menus/serialization/MenuDeserializer.java +++ b/hover/src/main/java/io/mattcarroll/hover/content/menus/serialization/MenuDeserializer.java @@ -15,7 +15,7 @@ */ package io.mattcarroll.hover.content.menus.serialization; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import org.json.JSONArray; import org.json.JSONException; diff --git a/hover/src/main/java/io/mattcarroll/hover/content/toolbar/ToolbarNavigator.java b/hover/src/main/java/io/mattcarroll/hover/content/toolbar/ToolbarNavigator.java index 0b608e2..d78f312 100644 --- a/hover/src/main/java/io/mattcarroll/hover/content/toolbar/ToolbarNavigator.java +++ b/hover/src/main/java/io/mattcarroll/hover/content/toolbar/ToolbarNavigator.java @@ -19,9 +19,9 @@ import android.content.res.TypedArray; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; -import android.support.annotation.NonNull; -import android.support.v4.content.ContextCompat; -import android.support.v7.widget.Toolbar; +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.appcompat.widget.Toolbar; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; diff --git a/hover/src/main/java/io/mattcarroll/hover/overlay/OverlayPermission.java b/hover/src/main/java/io/mattcarroll/hover/overlay/OverlayPermission.java index 78b4acc..3e7653c 100644 --- a/hover/src/main/java/io/mattcarroll/hover/overlay/OverlayPermission.java +++ b/hover/src/main/java/io/mattcarroll/hover/overlay/OverlayPermission.java @@ -20,8 +20,8 @@ import android.net.Uri; import android.os.Build; import android.provider.Settings; -import android.support.annotation.NonNull; -import android.support.annotation.RequiresApi; +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; /** * Provides queries and actions that are required for dealing with the user permission to display diff --git a/hover/src/main/java/io/mattcarroll/hover/view/InViewDragger.java b/hover/src/main/java/io/mattcarroll/hover/view/InViewDragger.java index 8a8667f..9deb4d5 100644 --- a/hover/src/main/java/io/mattcarroll/hover/view/InViewDragger.java +++ b/hover/src/main/java/io/mattcarroll/hover/view/InViewDragger.java @@ -17,9 +17,8 @@ import android.graphics.Point; import android.graphics.PointF; -import android.support.annotation.NonNull; -import android.util.Log; -import android.view.MotionEvent; +import android.graphics.Rect; +import androidx.annotation.NonNull; import android.view.View; import android.view.ViewGroup; @@ -29,165 +28,50 @@ /** * {@link Dragger} implementation that works within a {@link ViewGroup}. */ -public class InViewDragger implements Dragger { - +public class InViewDragger extends Dragger { private static final String TAG = "InViewDragger"; private final ViewGroup mContainer; - private final int mTouchAreaDiameter; - private final int mTapTouchSlop; - private boolean mIsActivated; - private boolean mIsDragging; - private boolean mIsDebugMode = false; - private View mDragView; - private DragListener mDragListener; - private PointF mOriginalViewPosition = new PointF(); - private PointF mCurrentViewPosition = new PointF(); - private PointF mOriginalTouchPosition = new PointF(); - - private final View.OnTouchListener mDragTouchListener = new View.OnTouchListener() { - @Override - public boolean onTouch(View view, MotionEvent motionEvent) { - switch (motionEvent.getAction()) { - case MotionEvent.ACTION_DOWN: - Log.d(TAG, "ACTION_DOWN"); - mIsDragging = false; - - mOriginalViewPosition = getDragViewCenterPosition(); - mCurrentViewPosition = new PointF(mOriginalViewPosition.x, mOriginalViewPosition.y); - mOriginalTouchPosition.set(motionEvent.getRawX(), motionEvent.getRawY()); - - mDragListener.onPress(mCurrentViewPosition.x, mCurrentViewPosition.y); - - return true; - case MotionEvent.ACTION_MOVE: - Log.v(TAG, "ACTION_MOVE. motionX: " + motionEvent.getRawX() + ", motionY: " + motionEvent.getRawY()); - float dragDeltaX = motionEvent.getRawX() - mOriginalTouchPosition.x; - float dragDeltaY = motionEvent.getRawY() - mOriginalTouchPosition.y; - mCurrentViewPosition = new PointF( - mOriginalViewPosition.x + dragDeltaX, - mOriginalViewPosition.y + dragDeltaY - ); - - if (mIsDragging || !isTouchWithinSlopOfOriginalTouch(dragDeltaX, dragDeltaY)) { - if (!mIsDragging) { - // Dragging just started - Log.d(TAG, "MOVE Start Drag."); - mIsDragging = true; - mDragListener.onDragStart(mCurrentViewPosition.x, mCurrentViewPosition.y); - } else { - moveDragViewTo(mCurrentViewPosition); - mDragListener.onDragTo(mCurrentViewPosition.x, mCurrentViewPosition.y); - } - } - - return true; - case MotionEvent.ACTION_UP: - if (!mIsDragging) { - Log.d(TAG, "ACTION_UP: Tap."); - mDragListener.onTap(); - } else { - Log.d(TAG, "ACTION_UP: Released from dragging."); - mDragListener.onReleasedAt(mCurrentViewPosition.x, mCurrentViewPosition.y); - } - - return true; - default: - return false; - } - } - }; - public InViewDragger(@NonNull ViewGroup container, int touchAreaDiameter, int touchSlop) { + public InViewDragger(@NonNull ViewGroup container, int touchSlop) { + super(touchSlop); mContainer = container; - mTouchAreaDiameter = touchAreaDiameter; - mTapTouchSlop = touchSlop; } @Override - public void enableDebugMode(boolean isDebugMode) { - mIsDebugMode = isDebugMode; - updateTouchControlViewAppearance(); + public View createTouchView(@NonNull Rect rect) { + View dragView = new View(mContainer.getContext()); + dragView.setId(R.id.hover_drag_view); + final int width = rect.right - rect.left; + final int height = rect.bottom - rect.top; + dragView.setLayoutParams(new ViewGroup.LayoutParams(width, height)); + mContainer.addView(dragView); + return dragView; } @Override - public void activate(@NonNull DragListener dragListener, @NonNull Point dragStartCenterPosition) { - if (!mIsActivated) { - Log.d(TAG, "Activating."); - mIsActivated = true; - mDragListener = dragListener; - createTouchControlView(dragStartCenterPosition); - } + public void destroyTouchView(@NonNull View touchView) { + mContainer.removeView(touchView); } @Override - public void deactivate() { - if (mIsActivated) { - Log.d(TAG, "Deactivating."); - mIsActivated = false; - destroyTouchControlView(); - } - } - - private void createTouchControlView(@NonNull Point dragStartCenterPosition) { - mDragView = new View(mContainer.getContext()); - mDragView.setId(R.id.hover_drag_view); - mDragView.setLayoutParams(new ViewGroup.LayoutParams(mTouchAreaDiameter, mTouchAreaDiameter)); - mDragView.setOnTouchListener(mDragTouchListener); - mContainer.addView(mDragView); - - moveDragViewTo(new PointF(dragStartCenterPosition.x, dragStartCenterPosition.y)); - updateTouchControlViewAppearance(); - } - - private void destroyTouchControlView() { - mContainer.removeView(mDragView); - - mDragView.setOnTouchListener(null); - mDragView = null; - } - - private void updateTouchControlViewAppearance() { - if (null != mDragView) { - if (mIsDebugMode) { - Log.d(TAG, "Making mDragView red: " + mDragView.hashCode()); - mDragView.setBackgroundColor(0x44FF0000); - } else { - mDragView.setBackgroundColor(0x00000000); - } - } - } - - private boolean isTouchWithinSlopOfOriginalTouch(float dx, float dy) { - double distance = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)); - return distance < mTapTouchSlop; - } - - private PointF getDragViewCenterPosition() { - return convertCornerToCenter(new PointF( - mDragView.getX(), - mDragView.getY() - )); - } - - private void moveDragViewTo(PointF centerPosition) { - Log.d(TAG, "Moving drag view (" + mDragView.hashCode() + ") to: " + centerPosition); - PointF cornerPosition = convertCenterToCorner(centerPosition); - mDragView.setX(cornerPosition.x); - mDragView.setY(cornerPosition.y); - } - - private PointF convertCornerToCenter(@NonNull PointF cornerPosition) { + public PointF getTouchViewPosition(@NonNull View touchView) { return new PointF( - cornerPosition.x + (mTouchAreaDiameter / 2), - cornerPosition.y + (mTouchAreaDiameter / 2) + touchView.getX(), + touchView.getY() ); } - private PointF convertCenterToCorner(@NonNull PointF centerPosition) { - return new PointF( - centerPosition.x - (mTouchAreaDiameter / 2), - centerPosition.y - (mTouchAreaDiameter / 2) - ); + @Override + public Point getContainerSize() { + Rect area = new Rect(); + mContainer.getGlobalVisibleRect(area); + return new Point(area.right, area.bottom); + } + + @Override + public void moveTouchViewTo(@NonNull View touchView, @NonNull PointF cornerPosition) { + touchView.setX(cornerPosition.x); + touchView.setY(cornerPosition.y); } } diff --git a/hover/src/main/java/io/mattcarroll/hover/window/HoverMenuService.java b/hover/src/main/java/io/mattcarroll/hover/window/HoverMenuService.java index 92f11fb..21598ba 100755 --- a/hover/src/main/java/io/mattcarroll/hover/window/HoverMenuService.java +++ b/hover/src/main/java/io/mattcarroll/hover/window/HoverMenuService.java @@ -20,8 +20,8 @@ import android.content.Context; import android.content.Intent; import android.os.IBinder; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import android.util.Log; import android.view.WindowManager; diff --git a/hover/src/main/java/io/mattcarroll/hover/window/InWindowDragger.java b/hover/src/main/java/io/mattcarroll/hover/window/InWindowDragger.java index 24a6aad..2c886c2 100755 --- a/hover/src/main/java/io/mattcarroll/hover/window/InWindowDragger.java +++ b/hover/src/main/java/io/mattcarroll/hover/window/InWindowDragger.java @@ -18,9 +18,8 @@ import android.content.Context; import android.graphics.Point; import android.graphics.PointF; -import android.support.annotation.NonNull; -import android.util.Log; -import android.view.MotionEvent; +import android.graphics.Rect; +import androidx.annotation.NonNull; import android.view.View; import io.mattcarroll.hover.Dragger; @@ -28,176 +27,46 @@ /** * {@link Dragger} implementation that works within a {@code Window}. */ -public class InWindowDragger implements Dragger { - +public class InWindowDragger extends Dragger { private static final String TAG = "InWindowDragger"; private final Context mContext; private final WindowViewController mWindowViewController; - private final int mTouchAreaDiameter; - private final float mTapTouchSlop; - private View mDragView; - private Dragger.DragListener mDragListener; - private boolean mIsActivated; - private boolean mIsDragging; - private boolean mIsDebugMode; - - private PointF mOriginalViewPosition = new PointF(); - private PointF mCurrentViewPosition = new PointF(); - private PointF mOriginalTouchPosition = new PointF(); - - private View.OnTouchListener mDragTouchListener = new View.OnTouchListener() { - @Override - public boolean onTouch(View view, MotionEvent motionEvent) { - switch (motionEvent.getAction()) { - case MotionEvent.ACTION_DOWN: - Log.d(TAG, "ACTION_DOWN"); - mIsDragging = false; - - mOriginalViewPosition = getDragViewCenterPosition(); - mCurrentViewPosition = new PointF(mOriginalViewPosition.x, mOriginalViewPosition.y); - mOriginalTouchPosition.set(motionEvent.getRawX(), motionEvent.getRawY()); - - mDragListener.onPress(mCurrentViewPosition.x, mCurrentViewPosition.y); - - return true; - case MotionEvent.ACTION_MOVE: - Log.d(TAG, "ACTION_MOVE. motionX: " + motionEvent.getRawX() + ", motionY: " + motionEvent.getRawY()); - float dragDeltaX = motionEvent.getRawX() - mOriginalTouchPosition.x; - float dragDeltaY = motionEvent.getRawY() - mOriginalTouchPosition.y; - mCurrentViewPosition = new PointF( - mOriginalViewPosition.x + dragDeltaX, - mOriginalViewPosition.y + dragDeltaY - ); - - if (mIsDragging || !isTouchWithinSlopOfOriginalTouch(dragDeltaX, dragDeltaY)) { - if (!mIsDragging) { - // Dragging just started - Log.d(TAG, "MOVE Start Drag."); - mIsDragging = true; - mDragListener.onDragStart(mCurrentViewPosition.x, mCurrentViewPosition.y); - } else { - moveDragViewTo(mCurrentViewPosition); - mDragListener.onDragTo(mCurrentViewPosition.x, mCurrentViewPosition.y); - } - } - return true; - case MotionEvent.ACTION_UP: - Log.d(TAG, "ACTION_UP"); - if (!mIsDragging) { - Log.d(TAG, "Reporting as a tap."); - mDragListener.onTap(); - } else { - Log.d(TAG, "Reporting as a drag release at: " + mCurrentViewPosition); - mDragListener.onReleasedAt(mCurrentViewPosition.x, mCurrentViewPosition.y); - } - - return true; - default: - return false; - } - } - }; - - /** - * Note: {@code view} must already be added to the {@code Window}. - * @param context context - * @param windowViewController windowViewController - * @param tapTouchSlop tapTouchSlop - */ public InWindowDragger(@NonNull Context context, @NonNull WindowViewController windowViewController, - int touchAreaDiameter, - float tapTouchSlop) { + int tapTouchSlop) { + super(tapTouchSlop); mContext = context; mWindowViewController = windowViewController; - mTouchAreaDiameter = touchAreaDiameter; - mTapTouchSlop = tapTouchSlop; - } - - public void activate(@NonNull DragListener dragListener, @NonNull Point dragStartCenterPosition) { - if (!mIsActivated) { - Log.d(TAG, "Activating."); - createTouchControlView(dragStartCenterPosition); - mDragListener = dragListener; - mDragView.setOnTouchListener(mDragTouchListener); - mIsActivated = true; - } - } - - public void deactivate() { - if (mIsActivated) { - Log.d(TAG, "Deactivating."); - mDragView.setOnTouchListener(null); - destroyTouchControlView(); - mIsActivated = false; - } } @Override - public void enableDebugMode(boolean isDebugMode) { - mIsDebugMode = isDebugMode; - updateTouchControlViewAppearance(); + public View createTouchView(@NonNull Rect rect) { + View dragView = new View(mContext); + final int width = rect.right - rect.left; + final int height = rect.bottom - rect.top; + mWindowViewController.addView(width, height, true, dragView); + return dragView; } - private void createTouchControlView(@NonNull final Point dragStartCenterPosition) { - // TODO: define dimen size - mDragView = new View(mContext); - mWindowViewController.addView(mTouchAreaDiameter, mTouchAreaDiameter, true, mDragView); - mWindowViewController.moveViewTo(mDragView, dragStartCenterPosition.x - (mTouchAreaDiameter / 2), dragStartCenterPosition.y - (mTouchAreaDiameter / 2)); - mDragView.setOnTouchListener(mDragTouchListener); - - updateTouchControlViewAppearance(); - } - - private void destroyTouchControlView() { - mWindowViewController.removeView(mDragView); - mDragView = null; - } - - private void updateTouchControlViewAppearance() { - if (null != mDragView) { - if (mIsDebugMode) { - mDragView.setBackgroundColor(0x44FF0000); - } else { - mDragView.setBackgroundColor(0x00000000); - } - } - } - - private boolean isTouchWithinSlopOfOriginalTouch(float dx, float dy) { - double distance = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)); - Log.d(TAG, "Drag distance " + distance + " vs slop allowance " + mTapTouchSlop); - return distance < mTapTouchSlop; - } - - private PointF getDragViewCenterPosition() { - Point cornerPosition = mWindowViewController.getViewPosition(mDragView); - return convertCornerToCenter(new PointF( - cornerPosition.x, - cornerPosition.y - )); + @Override + public void destroyTouchView(@NonNull View touchView) { + mWindowViewController.removeView(touchView); } - private void moveDragViewTo(PointF centerPosition) { - Log.d(TAG, "Center position: " + centerPosition); - PointF cornerPosition = convertCenterToCorner(centerPosition); - Log.d(TAG, "Corner position: " + cornerPosition); - mWindowViewController.moveViewTo(mDragView, (int) cornerPosition.x, (int) cornerPosition.y); + @Override + public PointF getTouchViewPosition(@NonNull View touchView) { + return new PointF(mWindowViewController.getViewPosition(touchView)); } - private PointF convertCornerToCenter(@NonNull PointF cornerPosition) { - return new PointF( - cornerPosition.x + (mDragView.getWidth() / 2), - cornerPosition.y + (mDragView.getHeight() / 2) - ); + @Override + public Point getContainerSize() { + return mWindowViewController.getWindowSize(); } - private PointF convertCenterToCorner(@NonNull PointF centerPosition) { - return new PointF( - centerPosition.x - (mDragView.getWidth() / 2), - centerPosition.y - (mDragView.getHeight() / 2) - ); + @Override + public void moveTouchViewTo(@NonNull View touchView, @NonNull PointF cornerPosition) { + mWindowViewController.moveViewTo(touchView, (int) cornerPosition.x, (int) cornerPosition.y); } } diff --git a/hover/src/main/java/io/mattcarroll/hover/window/WindowViewController.java b/hover/src/main/java/io/mattcarroll/hover/window/WindowViewController.java index b037678..4ccac68 100755 --- a/hover/src/main/java/io/mattcarroll/hover/window/WindowViewController.java +++ b/hover/src/main/java/io/mattcarroll/hover/window/WindowViewController.java @@ -18,7 +18,7 @@ import android.graphics.PixelFormat; import android.graphics.Point; import android.os.Build; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.view.Gravity; import android.view.View; import android.view.WindowManager; @@ -35,6 +35,10 @@ public WindowViewController(@NonNull WindowManager windowManager) { } public void addView(int width, int height, boolean isTouchable, @NonNull View view) { + addViewToWindow(view, buildLayoutParams(width, height, isTouchable)); + } + + private WindowManager.LayoutParams buildLayoutParams(final int width, final int height, final boolean isTouchable) { // If this view is untouchable then add the corresponding flag, otherwise set to zero which // won't have any effect on the OR'ing of flags. int touchableFlag = isTouchable ? 0 : WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; @@ -50,11 +54,13 @@ public void addView(int width, int height, boolean isTouchable, @NonNull View vi WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | touchableFlag, PixelFormat.TRANSLUCENT ); + + params.gravity = Gravity.TOP | Gravity.LEFT; params.x = 0; params.y = 0; - mWindowManager.addView(view, params); + return params; } public void removeView(@NonNull View view) { @@ -70,9 +76,14 @@ public Point getViewPosition(@NonNull View view) { public void moveViewTo(View view, int x, int y) { WindowManager.LayoutParams params = (WindowManager.LayoutParams) view.getLayoutParams(); + if (params == null) { + params = buildLayoutParams(view.getWidth(), view.getHeight(), true); + } + params.x = x; params.y = y; - mWindowManager.updateViewLayout(view, params); + + updateViewLayout(view, params); } public void showView(View view) { @@ -94,14 +105,45 @@ public void hideView(View view) { public void makeTouchable(View view) { WindowManager.LayoutParams params = (WindowManager.LayoutParams) view.getLayoutParams(); + if (params == null) { + params = buildLayoutParams(view.getWidth(), view.getHeight(), true); + } params.flags = params.flags & ~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE & ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; - mWindowManager.updateViewLayout(view, params); + + updateViewLayout(view, params); } public void makeUntouchable(View view) { WindowManager.LayoutParams params = (WindowManager.LayoutParams) view.getLayoutParams(); + if (params == null) { + params = buildLayoutParams(view.getWidth(), view.getHeight(), true); + } params.flags = params.flags | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; - mWindowManager.updateViewLayout(view, params); + + updateViewLayout(view, params); + } + + private void updateViewLayout(final View view, final WindowManager.LayoutParams params) { + try { + mWindowManager.updateViewLayout(view, params); + } catch (IllegalArgumentException e) { + // View is not attached to the window manager + addViewToWindow(view, params); + } + } + + private void addViewToWindow(final View view, final WindowManager.LayoutParams params) { + try { + mWindowManager.addView(view, params); + } catch (WindowManager.BadTokenException e) { + // Permission denied. Cannot add the View to the Window. + } + } + + public Point getWindowSize() { + final Point windowSize = new Point(); + mWindowManager.getDefaultDisplay().getSize(windowSize); + return new Point(windowSize.x, windowSize.y); } } diff --git a/hover/src/main/res/drawable-hdpi/ic_delete.png b/hover/src/main/res/drawable-hdpi/ic_delete.png deleted file mode 100644 index 8189a8a..0000000 Binary files a/hover/src/main/res/drawable-hdpi/ic_delete.png and /dev/null differ diff --git a/hover/src/main/res/drawable/ic_exit.xml b/hover/src/main/res/drawable/ic_exit.xml new file mode 100644 index 0000000..68170a9 --- /dev/null +++ b/hover/src/main/res/drawable/ic_exit.xml @@ -0,0 +1,13 @@ + + + + diff --git a/hover/src/main/res/layout/view_hover_menu_exit.xml b/hover/src/main/res/layout/view_hover_menu_exit.xml index f3cb63b..a7e3f95 100644 --- a/hover/src/main/res/layout/view_hover_menu_exit.xml +++ b/hover/src/main/res/layout/view_hover_menu_exit.xml @@ -1,23 +1,29 @@ - + android:layout_height="match_parent"> + - - \ No newline at end of file + android:background="@drawable/gradient_black_to_transparent" /> + + + + + + + + \ No newline at end of file diff --git a/hover/src/main/res/layout/view_toolbar_navigator.xml b/hover/src/main/res/layout/view_toolbar_navigator.xml index 54d2727..0bda395 100755 --- a/hover/src/main/res/layout/view_toolbar_navigator.xml +++ b/hover/src/main/res/layout/view_toolbar_navigator.xml @@ -6,7 +6,7 @@ android:orientation="vertical" > - 72dp + 48dp 8dp 7dp - 40dp + 72dp 75dp + 32dp + 24dp diff --git a/hoverdemo-helloworld/.gitignore b/hoverdemo-helloworld/.gitignore deleted file mode 100644 index 796b96d..0000000 --- a/hoverdemo-helloworld/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/hoverdemo-helloworld/README.md b/hoverdemo-helloworld/README.md deleted file mode 100644 index b0a80aa..0000000 --- a/hoverdemo-helloworld/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Hello World - -It doesn't take much to get started with your own Hover menu. At a minimum, you need to handle the -following responsibilities: - -1. Get permission to show an overlay (Android M and above). -1. Extend HoverMenuService to show your own Hover menu. -1. Implement a HoverMenu that provides the tabs and content you want in your menu. -1. Send an Intent to your HoverMenuService to start it. diff --git a/hoverdemo-helloworld/build.gradle b/hoverdemo-helloworld/build.gradle deleted file mode 100644 index 6ced383..0000000 --- a/hoverdemo-helloworld/build.gradle +++ /dev/null @@ -1,28 +0,0 @@ -apply plugin: 'com.android.application' - -android { - compileSdkVersion project.compileSdkVersion.toInteger() - buildToolsVersion project.buildToolsVersion - - defaultConfig { - applicationId "org.codecanon.hover.hoverdemo.helloworld" - minSdkVersion 15 - targetSdkVersion project.targetSdkVersion.toInteger() - versionCode 1 - versionName "1.0" - - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } -} - -dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - compile project(':hover') -} diff --git a/hoverdemo-helloworld/proguard-rules.pro b/hoverdemo-helloworld/proguard-rules.pro deleted file mode 100644 index ab601cf..0000000 --- a/hoverdemo-helloworld/proguard-rules.pro +++ /dev/null @@ -1,17 +0,0 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /Users/matt/Library/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/hoverdemo-helloworld/src/main/AndroidManifest.xml b/hoverdemo-helloworld/src/main/AndroidManifest.xml deleted file mode 100644 index 0b9d007..0000000 --- a/hoverdemo-helloworld/src/main/AndroidManifest.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/AllStatesHoverMenuService.java b/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/AllStatesHoverMenuService.java deleted file mode 100644 index 29a5b99..0000000 --- a/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/AllStatesHoverMenuService.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright 2016 Google Inc. All Rights Reserved. - * - * 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. - */ -package io.mattcarroll.hover.hoverdemo.helloworld; - -import android.content.Context; -import android.content.Intent; -import android.os.Handler; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.view.View; -import android.widget.ImageView; - -import org.codecanon.hover.hoverdemo.helloworld.R; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import io.mattcarroll.hover.Content; -import io.mattcarroll.hover.HoverMenu; -import io.mattcarroll.hover.HoverView; -import io.mattcarroll.hover.window.HoverMenuService; - -/** - * Extend {@link HoverMenuService} to get a Hover menu that displays the tabs and content - * in your custom {@link HoverMenu}. - * - * This menu presents a Hover Menu that automatically transitions through each state every few - * seconds. - */ -public class AllStatesHoverMenuService extends HoverMenuService { - - private static final String TAG = "AllStatesHoverMenuService"; - - private Handler mHandler = new Handler(); - private int mNextStateTransition = 0; - - private final List mStateTransitions = Arrays.asList( - new Runnable() { - @Override - public void run() { - getHoverView().expand(); - } - }, - new Runnable() { - @Override - public void run() { - getHoverView().collapse(); - } - }, - new Runnable() { - @Override - public void run() { - getHoverView().close(); - } - }, - new Runnable() { - @Override - public void run() { - getHoverView().collapse(); - } - }, - new Runnable() { - @Override - public void run() { - getHoverView().expand(); - } - }, - new Runnable() { - @Override - public void run() { - getHoverView().close(); - } - } - ); - - @Override - protected void onHoverMenuLaunched(@NonNull Intent intent, @NonNull HoverView hoverView) { - hoverView.setMenu(createHoverMenu()); - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - transitionToNextState(); - - mHandler.postDelayed(this, 3000); - } - }, 3000); - } - - @NonNull - private HoverMenu createHoverMenu() { - return new SingleSectionHoverMenu(getApplicationContext()); - } - - private void transitionToNextState() { - mStateTransitions.get(mNextStateTransition).run(); - mNextStateTransition = mNextStateTransition < mStateTransitions.size() - 1 - ? mNextStateTransition + 1 - : 0; - } - - @Override - protected void onHoverMenuExitingByUserRequest() { - mHandler.removeCallbacksAndMessages(null); - } - - private static class SingleSectionHoverMenu extends HoverMenu { - - private Context mContext; - private Section mSection; - - private SingleSectionHoverMenu(@NonNull Context context) { - mContext = context; - - mSection = new Section( - new SectionId("1"), - createTabView(), - createScreen() - ); - } - - private View createTabView() { - ImageView imageView = new ImageView(mContext); - imageView.setImageResource(R.drawable.tab_background); - imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - return imageView; - } - - private Content createScreen() { - return new HoverMenuScreen(mContext, "Screen 1"); - } - - @Override - public String getId() { - return "singlesectionmenu"; - } - - @Override - public int getSectionCount() { - return 1; - } - - @Nullable - @Override - public Section getSection(int index) { - if (0 == index) { - return mSection; - } else { - return null; - } - } - - @Nullable - @Override - public Section getSection(@NonNull SectionId sectionId) { - if (sectionId.equals(mSection.getId())) { - return mSection; - } else { - return null; - } - } - - @NonNull - @Override - public List

getSections() { - return Collections.singletonList(mSection); - } - } -} diff --git a/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/ChangingMenusHoverMenuService.java b/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/ChangingMenusHoverMenuService.java deleted file mode 100644 index 950ae15..0000000 --- a/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/ChangingMenusHoverMenuService.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2016 Google Inc. All Rights Reserved. - * - * 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. - */ -package io.mattcarroll.hover.hoverdemo.helloworld; - -import android.content.Context; -import android.content.Intent; -import android.os.Handler; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.view.View; -import android.widget.ImageView; - -import org.codecanon.hover.hoverdemo.helloworld.R; - -import java.util.ArrayList; -import java.util.List; - -import io.mattcarroll.hover.Content; -import io.mattcarroll.hover.HoverMenu; -import io.mattcarroll.hover.HoverView; -import io.mattcarroll.hover.window.HoverMenuService; - -/** - * Extend {@link HoverMenuService} to get a Hover menu that displays the tabs and content - * in your custom {@link HoverMenu}. - * - * This HoverMenuService switches menus at runtime. - */ -public class ChangingMenusHoverMenuService extends HoverMenuService { - - private static final String TAG = "ChangingMenusHoverMenuService"; - - private SingleSectionHoverMenu mHoverMenu1; - private SingleSectionHoverMenu mHoverMenu2; - private boolean mShowingMenu1 = true; - private Handler mHandler = new Handler(); - - @Override - public void onCreate() { - super.onCreate(); - mHoverMenu1 = new SingleSectionHoverMenu(getApplicationContext(), "changingmenus", 1, "This is menu 1"); - mHoverMenu2 = new SingleSectionHoverMenu(getApplicationContext(), "changingmenus", 3, "This is menu 2"); - } - - @Override - protected void onHoverMenuLaunched(@NonNull Intent intent, @NonNull HoverView hoverView) { - hoverView.setMenu(createHoverMenu()); - hoverView.collapse(); - - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - switchMenus(); - - mHandler.postDelayed(this, 3000); - } - }, 3000); - } - - @NonNull - private HoverMenu createHoverMenu() { - return mHoverMenu1; - } - - private void switchMenus() { - if (mShowingMenu1) { - getHoverView().setMenu(mHoverMenu2); - } else { - getHoverView().setMenu(mHoverMenu1); - } - mShowingMenu1 = !mShowingMenu1; - } - - @Override - protected void onHoverMenuExitingByUserRequest() { - mHandler.removeCallbacksAndMessages(null); - } - - private static class SingleSectionHoverMenu extends HoverMenu { - - private final String mId; - private Context mContext; - private List
mSections; - - private SingleSectionHoverMenu(@NonNull Context context, - @NonNull String menuId, - int sectionCount, - @NonNull String content) { - mId = menuId; - mContext = context; - - mSections = new ArrayList<>(sectionCount); - for (int i = 1; i <= sectionCount; ++i) { - mSections.add(new Section( - new SectionId(Integer.toString(i)), - createTabView(), - createScreen(content) - )); - } - } - - private View createTabView() { - ImageView imageView = new ImageView(mContext); - imageView.setImageResource(R.drawable.tab_background); - imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - return imageView; - } - - private Content createScreen(@NonNull String content) { - return new HoverMenuScreen(mContext, content); - } - - @Override - public String getId() { - return mId; - } - - @Override - public int getSectionCount() { - return mSections.size(); - } - - @Nullable - @Override - public Section getSection(int index) { - return mSections.get(index); - } - - @Nullable - @Override - public Section getSection(@NonNull SectionId sectionId) { - for (Section section : mSections) { - if (sectionId.equals(section.getId())) { - return section; - } - } - return null; - } - - @NonNull - @Override - public List
getSections() { - return new ArrayList<>(mSections); - } - } -} diff --git a/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/HoverMenuScreen.java b/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/HoverMenuScreen.java deleted file mode 100644 index 066ca37..0000000 --- a/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/HoverMenuScreen.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2016 Google Inc. All Rights Reserved. - * - * 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. - */ -package io.mattcarroll.hover.hoverdemo.helloworld; - -import android.content.Context; -import android.support.annotation.NonNull; -import android.view.Gravity; -import android.view.View; -import android.widget.TextView; - -import io.mattcarroll.hover.Content; - -/** - * A screen that is displayed in our Hello World Hover Menu. - */ -public class HoverMenuScreen implements Content { - - private final Context mContext; - private final String mPageTitle; - private final View mWholeScreen; - - public HoverMenuScreen(@NonNull Context context, @NonNull String pageTitle) { - mContext = context.getApplicationContext(); - mPageTitle = pageTitle; - mWholeScreen = createScreenView(); - } - - @NonNull - private View createScreenView() { - TextView wholeScreen = new TextView(mContext); - wholeScreen.setText("Screen: " + mPageTitle); - wholeScreen.setGravity(Gravity.CENTER); - return wholeScreen; - } - - // Make sure that this method returns the SAME View. It should NOT create a new View each time - // that it is invoked. - @NonNull - @Override - public View getView() { - return mWholeScreen; - } - - @Override - public boolean isFullscreen() { - return true; - } - - @Override - public void onShown() { - // No-op. - } - - @Override - public void onHidden() { - // No-op. - } -} diff --git a/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/MainActivity.java b/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/MainActivity.java deleted file mode 100644 index 726d6e9..0000000 --- a/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/MainActivity.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2016 Google Inc. All Rights Reserved. - * - * 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. - */ -package io.mattcarroll.hover.hoverdemo.helloworld; - -import android.content.Intent; -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.view.View; - -import org.codecanon.hover.hoverdemo.helloworld.R; - -import io.mattcarroll.hover.overlay.OverlayPermission; - -public class MainActivity extends AppCompatActivity { - - private static final int REQUEST_CODE_HOVER_PERMISSION = 1000; - - private boolean mPermissionsRequested = false; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - findViewById(R.id.button_launch_hover_single_section).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - Intent startHoverIntent = new Intent(MainActivity.this, SingleSectionHoverMenuService.class); - startService(startHoverIntent); - } - }); - - findViewById(R.id.button_launch_hover_multi_sections).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - Intent startHoverIntent = new Intent(MainActivity.this, MultipleSectionsHoverMenuService.class); - startService(startHoverIntent); - } - }); - - findViewById(R.id.button_launch_hover_foreground).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - Intent startHoverIntent = new Intent(MainActivity.this, SingleSectionNotificationHoverMenuService.class); - startService(startHoverIntent); - } - }); - - findViewById(R.id.button_launch_hover_changing_sections).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - Intent startHoverIntent = new Intent(MainActivity.this, MutatingSectionsHoverMenuService.class); - startService(startHoverIntent); - } - }); - - findViewById(R.id.button_launch_hover_reordering_sections).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - Intent startHoverIntent = new Intent(MainActivity.this, ReorderingHoverMenuService.class); - startService(startHoverIntent); - } - }); - - findViewById(R.id.button_launch_hover_all_states).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - Intent startHoverIntent = new Intent(MainActivity.this, AllStatesHoverMenuService.class); - startService(startHoverIntent); - } - }); - - findViewById(R.id.button_launch_hover_changing_menus).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - Intent startHoverIntent = new Intent(MainActivity.this, ChangingMenusHoverMenuService.class); - startService(startHoverIntent); - } - }); - - findViewById(R.id.button_launch_hover_menu_then_no_menu).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - Intent startHoverIntent = new Intent(MainActivity.this, MenuThenNoMenuHoverMenuService.class); - startService(startHoverIntent); - } - }); - } - - @Override - protected void onResume() { - super.onResume(); - - // On Android M and above we need to ask the user for permission to display the Hover - // menu within the "alert window" layer. Use OverlayPermission to check for the permission - // and to request it. - if (!mPermissionsRequested && !OverlayPermission.hasRuntimePermissionToDrawOverlay(this)) { - @SuppressWarnings("NewApi") - Intent myIntent = OverlayPermission.createIntentToRequestOverlayPermission(this); - startActivityForResult(myIntent, REQUEST_CODE_HOVER_PERMISSION); - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (REQUEST_CODE_HOVER_PERMISSION == requestCode) { - mPermissionsRequested = true; - } else { - super.onActivityResult(requestCode, resultCode, data); - } - } -} diff --git a/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/MenuThenNoMenuHoverMenuService.java b/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/MenuThenNoMenuHoverMenuService.java deleted file mode 100644 index a216664..0000000 --- a/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/MenuThenNoMenuHoverMenuService.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright 2016 Google Inc. All Rights Reserved. - * - * 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. - */ -package io.mattcarroll.hover.hoverdemo.helloworld; - -import android.content.Context; -import android.content.Intent; -import android.os.Handler; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.view.View; -import android.widget.ImageView; - -import org.codecanon.hover.hoverdemo.helloworld.R; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import io.mattcarroll.hover.Content; -import io.mattcarroll.hover.HoverMenu; -import io.mattcarroll.hover.HoverView; -import io.mattcarroll.hover.window.HoverMenuService; - -/** - * Extend {@link HoverMenuService} to get a Hover menu that displays the tabs and content - * in your custom {@link HoverMenu}. - * - * This menu presents a Hover Menu that automatically transitions from having a menu set, to not - * having a menu set, and does so while going through many different transitions. - */ -public class MenuThenNoMenuHoverMenuService extends HoverMenuService { - - private static final String TAG = "MenuThenNoMenuHoverMenuService"; - - private Handler mHandler = new Handler(); - private int mNextStateTransition = 0; - private HoverMenu mMenu; - - private final List mStateTransitions = Arrays.asList( - new Runnable() { - @Override - public void run() { - // Remove the menu. Should go to closed state. - getHoverView().setMenu(null); - } - }, - new Runnable() { - @Override - public void run() { - // Give the menu back. Should stay closed. - getHoverView().setMenu(mMenu); - } - }, - new Runnable() { - @Override - public void run() { - // Expand the menu from the closed state. - getHoverView().expand(); - } - }, - new Runnable() { - @Override - public void run() { - // Remove the menu. Should go to closed state. - getHoverView().setMenu(null); - } - }, - new Runnable() { - @Override - public void run() { - // Give the menu back. - getHoverView().setMenu(mMenu); - } - }, - new Runnable() { - @Override - public void run() { - // Go to collapsed state. - getHoverView().collapse(); - } - }, - new Runnable() { - @Override - public void run() { - // Expand the menu. - getHoverView().expand(); - } - } - ); - - @Override - protected void onHoverMenuLaunched(@NonNull Intent intent, @NonNull HoverView hoverView) { - hoverView.setMenu(createHoverMenu()); - hoverView.collapse(); - - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - transitionToNextState(); - - mHandler.postDelayed(this, 3000); - } - }, 3000); - } - - @NonNull - private HoverMenu createHoverMenu() { - mMenu = new SingleSectionHoverMenu(getApplicationContext()); - return mMenu; - } - - private void transitionToNextState() { - mStateTransitions.get(mNextStateTransition).run(); - mNextStateTransition = mNextStateTransition < mStateTransitions.size() - 1 - ? mNextStateTransition + 1 - : 0; - } - - @Override - protected void onHoverMenuExitingByUserRequest() { - mHandler.removeCallbacksAndMessages(null); - } - - private static class SingleSectionHoverMenu extends HoverMenu { - - private Context mContext; - private Section mSection; - - private SingleSectionHoverMenu(@NonNull Context context) { - mContext = context; - - mSection = new Section( - new SectionId("1"), - createTabView(), - createScreen() - ); - } - - private View createTabView() { - ImageView imageView = new ImageView(mContext); - imageView.setImageResource(R.drawable.tab_background); - imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - return imageView; - } - - private Content createScreen() { - return new HoverMenuScreen(mContext, "Screen 1"); - } - - @Override - public String getId() { - return "singlesectionmenu"; - } - - @Override - public int getSectionCount() { - return 1; - } - - @Nullable - @Override - public Section getSection(int index) { - if (0 == index) { - return mSection; - } else { - return null; - } - } - - @Nullable - @Override - public Section getSection(@NonNull SectionId sectionId) { - if (sectionId.equals(mSection.getId())) { - return mSection; - } else { - return null; - } - } - - @NonNull - @Override - public List
getSections() { - return Collections.singletonList(mSection); - } - } -} diff --git a/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/MultipleSectionsHoverMenuService.java b/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/MultipleSectionsHoverMenuService.java deleted file mode 100644 index ac1471c..0000000 --- a/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/MultipleSectionsHoverMenuService.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2016 Google Inc. All Rights Reserved. - * - * 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. - */ -package io.mattcarroll.hover.hoverdemo.helloworld; - -import android.content.Context; -import android.content.Intent; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.view.View; -import android.widget.ImageView; - -import org.codecanon.hover.hoverdemo.helloworld.R; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import io.mattcarroll.hover.HoverMenu; -import io.mattcarroll.hover.HoverView; -import io.mattcarroll.hover.window.HoverMenuService; - -/** - * Extend {@link HoverMenuService} to get a Hover menu that displays the tabs and content - * in your custom {@link HoverMenu}. - * - * This demo menu displays multiple sections of content. - */ -public class MultipleSectionsHoverMenuService extends HoverMenuService { - - private static final String TAG = "MultipleSectionsHoverMenuService"; - - @Override - protected void onHoverMenuLaunched(@NonNull Intent intent, @NonNull HoverView hoverView) { - hoverView.setMenu(createHoverMenu()); - hoverView.collapse(); - } - - @NonNull - private HoverMenu createHoverMenu() { - return new MultiSectionHoverMenu(getApplicationContext()); - } - - private static class MultiSectionHoverMenu extends HoverMenu { - - private final Context mContext; - private final List
mSections; - - public MultiSectionHoverMenu(@NonNull Context context) { - mContext = context.getApplicationContext(); - - mSections = Arrays.asList( - new Section( - new SectionId("1"), - createTabView(), - new HoverMenuScreen(mContext, "Screen 1") - ), - new Section( - new SectionId("2"), - createTabView(), - new HoverMenuScreen(mContext, "Screen 2") - ), - new Section( - new SectionId("3"), - createTabView(), - new HoverMenuScreen(mContext, "Screen 3") - ) - ); - } - - private View createTabView() { - ImageView imageView = new ImageView(mContext); - imageView.setImageResource(R.drawable.tab_background); - imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - return imageView; - } - - @Override - public String getId() { - return "multisectionmenu"; - } - - @Override - public int getSectionCount() { - return mSections.size(); - } - - @Nullable - @Override - public Section getSection(int index) { - return mSections.get(index); - } - - @Nullable - @Override - public Section getSection(@NonNull SectionId sectionId) { - for (Section section : mSections) { - if (section.getId().equals(sectionId)) { - return section; - } - } - return null; - } - - @NonNull - @Override - public List
getSections() { - return new ArrayList<>(mSections); - } - } - -} diff --git a/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/MutatingSectionsHoverMenuService.java b/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/MutatingSectionsHoverMenuService.java deleted file mode 100644 index c89ea73..0000000 --- a/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/MutatingSectionsHoverMenuService.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright 2016 Google Inc. All Rights Reserved. - * - * 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. - */ -package io.mattcarroll.hover.hoverdemo.helloworld; - -import android.content.Context; -import android.content.Intent; -import android.os.Handler; -import android.os.Looper; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.view.View; -import android.widget.ImageView; - -import org.codecanon.hover.hoverdemo.helloworld.R; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import io.mattcarroll.hover.HoverMenu; -import io.mattcarroll.hover.HoverView; -import io.mattcarroll.hover.window.HoverMenuService; - -/** - * Extend {@link HoverMenuService} to get a Hover menu that displays the tabs and content - * in your custom {@link HoverMenu}. - * - * This demo menu adds and removes sections at scheduled times to demonstrate how Hover deals with - * content coming and going. - */ -public class MutatingSectionsHoverMenuService extends HoverMenuService { - - private static final String TAG = "MutatingSectionsHoverMenuService"; - - @Override - protected void onHoverMenuLaunched(@NonNull Intent intent, @NonNull HoverView hoverView) { - hoverView.setMenu(createHoverMenu()); - hoverView.collapse(); - } - - @NonNull - private HoverMenu createHoverMenu() { - return new MutatingHoverMenu(getApplicationContext()); - } - - private static class MutatingHoverMenu extends HoverMenu { - - private final Context mContext; - private final List
mSections = new ArrayList<>(); - - private int mNextStep = 0; - private final List mMutationSteps = Arrays.asList( - new Runnable() { - @Override - public void run() { - insertTab(1); - } - }, - new Runnable() { - @Override - public void run() { - insertTab(1); - } - }, - new Runnable() { - @Override - public void run() { - insertTab(1); - } - }, - new Runnable() { - @Override - public void run() { - insertTab(0); - } - }, - new Runnable() { - @Override - public void run() { - insertTab(1); - } - }, - new Runnable() { - @Override - public void run() { - removeTab(3); - } - }, - new Runnable() { - @Override - public void run() { - removeTab(1); - } - }, - new Runnable() { - @Override - public void run() { - removeTab(0); - } - }, - new Runnable() { - @Override - public void run() { - changeTab(1); - } - }, - new Runnable() { - @Override - public void run() { - removeTab(1); - } - }, - new Runnable() { - @Override - public void run() { - changeTab(0); - } - } - ); - - MutatingHoverMenu(@NonNull Context context) { - mContext = context; - - insertTab(0); - - new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { - @Override - public void run() { - mMutationSteps.get(mNextStep).run(); - ++mNextStep; - - if (mNextStep < mMutationSteps.size()) { - new Handler(Looper.getMainLooper()).postDelayed(this, 1000); - } - } - }, 2000); - } - - private View createTabView() { - ImageView imageView = new ImageView(mContext); - imageView.setImageResource(R.drawable.tab_background); - imageView.setScaleType(ImageView.ScaleType.FIT_CENTER); - return imageView; - } - - private View createDifferentTabView() { - ImageView imageView = new ImageView(mContext); - imageView.setImageResource(R.drawable.tab_background_blue); - imageView.setScaleType(ImageView.ScaleType.FIT_CENTER); - return imageView; - } - - @Override - public String getId() { - return "mutatingmenu"; - } - - @Override - public int getSectionCount() { - return mSections.size(); - } - - @Nullable - @Override - public Section getSection(int index) { - return mSections.get(index); - } - - @Nullable - @Override - public Section getSection(@NonNull SectionId sectionId) { - for (Section section : mSections) { - if (section.getId().equals(sectionId)) { - return section; - } - } - return null; - } - - @NonNull - @Override - public List
getSections() { - return new ArrayList<>(mSections); - } - - private void insertTab(int position) { - String id = Integer.toString(mSections.size()); - mSections.add(position, new Section( - new SectionId(id), - createTabView(), - new HoverMenuScreen(mContext, "Screen " + id) - )); - notifyMenuChanged(); - } - - private void changeTab(int position) { - Section oldSection = mSections.get(position); - Section newSection = new Section( - oldSection.getId(), - createDifferentTabView(), - new HoverMenuScreen(mContext, "This is a new screen!") - ); - mSections.remove(position); - mSections.add(position, newSection); - - notifyMenuChanged(); - } - - private void removeTab(int position) { - mSections.remove(position); - notifyMenuChanged(); - } - } -} diff --git a/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/ReorderingHoverMenuService.java b/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/ReorderingHoverMenuService.java deleted file mode 100644 index 178c15b..0000000 --- a/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/ReorderingHoverMenuService.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 2016 Google Inc. All Rights Reserved. - * - * 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. - */ -package io.mattcarroll.hover.hoverdemo.helloworld; - -import android.content.Context; -import android.content.Intent; -import android.os.Handler; -import android.os.Looper; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.view.View; -import android.widget.ImageView; - -import org.codecanon.hover.hoverdemo.helloworld.R; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import io.mattcarroll.hover.HoverMenu; -import io.mattcarroll.hover.HoverView; -import io.mattcarroll.hover.window.HoverMenuService; - -/** - * Extend {@link HoverMenuService} to get a Hover menu that displays the tabs and content - * in your custom {@link HoverMenu}. - * - * This demo menu re-orders the Sections of its Hover Menu at scheduled times to demonstrate how - * Hover handles re-ordering menu Sections. - */ -public class ReorderingHoverMenuService extends HoverMenuService { - - private static final String TAG = "ReorderingHoverMenuService"; - - @Override - protected void onHoverMenuLaunched(@NonNull Intent intent, @NonNull HoverView hoverView) { - hoverView.setMenu(createHoverMenu()); - hoverView.collapse(); - } - - @NonNull - private HoverMenu createHoverMenu() { - return new ReorderingSectionHoverMenu(getApplicationContext()); - } - - private static class ReorderingSectionHoverMenu extends HoverMenu { - - private final Context mContext; - private final List
mSections = new ArrayList<>(); - - private int mNextStep = 0; - private final List mMutationSteps = Arrays.asList( - new Runnable() { - @Override - public void run() { - moveTab(1, 2); - } - }, - new Runnable() { - @Override - public void run() { - moveTab(3, 1); - } - }, - new Runnable() { - @Override - public void run() { - moveTab(2, 3); - } - } - ); - - ReorderingSectionHoverMenu(@NonNull Context context) { - mContext = context; - - insertTab(0); - insertTab(1); - insertTab(2); - insertTab(3); - - new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { - @Override - public void run() { - mMutationSteps.get(mNextStep).run(); - ++mNextStep; - - if (mNextStep < mMutationSteps.size()) { - new Handler(Looper.getMainLooper()).postDelayed(this, 1000); - } - } - }, 5000); - } - - private View createTabView() { - ImageView imageView = new ImageView(mContext); - imageView.setImageResource(R.drawable.tab_background); - imageView.setScaleType(ImageView.ScaleType.FIT_CENTER); - return imageView; - } - - @Override - public String getId() { - return "reorderingmenu"; - } - - @Override - public int getSectionCount() { - return mSections.size(); - } - - @Nullable - @Override - public Section getSection(int index) { - return mSections.get(index); - } - - @Nullable - @Override - public Section getSection(@NonNull SectionId sectionId) { - for (Section section : mSections) { - if (section.getId().equals(sectionId)) { - return section; - } - } - return null; - } - - @NonNull - @Override - public List
getSections() { - return new ArrayList<>(mSections); - } - - private void insertTab(int position) { - String id = Integer.toString(mSections.size()); - mSections.add(position, new Section( - new SectionId(id), - createTabView(), - new HoverMenuScreen(mContext, "Screen " + id) - )); - notifyMenuChanged(); - } - - private void moveTab(int startPosition, int endPosition) { - Section section = mSections.remove(startPosition); - mSections.add(endPosition, section); - notifyMenuChanged(); - } - } -} diff --git a/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/SingleSectionHoverMenuService.java b/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/SingleSectionHoverMenuService.java deleted file mode 100644 index 0dd88c9..0000000 --- a/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/SingleSectionHoverMenuService.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2016 Google Inc. All Rights Reserved. - * - * 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. - */ -package io.mattcarroll.hover.hoverdemo.helloworld; - -import android.content.Context; -import android.content.Intent; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.view.View; -import android.widget.ImageView; - -import org.codecanon.hover.hoverdemo.helloworld.R; - -import java.util.Collections; -import java.util.List; - -import io.mattcarroll.hover.Content; -import io.mattcarroll.hover.HoverMenu; -import io.mattcarroll.hover.HoverView; -import io.mattcarroll.hover.window.HoverMenuService; - -/** - * Extend {@link HoverMenuService} to get a Hover menu that displays the tabs and content - * in your custom {@link HoverMenu}. - * - * This menu presents the simplest possible Hover Menu, a menu with a single Section. - */ -public class SingleSectionHoverMenuService extends HoverMenuService { - - private static final String TAG = "SingleSectionHoverMenuService"; - - @Override - protected void onHoverMenuLaunched(@NonNull Intent intent, @NonNull HoverView hoverView) { - hoverView.setMenu(createHoverMenu()); - hoverView.collapse(); - } - - @NonNull - private HoverMenu createHoverMenu() { - return new SingleSectionHoverMenu(getApplicationContext()); - } - - private static class SingleSectionHoverMenu extends HoverMenu { - - private Context mContext; - private Section mSection; - - private SingleSectionHoverMenu(@NonNull Context context) { - mContext = context; - - mSection = new Section( - new SectionId("1"), - createTabView(), - createScreen() - ); - } - - private View createTabView() { - ImageView imageView = new ImageView(mContext); - imageView.setImageResource(R.drawable.tab_background); - imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - return imageView; - } - - private Content createScreen() { - return new HoverMenuScreen(mContext, "Screen 1"); - } - - @Override - public String getId() { - return "singlesectionmenu"; - } - - @Override - public int getSectionCount() { - return 1; - } - - @Nullable - @Override - public Section getSection(int index) { - if (0 == index) { - return mSection; - } else { - return null; - } - } - - @Nullable - @Override - public Section getSection(@NonNull SectionId sectionId) { - if (sectionId.equals(mSection.getId())) { - return mSection; - } else { - return null; - } - } - - @NonNull - @Override - public List
getSections() { - return Collections.singletonList(mSection); - } - } -} diff --git a/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/SingleSectionNotificationHoverMenuService.java b/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/SingleSectionNotificationHoverMenuService.java deleted file mode 100644 index 4be998d..0000000 --- a/hoverdemo-helloworld/src/main/java/io/mattcarroll/hover/hoverdemo/helloworld/SingleSectionNotificationHoverMenuService.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2016 Google Inc. All Rights Reserved. - * - * 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. - */ -package io.mattcarroll.hover.hoverdemo.helloworld; - -import android.app.Notification; -import android.content.Context; -import android.content.Intent; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.app.NotificationCompat; -import android.view.View; -import android.widget.ImageView; - -import org.codecanon.hover.hoverdemo.helloworld.R; - -import java.util.Collections; -import java.util.List; - -import io.mattcarroll.hover.Content; -import io.mattcarroll.hover.HoverMenu; -import io.mattcarroll.hover.HoverView; -import io.mattcarroll.hover.window.HoverMenuService; - -/** - * Extend {@link HoverMenuService} to get a Hover menu that displays the tabs and content - * in your custom {@link HoverMenu}. - * - * This menu presents the simplest possible Hover Menu, a menu with a single Section. This menu - * also runs as a foreground Service by providing a notification. - */ -public class SingleSectionNotificationHoverMenuService extends HoverMenuService { - - private static final String TAG = "SingleSectionNotificationHoverMenuService"; - - @Override - protected int getForegroundNotificationId() { - return 1000; - } - - @Nullable - @Override - protected Notification getForegroundNotification() { - return new NotificationCompat.Builder(this) - .setSmallIcon(R.drawable.tab_background) - .setContentTitle("Hover Menu") - .setContentText("Hover is running in a foreground Service.") - .build(); - } - - @Override - protected void onHoverMenuLaunched(@NonNull Intent intent, @NonNull HoverView hoverView) { - hoverView.setMenu(createHoverMenu()); - hoverView.collapse(); - } - - @NonNull - private HoverMenu createHoverMenu() { - return new SingleSectionHoverMenu(getApplicationContext()); - } - - private static class SingleSectionHoverMenu extends HoverMenu { - - private Context mContext; - private Section mSection; - - private SingleSectionHoverMenu(@NonNull Context context) { - mContext = context; - - mSection = new Section( - new SectionId("1"), - createTabView(), - createScreen() - ); - } - - private View createTabView() { - ImageView imageView = new ImageView(mContext); - imageView.setImageResource(R.drawable.tab_background); - imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - return imageView; - } - - private Content createScreen() { - return new HoverMenuScreen(mContext, "Notice the foreground Service notification?"); - } - - @Override - public String getId() { - return "singlesectionmenu_foreground"; - } - - @Override - public int getSectionCount() { - return 1; - } - - @Nullable - @Override - public Section getSection(int index) { - if (0 == index) { - return mSection; - } else { - return null; - } - } - - @Nullable - @Override - public Section getSection(@NonNull SectionId sectionId) { - if (sectionId.equals(mSection.getId())) { - return mSection; - } else { - return null; - } - } - - @NonNull - @Override - public List
getSections() { - return Collections.singletonList(mSection); - } - } -} diff --git a/hoverdemo-helloworld/src/main/res/drawable-xhdpi/tab_background.png b/hoverdemo-helloworld/src/main/res/drawable-xhdpi/tab_background.png deleted file mode 100644 index ba00401..0000000 Binary files a/hoverdemo-helloworld/src/main/res/drawable-xhdpi/tab_background.png and /dev/null differ diff --git a/hoverdemo-helloworld/src/main/res/drawable-xhdpi/tab_background_blue.png b/hoverdemo-helloworld/src/main/res/drawable-xhdpi/tab_background_blue.png deleted file mode 100644 index c958182..0000000 Binary files a/hoverdemo-helloworld/src/main/res/drawable-xhdpi/tab_background_blue.png and /dev/null differ diff --git a/hoverdemo-helloworld/src/main/res/drawable-xhdpi/tab_background_pink.png b/hoverdemo-helloworld/src/main/res/drawable-xhdpi/tab_background_pink.png deleted file mode 100644 index d6fcb02..0000000 Binary files a/hoverdemo-helloworld/src/main/res/drawable-xhdpi/tab_background_pink.png and /dev/null differ diff --git a/hoverdemo-helloworld/src/main/res/layout/activity_main.xml b/hoverdemo-helloworld/src/main/res/layout/activity_main.xml deleted file mode 100644 index 6637d91..0000000 --- a/hoverdemo-helloworld/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - - - -