ActionBarHelperBase.java revision 7ee7d6d5d6efee92f7d13212fa5883ecf58b79a3
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
17package com.example.android.actionbarcompat;
18
19import org.xmlpull.v1.XmlPullParser;
20import org.xmlpull.v1.XmlPullParserException;
21
22import android.app.Activity;
23import android.content.Context;
24import android.content.res.XmlResourceParser;
25import android.os.Bundle;
26import android.view.InflateException;
27import android.view.Menu;
28import android.view.MenuInflater;
29import android.view.MenuItem;
30import android.view.View;
31import android.view.ViewGroup;
32import android.view.Window;
33import android.widget.ImageButton;
34import android.widget.ImageView;
35import android.widget.LinearLayout;
36import android.widget.ProgressBar;
37import android.widget.TextView;
38
39import java.io.IOException;
40import java.util.HashSet;
41import java.util.Set;
42
43/**
44 * A class that implements the action bar pattern for pre-Honeycomb devices.
45 */
46public 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