Add: floating window, layout inspector

This commit is contained in:
hyb1996
2017-03-12 23:10:58 +08:00
parent a211d208d0
commit e81b661974
164 changed files with 7045 additions and 1206 deletions

View 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;
}
}
}

View 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);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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() {
}
}

View 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;
}
}
}
}