Back to project page Roundr.
The source code is released under:
Copyright (C) 2013 Mohammad Adib Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the te...
If you think the Android project Roundr listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
package wei.mark.standout.ui; /*from w w w . j a v a 2 s .c o m*/ import java.util.LinkedList; import java.util.Queue; import wei.mark.standout.R; import wei.mark.standout.StandOutWindow; import wei.mark.standout.StandOutWindow.StandOutLayoutParams; import wei.mark.standout.Utils; import wei.mark.standout.constants.StandOutFlags; import android.content.Context; import android.os.Bundle; import android.util.DisplayMetrics; import android.util.Log; import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.PopupWindow; import android.widget.TextView; /** * Special view that represents a floating window. * * @author Mark Wei <markwei@gmail.com> Edited by: Mohammad Adib * */ public class Window extends FrameLayout { public static final int VISIBILITY_GONE = 0; public static final int VISIBILITY_VISIBLE = 1; public static final int VISIBILITY_TRANSITION = 2; static final String TAG = "Window"; /** * Class of the window, indicating which application the window belongs to. */ public Class<? extends StandOutWindow> cls; /** * Id of the window. */ public int id; /** * Whether the window is shown, hidden/closed, or in transition. */ public int visibility; /** * Whether the window is focused. */ public boolean focused; /** * Original params from {@link StandOutWindow#getParams(int, Window)}. */ public StandOutLayoutParams originalParams; /** * Original flags from {@link StandOutWindow#getFlags(int)}. */ public int flags; /** * Touch information of the window. */ public TouchInfo touchInfo; /** * Data attached to the window. */ public Bundle data; /** * Context of the window. */ private final StandOutWindow mContext; private LayoutInflater mLayoutInflater; public Window(Context context) { super(context); mContext = null; } public Window(final StandOutWindow context, final int id) { super(context); context.setTheme(context.getThemeStyle()); mContext = context; mLayoutInflater = LayoutInflater.from(context); this.cls = context.getClass(); this.id = id; this.originalParams = context.getParams(id, this); this.flags = context.getFlags(id); this.touchInfo = new TouchInfo(); touchInfo.ratio = (float) originalParams.width / originalParams.height; this.data = new Bundle(); // create the window contents View content; FrameLayout body; if (Utils.isSet(flags, StandOutFlags.FLAG_DECORATION_SYSTEM)) { // requested system window decorations content = getSystemDecorations(); body = (FrameLayout) content.findViewById(R.id.body); } else { // did not request decorations. will provide own implementation content = new FrameLayout(context); content.setId(R.id.content); body = (FrameLayout) content; } addView(content); body.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // pass all touch events to the implementation boolean consumed = false; // handle move and bring to front consumed = context.onTouchHandleMove(id, Window.this, v, event) || consumed; // alert implementation consumed = context.onTouchBody(id, Window.this, v, event) || consumed; return consumed; } }); // attach the view corresponding to the id from the // implementation context.createAndAttachView(id, body); // make sure the implementation attached the view if (body.getChildCount() == 0) { throw new RuntimeException("You must attach your view to the given frame in createAndAttachView()"); } // implement StandOut specific workarounds if (!Utils.isSet(flags, StandOutFlags.FLAG_FIX_COMPATIBILITY_ALL_DISABLE)) { fixCompatibility(body); } // implement StandOut specific additional functionality if (!Utils.isSet(flags, StandOutFlags.FLAG_ADD_FUNCTIONALITY_ALL_DISABLE)) { addFunctionality(body); } // attach the existing tag from the frame to the window setTag(body.getTag()); } @Override public boolean onInterceptTouchEvent(MotionEvent event) { StandOutLayoutParams params = getLayoutParams(); // focus window if (event.getAction() == MotionEvent.ACTION_DOWN) { if (mContext.getFocusedWindow() != this) { mContext.focus(id); } } // multitouch if (event.getPointerCount() >= 2 && Utils.isSet(flags, StandOutFlags.FLAG_WINDOW_PINCH_RESIZE_ENABLE) && (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN) { touchInfo.scale = 1; touchInfo.dist = -1; touchInfo.firstWidth = params.width; touchInfo.firstHeight = params.height; return true; } return false; } @Override public boolean onTouchEvent(MotionEvent event) { // handle touching outside switch (event.getAction()) { case MotionEvent.ACTION_OUTSIDE: // unfocus window if (mContext.getFocusedWindow() == this) { mContext.unfocus(this); } // notify implementation that ACTION_OUTSIDE occurred mContext.onTouchBody(id, this, this, event); break; } // handle multitouch if (event.getPointerCount() >= 2 && Utils.isSet(flags, StandOutFlags.FLAG_WINDOW_PINCH_RESIZE_ENABLE)) { // 2 fingers or more float x0 = event.getX(0); float y0 = event.getY(0); float x1 = event.getX(1); float y1 = event.getY(1); double dist = Math.sqrt(Math.pow(x0 - x1, 2) + Math.pow(y0 - y1, 2)); switch (event.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_MOVE: if (touchInfo.dist == -1) { touchInfo.dist = dist; } touchInfo.scale *= dist / touchInfo.dist; touchInfo.dist = dist; // scale the window with anchor point set to middle edit().setAnchorPoint(.5f, .5f).setSize((int) (touchInfo.firstWidth * touchInfo.scale), (int) (touchInfo.firstHeight * touchInfo.scale)).commit(); break; } } return true; } @Override public boolean dispatchKeyEvent(KeyEvent event) { if (mContext.onKeyEvent(id, this, event)) { Log.d(TAG, "Window " + id + " key event " + event + " cancelled by implementation."); return false; } if (event.getAction() == KeyEvent.ACTION_UP) { switch (event.getKeyCode()) { case KeyEvent.KEYCODE_BACK: mContext.unfocus(this); return true; } } return super.dispatchKeyEvent(event); } /** * Request or remove the focus from this window. * * @param focus * Whether we want to gain or lose focus. * @return True if focus changed successfully, false if it failed. */ public boolean onFocus(boolean focus) { if (!Utils.isSet(flags, StandOutFlags.FLAG_WINDOW_FOCUSABLE_DISABLE)) { // window is focusable if (focus == focused) { // window already focused/unfocused return false; } focused = focus; // alert callbacks and cancel if instructed if (mContext.onFocusChange(id, this, focus)) { Log.d(TAG, "Window " + id + " focus change " + (focus ? "(true)" : "(false)") + " cancelled by implementation."); focused = !focus; return false; } if (!Utils.isSet(flags, StandOutFlags.FLAG_WINDOW_FOCUS_INDICATOR_DISABLE)) { // change visual state View content = findViewById(R.id.content); if (focus) { // gaining focus content.setBackgroundResource(R.drawable.border_focused); } else { // losing focus if (Utils.isSet(flags, StandOutFlags.FLAG_DECORATION_SYSTEM)) { // system decorations content.setBackgroundResource(R.drawable.border); } else { // no decorations content.setBackgroundResource(0); } } } // set window manager params StandOutLayoutParams params = getLayoutParams(); params.setFocusFlag(focus); mContext.updateViewLayout(id, params); if (focus) { mContext.setFocusedWindow(this); } else { if (mContext.getFocusedWindow() == this) { mContext.setFocusedWindow(null); } } return true; } return false; } @Override public void setLayoutParams(ViewGroup.LayoutParams params) { if (params instanceof StandOutLayoutParams) { super.setLayoutParams(params); } else { throw new IllegalArgumentException("Window" + id + ": LayoutParams must be an instance of StandOutLayoutParams."); } } /** * Convenience method to start editting the size and position of this * window. Make sure you call {@link Editor#commit()} when you are done to * update the window. * * @return The Editor associated with this window. */ public Editor edit() { return new Editor(); } @Override public StandOutLayoutParams getLayoutParams() { StandOutLayoutParams params = (StandOutLayoutParams) super.getLayoutParams(); if (params == null) { params = originalParams; } return params; } /** * Returns the system window decorations if the implementation sets * {@link #FLAG_DECORATION_SYSTEM}. * * <p> * The system window decorations support hiding, closing, moving, and * resizing. * * @return The frame view containing the system window decorations. */ private View getSystemDecorations() { final View decorations = mLayoutInflater.inflate(R.layout.system_window_decorators, null); // icon final ImageView icon = (ImageView) decorations.findViewById(R.id.window_icon); icon.setImageResource(mContext.getAppIcon()); icon.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { PopupWindow dropDown = mContext.getDropDown(id); if (dropDown != null) { dropDown.showAsDropDown(icon); } } }); // title TextView title = (TextView) decorations.findViewById(R.id.title); title.setText(mContext.getTitle(id)); // hide View hide = decorations.findViewById(R.id.hide); hide.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mContext.hide(id); } }); hide.setVisibility(View.GONE); // close View close = decorations.findViewById(R.id.close); close.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mContext.close(id); } }); // move View titlebar = decorations.findViewById(R.id.titlebar); titlebar.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // handle dragging to move boolean consumed = mContext.onTouchHandleMove(id, Window.this, v, event); return consumed; } }); // resize View corner = decorations.findViewById(R.id.corner); corner.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // handle dragging to move boolean consumed = mContext.onTouchHandleResize(id, Window.this, v, event); return consumed; } }); // set window appearance and behavior based on flags if (Utils.isSet(flags, StandOutFlags.FLAG_WINDOW_HIDE_ENABLE)) { hide.setVisibility(View.VISIBLE); } if (Utils.isSet(flags, StandOutFlags.FLAG_DECORATION_CLOSE_DISABLE)) { close.setVisibility(View.GONE); } if (Utils.isSet(flags, StandOutFlags.FLAG_DECORATION_MOVE_DISABLE)) { titlebar.setOnTouchListener(null); } if (Utils.isSet(flags, StandOutFlags.FLAG_DECORATION_RESIZE_DISABLE)) { corner.setVisibility(View.GONE); } return decorations; } /** * Implement StandOut specific additional functionalities. * * <p> * Currently, this method does the following: * * <p> * Attach resize handles: For every View found to have id R.id.corner, * attach an OnTouchListener that implements resizing the window. * * @param root * The view hierarchy that is part of the window. */ void addFunctionality(View root) { // corner for resize if (!Utils.isSet(flags, StandOutFlags.FLAG_ADD_FUNCTIONALITY_RESIZE_DISABLE)) { View corner = root.findViewById(R.id.corner); if (corner != null) { corner.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // handle dragging to move boolean consumed = mContext.onTouchHandleResize(id, Window.this, v, event); return consumed; } }); } } // window_icon for drop down if (!Utils.isSet(flags, StandOutFlags.FLAG_ADD_FUNCTIONALITY_DROP_DOWN_DISABLE)) { final View icon = root.findViewById(R.id.window_icon); if (icon != null) { icon.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { PopupWindow dropDown = mContext.getDropDown(id); if (dropDown != null) { dropDown.showAsDropDown(icon); } } }); } } } /** * Iterate through each View in the view hiearchy and implement StandOut * specific compatibility workarounds. * * <p> * Currently, this method does the following: * * <p> * Nothing yet. * * @param root * The root view hierarchy to iterate through and check. */ void fixCompatibility(View root) { Queue<View> queue = new LinkedList<View>(); queue.add(root); View view = null; while ((view = queue.poll()) != null) { // do nothing yet // iterate through children if (view instanceof ViewGroup) { ViewGroup group = (ViewGroup) view; for (int i = 0; i < group.getChildCount(); i++) { queue.add(group.getChildAt(i)); } } } } /** * Convenient way to resize or reposition a Window. The Editor allows you to * easily resize and reposition the window around anchor points. * * @author Mark Wei <markwei@gmail.com> * */ public class Editor { /** * Special value for width, height, x, or y positions that represents * that the value should not be changed. */ public static final int UNCHANGED = Integer.MIN_VALUE; /** * Layout params of the window associated with this Editor. */ StandOutLayoutParams mParams; /** * The relative position of the anchor point. The anchor point is only * used by the {@link Editor}. * * <p> * The anchor point effects the following methods: * * <p> * {@link #setSize(int, int)} and {@link #setSize(float, float)}. The * window will expand or shrink around the anchor point. * * <p> * Values must be between 0 and 1, inclusive. 0 means the left/top, 0.5 * is the center, 1 is the right/bottom. */ float anchorX, anchorY; /** * Width and height of the screen. */ int displayWidth, displayHeight; public Editor() { mParams = getLayoutParams(); anchorX = anchorY = 0; DisplayMetrics metrics = mContext.getResources().getDisplayMetrics(); displayWidth = metrics.widthPixels; displayHeight = (int) (metrics.heightPixels - 25 * metrics.density); } public Editor setAnchorPoint(float x, float y) { if (x < 0 || x > 1 || y < 0 || y > 1) { throw new IllegalArgumentException("Anchor point must be between 0 and 1, inclusive."); } anchorX = x; anchorY = y; return this; } /** * Set the size of this window in absolute pixels. * * @param width * @param height * @return The same Editor, useful for method chaining. */ public Editor setSize(int width, int height) { return setSize(width, height, false); } /** * Set the size of this window in absolute pixels. * * @param width * @param height * @param skip * Don't call {@link #setPosition(int, int)} to avoid stack * overflow. * @return The same Editor, useful for method chaining. */ private Editor setSize(int width, int height, boolean skip) { if (mParams != null) { if (anchorX < 0 || anchorX > 1 || anchorY < 0 || anchorY > 1) { throw new IllegalStateException("Anchor point must be between 0 and 1, inclusive."); } int lastWidth = mParams.width; int lastHeight = mParams.height; if (width != UNCHANGED) { mParams.width = width; } if (height != UNCHANGED) { mParams.height = height; } // set max width/height int maxWidth = mParams.maxWidth; int maxHeight = mParams.maxHeight; if (Utils.isSet(flags, StandOutFlags.FLAG_WINDOW_EDGE_LIMITS_ENABLE)) { maxWidth = (int) Math.min(maxWidth, displayWidth); maxHeight = (int) Math.min(maxHeight, displayHeight); } // keep window between min and max mParams.width = Math.min(Math.max(mParams.width, mParams.minWidth), maxWidth); mParams.height = Math.min(Math.max(mParams.height, mParams.minHeight), maxHeight); // keep window in aspect ratio if (Utils.isSet(flags, StandOutFlags.FLAG_WINDOW_ASPECT_RATIO_ENABLE)) { int ratioWidth = (int) (mParams.height * touchInfo.ratio); int ratioHeight = (int) (mParams.width / touchInfo.ratio); if (ratioHeight >= mParams.minHeight && ratioHeight <= mParams.maxHeight) { // width good adjust height mParams.height = ratioHeight; } else { // height good adjust width mParams.width = ratioWidth; } } if (!skip) { // set position based on anchor point setPosition((int) (mParams.x + lastWidth * anchorX), (int) (mParams.y + lastHeight * anchorY)); } } return this; } /** * Set the position of this window in absolute pixels. * * @param x * @param y * @return The same Editor, useful for method chaining. */ public Editor setPosition(int x, int y) { return setPosition(x, y, false); } /** * Set the position of this window in absolute pixels. * * @param x * @param y * @param skip * Don't call {@link #setPosition(int, int)} and * {@link #setSize(int, int)} to avoid stack overflow. * @return The same Editor, useful for method chaining. */ private Editor setPosition(int x, int y, boolean skip) { if (mParams != null) { if (anchorX < 0 || anchorX > 1 || anchorY < 0 || anchorY > 1) { throw new IllegalStateException("Anchor point must be between 0 and 1, inclusive."); } // sets the x and y correctly according to anchorX and // anchorY if (x != UNCHANGED) { mParams.x = (int) (x - mParams.width * anchorX); } if (y != UNCHANGED) { mParams.y = (int) (y - mParams.height * anchorY); } if (Utils.isSet(flags, StandOutFlags.FLAG_WINDOW_EDGE_LIMITS_ENABLE)) { // if gravity is not TOP|LEFT throw exception if (mParams.gravity != (Gravity.TOP | Gravity.LEFT)) { throw new IllegalStateException("The window " + id + " gravity must be TOP|LEFT if FLAG_WINDOW_EDGE_LIMITS_ENABLE or FLAG_WINDOW_EDGE_TILE_ENABLE is set."); } // keep window inside edges mParams.x = Math.min(Math.max(mParams.x, 0), displayWidth - mParams.width); mParams.y = Math.min(Math.max(mParams.y, 0), displayHeight - mParams.height); } } return this; } /** * Commit the changes to this window. Updates the layout. This Editor * cannot be used after you commit. */ public void commit() { if (mParams != null) { mContext.updateViewLayout(id, mParams); mParams = null; } } } }