0
点赞
收藏
分享

微信扫一扫

10 导航抽屉:周游世界

暮晨夜雪 2022-04-07 阅读 73

1.引入

1.1 引入

容易导航的应用更出色。

这一章将介绍导航抽屉,这是一个滑出式面板用手指划屏幕或者单击动作条上的一个图标时这个面板就会出现

我们会介绍如何用它显示一个链接列表,带你去访问应用的所有主要导航项目。你会看到,通过切换片段可以很容易地到达这些导航项目,并快速显示

1.2 再来看看披萨应用

第9章中,我们给出了披萨应用顶级屏幕的一个草图。

这里有一个选项列表,列出了用户在这个应用中可以访问哪些地方。

前3个选项分别链接到披萨、意大利面和分店的类别屏幕,最后一个选项链接到一个详细信息/编辑屏幕,用户可以在这里创建一个订单。

到目前为止,你已经看到了如何为动作条增加动作项。这种方法最适合“创建订单”之类的主动选项。

那么类别屏幕呢?因为这些屏幕更被动,只是用来在应用中导航,所以我们要用一种不同的方法

我们将Pizzas,Pasta和Stores选项增加到一个导航抽屉中。导航抽屉(Navigation drawer)是一个滑出式面板,其中包含一些链接,指向应用的主要部分。这些主要部分称为应用的主要导航项目,它们通常是应用中的主要导航点,也就是顶级屏幕和类别:

1.3 导航抽屉解析

要用一种特殊类型的布局来实现导航抽屉,这个布局名称为DrawerLayout.DrawerLayout管理两个视图:

1.一个视图对应主内容。这通常是一个FrameLayout,以便显示和切换片段。

2.另一个视图对应导航抽屉,通常是一个ListView.

默认地,DrawerLayout显示包含主内容的视图。他看上去就像一个普通的活动:

单击导航抽屉图标或者从屏幕下方向上划动手指,导航抽屉的视图会滑出,并覆盖主内容:

然后可以用这个内容在应用中完成导航。

1.4 披萨应用结构

我们要修改MainActivity,让它使用一个抽屉布局。这个活动将包含一个帧布局来显示片段,另外包含一个列表视图显示选项列表。

这个列表视图将包含对应Home、Pizzas、Pasta和Stores的选项,让用户能很容易地导航到应用的主要导航项目。我们将为这些选项分别创建片段,这说明可以在运行时切换片段,而且用户能够从不同的屏幕访问导航抽屉。

我们要完成以下步骤:

1.为主要导航项目创建片段

2.创建和初始化导航抽屉。

        导航抽屉包含一个ListView,其中将显示一个选项列表。

3.让ListView响应单击列表项的事件。

        这样用户就能导航到应用的主要导航项目。

4.增加一个ActionBarDrawerToggle

        这就允许用户通过动作条控制抽屉,而且活动可以响应抽屉的打开和关闭时间。

2.披萨应用结构创建

2.1 为主要导航项目创建片段

2.1.1 创建TopFragment

将用TopFragment显示顶级内容。对于现在来说,要用它来显示文本“Top Fragment”,这样就能知道目前在显示哪一个片段。创建一个新的空片段,片段名为TopFragment,布局名为fragment_top。

代码为:


public class TopFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_top,container,false);
    }
}

下面是fragment_top.xml的代码:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <!-- TODO: Update blank fragment layout -->
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/title_top" />

</RelativeLayout>

2.1.2 创建PizzaFragment

我们将使用一个ListFragment(名为PizzaFragment)来显示披萨列表,注意ListFragment不需要布局,它会使用自己的布局。

在stings.xml中增加一个新的字符串数组资源,名为"pizzas

<string-array name="pizzas">
        <item>Diavolo</item>
        <item>Funghi</item>
    </string-array>

PizzaFragment的代码为:

package com.hfad.bitsandpizzas;


import android.app.AlertDialog;
import android.app.ListFragment;
import android.os.Bundle;

import androidx.fragment.app.Fragment;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

/**
 * A simple {@link Fragment} subclass.
 */
public class PizzaFragment extends ListFragment {


    public PizzaFragment() {
        // Required empty public constructor
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        ArrayAdapter<String> adapter=new ArrayAdapter<>(
                inflater.getContext(),
                android.R.layout.simple_list_item_1,
                getResources().getStringArray(R.array.pizzas)
        );
        setListAdapter(adapter);
        return super.onCreateView(inflater,container,savedInstanceState);
    }

}

2.1.3 创建PastaFragment

同样使用ListFragment来表示PastaFragment来显示意面列表。

在stings.xml中增加一个新的字符串数组资源,名为"pasta".

<string-array name="pasta">
        <item>Spaghetti Bolognese</item>
        <item>Lasagne</item>
    </string-array>

PastaFragment的代码为:

package com.hfad.bitsandpizzas;


import android.app.ListFragment;
import android.os.Bundle;

import androidx.fragment.app.Fragment;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

/**
 * A simple {@link Fragment} subclass.
 */
public class PastaFragment extends ListFragment {


