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按鍵怎么沒有反應。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM