1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 package org.mozilla.gecko; 6 7 import android.content.res.TypedArray; 8 import android.graphics.Canvas; 9 import android.graphics.Paint; 10 import org.mozilla.gecko.animation.AnimationUtils; 11 import org.mozilla.gecko.menu.GeckoMenu; 12 import org.mozilla.gecko.widget.GeckoPopupMenu; 13 14 import android.content.Context; 15 import android.util.AttributeSet; 16 import android.view.LayoutInflater; 17 import android.view.Menu; 18 import android.view.View; 19 import android.view.ViewGroup; 20 import android.view.animation.Animation; 21 import android.view.animation.ScaleAnimation; 22 import android.view.animation.TranslateAnimation; 23 import android.widget.Button; 24 import android.widget.ImageButton; 25 import android.widget.LinearLayout; 26 27 class ActionModeCompatView extends LinearLayout implements GeckoMenu.ActionItemBarPresenter { 28 private final String LOGTAG = "GeckoActionModeCompatPresenter"; 29 30 private static final int SPEC = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); 31 32 private Button mTitleView; 33 private ImageButton mMenuButton; 34 private ViewGroup mActionButtonBar; 35 private GeckoPopupMenu mPopupMenu; 36 37 // Maximum number of items to show as actions 38 private static final int MAX_ACTION_ITEMS = 4; 39 40 private int mActionButtonsWidth; 41 42 private Paint mBottomDividerPaint; 43 private int mBottomDividerOffset; 44 ActionModeCompatView(Context context, AttributeSet attrs)45 public ActionModeCompatView(Context context, AttributeSet attrs) { 46 super(context, attrs); 47 init(context, attrs, 0); 48 } 49 ActionModeCompatView(Context context, AttributeSet attrs, int style)50 public ActionModeCompatView(Context context, AttributeSet attrs, int style) { 51 super(context, attrs, style); 52 init(context, attrs, style); 53 } 54 init(final Context context, final AttributeSet attrs, final int defStyle)55 public void init(final Context context, final AttributeSet attrs, final int defStyle) { 56 LayoutInflater.from(context).inflate(R.layout.actionbar, this); 57 58 mTitleView = (Button) findViewById(R.id.actionmode_title); 59 mMenuButton = (ImageButton) findViewById(R.id.actionbar_menu); 60 mActionButtonBar = (ViewGroup) findViewById(R.id.actionbar_buttons); 61 62 mPopupMenu = new GeckoPopupMenu(getContext(), mMenuButton); 63 mPopupMenu.getMenu().setActionItemBarPresenter(this); 64 65 mMenuButton.setOnClickListener(new View.OnClickListener() { 66 @Override 67 public void onClick(View v) { 68 openMenu(); 69 } 70 }); 71 72 // The built-in action bar uses colorAccent for the divider so we duplicate that here. 73 final TypedArray arr = context.obtainStyledAttributes(attrs, new int[] { R.attr.colorAccent }, defStyle, 0); 74 final int bottomDividerColor = arr.getColor(0, 0); 75 arr.recycle(); 76 77 mBottomDividerPaint = new Paint(); 78 mBottomDividerPaint.setColor(bottomDividerColor); 79 mBottomDividerOffset = getResources().getDimensionPixelSize(R.dimen.action_bar_divider_height); 80 } 81 initForMode(final ActionModeCompat mode)82 public void initForMode(final ActionModeCompat mode) { 83 mTitleView.setOnClickListener(mode); 84 mPopupMenu.setOnMenuItemClickListener(mode); 85 mPopupMenu.setOnMenuItemLongClickListener(mode); 86 } 87 getTitle()88 public CharSequence getTitle() { 89 return mTitleView.getText(); 90 } 91 setTitle(CharSequence title)92 public void setTitle(CharSequence title) { 93 mTitleView.setText(title); 94 } 95 setTitle(int resId)96 public void setTitle(int resId) { 97 mTitleView.setText(resId); 98 } 99 getMenu()100 public GeckoMenu getMenu() { 101 return mPopupMenu.getMenu(); 102 } 103 104 @Override invalidate()105 public void invalidate() { 106 // onFinishInflate may not have been called yet on some versions of Android 107 if (mPopupMenu != null && mMenuButton != null) { 108 mMenuButton.setVisibility(mPopupMenu.getMenu().hasVisibleItems() ? View.VISIBLE : View.GONE); 109 } 110 super.invalidate(); 111 } 112 113 /* GeckoMenu.ActionItemBarPresenter */ 114 @Override addActionItem(View actionItem)115 public boolean addActionItem(View actionItem) { 116 final int count = mActionButtonBar.getChildCount(); 117 if (count >= MAX_ACTION_ITEMS) { 118 return false; 119 } 120 121 int maxWidth = mActionButtonBar.getMeasuredWidth(); 122 if (maxWidth == 0) { 123 mActionButtonBar.measure(SPEC, SPEC); 124 maxWidth = mActionButtonBar.getMeasuredWidth(); 125 } 126 127 // If the menu button is already visible, no need to account for it 128 if (mMenuButton.getVisibility() == View.GONE) { 129 // Since we don't know how many items will be added, we always reserve space for the overflow menu 130 mMenuButton.measure(SPEC, SPEC); 131 maxWidth -= mMenuButton.getMeasuredWidth(); 132 } 133 134 if (mActionButtonsWidth <= 0) { 135 mActionButtonsWidth = 0; 136 137 // Loop over child views, measure them, and add their width to the taken width 138 for (int i = 0; i < count; i++) { 139 View v = mActionButtonBar.getChildAt(i); 140 v.measure(SPEC, SPEC); 141 mActionButtonsWidth += v.getMeasuredWidth(); 142 } 143 } 144 145 actionItem.measure(SPEC, SPEC); 146 int w = actionItem.getMeasuredWidth(); 147 if (mActionButtonsWidth + w < maxWidth) { 148 // We cache the new width of our children. 149 mActionButtonsWidth += w; 150 mActionButtonBar.addView(actionItem); 151 return true; 152 } 153 154 return false; 155 } 156 157 /* GeckoMenu.ActionItemBarPresenter */ 158 @Override removeActionItem(View actionItem)159 public void removeActionItem(View actionItem) { 160 actionItem.measure(SPEC, SPEC); 161 mActionButtonsWidth -= actionItem.getMeasuredWidth(); 162 mActionButtonBar.removeView(actionItem); 163 } 164 openMenu()165 public void openMenu() { 166 mPopupMenu.openMenu(); 167 } 168 closeMenu()169 public void closeMenu() { 170 mPopupMenu.dismiss(); 171 } 172 animateIn()173 public void animateIn() { 174 long duration = AnimationUtils.getShortDuration(getContext()); 175 TranslateAnimation t = new TranslateAnimation(Animation.RELATIVE_TO_SELF, -0.5f, Animation.RELATIVE_TO_SELF, 0f, 176 Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f); 177 t.setDuration(duration); 178 179 ScaleAnimation s = new ScaleAnimation(1f, 1f, 0f, 1f, 180 Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); 181 s.setDuration((long) (duration * 1.5f)); 182 183 mTitleView.startAnimation(t); 184 mActionButtonBar.startAnimation(s); 185 186 if ((mMenuButton.getVisibility() == View.VISIBLE) && 187 (mPopupMenu.getMenu().size() > 0)) { 188 mMenuButton.startAnimation(s); 189 } 190 } 191 192 @Override onDraw(Canvas canvas)193 protected void onDraw(Canvas canvas) { 194 super.onDraw(canvas); 195 196 // Draw the divider at the bottom of the screen. We could do this with a layer-list 197 // but then we'd have overdraw (http://stackoverflow.com/a/13509472). 198 final int bottom = getHeight(); 199 final int top = bottom - mBottomDividerOffset; 200 canvas.drawRect(0, top, getWidth(), bottom, mBottomDividerPaint); 201 } 202 } 203