Android系统示例之ActionBarCompat

导入工程ActionBarCompat时,出现错误。从其他工程下拷贝project.propertiest文件过来,问题仍在。拷贝后需要重启Eclipse才解决。问题如下:

[2013-07-03 16:09:00 - ActionBarCompat] Project has no project.properties file! Edit the project properties to set one.

生词:compat 紧密的,紧凑的,简洁的;

image

 

Action Bar Compat 字面上就可以理解这个示例

 

工程包名:com.example.android.actionbarcompat

public abstract class ActionBarActivity extends Activity

 

在这个类中重写了以下方法

public MenuInflater getMenuInflater()

protected void onCreate(Bundle savedInstanceState)

protected void onPostCreate(Bundle savedInstanceState)

public boolean onCreateOptionsMenu(Menu menu)

protected void onTitleChanged(CharSequence title, int color)

 

创建了ActionBarHelper对象

final ActionBarHelper mActionBarHelper = ActionBarHelper.createInstance(this);

 

在ActionBarHelper类中,有一个工厂方法,针对不同的平台返回不同的实例

public static ActionBarHelper createInstance(Activity activity)

在createInstance(Activity)中,根据系统版本不同创建不同的ActionBarHelper实例

ICE_CREAM_SANDWICH 数值14,对应4.0。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
    return new ActionBarHelperICS(activity);

HONEYCOMB 数值11,对应3.0。
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
    return new ActionBarHelperHoneycomb(activity);

11以下,3.0以下。
} else {
    return new ActionBarHelperBase(activity);
}

在ActionBarHelper类中定义了几个方法,都是空实现,需要在子类中具体实现。

public void onCreate(Bundle savedInstanceState)

public void onPostCreate(Bundle savedInstanceState)

public boolean onCreateOptionsMenu(Menu menu)

protected void onTitleChanged(CharSequence title, int color)

public MenuInflater getMenuInflater(MenuInflater superMenuInflater)

 

一个抽象方法,在所有非抽象子类中必须被实现。

public abstract void setRefreshActionItemState(boolean refreshing);

子类一:ActionBarHelperBase

 

在onCreate方法中

mActivity.requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);

注:int android.view.Window.FEATURE_CUSTOM_TITLE = 7 [0x7]
Flag for custom title. You cannot combine this feature with other title features.

生词:

custom n. 习惯,风俗,惯例;adj. 定做的,定制的

feature n. 特征,特色,容貌,特写,故事片

注:boolean android.app.Activity.requestWindowFeature(int featureId)
Enable extended window features. This is a convenience for calling getWindow().requestFeature().


在onPostCreate方法中

mActivity.getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.actionbar_compat);

注:void android.view.Window.setFeatureInt(int featureId, int value)
Set the integer value for a feature. The range of the value depends on the feature being set.

For FEATURE_PROGRESSS, it should go from 0 to 10000. At 10000 the progress is complete and the indicator hidden.

生词:

indicator n. 指示器,指示符,指示牌

 

res/values/ids.xml文件

<resources>
    <item type="id" name="actionbar_compat" />
    <item type="id" name="actionbar_compat_title" />
    <item type="id" name="actionbar_compat_item_refresh_progress" />
    <item type="id" name="actionbar_compat_item_refresh" />
    <item type="id" name="menu_refresh" />
</resources>

 

res/layout/actionbar_compact.xml文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@id/actionbar_compat"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal" />

 

方法getActionBarCompact,返回在actionbar_compact.xml文件中定义的,id值为actionbar_compact的LinearLayout对象

private ViewGroup getActionBarCompat() {
    return (ViewGroup) mActivity.findViewById(R.id.actionbar_compat);
}


注:android.view.ViewGroup

A ViewGroup is a special view that can contain other views (called children.) The view group is the base class for layouts and views containers. This class also defines the android.view.ViewGroup.LayoutParams class which serves as the base class for layouts parameters.

要点:ViewGroup是一个特殊的View。ViewGroup是布局和视图容器的父类。在ViewGroup中,定义了LayoutParams。

API显示:ViewGroup继承了View,直接继承了ViewGroup的子类有:

AbsoluteLayout, AdapterView<T extends Adapter>, FragmentBreadCrumbs, FrameLayout, GridLayout, LinearLayout, PagerTitleStrip, RelativeLayout, SlidingDrawer, ViewPager

非直接继承了ViewGroup的子类有:

AbsListView, AbsSpinner, AdapterViewAnimator, AdapterViewFlipper, AppWidgetHostView, CalendarView, DatePicker, DialerFilter, ExpandableListView, FragmentTabHost, Gallery, GestureOverlayView, GridView, HorizontalScrollView, ImageSwitcher, ListView, MediaController, NumberPicker, PagerTabStrip, RadioGroup, ScrollView, SearchView, Spinner, StackView, TabHost, TabWidget, TableLayout, TableRow, TextSwitcher, TimerPicker, TwoLineListItem, ViewAnimator, ViewFlipper, ViewSwitcher, WebView, ZoomControls.

 

方法getMenuInflater,返回一个WrappedMenuInflater实例。

public MenuInflater getMenuInflater(MenuInflater superMenuInflater) {
    return new WrappedMenuInflater(mActivity, superMenuInflater);
}

 

WrappedMenuInflater继承了MenuInflater。

注:android.view.MenuInflater
This class is used to instantiate menu XML files into Menu objects.

For performance reasons, menu inflation relies heavily on pre-processing of XML files that is done at build time. Therefore, it is not currently possible to use MenuInflater with an XmlPullParser over a plain XML file at runtime; it only works with an XmlPullParser returned from a compiled resource (R. something file.)

要点:MenuInflater 将 menu XML文件实例化为Menu对象。

生词:instantiate v. 举例说明,实例化

 

在WrappedMenuInflater中,包装了一个MenuInflater对象。

 

注:private void WrappedMenuInflater.loadActionBarMetadata(int menuResId)

Loads action bar metadata from a menu resource, storing a list of menu item IDs that should be shown on-screen (i.e. those with showAsAction set to always or ifRoom).

 

res/menu/main.xml文件

xmlns:android=http://schemas.android.com/apk/res/android

『xmlns』,XML NameSpace,

『android』表示“名称空间的名字”,

http://schemas.android.com/apk/res/android』 表示“名称空间的值”,

『android:title』表示“title表示属性名。android表示名称空间。”

<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/menu_refresh"
        android:title="@string/menu_refresh"
        android:icon="@drawable/ic_action_refresh"
        android:orderInCategory="1"
        android:showAsAction="always" />
    <item android:id="@+id/menu_search"
        android:title="@string/menu_search"
        android:icon="@drawable/ic_action_search"
        android:orderInCategory="0"
        android:showAsAction="always" />

    <item android:id="@+id/menu_share"
        android:title="@string/menu_share"
        android:icon="@drawable/ic_menu_share"
        android:orderInCategory="1"
        android:showAsAction="never" />
</menu>

 

解析res/menu/main.xml文件方法

private void loadActionBarMetadata(int menuResId) {
    XmlResourceParser parser = null;
    try {

        将menuResId资源文件转成XmlResourceParser对象。
        parser = mActivity.getResources().getXml(menuResId);

        int eventType = parser.getEventType();
        int itemId;
        int showAsAction;

        判断解析工作是否已经到文件结尾了。

        boolean eof = false;
        while (!eof) {
            switch (eventType) {
                case XmlPullParser.START_TAG:

                    如果开始标签不是item的话,不做处理。
                    if (!parser.getName().equals("item")) {
                        break;
                    }

                    getAttributeResourceValue方法原型

                    int android.util.AttributeSet.getAttributeResourceValue(String namespace, String attribute, int defaultValue);

                    namespace,名称空间,MENU_RES_NAMESPACEhttp://schemas.android.com/apk/res/android

                    attribute,MENU_ATTR_ID,字符串数组:id

                    itemId = parser.getAttributeResourceValue(MENU_RES_NAMESPACE,
                            MENU_ATTR_ID, 0);
                    if (itemId == 0) {
                        break;
                    }

                    showAsAction = parser.getAttributeIntValue(MENU_RES_NAMESPACE,
                            MENU_ATTR_SHOW_AS_ACTION, -1);

                    属性android:showAsAction可选值有{never, ifRoom, always, withText, collapseActionView}
                    if (showAsAction == MenuItem.SHOW_AS_ACTION_ALWAYS ||
                            showAsAction == MenuItem.SHOW_AS_ACTION_IF_ROOM) {
                        mActionItemIds.add(itemId);
                    }
                    break;

                case XmlPullParser.END_DOCUMENT:
                    eof = true;
                    break;
            }

            eventType = parser.next();
        }
    } catch (XmlPullParserException e) {
        throw new InflateException("Error inflating menu XML", e);
    } catch (IOException e) {
        throw new InflateException("Error inflating menu XML", e);
    } finally {
        if (parser != null) {
            parser.close();
        }
    }
}

 

