08 工具栏

使用AppCompat库

AndroidManifest.xml

...
    <application
        ...
        android:theme="@style/AppTheme">
        ...
    </application>
...

res/values/styles.xml

...    
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        ...
    </style>
...

修改 SingleFragmentActivity 和 CrimePagerActivity ,让它们直接继承 AppCompatActivity类。AppCompatActivity 恰恰就是 FragmentActivity 的子类。


在XML文件中定义菜单

右键单击res目录,New => Android resource file,Resource type选择Menu,创建资源文件fragment_crime_list.xml:

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/menu_item_new_crime"
        android:icon="@drawable/ic_menu_add"
        android:title="@string/new_crime"
        app:showAsAction="ifRoom|withText" />
    <item
        android:id="@+id/menu_item_show_subtitle"
        android:title="@string/show_subtitle"
        app:showAsAction="ifRoom" />
</menu>

创建菜单

Activity 类提供了管理菜单的回调函数,需要选项菜单时,Android会调用Activity 的 onCreateOptionsMenu(Menu) 方法。按照CriminalIntent应用的设计,选项菜单相关的回调函数需在fragment而非activity里实现。Fragment 有一套自己的选项菜单回调函数。

//实例化选项菜单
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    super.onCreateOptionsMenu(menu, inflater);

    inflater.inflate(R.menu.fragment_crime_list, menu);
}

在以上方法中,调用 MenuInflater.inflate(int, Menu) 方法并传入菜单文件的资源ID,
将布局文件中定义的菜单项目填充到 Menu 实例中。然后通知FragmentManager其管理的fragment(CrimeListFragment)需接收选项菜单方法回调:

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    /**
     * 通知FragmentManager其管理的fragment(CrimeListFragment)需接收选项菜单方法回调
     */
    setHasOptionsMenu(true);
}

响应菜单项选择

新增addCrime(Crime)方法:

CrimeLab.java

//添加新的陋习
public void addCrime(Crime crime) {
    mCrimes.add(crime);
}

响应菜单项选择事件:

//响应菜单项选择事件
@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.menu_item_new_crime:
            Log.d(TAG, "点击了[添加新的陋习]菜单...");
            Crime crime = new Crime();
            CrimeLab.get(getActivity()).addCrime(crime);
            Intent intent = CrimePagerActivity.newIntent(getActivity(), crime.getId());
            startActivity(intent);
            return true;
        default:
            return super.onOptionsItemSelected(item);
    }
}

onOptionsItemSelected(MenuItem) 方法返回的是布尔值。一旦完成菜单项事件处理,应返回 true 值以表明全部任务已完成。另外,默认 case 表达式中,如果菜单项ID不存在,超类版本方法会被调用。


实现层级式导航

CriminalIntent应用主要靠后退键在应用内导航。后退键导航又称为临时性导航,只能返回到上一次浏览过的用户界面;而层级式导航(hierarchical navigation,有时又称为ancestral navigation)可在应用内逐级向上导航。

为AndroidManifest.xml添加parentActivityName属性:

...
    <application
        ...
        <activity
            android:name=".CrimePagerActivity"
            android:parentActivityName=".CrimeListActivity">
        </activity>
    </application>
...

实现[显示/隐藏子标题]菜单项

在fragment_crime_list.xml中添加新的item标签(前面已添加)。

设置工具栏子标题

CrimeListFragment.java

//设置工具栏子标题
private void updateSubtitle() {
    CrimeLab crimeLab = CrimeLab.get(getActivity());
    int crimeCount = crimeLab.getCrimes().size();
    @SuppressLint("StringFormatMatches") String subtitle = getString(R.string.subtitle_format, crimeCount);

    //实现菜单项文字与子标题的联动
    if (!mSubtitleVisible) subtitle = null;

    AppCompatActivity activity = (AppCompatActivity) getActivity();
    activity.getSupportActionBar().setSubtitle(subtitle);
}

响应菜单单击事件

CrimeListFragment.java

//响应菜单项选择事件
@Override
public boolean onOptionsItemSelected(MenuItem item) {

    switch (item.getItemId()) {
        case R.id.menu_item_new_crime:
            Log.d(TAG, "点击了[添加新的陋习]菜单...");
            //...
            return true;
        case R.id.menu_item_show_subtitle:
            String msg = mSubtitleVisible ? "隐藏子标题" : "显示子标题";
            Log.d(TAG, msg + "...");

            mSubtitleVisible = !mSubtitleVisible;
            getActivity().invalidateOptionsMenu();    //菜单项已经改变,应该重建

            updateSubtitle();
            return true;
        default:
            return super.onOptionsItemSelected(item);
    }
}

更新菜单项的文字

CrimeListFragment.java

//实例化选项菜单
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    super.onCreateOptionsMenu(menu, inflater);

    inflater.inflate(R.menu.fragment_crime_list, menu);

    //更新菜单项的文字
    MenuItem subtitleItem = menu.findItem(R.id.menu_item_show_subtitle);
    if (mSubtitleVisible) subtitleItem.setTitle(R.string.hide_subtitle);
    else subtitleItem.setTitle(R.string.show_subtitle);
}

一些问题

新建记录后回退子标题不刷新

在返回 CrimeListActivity 界面时,再次刷新子标题显示就能解决这个问题。也就是说,在 onResume 方法里再次调用 updateSubtitle 方法。既然 onResume 和onCreate 方法会调用 updateUI 方法,那就在 updateUI 方法里直接调用 updateSubtitle 方法。

但是使用向上按钮回退子标题显示会被重置,这是因为回退到的目标activity会被完全重建。

旋转设备显示的子标题消失

使用onSaveInstanceState(Bundle)来解决。


实现[删除此条陋习]菜单

添加删除陋习的方法

CrimeLab.java

//删除指定ID的陋习
public boolean removeCrime(UUID id) {
    return mCrimes.remove(getCrime(id));
}

新建删除陋习的菜单布局

fragment_crime.xml

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/menu_item_remove_crime"
        android:icon="@drawable/ic_menu_remove"
        android:title="@string/remove_crime"
        app:showAsAction="ifRoom|withText" />

</menu>

实例化选项菜单

CrimeFragment.java

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    super.onCreateOptionsMenu(menu, inflater);

    inflater.inflate(R.menu.fragment_crime, menu);
}

响应菜单项选择事件

CrimeFragment.java

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.menu_item_remove_crime:
            Log.d(TAG, "点击了[删除此条陋习]菜单...");
            CrimeLab.get(getActivity()).removeCrime(mCrime.getId());
            getActivity().finish();
            return true;
        default:
            return super.onOptionsItemSelected(item);
    }
}

记得使用CrimeFragment托管活动(CrimePagerActivity)的finish()方法回退到前一个activity(CrimeListActivity)界面。


实现用于RecyclerView的空视图

新建无数据时显示的视图

list_item_empty.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">
    <TextView
        android:id="@+id/list_item_empty_hint"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="30dp"
        android:text="@string/empty_crime" />
</RelativeLayout>

新建EmptyHolder类

CrimeListFragment.java

private class EmptyHolder extends RecyclerView.ViewHolder {
    private TextView mEmptyHintTextView;

    public EmptyHolder(@NonNull View itemView) {
        super(itemView);

        mEmptyHintTextView = itemView.findViewById(R.id.list_item_empty_hint);
    }
}

设置无数据时的viewType

CrimeListFragment.java

private class CrimeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private static final int EMPTY_VIEW_TYPE = -1;
    //...
}

重写getItemViewType(int)方法

CrimeListFragment.CrimeAdapter

@Override
public int getItemViewType(int position) {
    if (mCrimes.size() <= 0) return EMPTY_VIEW_TYPE;

    return super.getItemViewType(position);
}

同时修改getItemCount()方法,当没有数据时也要显示(要显示提示信息):

@Override
public int getItemCount() {
    return mCrimes.size() > 0 ? mCrimes.size() : 1;
}

根据viewType创建不同的视图

CrimeListFragment.CrimeAdapter

@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {EMPTY_VIEW
    LayoutInflater layoutInflater = LayoutInflater.from(getActivity());

    if (viewType == EMPTY_VIEW_TYPE) {
        Log.d(TAG, "没有陋习可以显示...");
        View view = layoutInflater.inflate(R.layout.list_item_empty, parent, false);

        return new EmptyHolder(view);
    }

    Log.d(TAG, "创建了View视图,并封装到了ViewHolder中。");
    View view = layoutInflater.inflate(R.layout.list_item_crime, parent, false);

    return new CrimeHolder(view);
}

根据ViewHolder的类型决定是否绑定数据

CrimeListFragment.CrimeAdapter

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
    if (viewHolder instanceof CrimeHolder) {
        Log.d(TAG, "绑定了第 " + position + " 条数据。");

        Crime crime = mCrimes.get(position);
        ((CrimeHolder)viewHolder).bindCrime(crime);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值