    public PastaFragment() {
        // Required empty public constructor
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        ArrayAdapter<String> adapter=new ArrayAdapter<>(
                inflater.getContext(),
                android.R.layout.simple_list_item_1,
                getResources().getStringArray(R.array.pasta)
        );
        setListAdapter(adapter);
        return super.onCreateView(inflater,container,savedInstanceState);
    }

}

2.1.4 创建StoresFragment

同样来创建StoresFragment来显示分店列表。

<string-array name="stores">
        <item>Cambridge</item>
        <item>Sebastopol</item>
    </string-array>

代码为:

package com.hfad.bitsandpizzas;


import android.app.ListFragment;
import android.os.Bundle;

import androidx.fragment.app.Fragment;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

/**
 * A simple {@link Fragment} subclass.
 */
public class StoresFragment extends ListFragment {


    public StoresFragment() {
        // Required empty public constructor
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        ArrayAdapter<String> adapter=new ArrayAdapter<>(
                inflater.getContext(),
                android.R.layout.simple_list_item_1,
                getResources().getStringArray(R.array.stores)
        );
        setListAdapter(adapter);
        return super.onCreateView(inflater,container,savedInstanceState);
    }

}

2.2 .创建和初始化导航抽屉

2.2.1 增加DrawerLayout

接下来修改MainActivity.java的布局,让它使用一个DrawerLayout布局。前面已经说过,这包含一个用来显示片段的FrameLayout,另外包含一个ListView来显示导航抽屉。

<androidx.drawerlayout.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <FrameLayout
        android:id="@+id/content_frame"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    <ListView android:id="@+id/drawer"
        android:layout_width="240dp"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:choiceMode="singleChoice"
        android:divider="@android:color/transparent"
        android:dividerHeight="0dp"
        android:background="#ffffff"/>
</androidx.drawerlayout.widget.DrawerLayout>

DrawerLayout是这个新布局的根组件,这是因为它要控制屏幕上显示的所有组件。DrawerLayout类来自v4支持库,因此要使用它的完全类路径androidx.drawerlayout.widget.DrawerLayout。

DrawerLayout中的第一个元素用来显示内容。此处,这里是一个FrameLayout(帧布局),我们要用它显示片段。这里希望这个布局尽可能大,因此将它的layout_width和layout_height属性设置为"match_parent".

DrawerLayout中的第二个元素用来定义抽屉自身。如果使用ListView,这会显示一个包含选项列表的抽屉。通常希望在它滑出时水平地部分填充屏幕,所以这里将它的layout_height属性设置为"match_parent",另外将layout_width属性设置为一个固定的宽度。

 下面对activity_main.xml的完整代码进行解读:

要注意<ListV  iew>元素使用的设置,因为将来你创建的导航抽屉很可能也会使用类似的样式。

要设置抽屉的大小,可以使用layout _ width和layout_height属性。我们将layout _ width设置为“240dp”,这样当抽屉打开时它的宽度将是240dp。

将layout _ gravity属性设置为"start"、如果使用的语言是从左向右读,这会把抽屉放在左边,而另外一些国家的文字要从右向左读,这种情况下就会把抽屉放在右边。

divider,dividerHeight和background属性用来去掉选项之间的分隔线,并设置背景颜色。

最后,设置choiceMode属性为"singleChoice",这表示一次只能选择一项。

2.2.2 初始化抽屉列表

既然已经为activity_main.xml增加了抽屉布局,下面要在MainActivity.java中指定它的行为。

首先要填充列表视图,因此要为strings.xml增加一个选项数组,然后使用一个数组适配器填充这个列表。

    <string-array name="titles">
        <item>Home</item>
        <item>Pizzas</item>
        <item>Pasta</item>
        <item>Stores</item>
    </string-array>

这些是导航抽屉中将要显示的选项。把这个数组增加到strings.xml中。

我们将在MainActivity.java的onCreate()方法中填充列表视图。下面使用私有变量表示数组和列表视图,代码为:

public class MainActivity extends Activity {

    //增加一个ShareActionProvider私有变量
    private ShareActionProvider shareActionProvider;

    //将在后面的方法中用到,因此将他们设为私有的类变量
    private String[] titles;
    private ListView drawerList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //....
        titles=getResources().getStringArray(R.array.titles);
        drawerList=(ListView)findViewById(R.id.drawer);
        drawerList.setAdapter(new ArrayAdapter<String>(this,//使用一个ArrayAdapter填充ListView
                android.R.layout.simple_list_item_1,titles));//使用simple_list_item_1意味着用户单击的项会高亮显示
    }

...

}

 已经用选项列表thitles填充了 列表视图drawerList,下面要让这个列表相应列表项单击事件。

2.3 让ListView相应单击列表项的事件

2.3.1 使用OnItemClickListener相应列表视图的单击事件

要让列表视图响应单击事件,需要使用一个onItemClickListener.我们要创建一个监听器,实现它的onItemClick()方法,为列表视图指定这个监听器。

下面是代码:

public class MainActivity extends Activity {
    
    //...
 
    
    private class DrawerItemClickListener implements ListView.OnItemClickListener{

        //用户单击导航抽屉中的一项时,会调用onItemClick()方法
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            
        }
    }
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //...
        drawerList.setOnItemClickListener(new DrawerItemClickListener());//为抽屉的ListView增加OnItemClickListener的一个新实例
    }

...
}