注:void com.example.android.actionbarcompat.ActionBarActivity.onPostCreate(Bundle savedInstanceState)

Called when activity start-up is complete (after onStart and onRestoreInstanceState have been called).

Applications will generally not implement this method; it is intended for system classes to do final initialization after application code has run.


在 ActionBarHelperBase.onPostCreate(Bundle savedInstanceState) 方法中

public void onPostCreate(Bundle savedInstanceState) {
    mActivity.getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE,
            R.layout.actionbar_compat);
    setupActionBar();

    SimpleMenu menu = new SimpleMenu(mActivity);
    mActivity.onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, menu);
    mActivity.onPrepareOptionsMenu(menu);
    for (int i = 0; i < menu.size(); i++) {
        MenuItem item = menu.getItem(i);
        if (mActionItemIds.contains(item.getItemId())) {
            addActionItemCompatFromMenuItem(item);
        }
    }
}

 

res/values/actionbar_compat.xml文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@id/actionbar_compat"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal" />

 

在 ActionBarHelperBase.setupActionBar() 方法中

private void setupActionBar() {
    final ViewGroup actionBarCompat = getActionBarCompat();
    if (actionBarCompat == null) {
        return;
    }

    LinearLayout.LayoutParams springLayoutParams = new LinearLayout.LayoutParams(
            0, ViewGroup.LayoutParams.FILL_PARENT);
    springLayoutParams.weight = 1;

    // Add Home button
    SimpleMenu tempMenu = new SimpleMenu(mActivity);
    SimpleMenuItem homeItem = new SimpleMenuItem(
            tempMenu, android.R.id.home, 0, mActivity.getString(R.string.app_name));
    homeItem.setIcon(R.drawable.ic_home);
    addActionItemCompatFromMenuItem(homeItem);

    // Add title text
    TextView titleText = new TextView(mActivity, null, R.attr.actionbarCompatTitleStyle);
    titleText.setLayoutParams(springLayoutParams);
    titleText.setText(mActivity.getTitle());
    actionBarCompat.addView(titleText);
}

TextView构造方法

android.widget.TextView.TextView(Context context, AttributeSet attrs, int defStyle)

LayoutParams构造方法

android.widget.LinearLayout.LayoutParams.LayoutParams(int width, int height)

 

添加HomeButton时,定义了SimpleMenu和SimpleMenuItem两个类。

SimpleMenu 实现了android.view.Menu接口。

SimpleMenuItem 实现了android.view.MenuItem接口。

 

在SimpleMenu中,方法addInternal 将SimpleMenuItem实例添加到ArrayList对象中。

private MenuItem addInternal(int itemId, int order, CharSequence title) {
    final SimpleMenuItem item = new SimpleMenuItem(this, itemId, order, title);
    mItems.add(findInsertIndex(mItems, order), item);
    return item;
}

在SimpleMenu中,方法findInsertIndex找出SimpleMenuItem将要插入到哪个位置中。

private static int findInsertIndex(ArrayList<? extends MenuItem> items, int order) {
    for (int i = items.size() - 1; i >= 0; i--) {
        MenuItem item = items.get(i);

        如果参数order的数值大于等于item的order数值,插入到item的后面。
        if (item.getOrder() <= order) {
            return i + 1;
        }
    }

    return 0;
}

在SimpleMenu中,方法 findItemIndex(int id) 找出itemId对应的在ArrayList的下标。

public int findItemIndex(int id) {
    final int size = size();

    for (int i = 0; i < size; i++) {
        SimpleMenuItem item = mItems.get(i);
        if (item.getItemId() == id) {
            return i;
        }
    }

    return -1;
}


在SimpleMenuItem中,定义了两个变量和图标有关系,

private Drawable mIconDrawable;
private int mIconResId = 0;

这两个变量有点相互排斥的,

当设置Drawable时,mIconDrawable被赋值,mIconResId被置0.

当设置int类型的mIconResId时,mIconResId被赋值,mIconDrawable被赋null。

当获取Drawable时,如果mIconDrawable不为空时,直接返回,否则将mIconResId转换为Drawable对象返回。

public MenuItem setIcon(Drawable icon) {
    mIconResId = 0;
    mIconDrawable = icon;
    return this;
}

public MenuItem setIcon(int iconResId) {
    mIconDrawable = null;
    mIconResId = iconResId;
    return this;
}

public Drawable getIcon() {
    if (mIconDrawable != null) {
        return mIconDrawable;
    }

    if (mIconResId != 0) {
        return mMenu.getResources().getDrawable(mIconResId);
    }

    return null;
}

 

在 ActionBarHelperBase.addActionItemCompatFromMenuItem(final menuItem item) 方法中

创建 ImageButton 对象

ImageButton actionButton = new ImageButton(mActivity, null,
                itemId == android.R.id.home
                        ? R.attr.actionbarCompatItemHomeStyle
                        : R.attr.actionbarCompatItemStyle);

ImageButton 构造方法原型

android.widget.ImageButton.ImageButton(Context context, AttributeSet attrs, int defStyle)

设置 ImageButton 的宽和高

    actionButton.setLayoutParams(new ViewGroup.LayoutParams(
            (int) mActivity.getResources().getDimension(
                    itemId == android.R.id.home
                            ? R.dimen.actionbar_compat_button_home_width
                            : R.dimen.actionbar_compat_button_width),
            ViewGroup.LayoutParams.FILL_PARENT));

设置 ImageButton 的 id

actionButton.setId(R.id.actionbar_compat_item_refresh);

创建ProgressBar对象

ProgressBar indicator = new ProgressBar(mActivity, null,
                R.attr.actionbarCompatProgressIndicatorStyle);

从dimens.xml文件中得到尺寸像素大小

final int buttonWidth = mActivity.getResources().getDimensionPixelSize(
                    R.dimen.actionbar_compat_button_width);

 

private View addActionItemCompatFromMenuItem(final MenuItem item) {
    final int itemId = item.getItemId();

    final ViewGroup actionBar = getActionBarCompat();
    if (actionBar == null) {
        return null;
    }

    // Create the button
    ImageButton actionButton = new ImageButton(mActivity, null,
            itemId == android.R.id.home
                    ? R.attr.actionbarCompatItemHomeStyle
                    : R.attr.actionbarCompatItemStyle);
    actionButton.setLayoutParams(new ViewGroup.LayoutParams(
            (int) mActivity.getResources().getDimension(
                    itemId == android.R.id.home
                            ? R.dimen.actionbar_compat_button_home_width
                            : R.dimen.actionbar_compat_button_width),
            ViewGroup.LayoutParams.FILL_PARENT));
    if (itemId == R.id.menu_refresh) {
        actionButton.setId(R.id.actionbar_compat_item_refresh);
    }
    actionButton.setImageDrawable(item.getIcon());
    actionButton.setScaleType(ImageView.ScaleType.CENTER);
    actionButton.setContentDescription(item.getTitle());
    actionButton.setOnClickListener(new View.OnClickListener() {
        public void onClick(View view) {
            mActivity.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item);
        }
    });

    actionBar.addView(actionButton);

    if (item.getItemId() == R.id.menu_refresh) {
        // Refresh buttons should be stateful, and allow for indeterminate progress indicators,
        // so add those.
        ProgressBar indicator = new ProgressBar(mActivity, null,
                R.attr.actionbarCompatProgressIndicatorStyle);

        final int buttonWidth = mActivity.getResources().getDimensionPixelSize(
                R.dimen.actionbar_compat_button_width);
        final int buttonHeight = mActivity.getResources().getDimensionPixelSize(
                R.dimen.actionbar_compat_height);
        final int progressIndicatorWidth = buttonWidth / 2;

        LinearLayout.LayoutParams indicatorLayoutParams = new LinearLayout.LayoutParams(
                progressIndicatorWidth, progressIndicatorWidth);
        indicatorLayoutParams.setMargins(
                (buttonWidth - progressIndicatorWidth) / 2,
                (buttonHeight - progressIndicatorWidth) / 2,
                (buttonWidth - progressIndicatorWidth) / 2,
                0);
        indicator.setLayoutParams(indicatorLayoutParams);
        indicator.setVisibility(View.GONE);
        indicator.setId(R.id.actionbar_compat_item_refresh_progress);
        actionBar.addView(indicator);
    }

    return actionButton;
}


