Add: floating window, layout inspector
This commit is contained in:
71
app/src/main/java/com/stardust/hover/HoverMenuBuilder.java
Normal file
71
app/src/main/java/com/stardust/hover/HoverMenuBuilder.java
Normal file
@@ -0,0 +1,71 @@
|
||||
package com.stardust.hover;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import io.mattcarroll.hover.HoverMenu;
|
||||
import io.mattcarroll.hover.HoverMenuAdapter;
|
||||
import io.mattcarroll.hover.Navigator;
|
||||
import io.mattcarroll.hover.defaulthovermenu.view.ViewHoverMenu;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/3/11.
|
||||
*/
|
||||
|
||||
public class HoverMenuBuilder {
|
||||
|
||||
public static final int DISPLAY_MODE_WINDOW = 1; // Display directly in a window.
|
||||
public static final int DISPLAY_MODE_VIEW = 2; // Display within View hierarchy.
|
||||
|
||||
private Context mContext;
|
||||
private int mDisplayMode = DISPLAY_MODE_WINDOW;
|
||||
private WindowManager mWindowManager;
|
||||
private Navigator mNavigator;
|
||||
private HoverMenuAdapter mAdapter;
|
||||
private String mSavedVisualState = null;
|
||||
|
||||
public HoverMenuBuilder(@NonNull Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public HoverMenuBuilder displayWithinWindow() {
|
||||
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
|
||||
mDisplayMode = DISPLAY_MODE_WINDOW;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HoverMenuBuilder displayWithinView(@NonNull ViewGroup container) {
|
||||
mDisplayMode = DISPLAY_MODE_VIEW;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HoverMenuBuilder useNavigator(@Nullable Navigator navigator) {
|
||||
mNavigator = navigator;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HoverMenuBuilder useAdapter(@Nullable HoverMenuAdapter adapter) {
|
||||
mAdapter = adapter;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HoverMenuBuilder restoreVisualState(@NonNull String visualState) {
|
||||
mSavedVisualState = visualState;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HoverMenu build() {
|
||||
if (DISPLAY_MODE_WINDOW == mDisplayMode) {
|
||||
WindowHoverMenu windowHoverMenu = new WindowHoverMenu(mContext, mWindowManager, mNavigator, mSavedVisualState);
|
||||
windowHoverMenu.setAdapter(mAdapter);
|
||||
return windowHoverMenu;
|
||||
} else {
|
||||
ViewHoverMenu viewHoverMenu = new ViewHoverMenu(mContext);
|
||||
viewHoverMenu.setAdapter(mAdapter);
|
||||
return viewHoverMenu;
|
||||
}
|
||||
}
|
||||
}
|
||||
129
app/src/main/java/com/stardust/hover/HoverMenuService.java
Normal file
129
app/src/main/java/com/stardust/hover/HoverMenuService.java
Normal file
@@ -0,0 +1,129 @@
|
||||
package com.stardust.hover;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.IBinder;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import io.mattcarroll.hover.HoverMenu;
|
||||
import io.mattcarroll.hover.HoverMenuAdapter;
|
||||
import io.mattcarroll.hover.Navigator;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/3/11.
|
||||
*/
|
||||
|
||||
public abstract class HoverMenuService extends Service {
|
||||
|
||||
private static final String TAG = "HoverMenuService";
|
||||
|
||||
private static final String PREF_FILE = "hover_menu";
|
||||
private static final String PREF_HOVER_MENU_VISUAL_STATE = "hover_menu_visual_state";
|
||||
|
||||
|
||||
private HoverMenu mHoverMenu;
|
||||
private boolean mIsRunning;
|
||||
private SharedPreferences mPrefs;
|
||||
private HoverMenu.OnExitListener mWindowHoverMenuMenuExitListener = new HoverMenu.OnExitListener() {
|
||||
@Override
|
||||
public void onExitByUserRequest() {
|
||||
Log.d(TAG, "Menu exit requested.");
|
||||
savePreferredLocation();
|
||||
mHoverMenu.hide();
|
||||
onHoverMenuExitingByUserRequest();
|
||||
stopSelf();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
Log.d(TAG, "onCreate()");
|
||||
mPrefs = getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
|
||||
|
||||
mHoverMenu = new HoverMenuBuilder(getContextForHoverMenu())
|
||||
.displayWithinWindow()
|
||||
.useNavigator(createNavigator())
|
||||
.useAdapter(createHoverMenuAdapter())
|
||||
.restoreVisualState(loadPreferredLocation())
|
||||
.build();
|
||||
mHoverMenu.addOnExitListener(mWindowHoverMenuMenuExitListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (!mIsRunning) {
|
||||
Log.d(TAG, "onStartCommand() - showing Hover menu.");
|
||||
mIsRunning = true;
|
||||
mHoverMenu.show();
|
||||
}
|
||||
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
Log.d(TAG, "onDestroy()");
|
||||
mHoverMenu.hide();
|
||||
mIsRunning = false;
|
||||
}
|
||||
|
||||
|
||||
public HoverMenu getHoverMenu() {
|
||||
return mHoverMenu;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for subclasses to return a custom Context to be used in the creation of the {@code HoverMenu}.
|
||||
* For example, subclasses might choose to provide a ContextThemeWrapper.
|
||||
*
|
||||
* @return context for HoverMenu initialization
|
||||
*/
|
||||
protected Context getContextForHoverMenu() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses can use this hook method to return a customized {@link Navigator} to be used
|
||||
* throughout the entire Hover menu. This {@link Navigator} will be used for every tab in the
|
||||
* Hover menu, so only supply a {@code Navigator} if you truly want every screen to display it.
|
||||
* <p>
|
||||
* If you want only a portion of the screens in the Hover menu to look different, then consider
|
||||
* using composition of {@link NavigatorContent} to achieve the desired effect. For example,
|
||||
* if you want a {@code Toolbar} to appear on one or more screens, consider placing your content
|
||||
* within a {@link ToolbarNavigatorContent}, and then add the {@code ToolbarNavigatorContent}
|
||||
* as the content of the default {@code Navigator}.
|
||||
*
|
||||
* @return Custom Navigator to use on every screen in the Hover menu
|
||||
*/
|
||||
protected Navigator createNavigator() {
|
||||
return null; // Subclasses can override this to provide a custom Navigator.
|
||||
}
|
||||
|
||||
abstract protected HoverMenuAdapter createHoverMenuAdapter();
|
||||
|
||||
/**
|
||||
* Hook method for subclasses to take action when the user exits the HoverMenu. This method runs
|
||||
* just before this {@code HoverMenuService} calls {@code stopSelf()}.
|
||||
*/
|
||||
protected void onHoverMenuExitingByUserRequest() {
|
||||
// Hook for subclasses.
|
||||
}
|
||||
|
||||
private void savePreferredLocation() {
|
||||
String memento = mHoverMenu.getVisualState();
|
||||
mPrefs.edit().putString(PREF_HOVER_MENU_VISUAL_STATE, memento).apply();
|
||||
}
|
||||
|
||||
private String loadPreferredLocation() {
|
||||
return mPrefs.getString(PREF_HOVER_MENU_VISUAL_STATE, null);
|
||||
}
|
||||
}
|
||||
1115
app/src/main/java/com/stardust/hover/HoverMenuView.java
Normal file
1115
app/src/main/java/com/stardust/hover/HoverMenuView.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,30 @@
|
||||
package com.stardust.hover;
|
||||
|
||||
import io.mattcarroll.hover.defaulthovermenu.*;
|
||||
import io.mattcarroll.hover.defaulthovermenu.HoverMenuView;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/3/12.
|
||||
*/
|
||||
|
||||
public class SimpleHoverMenuTransitionListener implements HoverMenuView.HoverMenuTransitionListener {
|
||||
@Override
|
||||
public void onCollapsing() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCollapsed() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExpanding() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExpanded() {
|
||||
|
||||
}
|
||||
}
|
||||
254
app/src/main/java/com/stardust/hover/WindowHoverMenu.java
Normal file
254
app/src/main/java/com/stardust/hover/WindowHoverMenu.java
Normal file
@@ -0,0 +1,254 @@
|
||||
package com.stardust.hover;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.PointF;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import io.mattcarroll.hover.HoverMenu;
|
||||
import io.mattcarroll.hover.HoverMenuAdapter;
|
||||
import io.mattcarroll.hover.Navigator;
|
||||
import io.mattcarroll.hover.defaulthovermenu.HoverMenuView;
|
||||
import io.mattcarroll.hover.defaulthovermenu.window.InWindowDragger;
|
||||
import io.mattcarroll.hover.defaulthovermenu.window.WindowViewController;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/3/11.
|
||||
*/
|
||||
|
||||
public class WindowHoverMenu implements HoverMenu {
|
||||
|
||||
private HoverMenuView.HoverMenuTransitionListener mHoverMenuTransitionListener;
|
||||
|
||||
private static final String TAG = "WindowHoverMenu";
|
||||
|
||||
public WindowViewController getWindowViewController() {
|
||||
return mWindowViewController;
|
||||
}
|
||||
|
||||
private WindowViewController mWindowViewController; // Shows/hides/positions Views in a Window.
|
||||
|
||||
|
||||
private HoverMenuView mHoverMenuView; // The visual presentation of the Hover menu.
|
||||
private boolean mIsShowingHoverMenu; // Are we currently display mHoverMenuView?
|
||||
private boolean mIsInDragMode; // If we're not in drag mode then we're in menu mode.
|
||||
private Set<OnExitListener> mOnExitListeners = new HashSet<>();
|
||||
|
||||
private HoverMenuView.HoverMenuTransitionListener mHoverMenuTransitionListenerProxy = new HoverMenuView.HoverMenuTransitionListener() {
|
||||
@Override
|
||||
public void onCollapsing() {
|
||||
if (mHoverMenuTransitionListener != null)
|
||||
mHoverMenuTransitionListener.onCollapsing();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCollapsed() {
|
||||
mIsInDragMode = true;
|
||||
// When collapsed, we make mHoverMenuView untouchable so that the WindowDragWatcher can
|
||||
// take over. We do this so that touch events outside the drag area can propagate to
|
||||
// applications on screen.
|
||||
mWindowViewController.makeUntouchable(mHoverMenuView);
|
||||
if (mHoverMenuTransitionListener != null)
|
||||
mHoverMenuTransitionListener.onCollapsed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExpanding() {
|
||||
mIsInDragMode = false;
|
||||
if (mHoverMenuTransitionListener != null)
|
||||
mHoverMenuTransitionListener.onExpanding();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExpanded() {
|
||||
mWindowViewController.makeTouchable(mHoverMenuView);
|
||||
if (mHoverMenuTransitionListener != null)
|
||||
mHoverMenuTransitionListener.onExpanded();
|
||||
}
|
||||
};
|
||||
|
||||
private HoverMenuView.HoverMenuExitRequestListener mMenuExitRequestListener = new HoverMenuView.HoverMenuExitRequestListener() {
|
||||
@Override
|
||||
public void onExitRequested() {
|
||||
hide();
|
||||
}
|
||||
};
|
||||
|
||||
public WindowHoverMenu(@NonNull Context context, @NonNull WindowManager windowManager, @Nullable Navigator navigator, @Nullable String savedVisualState) {
|
||||
mWindowViewController = new WindowViewController(windowManager);
|
||||
|
||||
InWindowDragger inWindowDragger = new InWindowDragger(
|
||||
context,
|
||||
mWindowViewController,
|
||||
ViewConfiguration.get(context).getScaledTouchSlop()
|
||||
);
|
||||
|
||||
PointF anchorState = new PointF(2, 0.5f); // Default to right side, half way down. See CollapsedMenuAnchor.
|
||||
if (null != savedVisualState) {
|
||||
try {
|
||||
VisualStateMemento visualStateMemento = VisualStateMemento.fromJsonString(savedVisualState);
|
||||
anchorState.set(visualStateMemento.getAnchorSide(), visualStateMemento.getNormalizedPositionY());
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
mHoverMenuView = new HoverMenuView(context, navigator, inWindowDragger, anchorState);
|
||||
mHoverMenuView.setHoverMenuExitRequestListener(mMenuExitRequestListener);
|
||||
mHoverMenuView.setHoverMenuTransitionListener(mHoverMenuTransitionListenerProxy);
|
||||
}
|
||||
|
||||
|
||||
public void setHoverMenuTransitionListener(HoverMenuView.HoverMenuTransitionListener hoverMenuTransitionListener) {
|
||||
mHoverMenuTransitionListener = hoverMenuTransitionListener;
|
||||
}
|
||||
|
||||
public HoverMenuView getHoverMenuView() {
|
||||
return mHoverMenuView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVisualState() {
|
||||
PointF anchor = mHoverMenuView.getAnchorState();
|
||||
return new VisualStateMemento((int) anchor.x, anchor.y).toJsonString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreVisualState(@NonNull String savedVisualState) {
|
||||
try {
|
||||
VisualStateMemento memento = VisualStateMemento.fromJsonString(savedVisualState);
|
||||
mHoverMenuView.setAnchorState(new PointF(memento.getAnchorSide(), memento.getNormalizedPositionY()));
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAdapter(@Nullable HoverMenuAdapter adapter) {
|
||||
mHoverMenuView.setAdapter(adapter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes and displays the Hover menu. To destroy and remove the Hover menu, use {@link #hide()}.
|
||||
*/
|
||||
@Override
|
||||
public void show() {
|
||||
if (!mIsShowingHoverMenu) {
|
||||
mWindowViewController.addView(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, false, mHoverMenuView);
|
||||
|
||||
// Sync our control state with the HoverMenuView state.
|
||||
if (mHoverMenuView.isExpanded()) {
|
||||
mWindowViewController.makeTouchable(mHoverMenuView);
|
||||
} else {
|
||||
collapseMenu();
|
||||
}
|
||||
|
||||
mIsShowingHoverMenu = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exits the Hover menu system. This method is the inverse of {@link #show()}.
|
||||
*/
|
||||
@Override
|
||||
public void hide() {
|
||||
if (mIsShowingHoverMenu) {
|
||||
mIsShowingHoverMenu = false;
|
||||
|
||||
// Notify our exit listeners that we're exiting.
|
||||
notifyOnExitListeners();
|
||||
|
||||
// Cleanup the control structures and Views.
|
||||
mWindowViewController.removeView(mHoverMenuView);
|
||||
mHoverMenuView.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands the Hover menu to show all of its tabs and a content area for the selected tab. To
|
||||
* collapse the menu down a single active tab, use {@link #collapseMenu()}.
|
||||
*/
|
||||
@Override
|
||||
public void expandMenu() {
|
||||
if (mIsInDragMode) {
|
||||
mHoverMenuView.expand();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapses the Hover menu down to its single active tab and allows the tab to be dragged
|
||||
* around the display. This method is the inverse of {@link #expandMenu()}.
|
||||
*/
|
||||
@Override
|
||||
public void collapseMenu() {
|
||||
if (!mIsInDragMode) {
|
||||
mHoverMenuView.collapse();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addOnExitListener(@NonNull OnExitListener onExitListener) {
|
||||
mOnExitListeners.add(onExitListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeOnExitListener(@NonNull OnExitListener onExitListener) {
|
||||
mOnExitListeners.remove(onExitListener);
|
||||
}
|
||||
|
||||
private void notifyOnExitListeners() {
|
||||
for (OnExitListener listener : mOnExitListeners) {
|
||||
listener.onExitByUserRequest();
|
||||
}
|
||||
}
|
||||
|
||||
private static class VisualStateMemento {
|
||||
|
||||
private static final String JSON_KEY_ANCHOR_SIDE = "anchor_side";
|
||||
private static final String JSON_KEY_NORMALIZED_POSITION_Y = "normalized_position_y";
|
||||
|
||||
public static VisualStateMemento fromJsonString(@NonNull String jsonString) throws JSONException {
|
||||
JSONObject jsonObject = new JSONObject(jsonString);
|
||||
int anchorSide = jsonObject.getInt(JSON_KEY_ANCHOR_SIDE);
|
||||
float normalizedPositionY = (float) jsonObject.getDouble(JSON_KEY_NORMALIZED_POSITION_Y);
|
||||
return new VisualStateMemento(anchorSide, normalizedPositionY);
|
||||
}
|
||||
|
||||
private int mAnchorSide;
|
||||
private float mNormalizedPositionY;
|
||||
|
||||
public VisualStateMemento(int anchorSide, float normalizedPositionY) {
|
||||
mAnchorSide = anchorSide;
|
||||
mNormalizedPositionY = normalizedPositionY;
|
||||
}
|
||||
|
||||
public int getAnchorSide() {
|
||||
return mAnchorSide;
|
||||
}
|
||||
|
||||
public float getNormalizedPositionY() {
|
||||
return mNormalizedPositionY;
|
||||
}
|
||||
|
||||
public String toJsonString() {
|
||||
try {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put(JSON_KEY_ANCHOR_SIDE, mAnchorSide);
|
||||
jsonObject.put(JSON_KEY_NORMALIZED_POSITION_Y, mNormalizedPositionY);
|
||||
return jsonObject.toString();
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user