onItemClick()方法需要包含适当的代码,用户点击列表视图中的一项时会运行这些代码。我们要让它调用一个新的selectItem()方法,传入用户选择的项的位置。

下面就来编写这个方法:

这个方法要完成3个工作:

1.切换帧布局中的片段

2.修改动作条中的标题来反映布局的变化

3.关闭导航抽屉

2.3.2  目前的selectItem()方法

单击导航抽屉中的一项时,它会调用selectItem(),这个方法会显示一个片段:

private class DrawerItemClickListener implements ListView.OnItemClickListener{

        //用户单击导航抽屉中的一项时,会调用onItemClick()方法
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            selectItem(position);
        }
}

        //检查所单击的项的位置
        private void selectItem(int position){
            Fragment fragment;
            switch (position){
                case 1:
                    fragment=new PizzaFragment();
                    break;
                case 2:
                    fragment=new PastaFragment();
                    break;
                case 3:
                    fragment=new StoresFragment();
                    break;
                default:
                    fragment=new TopFragment();
            }
            FragmentTransaction ft=getFragmentManager().beginTransaction();
            ft.replace(R.id.content_frame,fragment);
            ft.addToBackStack(null);
            ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);//使用片段事务替换当前显示的片段
            ft.commit();
        }
    

既然SelectItem()方法可以显示正确的片段,下面再让它改变动作条的标题。

2.3.3  改变动作条标题

除了切换所显示的片段,还需要改变动作条的标题,让它反映当前显示的片段。默认地,我们希望动作条显示应用名,不过如果用户单击了某个选项,如Pizzas,则希望能把动作条标题改为“Pizzas”。这样一来,用户就能知道他们处于应用的什么位置。

为此,我们要使用所选项的位置,从titles数组得到要显示的标题。然后使用ActionBar setTitle()方法更新动作条标题。我们把这些代码放在一个单独的方法中,因为后面还需要用到这个方法。下面给出相应的代码:


       private void selectItem(int position){
             ...
            //设置动作条标题
            setActionBarTitle(position);
        }

        private void setActionBarTitle(int position){
            String title;
            if (position==0){//如果用户单击"Home"选项,使用应用名作为标题
                title=getResources().getString(R.string.app_name);
            }else{//否则,从titles数组得到对应所单击位置的String,并使用这个字符串
                title=titles[position];
            }
            getActionBar().setTitle(title);//显示动作条中的标题
        }
    }

2.3.4  关闭导航抽屉

最后要让selectItem()代码关闭导航抽屉。这样用户就不用自行关闭了。

要得到DrawerLayout的引用,调用它的closeDrawer()方法来关闭导航抽屉。closeDrawer()方法有一个参数,也就是导航抽屉中使用的视图,在这里就是显示选项列表的ListView.

 DrawerLayout。DrawerLayout管理两个视图:

  • 一个视图对应主内容。这通常是一个FrameLayout,以便显示和切换片段。
  • 另一个视图对应导航抽屉,通常是一个ListView。

 完整的MainActivity.java代码:

package com.hfad.bitsandpizzas;

import android.app.ActionBar;
import android.app.Activity;

import android.app.Fragment;
import android.app.FragmentTransaction;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;//onCreateOptionsMenu()方法要使用Menu类
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.ShareActionProvider;

import androidx.annotation.NonNull;
import androidx.drawerlayout.widget.DrawerLayout;

public class MainActivity extends Activity {

    //增加一个ShareActionProvider私有变量
    private ShareActionProvider shareActionProvider;

    //将在后面的方法中用到,因此将他们设为私有的类变量
    private String[] titles;
    private ListView drawerList;

    private DrawerLayout drawerLayout;

    private class DrawerItemClickListener implements ListView.OnItemClickListener{