res/values/attrs.xml文件

<resources>

    <declare-styleable name="AppTheme">
        <attr name="actionbarCompatTitleStyle" format="reference" />
        <attr name="actionbarCompatItemStyle" format="reference" />
        <attr name="actionbarCompatItemHomeStyle" format="reference" />
        <attr name="actionbarCompatProgressIndicatorStyle" format="reference" />
    </declare-styleable>

    <declare-styleable name="BezelImageView">
        <attr name="maskDrawable" format="reference" />
        <attr name="borderDrawable" format="reference" />
    </declare-styleable>

</resources>

res/values/dimens.xml文件

<resources>
    <dimen name="actionbar_compat_height">48dp</dimen>
    <dimen name="actionbar_compat_button_width">48dp</dimen>
    <dimen name="actionbar_compat_button_home_width">56dp</dimen>
</resources>

res/layout/main.xml文件

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <Button android:id="@+id/toggle_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="@string/toggle_title" />
</FrameLayout>

在MainActivity.java文件中,将res/menu/main.xml文件渲染出来。

public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater menuInflater = getMenuInflater();
    menuInflater.inflate(R.menu.main, menu);

    // Calling super after populating the menu is necessary here to ensure that the
    // action bar helpers have a chance to handle this event.
    return super.onCreateOptionsMenu(menu);
}

public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case android.R.id.home:
            Toast.makeText(this, "Tapped home", Toast.LENGTH_SHORT).show();
            break;

        case R.id.menu_refresh:
            Toast.makeText(this, "Fake refreshing...", Toast.LENGTH_SHORT).show();
            getActionBarHelper().setRefreshActionItemState(true);
            getWindow().getDecorView().postDelayed(
                    new Runnable() {
                        @Override
                        public void run() {
                            getActionBarHelper().setRefreshActionItemState(false);
                        }
                    }, 1000);
            break;

        case R.id.menu_search:
            Toast.makeText(this, "Tapped search", Toast.LENGTH_SHORT).show();
            break;

        case R.id.menu_share:
            Toast.makeText(this, "Tapped share", Toast.LENGTH_SHORT).show();
            break;
    }
    return super.onOptionsItemSelected(item);
}


在 ActionBarHelperBase.setRefreshActionItemState(boolean refreshing) 文件中

设置refreshButton为View.GONE, 设置refreshIndicator为View.VISIBLE

public void setRefreshActionItemState(boolean refreshing) {
    View refreshButton = mActivity.findViewById(R.id.actionbar_compat_item_refresh);
    View refreshIndicator = mActivity.findViewById(R.id.actionbar_compat_item_refresh_progress);

    if (refreshButton != null) {
        refreshButton.setVisibility(refreshing ? View.GONE : View.VISIBLE);
    }
    if (refreshIndicator != null) {
        refreshIndicator.setVisibility(refreshing ? View.VISIBLE : View.GONE);
    }
}

注:boolean android.app.Activity.onMenuItemSelected(int featureId, MenuItem item)
Default implementation of android.view.Window.Callback.onMenuItemSelected for activities.

This calls through to the new onOptionsItemSelected method for the android.view.Window.FEATURE_OPTIONS_PANEL panel, so that subclasses of Activity don't need to deal with feature codes.

actionButton.setContentDescription(item.getTitle()); 出现错误:

Call requires API level 4 (current min is 3): android.widget.ImageButton#setContentDescription

出错的原因为AndroidManifest.xml中, uses-sdk中的android:minSdkVersion指定错误,按照错误提示变更minSdkVersion即可。

如该例中:<uses-sdk android:minSdkVersion="4" android:targetSdkVersion="14" />

遗留问题:

点击标题栏最前面的Home按键怎么没有反应。

原文地址:https://www.cnblogs.com/fengzhblog/p/3170066.html