1 /* 2 * Copyright 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.example.android.actionbarcompat; 18 19 import org.xmlpull.v1.XmlPullParser; 20 import org.xmlpull.v1.XmlPullParserException; 21 22 import android.app.Activity; 23 import android.content.Context; 24 import android.content.res.XmlResourceParser; 25 import android.os.Bundle; 26 import android.view.InflateException; 27 import android.view.Menu; 28 import android.view.MenuInflater; 29 import android.view.MenuItem; 30 import android.view.View; 31 import android.view.ViewGroup; 32 import android.view.Window; 33 import android.widget.ImageButton; 34 import android.widget.ImageView; 35 import android.widget.LinearLayout; 36 import android.widget.ProgressBar; 37 import android.widget.TextView; 38 39 import java.io.IOException; 40 import java.util.HashSet; 41 import java.util.Set; 42 43 /** 44 * A class that implements the action bar pattern for pre-Honeycomb devices. 45 */ 46 public class ActionBarHelperBase extends ActionBarHelper { 47 private static final String MENU_RES_NAMESPACE = "http://schemas.android.com/apk/res/android"; 48 private static final String MENU_ATTR_ID = "id"; 49 private static final String MENU_ATTR_SHOW_AS_ACTION = "showAsAction"; 50 51 protected Set<Integer> mActionItemIds = new HashSet<Integer>(); 52 53 protected ActionBarHelperBase(Activity activity) { 54 super(activity); 55 } 56 57 /**{@inheritDoc}*/ 58 @Override 59 public void onCreate(Bundle savedInstanceState) { 60 mActivity.requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); 61 } 62 63 /**{@inheritDoc}*/ 64 @Override 65 public void onPostCreate(Bundle savedInstanceState) { 66 mActivity.getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, 67 R.layout.actionbar_compat); 68 setupActionBar(); 69 70 SimpleMenu menu = new SimpleMenu(mActivity); 71 mActivity.onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, menu); 72 mActivity.onPrepareOptionsMenu(menu); 73 for (int i = 0; i < menu.size(); i++) { 74 MenuItem item = menu.getItem(i); 75 if (mActionItemIds.contains(item.getItemId())) { 76 addActionItemCompatFromMenuItem(item); 77 } 78 } 79 } 80 81 /** 82 * Sets up the compatibility action bar with the given title. 83 */ 84 private void setupActionBar() { 85 final ViewGroup actionBarCompat = getActionBarCompat(); 86 if (actionBarCompat == null) { 87 return; 88 } 89 90 LinearLayout.LayoutParams springLayoutParams = new LinearLayout.LayoutParams( 91 0, ViewGroup.LayoutParams.FILL_PARENT); 92 springLayoutParams.weight = 1; 93 94 // Add Home button 95 SimpleMenu tempMenu = new SimpleMenu(mActivity); 96 SimpleMenuItem homeItem = new SimpleMenuItem( 97 tempMenu, android.R.id.home, 0, mActivity.getString(R.string.app_name)); 98 homeItem.setIcon(R.drawable.ic_home); 99 addActionItemCompatFromMenuItem(homeItem); 100 101 // Add title text 102 TextView titleText = new TextView(mActivity, null, R.attr.actionbarCompatTitleStyle); 103 titleText.setLayoutParams(springLayoutParams); 104 titleText.setText(mActivity.getTitle()); 105 actionBarCompat.addView(titleText); 106 } 107 108 /**{@inheritDoc}*/ 109 @Override 110 public void setRefreshActionItemState(boolean refreshing) { 111 View refreshButton = mActivity.findViewById(R.id.actionbar_compat_item_refresh); 112 View refreshIndicator = mActivity.findViewById( 113 R.id.actionbar_compat_item_refresh_progress); 114 115 if (refreshButton != null) { 116 refreshButton.setVisibility(refreshing ? View.GONE : View.VISIBLE); 117 } 118 if (refreshIndicator != null) { 119 refreshIndicator.setVisibility(refreshing ? View.VISIBLE : View.GONE); 120 } 121 } 122 123 /** 124 * Action bar helper code to be run in {@link Activity#onCreateOptionsMenu(android.view.Menu)}. 125 * 126 * NOTE: This code will mark on-screen menu items as invisible. 127 */ 128 @Override 129 public boolean onCreateOptionsMenu(Menu menu) { 130 // Hides on-screen action items from the options menu. 131 for (Integer id : mActionItemIds) { 132 menu.findItem(id).setVisible(false); 133 } 134 return true; 135 } 136 137 /**{@inheritDoc}*/ 138 @Override 139 protected void onTitleChanged(CharSequence title, int color) { 140 TextView titleView = (TextView) mActivity.findViewById(R.id.actionbar_compat_title); 141 if (titleView != null) { 142 titleView.setText(title); 143 } 144 } 145 146 /** 147 * Returns a {@link android.view.MenuInflater} that can read action bar metadata on 148 * pre-Honeycomb devices. 149 */ 150 public MenuInflater getMenuInflater(MenuInflater superMenuInflater) { 151 return new WrappedMenuInflater(mActivity, superMenuInflater); 152 } 153 154 /** 155 * Returns the {@link android.view.ViewGroup} for the action bar on phones (compatibility action 156 * bar). Can return null, and will return null on Honeycomb. 157 */ 158 private ViewGroup getActionBarCompat() { 159 return (ViewGroup) mActivity.findViewById(R.id.actionbar_compat); 160 } 161 162 /** 163 * Adds an action button to the compatibility action bar, using menu information from a {@link 164 * android.view.MenuItem}. If the menu item ID is <code>menu_refresh</code>, the menu item's 165 * state can be changed to show a loading spinner using 166 * {@link com.example.android.actionbarcompat.ActionBarHelperBase#setRefreshActionItemState(boolean)}. 167 */ 168 private View addActionItemCompatFromMenuItem(final MenuItem item) { 169 final int itemId = item.getItemId(); 170 171 final ViewGroup actionBar = getActionBarCompat(); 172 if (actionBar == null) { 173 return null; 174 } 175 176 // Create the button 177 ImageButton actionButton = new ImageButton(mActivity, null, 178 itemId == android.R.id.home 179 ? R.attr.actionbarCompatItemHomeStyle 180 : R.attr.actionbarCompatItemStyle); 181 actionButton.setLayoutParams(new ViewGroup.LayoutParams( 182 (int) mActivity.getResources().getDimension( 183 itemId == android.R.id.home 184 ? R.dimen.actionbar_compat_button_home_width 185 : R.dimen.actionbar_compat_button_width), 186 ViewGroup.LayoutParams.FILL_PARENT)); 187 if (itemId == R.id.menu_refresh) { 188 actionButton.setId(R.id.actionbar_compat_item_refresh); 189 } 190 actionButton.setImageDrawable(item.getIcon()); 191 actionButton.setScaleType(ImageView.ScaleType.CENTER); 192 actionButton.setContentDescription(item.getTitle()); 193 actionButton.setOnClickListener(new View.OnClickListener() { 194 public void onClick(View view) { 195 mActivity.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item); 196 } 197 }); 198 199 actionBar.addView(actionButton); 200 201 if (item.getItemId() == R.id.menu_refresh) { 202 // Refresh buttons should be stateful, and allow for indeterminate progress indicators, 203 // so add those. 204 ProgressBar indicator = new ProgressBar(mActivity, null, 205 R.attr.actionbarCompatProgressIndicatorStyle); 206 207 final int buttonWidth = mActivity.getResources().getDimensionPixelSize( 208 R.dimen.actionbar_compat_button_width); 209 final int buttonHeight = mActivity.getResources().getDimensionPixelSize( 210 R.dimen.actionbar_compat_height); 211 final int progressIndicatorWidth = buttonWidth / 2; 212 213 LinearLayout.LayoutParams indicatorLayoutParams = new LinearLayout.LayoutParams( 214 progressIndicatorWidth, progressIndicatorWidth); 215 indicatorLayoutParams.setMargins( 216 (buttonWidth - progressIndicatorWidth) / 2, 217 (buttonHeight - progressIndicatorWidth) / 2, 218 (buttonWidth - progressIndicatorWidth) / 2, 219 0); 220 indicator.setLayoutParams(indicatorLayoutParams); 221 indicator.setVisibility(View.GONE); 222 indicator.setId(R.id.actionbar_compat_item_refresh_progress); 223 actionBar.addView(indicator); 224 } 225 226 return actionButton; 227 } 228 229 /** 230 * A {@link android.view.MenuInflater} that reads action bar metadata. 231 */ 232 private class WrappedMenuInflater extends MenuInflater { 233 MenuInflater mInflater; 234 235 public WrappedMenuInflater(Context context, MenuInflater inflater) { 236 super(context); 237 mInflater = inflater; 238 } 239 240 @Override 241 public void inflate(int menuRes, Menu menu) { 242 loadActionBarMetadata(menuRes); 243 mInflater.inflate(menuRes, menu); 244 } 245 246 /** 247 * Loads action bar metadata from a menu resource, storing a list of menu item IDs that 248 * should be shown on-screen (i.e. those with showAsAction set to always or ifRoom). 249 * @param menuResId 250 */ 251 private void loadActionBarMetadata(int menuResId) { 252 XmlResourceParser parser = null; 253 try { 254 parser = mActivity.getResources().getXml(menuResId); 255 256 int eventType = parser.getEventType(); 257 int itemId; 258 int showAsAction; 259 260 boolean eof = false; 261 while (!eof) { 262 switch (eventType) { 263 case XmlPullParser.START_TAG: 264 if (!parser.getName().equals("item")) { 265 break; 266 } 267 268 itemId = parser.getAttributeResourceValue(MENU_RES_NAMESPACE, 269 MENU_ATTR_ID, 0); 270 if (itemId == 0) { 271 break; 272 } 273 274 showAsAction = parser.getAttributeIntValue(MENU_RES_NAMESPACE, 275 MENU_ATTR_SHOW_AS_ACTION, -1); 276 if (showAsAction == MenuItem.SHOW_AS_ACTION_ALWAYS || 277 showAsAction == MenuItem.SHOW_AS_ACTION_IF_ROOM) { 278 mActionItemIds.add(itemId); 279 } 280 break; 281 282 case XmlPullParser.END_DOCUMENT: 283 eof = true; 284 break; 285 } 286 287 eventType = parser.next(); 288 } 289 } catch (XmlPullParserException e) { 290 throw new InflateException("Error inflating menu XML", e); 291 } catch (IOException e) { 292 throw new InflateException("Error inflating menu XML", e); 293 } finally { 294 if (parser != null) { 295 parser.close(); 296 } 297 } 298 } 299 300 } 301 } 302