        //用户单击导航抽屉中的一项时,会调用onItemClick()方法
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            selectItem(position);
        }

    }



    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ActionBar actionBar=getActionBar();
        actionBar.setDisplayShowHomeEnabled(true);//这会在动作条中启用向上按钮
        titles=getResources().getStringArray(R.array.titles);
        drawerList=(ListView)findViewById(R.id.drawer);
        drawerLayout=(DrawerLayout)findViewById(R.id.drawer_layout);//得到DrawerLayout的引用
        //发布ListView
        drawerList.setAdapter(new ArrayAdapter<String>(this,//使用一个ArrayAdapter填充ListView
                android.R.layout.simple_list_item_1,titles));//使用simple_list_item_1意味着用户单击的项会高亮显示
        drawerList.setOnItemClickListener(new DrawerItemClickListener());//为抽屉的ListView增加OnItemClickListener的一个新实例
        if (savedInstanceState == null){
            selectItem(0);//如果MainActivity是新建的,则使用selectItem()方法显示TopFragment
        }
    }

    //实现这个方法会把菜单资源文件中的菜单项增加到动作条
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main,menu);// 参数1:菜单资源文件  参数2:是一个Menu对象,表示动作条
        MenuItem menuItem=menu.findItem(R.id.action_share);
        shareActionProvider=(ShareActionProvider)menuItem.getActionProvider();//得到共享动作提供者的一个引用,并赋给这个私有变量。然后调用setIntent()方法
        setIntent("This is example text");
        return super.onCreateOptionsMenu(menu);
    }

    private void setIntent(String text){
        Intent intent = new Intent(Intent.ACTION_SEND);
        intent.setType("text/plain");
        intent.putExtra(Intent.EXTRA_TEXT, text);
        shareActionProvider.setShareIntent(intent);
    }

    //MenuItem对象是动作条上单击的动作项
    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()){
            case R.id.action_create_order:
                //让创建订单动作项完成一些工作
                Intent intent = new Intent(this, OrderActivity.class);//单击创建订单动作时要用这个意图启动OrderActivity
                startActivity(intent);
                return  true;
            case R.id.action_settings:
                //AS会为我们创建一个setting项,在这里增加一些代码让它做些工作
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    private void setActionBarTitle(int position){
        String title;
        if (position==0){//如果用户单击"Home"选项,使用应用名作为标题
            title=getResources().getString(R.string.app_name);
        }else{//否则,从titles数组得到对应所单击位置的String,并使用这个字符串
            title=titles[position];
        }
        getActionBar().setTitle(title);//显示动作条中的标题
    }

    //检查所单击的项的位置
    private void selectItem(int position){
        Fragment fragment;
        switch (position){
            case 1:
                fragment=new PizzaFragment();
                break;
            case 2:
                fragment=new PastaFragment();
                break;
            case 3:
                fragment=new StoresFragment();
                break;
            default:
                fragment=new TopFragment();
        }
        FragmentTransaction ft=getFragmentManager().beginTransaction();
        ft.replace(R.id.content_frame,fragment);
        ft.addToBackStack(null);
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);//使用片段事务替换当前显示的片段
        ft.commit();
        //设置动作条标题
        setActionBarTitle(position);
        //关闭导航抽屉
        drawerLayout.closeDrawer(drawerList);//drawerList是DrawerLayout的抽屉。这会告诉DrawerLayout关闭drawList抽屉

    }
}

2.4 增加一个ActionBarDrawerToggle

2.4.1 ActionBarDrawerToggle相关

ActionBarDrawerToggle是用来在DrawerLayout发生状态改变的时候,相应的其他控件也随之动态改变的一种安卓 Material Design设计思想,监听Drawer拉出、隐藏。

ActionBarDrawerToggle是一个开关,用于打开/关闭DrawerLayout抽屉

ActionBarDrawerToggle 提供了一个方便的方式来配合DrawerLayout和ActionBar,以实现推荐的抽屉功能。即点击ActionBar的home按钮,即可弹出DrawerLayout抽屉

要建立DrawerListener,最好的办法是使用一个ActionBarDrawerToggle。ActionBarDrawerToggle是动作条使用的一种特殊类型的DrawerListener。它不仅允许监听DrawerLayout事件(就像一个普通的DrawerListener) ,另外还允许单击动作条上的一个图标打开和关闭抽屉。

首先在strings.xml中创建两个String资源,分别描述“open drawer”和“close drawer”动作。为了让应用更便于使用,需要有这两个字符串资源:

    <string name="open_drawer">Open drawer</string>
    <string name="close_drawer">Close drawer</string>

然后调用ActionBarDrawerToggle的构造函数并传入4个函数,创建一个新的AcitonBarDrawerToggle,这4个参数包括:

  1. Context(通常对应当前上下文)
  2. DrawerLayout
  3. 2个String资源

然后覆盖AcionBarDrawerToggle的onDrawerClosed()和onDrawerOpened()方法:

抽屉关闭时会调用onDrawerClosed(View view)方法

抽屉打开时会调用onDrawerOpened(View drawerView)方法 

一旦创建了ActionBarDrawerToggle,使用DrawerLayout的setDrawerListener()方法为DrawerLayout设置这个监听器:

2.4.2 运行时修改动作条上的动作项

如果动作条上有一些像特定于某个特定片段的内容,你可能希望在抽屉打开时隐藏这些项,而在抽屉关闭时再次显示。

需要以这种方式修改动作条的内容,必须做到两点。

首先,需要调用活动的invalidateOptionsMenu()方法。这会告诉Andorid动作条上显示的菜单项有变化,需要重新创建

调用invalidateOptionsMenu()方法时,会调用活动的onPrepaerOptionsMenu()方法,可以覆盖这个方法来指定如何改变菜单项。

我们需要根据抽屉打开还是关闭来改变动作条上共享动作的可见性。因此需要在ActionBarDrawerToggle的onDrawerClosed()和onDrawerOpened()方法中调用invalidateOptionsMenu()方法:

 然后使用活动的onPrepareOptionsMenu()方法设置共享动作的可见性。

2.4.3 更新后的完整代码

public class MainActivity extends Activity {

    //...

    private ActionBarDrawerToggle drawerToggle;

    private class DrawerItemClickListener implements ListView.OnItemClickListener{


    @Override
    protected void onCreate(Bundle savedInstanceState) {
         //...
        //创建 ActionBarDrawerToggle
        drawerToggle=new ActionBarDrawerToggle(this,drawerLayout,R.string.open_drawer,R.string.close_drawer){

            @Override
            public void onDrawerOpened(View drawerView) {
                super.onDrawerOpened(drawerView);
                invalidateOptionsMenu();
            }

            @Override
            public void onDrawerClosed(View drawerView) {
                super.onDrawerClosed(drawerView);
                invalidateOptionsMenu();
            }
        };
        //设置DrawerLayout的抽屉监听器为ActionBarDrawerToggle
        drawerLayout.setDrawerListener(drawerToggle);
    }

