/* * Copyright (C) 2010 Cyril Mottier (http://www.cyrilmottier.com) * * 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 greendroid.widget; import java.util.ArrayList; import java.util.List; import android.content.Context; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.PopupWindow; import com.cyrilmottier.android.greendroid.R; /** * Abstraction of a {@link QuickAction} wrapper. A QuickActionWidget is * displayed on top of the user interface (it overlaps all UI elements but the * notification bar). Clients may listen to user actions using a * {@link OnQuickActionClickListener} . * * @author Benjamin Fellous * @author Cyril Mottier */ public abstract class QuickActionWidget extends PopupWindow { private static final int MEASURE_AND_LAYOUT_DONE = 1 << 1; private final int[] mLocation = new int[2]; protected final Rect mRect = new Rect(); private int mPrivateFlags; private Context mContext; private boolean mDismissOnClick; private int mArrowOffsetY; private int mPopupY; private boolean mIsOnTop; private int mScreenHeight; private int mScreenWidth; private boolean mIsDirty; private OnQuickActionClickListener mOnQuickActionClickListener; private ArrayList<QuickAction> mQuickActions = new ArrayList<QuickAction>(); /** * Interface that may be used to listen to clicks on quick actions. * * @author Benjamin Fellous * @author Cyril Mottier */ public static interface OnQuickActionClickListener { /** * Clients may implement this method to be notified of a click on a * particular quick action. * * @param position Position of the quick action that have been clicked. */ void onQuickActionClicked(QuickActionWidget widget, int position); } /** * Creates a new QuickActionWidget for the given context. * * @param context The context in which the QuickActionWidget is running in */ public QuickActionWidget(Context context) { super(context); mContext = context; initializeDefault(); setFocusable(true); setTouchable(true); setOutsideTouchable(true); setWidth(WindowManager.LayoutParams.WRAP_CONTENT); setHeight(WindowManager.LayoutParams.WRAP_CONTENT); final WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); mScreenWidth = windowManager.getDefaultDisplay().getWidth(); mScreenHeight = windowManager.getDefaultDisplay().getHeight(); } /** * Equivalent to {@link PopupWindow#setContentView(View)} but with a layout * identifier. * * @param layoutId The layout identifier of the view to use. */ public void setContentView(int layoutId) { setContentView(LayoutInflater.from(mContext).inflate(layoutId, null)); } private void initializeDefault() { mDismissOnClick = true; mArrowOffsetY = mContext.getResources().getDimensionPixelSize(R.dimen.gd_arrow_offset); } /** * Returns the arrow offset for the Y axis. * * @see {@link #setArrowOffsetY(int)} * @return The arrow offset. */ public int getArrowOffsetY() { return mArrowOffsetY; } /** * Sets the arrow offset to a new value. Setting an arrow offset may be * particular useful to warn which view the QuickActionWidget is related to. * By setting a positive offset, the arrow will overlap the view given by * {@link #show(View)}. The default value is 5dp. * * @param offsetY The offset for the Y axis */ public void setArrowOffsetY(int offsetY) { mArrowOffsetY = offsetY; } /** * Returns the width of the screen. * * @return The width of the screen */ protected int getScreenWidth() { return mScreenWidth; } /** * Returns the height of the screen. * * @return The height of the screen */ protected int getScreenHeight() { return mScreenHeight; } /** * By default, a {@link QuickActionWidget} is dismissed once the user * clicked on a {@link QuickAction}. This behavior can be changed using this * method. * * @param dismissOnClick True if you want the {@link QuickActionWidget} to * be dismissed on click else false. */ public void setDismissOnClick(boolean dismissOnClick) { mDismissOnClick = dismissOnClick; } public boolean getDismissOnClick() { return mDismissOnClick; } /** * @param listener */ public void setOnQuickActionClickListener(OnQuickActionClickListener listener) { mOnQuickActionClickListener = listener; } /** * Add a new QuickAction to this {@link QuickActionWidget}. Adding a new * {@link QuickAction} while the {@link QuickActionWidget} is currently * being shown does nothing. The new {@link QuickAction} will be displayed * on the next call to {@link #show(View)}. * * @param action The new {@link QuickAction} to add */ public void addQuickAction(QuickAction action) { if (action != null) { mQuickActions.add(action); mIsDirty = true; } } /** * Removes all {@link QuickAction} from this {@link QuickActionWidget}. */ public void clearAllQuickActions() { if (!mQuickActions.isEmpty()) { mQuickActions.clear(); mIsDirty = true; } } /** * Call that method to display the {@link QuickActionWidget} anchored to the * given view. * * @param anchor The view the {@link QuickActionWidget} will be anchored to. */ public void show(View anchor) { final View contentView = getContentView(); if (contentView == null) { throw new IllegalStateException("You need to set the content view using the setContentView method"); } // Replaces the background of the popup with a cleared background setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); final int[] loc = mLocation; anchor.getLocationOnScreen(loc); mRect.set(loc[0], loc[1], loc[0] + anchor.getWidth(), loc[1] + anchor.getHeight()); if (mIsDirty) { clearQuickActions(); populateQuickActions(mQuickActions); } onMeasureAndLayout(mRect, contentView); if ((mPrivateFlags & MEASURE_AND_LAYOUT_DONE) != MEASURE_AND_LAYOUT_DONE) { throw new IllegalStateException("onMeasureAndLayout() did not set the widget specification by calling" + " setWidgetSpecs()"); } showArrow(); prepareAnimationStyle(); try { showAtLocation(anchor, Gravity.NO_GRAVITY, getShowAtX(), mPopupY); } catch (Exception e) { Log.w("quick-action-show", e); } } protected int getShowAtX() { return 0; } protected void clearQuickActions() { if (!mQuickActions.isEmpty()) { onClearQuickActions(); } } protected void onClearQuickActions() { } protected abstract void populateQuickActions(List<QuickAction> quickActions); protected abstract void onMeasureAndLayout(Rect anchorRect, View contentView); protected void setWidgetSpecs(int popupY, boolean isOnTop) { mPopupY = popupY; mIsOnTop = isOnTop; mPrivateFlags |= MEASURE_AND_LAYOUT_DONE; } private void showArrow() { final View contentView = getContentView(); final int arrowId = mIsOnTop ? R.id.gdi_arrow_down : R.id.gdi_arrow_up; final View arrow = contentView.findViewById(arrowId); final View arrowUp = contentView.findViewById(R.id.gdi_arrow_up); final View arrowDown = contentView.findViewById(R.id.gdi_arrow_down); if (arrowId == R.id.gdi_arrow_up) { arrowUp.setVisibility(View.VISIBLE); arrowDown.setVisibility(View.INVISIBLE); } else if (arrowId == R.id.gdi_arrow_down) { arrowUp.setVisibility(View.INVISIBLE); arrowDown.setVisibility(View.VISIBLE); } ViewGroup.MarginLayoutParams param = (ViewGroup.MarginLayoutParams) arrow.getLayoutParams(); param.leftMargin = getArrowLeftMargin(arrow); } protected int getArrowLeftMargin(View arrow) { return mRect.centerX() - (arrow.getMeasuredWidth()) / 2; } private void prepareAnimationStyle() { final int screenWidth = mScreenWidth; final boolean onTop = mIsOnTop; final int arrowPointX = mRect.centerX(); if (arrowPointX <= screenWidth / 4) { setAnimationStyle(onTop ? R.style.GreenDroid_Animation_PopUp_Left : R.style.GreenDroid_Animation_PopDown_Left); } else if (arrowPointX >= 3 * screenWidth / 4) { setAnimationStyle(onTop ? R.style.GreenDroid_Animation_PopUp_Right : R.style.GreenDroid_Animation_PopDown_Right); } else { setAnimationStyle(onTop ? R.style.GreenDroid_Animation_PopUp_Center : R.style.GreenDroid_Animation_PopDown_Center); } } protected Context getContext() { return mContext; } protected OnQuickActionClickListener getOnQuickActionClickListener() { return mOnQuickActionClickListener; } }