    //抽屉打开或关闭时设置共享动作的可见性
    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        //抽屉打开或关闭时设置共享动作的可见性
        boolean drawerOpen=drawerLayout.isDrawerOpen(drawerList);
        menu.findItem(R.id.action_share).setVisible(!drawerOpen);
        return super.onPrepareOptionsMenu(menu);
    }

}

 效果图:

2.5 支持向上按钮

2.5.1 需求:允许打开和关闭抽屉

 我们已经为MainActivity增加了一个导航抽屉,填充了一个选项列表,在单击选项时让活动做出响应,而且了解了抽屉打开时如何隐藏动作项。最后要做的是允许用户单击动作条上的一个图标来打开和关闭抽屉

首先,启用动作条中的图标。为此,要在 活动的onCreate()方法中使用下面这两个方法调用:

setHomeButtonEnabled这个小于4.0版本的默认值为true的。但是在4.0及其以上是false,该方法的作用:决定左上角的图标是否可以点击。没有向左的小图标。 true 图标可以点击  false 不可以点击.

actionBar.setDisplayHomeAsUpEnabled(true)    // 给左上角图标的左边加上一个返回的图标 。对应ActionBar.DISPLAY_HOME_AS_UP

这两个代码行会启用活动的向上按钮。由于我们在使用ActionBarDrawerToggle,所以会用向上按钮激活抽屉而不是沿着应用的层次结构导航。

接下来,需要让ActionBarDrawerToggle处理单击事件。为此,要从活动的onOptionsItemSelected ()方法调用ActionBa-rDrawerToggle的onOptionsItemSelected()方法,如下所示:

如果ActionBarDrawerToggle处理了单击时间,下面的代码:

 会返回true。如果它返回false,这说明单击了动作条上的另一个动作项,所以会运行活动的onOptionsItemSelected()方法的其余代码。

2.5.2 同步ActionBarDrawerToggle状态

要让这个ActionBarDrawerToggle正常工作,还需要做两件事。

首先,需要在活动的postCreate()方法中调用ActionBarDrawerToggle的syncState()方法。利用syncState()方法,可以保证抽屉图标的状态与DrawerLayout的状态同步

需要在活动的onPostCreate()方法中调用syncState()方法,保证创建活动后ActionBarDrawerToggle能有正确的状态:

 onPostCreate方法是指onCreate方法彻底执行完毕的回调,onPostResume类似,这两个方法官方说法是一般不会重写,现在知道的做法也就只有在使用ActionBarDrawerToggle的使用在onPostCreate需要在屏幕旋转时候等同步下状态。

参考链接:Activity生命周期中三个不常用的方法:onContentChanged,onPostCreate,onPostResume_星辰旋风的博客-CSDN博客_onpostresume

最后,如果设备配置有变化,还需要将配置变化的详细信息传递给ActionBarDrawerToggle。为此,要从活动的onconfigurationChanged()方法调用ActionBarDrawerToggle的onConfigurationChanged()方法:

2.5.3 完整代码

截止到目前的所有代码:

package com.hfad.bitsandpizzas;

import android.app.ActionBar;
import android.app.Activity;

import android.app.Fragment;
import android.app.FragmentTransaction;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.Menu;//onCreateOptionsMenu()方法要使用Menu类
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.ShareActionProvider;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.drawerlayout.widget.DrawerLayout;

public class MainActivity extends Activity {

    //增加一个ShareActionProvider私有变量
    private ShareActionProvider shareActionProvider;

    //将在后面的方法中用到,因此将他们设为私有的类变量
    private String[] titles;
    private ListView drawerList;

    private DrawerLayout drawerLayout;

    private ActionBarDrawerToggle drawerToggle;

    private class DrawerItemClickListener implements ListView.OnItemClickListener{

        //用户单击导航抽屉中的一项时,会调用onItemClick()方法
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            selectItem(position);
        }

    }



    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ActionBar actionBar=getActionBar();
        actionBar.setDisplayShowHomeEnabled(true);//这会在动作条中启用向上按钮
        titles=getResources().getStringArray(R.array.titles);
        drawerList=(ListView)findViewById(R.id.drawer);
        drawerLayout=(DrawerLayout)findViewById(R.id.drawer_layout);//得到DrawerLayout的引用
        //发布ListView
        drawerList.setAdapter(new ArrayAdapter<String>(this,//使用一个ArrayAdapter填充ListView
                android.R.layout.simple_list_item_1,titles));//使用simple_list_item_1意味着用户单击的项会高亮显示
        drawerList.setOnItemClickListener(new DrawerItemClickListener());//为抽屉的ListView增加OnItemClickListener的一个新实例
        if (savedInstanceState == null){
            selectItem(0);//如果MainActivity是新建的,则使用selectItem()方法显示TopFragment
        }

        //创建 ActionBarDrawerToggle
        drawerToggle=new ActionBarDrawerToggle(this,drawerLayout,R.string.open_drawer,R.string.close_drawer){

            @Override
            public void onDrawerOpened(View drawerView) {
                super.onDrawerOpened(drawerView);
                invalidateOptionsMenu();
            }

            @Override
            public void onDrawerClosed(View drawerView) {
                super.onDrawerClosed(drawerView);
                invalidateOptionsMenu();
            }
        };
        //设置DrawerLayout的抽屉监听器为ActionBarDrawerToggle
        drawerLayout.setDrawerListener(drawerToggle);
        //启用向上图标,使ActionBarDrawerToggle能使用向上按钮。
        getActionBar().setDisplayHomeAsUpEnabled(true);
        getActionBar().setHomeButtonEnabled(true);
    }

    @Override
    protected void onPostCreate(@Nullable Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        drawerToggle.syncState();
    }

    @Override
    public void onConfigurationChanged(@NonNull Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        drawerToggle.onConfigurationChanged(newConfig);
    }

    //抽屉打开或关闭时设置共享动作的可见性
    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        //抽屉打开或关闭时设置共享动作的可见性
        boolean drawerOpen=drawerLayout.isDrawerOpen(drawerList);
        menu.findItem(R.id.action_share).setVisible(!drawerOpen);
        return super.onPrepareOptionsMenu(menu);
    }

    //实现这个方法会把菜单资源文件中的菜单项增加到动作条
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main,menu);// 参数1:菜单资源文件  参数2:是一个Menu对象,表示动作条
        MenuItem menuItem=menu.findItem(R.id.action_share);
        shareActionProvider=(ShareActionProvider)menuItem.getActionProvider();//得到共享动作提供者的一个引用,并赋给这个私有变量。然后调用setIntent()方法
        setIntent("This is example text");
        return super.onCreateOptionsMenu(menu);
    }

    private void setIntent(String text){
        Intent intent = new Intent(Intent.ACTION_SEND);
        intent.setType("text/plain");
        intent.putExtra(Intent.EXTRA_TEXT, text);
        shareActionProvider.setShareIntent(intent);
    }

    //MenuItem对象是动作条上单击的动作项
    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        if (drawerToggle.onOptionsItemSelected(item)){
            return true;
        }
        switch (item.getItemId()){
            case R.id.action_create_order:
                //让创建订单动作项完成一些工作
                Intent intent = new Intent(this, OrderActivity.class);//单击创建订单动作时要用这个意图启动OrderActivity
                startActivity(intent);
                return  true;
            case R.id.action_settings:
                //AS会为我们创建一个setting项,在这里增加一些代码让它做些工作
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    private void setActionBarTitle(int position){
        String title;
        if (position==0){//如果用户单击"Home"选项,使用应用名作为标题
            title=getResources().getString(R.string.app_name);
        }else{//否则,从titles数组得到对应所单击位置的String,并使用这个字符串
            title=titles[position];
        }
        getActionBar().setTitle(title);//显示动作条中的标题
    }

    //检查所单击的项的位置
    private void selectItem(int position){
        Fragment fragment;
        switch (position){
            case 1:
                fragment=new PizzaFragment();
                break;
            case 2:
                fragment=new PastaFragment();
                break;
            case 3:
                fragment=new StoresFragment();
                break;
            default:
                fragment=new TopFragment();
        }
        FragmentTransaction ft=getFragmentManager().beginTransaction();
        ft.replace(R.id.content_frame,fragment);
        ft.addToBackStack(null);
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);//使用片段事务替换当前显示的片段
        ft.commit();
        //设置动作条标题
        setActionBarTitle(position);
        //关闭导航抽屉
        drawerLayout.closeDrawer(drawerList);//drawerList是DrawerLayout的抽屉。这会告诉DrawerLayout关闭drawList抽屉

    }
}

运行代码:

 当抽屉关闭时,共享动作可见,抽屉打开时共享动作隐藏。

 

2.6 标题和片段不同步

2.6.1 不同步的问题

单击导航抽屉中的某个选项时,动作条中的标题会反映当前显示的片段

举例来说,先点击Pizzas选项,动作条标题会设置为"Pizzas":

如果单击后退按钮,标题并没有更新来反映当前显示的片段

 假设单击Stores选项,然后再单击Pizzas选项,这会显示一个披萨列表,而且标题会反映这一点。如果接下来再单击后退按钮,现在会显示StoresFragment,但是标题还是"Stores"。、

 同样旋转时,也会发生问题。因此需要对这些问题来进行修正,首先要在旋转设备时保证动作条标题同步。

2.6.2 处理配置变化

你已经知道,旋转设备时会撤销当前活动,然后重新创建活动。这意味着你对用户界面做的所有修改都会丢失,这也包括对动作条标题的改变。

与前几章中一样,我们要使用活动的onSaveInstancestate()方法保存导航抽屉中当前所选项的位置。然后可以在oncreate()方法中用这个位置更新动作条中的标题。

需要对原代码进行更新,更新为图中的4个地方的代码。

 

2.6.3 响应后退堆栈的变化

最后还有一个问题需要解决:如何让动作条标题反映用户单击某一项时显示的片段可以为活动的片段管理器增加一个FragmentManager.onBackStackChangedListener解决这个问题

FragmentManager.OnBackStackChangedListener接口会监听后退堆栈的变化。这包括在后退堆栈中增加片段事务,以及用户单击后退按钮导航到之前的后退堆栈记录。

可以如下在活动的片段管理器增加一个onBackStackChangedListener:

后退堆栈改变时,会调用onBackStackChangedListener的onBackStackChanged()方法。用户单击后退按钮时运行的代码就要增加到这个方法中

调用onBackstackChanged()方法时,我们要做到3件事。

  • 更新currentPosition变量,让它反映当前显示片段在列表视图中的位置。
  • 调用setActionBarTitle()方法,传入currentPosition的值。
  • 调用导航抽屉列表视图的setItemChecked()方法,确保正确地窦出显示相应的选项。

所有这些工作有一个前提,就是我们需要知道当前显示的片段在列表视图中的位置。那么怎么得到这个位置呢?

2.6.4 为片段增加标记

要得出currehtPosition的值,需要检查当前与活动关联的片段的类型。例如,如果关联片段是一个PizzaFragment实例,就要将currentPosition设置为1。

可以为各个片段增加一个String标记,得到当前关联片段的引用。接下来再使用片段管理器的findFragmentByTag()方法获取片段。

我们要在片段事务中为片段增加一个标记。下面是selectItem()方法中使用的片段事务,用来替换当前显示的片段:

 

要为片段增加一个标记,需要在事务中为replace()方法增加一个额外的String参数:

 在上面代码中,我们为replace()方法增加了一个标记参数"visible _ fragment"。MainActivity中显示的每个片段都会用这个值增加标记。

接下来,使用片段管理器的findFragmentByTag()方法得到当前关联片段的一个引用。

2.6.5 使用标记查找片段

获取当前与活动关联的片段,我们要在片段事务中将所设置的标记传递到findFragmentByTag()方法:

 findFragmentByTag()方法首先搜索当前与活动关联的所有片段。如果无法找到有相应标记的片段,它就会继续搜索后退堆栈中的所有片段。通过为所有片段指定相同的标记"visible _ fragment",上面的代码会得到当前与活动关联的所有片段的引用

下面是onBackstackListener的完整代码。我们要使用findFragmentByTag()方法得到当前关联片段的一个引用。然后检查它是哪一个片段类型的实例,从而可以得出currentPosition的值:

 以上代码就可以让动作条标题与用户单击后退按钮时显示的片段同步。

2.6.6 完整代码

package com.hfad.bitsandpizzas;

import android.app.ActionBar;
import android.app.Activity;

import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.Menu;//onCreateOptionsMenu()方法要使用Menu类
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.ShareActionProvider;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.drawerlayout.widget.DrawerLayout;

public class MainActivity extends Activity {

    private class DrawerItemClickListener implements ListView.OnItemClickListener{

        //用户单击导航抽屉中的一项时,会调用onItemClick()方法
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            selectItem(position);
        }

    }

    //增加一个ShareActionProvider私有变量
    private ShareActionProvider shareActionProvider;
    //将在后面的方法中用到,因此将他们设为私有的类变量
    private String[] titles;
    private ListView drawerList;
    private DrawerLayout drawerLayout;
    private ActionBarDrawerToggle drawerToggle;
    private int currentPosition = 0;




    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);//加载主活动布局
        titles=getResources().getStringArray(R.array.titles);
        drawerList=(ListView)findViewById(R.id.drawer);
        drawerLayout=(DrawerLayout)findViewById(R.id.drawer_layout);//得到DrawerLayout的引用
        //发布ListView
        drawerList.setAdapter(new ArrayAdapter<String>(this,//使用一个ArrayAdapter填充ListView
                android.R.layout.simple_list_item_1,titles));//使用simple_list_item_1意味着用户单击的项会高亮显示
        drawerList.setOnItemClickListener(new DrawerItemClickListener());//为抽屉的ListView增加OnItemClickListener的一个新实例
        if (savedInstanceState != null) {
            currentPosition = savedInstanceState.getInt("position");
            setActionBarTitle(currentPosition);//如果活动被撤销再重新创建,就要设置正确的动作条标题
        } else {
            selectItem(0);//默认显示TopFragment
        }

        //创建 ActionBarDrawerToggle
        drawerToggle=new ActionBarDrawerToggle(this,
                drawerLayout,R.string.open_drawer,R.string.close_drawer){
            @Override
            public void onDrawerClosed(View view) {
                super.onDrawerClosed(view);
                invalidateOptionsMenu();//抽屉打开或关闭时 调用InvalidateOptionsMenu()
                //因为想改变动作条上显示的动作项
            }

            @Override
            public void onDrawerOpened(View drawerView) {
                super.onDrawerOpened(drawerView);
                invalidateOptionsMenu();
            }
        };
        //设置DrawerLayout的抽屉监听器为ActionBarDrawerToggle
        drawerLayout.setDrawerListener(drawerToggle);
        //启用向上图标,使ActionBarDrawerToggle能使用向上按钮。
        getActionBar().setDisplayHomeAsUpEnabled(true);
        getActionBar().setHomeButtonEnabled(true);//启动动作条上的向上按钮,从而可以用它打开抽屉

        getFragmentManager().addOnBackStackChangedListener(
                new FragmentManager.OnBackStackChangedListener() {
                    //后退堆栈改变时 会调用这个方法
                    @Override
                    public void onBackStackChanged() {
                        FragmentManager fragmentManager=getFragmentManager();
                        Fragment fragment=fragmentManager.findFragmentByTag("visible_fragment");
                        //检查当前与活动关联的片段是哪个类的实例,并相应地设置currentposition
                        if (fragment instanceof TopFragment){
                            currentPosition=0;
                        }else if (fragment instanceof PizzaFragment){
                            currentPosition=1;
                        }else if (fragment instanceof PastaFragment){
                            currentPosition=2;
                        }else if (fragment instanceof StoresFragment){
                            currentPosition=3;
                        }
                        setActionBarTitle(currentPosition);//设置动作条标题
                        drawerList.setItemChecked(currentPosition,true);//突出显示抽屉ListView中相应的项
                    }
                }
        );


    }

    @Override
    protected void onPostCreate(@Nullable Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        drawerToggle.syncState();//将ActionBarDrawerToggle的状态与抽屉状态同步
    }

    @Override
    public void onConfigurationChanged(@NonNull Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        drawerToggle.onConfigurationChanged(newConfig);//将配置变化的详细信息传递给ActionBarDrawerToggle
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("position", currentPosition);//如果活动被撤销,则保存currentPosition的状态
    }

    //抽屉打开或关闭时设置共享动作的可见性
    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        //抽屉打开或关闭时设置共享动作的可见性
        boolean drawerOpen=drawerLayout.isDrawerOpen(drawerList);
        menu.findItem(R.id.action_share).setVisible(!drawerOpen);
        return super.onPrepareOptionsMenu(menu);
    }

    //实现这个方法会把菜单资源文件中的菜单项增加到动作条
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main,menu);// 参数1:菜单资源文件  参数2:是一个Menu对象,表示动作条
        MenuItem menuItem=menu.findItem(R.id.action_share);
        shareActionProvider=(ShareActionProvider)menuItem.getActionProvider();//得到共享动作提供者的一个引用,并赋给这个私有变量。然后调用setIntent()方法
        setIntent("This is example text");
        return super.onCreateOptionsMenu(menu);
    }

    //向共享动作传递一个意图来完成共享
    private void setIntent(String text){
        Intent intent = new Intent(Intent.ACTION_SEND);
        intent.setType("text/plain");
        intent.putExtra(Intent.EXTRA_TEXT, text);
        shareActionProvider.setShareIntent(intent);
    }

    /**
     * 用户单击动作条上的一项时会调用这个方法
     * @param item
     * @return
     */
    //MenuItem对象是动作条上单击的动作项
    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        if (drawerToggle.onOptionsItemSelected(item)){//如果单击ActionBarDrawerToggle,让他处理这个单击事件
            return true;
        }
        switch (item.getItemId()){
            case R.id.action_create_order:
                //让创建订单动作项完成一些工作
                Intent intent = new Intent(this, OrderActivity.class);//单击创建订单动作时要用这个意图启动OrderActivity
                startActivity(intent);
                return  true;
            case R.id.action_settings:
                //AS会为我们创建一个setting项,在这里增加一些代码让它做些工作
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    private void setActionBarTitle(int position){
        String title;
        if (position==0){//如果用户单击"Home"选项,使用应用名作为标题
            title=getResources().getString(R.string.app_name);
        }else{//否则,从titles数组得到对应所单击位置的String,并使用这个字符串
            title=titles[position];
        }
        getActionBar().setTitle(title);//显示动作条中的标题
    }

    //检查所单击的项的位置
    private void selectItem(int position){
        currentPosition = position;
        Fragment fragment;
        switch (position){
            case 1:
                fragment=new PizzaFragment();
                break;
            case 2:
                fragment=new PastaFragment();
                break;
            case 3:
                fragment=new StoresFragment();
                break;
            default:
                fragment=new TopFragment();
        }
        //显示这个片段
        FragmentTransaction ft=getFragmentManager().beginTransaction();
        //将当前与活动关联的片段增加一个String 标签
        ft.replace(R.id.content_frame,fragment,"visible_fragment");
        ft.addToBackStack(null);
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);//使用片段事务替换当前显示的片段
        ft.commit();
        //设置动作条标题
        setActionBarTitle(position);
        //关闭导航抽屉
        drawerLayout.closeDrawer(drawerList);//drawerList是DrawerLayout的抽屉。这会告诉DrawerLayout关闭drawList抽屉

    }
}

3.本章总结

  1. 使用DrawerLayout创建一个带导航抽屉的活动。可以使用这个抽屉导航到应用的主要导航项目。
  2. 如果使用一个动作条,可以使用ActionBarDrawer-Toggle作为DrawerListener,以响应抽屉的打开和关闭,另外会为动作条增加一个图标来打开和关闭抽屉。
  3. 要在运行时改变动作条中的动作项,可以调用inv a lidateoptions Menu( ),并在活动的onPrepareoptionsMenu()方法中指定如何改变。
  4. 可以实现FragmentManager.OnBackstackchang-edListener()响应后退堆栈的变化。
  5. 片段管理器的findFragmentByTag()方法会搜索有指定标记的片段。
     

举报

相关推荐

0 条评论