Google示例APP,教你实现Tab+ViewPaper,教你实现Drawer导航

来源:互联网 发布:python正则表达式匹配 编辑:程序博客网 时间:2024/05/21 00:49

前言

昨天看了Google教程-实现有效的导航之后,也看了看示例APP的源码,没有什么特别的难点,不过在此自己记录一下所学的知识,这样以后就不用再上网找了。

通过学习Drawer和ToolBar的使用,我终于能看懂在Android Studio中的有个叫Nevigation Drawer的起始项目了。

这次分别记录两个APP,一个是Tab+ViewPaper,一个是Drawer导航。


正文

Tab + ViewPaper,实现带有Tab的滑动视图

运行效果:

首先是ViewPaper + Tab
这里写图片描述

这里写图片描述

然后是ViewPaper + PagerTitleStrip

这里写图片描述

两种效果一样,不过第二种没有点击标题然后切换视图的效果,而且样式也不太好。

先看第一种导航的布局文件:

<android.support.v4.view.ViewPager    xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/pager"    android:layout_width="match_parent"    android:layout_height="match_parent" />

只有一个viewpaper

再看它的代码:

public class MainActivity extends FragmentActivity implements ActionBar.TabListener {    AppSectionsPagerAdapter mAppSectionsPagerAdapter;    ViewPager mViewPager;    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        // Create the adapter that will return a fragment for each of the three primary sections        // of the app.        mAppSectionsPagerAdapter = new AppSectionsPagerAdapter(getSupportFragmentManager());        // Set up the action bar.        final ActionBar actionBar = getActionBar();        // Specify that the Home/Up button should not be enabled, since there is no hierarchical        // parent.        actionBar.setHomeButtonEnabled(false);        // Specify that we will be displaying tabs in the action bar.        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);        // Set up the ViewPager, attaching the adapter and setting up a listener for when the        // user swipes between sections.        mViewPager = (ViewPager) findViewById(R.id.pager);        mViewPager.setAdapter(mAppSectionsPagerAdapter);        mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {            @Override            public void onPageSelected(int position) {                // When swiping between different app sections, select the corresponding tab.                // We can also use ActionBar.Tab#select() to do this if we have a reference to the                // Tab.                actionBar.setSelectedNavigationItem(position);            }        });        // For each of the sections in the app, add a tab to the action bar.        for (int i = 0; i < mAppSectionsPagerAdapter.getCount(); i++) {            // Create a tab with text corresponding to the page title defined by the adapter.            // Also specify this Activity object, which implements the TabListener interface, as the            // listener for when this tab is selected.            actionBar.addTab(                    actionBar.newTab()                            .setText(mAppSectionsPagerAdapter.getPageTitle(i))                            .setTabListener(this));        }    }    @Override    public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {    }    @Override    public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {        // When the given tab is selected, switch to the corresponding page in the ViewPager.        mViewPager.setCurrentItem(tab.getPosition());    }    @Override    public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {    }    /**     * A {@link FragmentPagerAdapter} that returns a fragment corresponding to one of the primary     * sections of the app.     */    public static class AppSectionsPagerAdapter extends FragmentPagerAdapter {        public AppSectionsPagerAdapter(FragmentManager fm) {            super(fm);        }        @Override        public Fragment getItem(int i) {            switch (i) {                case 0:                    // The first section of the app is the most interesting -- it offers                    // a launchpad into the other demonstrations in this example application.                    return new LaunchpadSectionFragment();                default:                    // The other sections of the app are dummy placeholders.                    Fragment fragment = new DummySectionFragment();                    Bundle args = new Bundle();                    args.putInt(DummySectionFragment.ARG_SECTION_NUMBER, i + 1);                    fragment.setArguments(args);                    return fragment;            }        }        @Override        public int getCount() {            return 3;        }        @Override        public CharSequence getPageTitle(int position) {            return "Section " + (position + 1);        }    }

这是和第一种导航有关的全部代码,我们看到创建一个ViewPaper的适配器,这个适配器继承FragmentPagerAdapter来为ViewPaper填充内容,然后ViewPaper设置了滑动改变的监听器,这个监听器的作用是,当用户滑动内容是,让Tab跟着变。

为了与之对应,当用户点击Tab的时候,ViewPaper的内容当让也要变。所以我们还要继承ActionBar.TabListener这个接口,在onTabSelected()方法里设置当前显示哪一个内容。

当我们使用Tab导航的时候,不要忘了设置Tab导航模式是actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

第二种导航,先看布局文件:

<android.support.v4.view.ViewPager    xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/pager"    android:layout_width="match_parent"    android:layout_height="match_parent">    <!--    This title strip will display the currently visible page title, as well as the page    titles for adjacent pages.    -->    <android.support.v4.view.PagerTitleStrip        android:id="@+id/pager_title_strip"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_gravity="top"        android:background="#33b5e5"        android:textColor="#fff"        android:paddingTop="4dp"        android:paddingBottom="4dp" /></android.support.v4.view.ViewPager>

PagerTitleStrip嵌套在ViewPaper,它们都是v4包下的。

然我们再来看看它们对应的代码:

public class CollectionDemoActivity extends FragmentActivity {    DemoCollectionPagerAdapter mDemoCollectionPagerAdapter;    ViewPager mViewPager;    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_collection_demo);        // Create an adapter that when requested, will return a fragment representing an object in        // the collection.        //         // ViewPager and its adapters use support library fragments, so we must use        // getSupportFragmentManager.        mDemoCollectionPagerAdapter = new DemoCollectionPagerAdapter(getSupportFragmentManager());        // Set up action bar.        final ActionBar actionBar = getActionBar();        // Specify that the Home button should show an "Up" caret, indicating that touching the        // button will take the user one step up in the application's hierarchy.        actionBar.setDisplayHomeAsUpEnabled(true);        // Set up the ViewPager, attaching the adapter.        mViewPager = (ViewPager) findViewById(R.id.pager);        mViewPager.setAdapter(mDemoCollectionPagerAdapter);    }    @Override    public boolean onOptionsItemSelected(MenuItem item) {        switch (item.getItemId()) {            case android.R.id.home:                // This is called when the Home (Up) button is pressed in the action bar.                // Create a simple intent that starts the hierarchical parent activity and                // use NavUtils in the Support Package to ensure proper handling of Up.                Intent upIntent = new Intent(this, MainActivity.class);                if (NavUtils.shouldUpRecreateTask(this, upIntent)) {                    // This activity is not part of the application's task, so create a new task                    // with a synthesized back stack.                    TaskStackBuilder.from(this)                            // If there are ancestor activities, they should be added here.                            .addNextIntent(upIntent)                            .startActivities();                    finish();                } else {                    // This activity is part of the application's task, so simply                    // navigate up to the hierarchical parent activity.                    NavUtils.navigateUpTo(this, upIntent);                }                return true;        }        return super.onOptionsItemSelected(item);    }    /**     * A {@link android.support.v4.app.FragmentStatePagerAdapter} that returns a fragment     * representing an object in the collection.     */    public static class DemoCollectionPagerAdapter extends FragmentStatePagerAdapter {        public DemoCollectionPagerAdapter(FragmentManager fm) {            super(fm);        }        @Override        public Fragment getItem(int i) {            Fragment fragment = new DemoObjectFragment();            Bundle args = new Bundle();            args.putInt(DemoObjectFragment.ARG_OBJECT, i + 1); // Our object is just an integer :-P            fragment.setArguments(args);            return fragment;        }        @Override        public int getCount() {            // For this contrived example, we have a 100-object collection.            return 100;        }        @Override        public CharSequence getPageTitle(int position) {            return "OBJECT " + (position + 1);        }    }    /**     * A dummy fragment representing a section of the app, but that simply displays dummy text.     */    public static class DemoObjectFragment extends Fragment {        public static final String ARG_OBJECT = "object";        @Override        public View onCreateView(LayoutInflater inflater, ViewGroup container,                Bundle savedInstanceState) {            View rootView = inflater.inflate(R.layout.fragment_collection_object, container, false);            Bundle args = getArguments();            ((TextView) rootView.findViewById(android.R.id.text1)).setText(                    Integer.toString(args.getInt(ARG_OBJECT)));            return rootView;        }    }}

它的整体结构是viewpaper里面显示fragment,通过自己编写的 DemoCollectionPagerAdapter 类作为viewpaper的适配器,给viewpaper提供相应的内容,我们看到,应为DemoCollectionPagerAdapter 继承了FragmentStatePaperAdapter,所以我们不用担心每次都新建Fragment实例,用给系统内存带来影响,因为它会把不显示的Fragment自动销毁掉,所以达到最小化内存的使用。

第二种导航中除了对功能性,我们还注意到

actionBar.setDisplayHomeAsUpEnabled(true);

通过这个设置,我们可以点击这个Activity的ActionBar上的Icon来返回层次逻辑上的上一个Activity,不过还有文章可以说

在Activity覆写的onOptionsItemSelected()方法里面,官方教程考虑到了这个情况,如果这个Activity是从别的APP启动,那么点击ActionBar上的Icon,会出现不能返回的情况。因为这时这个Activity所在APP的返回堆栈是空的,所以点击返回键以后没用。

一般用户都期望在这时点击ActionBar上的Icon,应该返回这个Activity的逻辑上层,所以我们如果检查到这种情况,应该在这个APP的返回堆栈中先添加这个Activity的所有父Activity,也就是逻辑上层Activity:

@Override    public boolean onOptionsItemSelected(MenuItem item) {        switch (item.getItemId()) {            case android.R.id.home:                // This is called when the Home (Up) button is pressed in the action bar.                // Create a simple intent that starts the hierarchical parent activity and                // use NavUtils in the Support Package to ensure proper handling of Up.                Intent upIntent = new Intent(this, MainActivity.class);                if (NavUtils.shouldUpRecreateTask(this, upIntent)) {                    // This activity is not part of the application's task, so create a new task                    // with a synthesized back stack.                    TaskStackBuilder.from(this)                            // If there are ancestor activities, they should be added here.                            .addNextIntent(upIntent)                            .startActivities();                    finish();                } else {                    // This activity is part of the application's task, so simply                    // navigate up to the hierarchical parent activity.                    NavUtils.navigateUpTo(this, upIntent);                }                return true;        }        return super.onOptionsItemSelected(item);    }

关于这块的具体内容可一看上一篇博客。

对了,NavUtils.navigateUpTo(this, upIntent);这个方法通过代码来确定上层逻辑是谁,我们也可以在Manifest问津里通过xml文件来制定,然后直接把这个点击交给onOptionsItemSelected()的超类来处理就好。

还有一个和小神奇的事情,我在一个Fragment的代码看到:

@Override        public View onCreateView(LayoutInflater inflater, ViewGroup container,                Bundle savedInstanceState) {            View rootView = inflater.inflate(R.layout.fragment_section_dummy, container, false);            Bundle args = getArguments();            ((TextView) rootView.findViewById(android.R.id.text1)).setText(                    getString(R.string.dummy_section_text, args.getInt(ARG_SECTION_NUMBER)));            return rootView;        }

这个setText()里面竟然带了两个参数,我马上看了一下id是dummy_section_text的字符串:

<string name="dummy_section_text">Section %1d is just a dummy section.</string>

原来是这样,第二个参数来填上面这个%1d,瞬间有一种C语言的感觉有没有。


Drawer导航,实现侧边导航

先来看看效果:

这里写图片描述

这里写图片描述

大概是这个效果,除了这个意外,还要考虑点击ActionBar展开关闭侧边导航,以及ActonBar名字的改变(在Drawer打开和关闭的时候)Action Item的显示和隐藏。

好了,我们先看一下主Activity的布局文件:

<android.support.v4.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/left_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="#111"/></android.support.v4.widget.DrawerLayout>

注意了使用Drawer导航就要像这样使用XML文件,第一个View一定是显示主布局的View,通常会充满父布局,第二个View是Drawer导航View,这里是一个ListView,也可以是其他的Android组件。这里我们设置第二个View的android:layout_gravity属性为start,这样保证了RTL布局中它会在右边,LTR布局中它会在左侧。

对了,Drawer的高度要匹配父布局,宽度单位要用dp,不能超过320dp,保证用户始终能看到显示主内容的视图。

让我们看一下代码,因为只有一个Java文件,我就直接贴出来了:

public class MainActivity extends Activity {    private DrawerLayout mDrawerLayout;    private ListView mDrawerList;    private ActionBarDrawerToggle mDrawerToggle;    private CharSequence mDrawerTitle;    private CharSequence mTitle;    private String[] mPlanetTitles;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mTitle = mDrawerTitle = getTitle();        mPlanetTitles = getResources().getStringArray(R.array.planets_array);        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);        mDrawerList = (ListView) findViewById(R.id.left_drawer);        // set a custom shadow that overlays the main content when the drawer opens        mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);        // set up the drawer's list view with items and click listener        mDrawerList.setAdapter(new ArrayAdapter<String>(this,                R.layout.drawer_list_item, mPlanetTitles));        mDrawerList.setOnItemClickListener(new DrawerItemClickListener());        // enable ActionBar app icon to behave as action to toggle nav drawer        getActionBar().setDisplayHomeAsUpEnabled(true);        getActionBar().setHomeButtonEnabled(true);        // ActionBarDrawerToggle ties together the the proper interactions        // between the sliding drawer and the action bar app icon        mDrawerToggle = new ActionBarDrawerToggle(                this,                  /* host Activity */                mDrawerLayout,         /* DrawerLayout object */                R.drawable.ic_drawer,  /* nav drawer image to replace 'Up' caret */                R.string.drawer_open,  /* "open drawer" description for accessibility */                R.string.drawer_close  /* "close drawer" description for accessibility */                ) {            public void onDrawerClosed(View view) {                getActionBar().setTitle(mTitle);                invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()            }            public void onDrawerOpened(View drawerView) {                getActionBar().setTitle(mDrawerTitle);                invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()            }        };        mDrawerLayout.setDrawerListener(mDrawerToggle);        if (savedInstanceState == null) {            selectItem(0);        }    }    @Override    public boolean onCreateOptionsMenu(Menu menu) {        MenuInflater inflater = getMenuInflater();        inflater.inflate(R.menu.main, menu);        return super.onCreateOptionsMenu(menu);    }    /* Called whenever we call invalidateOptionsMenu() */    @Override    public boolean onPrepareOptionsMenu(Menu menu) {        // If the nav drawer is open, hide action items related to the content view        boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);        menu.findItem(R.id.action_websearch).setVisible(!drawerOpen);        return super.onPrepareOptionsMenu(menu);    }    @Override    public boolean onOptionsItemSelected(MenuItem item) {         // The action bar home/up action should open or close the drawer.         // ActionBarDrawerToggle will take care of this.        if (mDrawerToggle.onOptionsItemSelected(item)) {            return true;        }        // Handle action buttons        switch(item.getItemId()) {        case R.id.action_websearch:            // create intent to perform web search for this planet            Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);            intent.putExtra(SearchManager.QUERY, getActionBar().getTitle());            // catch event that there's no activity to handle intent            if (intent.resolveActivity(getPackageManager()) != null) {                startActivity(intent);            } else {                Toast.makeText(this, R.string.app_not_available, Toast.LENGTH_LONG).show();            }            return true;        default:            return super.onOptionsItemSelected(item);        }    }    /* The click listner for ListView in the navigation drawer */    private class DrawerItemClickListener implements ListView.OnItemClickListener {        @Override        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {            selectItem(position);        }    }    private void selectItem(int position) {        // update the main content by replacing fragments        Fragment fragment = new PlanetFragment();        Bundle args = new Bundle();        args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position);        fragment.setArguments(args);        FragmentManager fragmentManager = getFragmentManager();        fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();        // update selected item and title, then close the drawer        mDrawerList.setItemChecked(position, true);        setTitle(mPlanetTitles[position]);        mDrawerLayout.closeDrawer(mDrawerList);    }    @Override    public void setTitle(CharSequence title) {        mTitle = title;        getActionBar().setTitle(mTitle);    }    /**     * When using the ActionBarDrawerToggle, you must call it during     * onPostCreate() and onConfigurationChanged()...     */    @Override    protected void onPostCreate(Bundle savedInstanceState) {        super.onPostCreate(savedInstanceState);        // Sync the toggle state after onRestoreInstanceState has occurred.        mDrawerToggle.syncState();    }    @Override    public void onConfigurationChanged(Configuration newConfig) {        super.onConfigurationChanged(newConfig);        // Pass any configuration change to the drawer toggls        mDrawerToggle.onConfigurationChanged(newConfig);    }    /**     * Fragment that appears in the "content_frame", shows a planet     */    public static class PlanetFragment extends Fragment {        public static final String ARG_PLANET_NUMBER = "planet_number";        public PlanetFragment() {            // Empty constructor required for fragment subclasses        }        @Override        public View onCreateView(LayoutInflater inflater, ViewGroup container,                Bundle savedInstanceState) {            View rootView = inflater.inflate(R.layout.fragment_planet, container, false);            int i = getArguments().getInt(ARG_PLANET_NUMBER);            String planet = getResources().getStringArray(R.array.planets_array)[i];            int imageId = getResources().getIdentifier(planet.toLowerCase(Locale.getDefault()),                            "drawable", getActivity().getPackageName());            ((ImageView) rootView.findViewById(R.id.image)).setImageResource(imageId);            getActivity().setTitle(planet);            return rootView;        }    }}

基本上对于每行代码官方都给出了简练的解释,但是我还是简单记录一下重点吧,嘻嘻:

首先第一件事当然是给Drawer导航提供内容,因为这个Drawer导航是一个ListView,所以我们要提供适配器,以及一个用来监听Item点击事件的监听器:

        mDrawerList.setAdapter(new ArrayAdapter<String>(this,                R.layout.drawer_list_item, mPlanetTitles));        mDrawerList.setOnItemClickListener(new DrawerItemClickListener());

这个相关代码比较简单,大家可以自己会看上面的代码,主要就是用数组填充了ListView,然后点击每一项可以切换相应的Fragment。

然后看一下重点,ActionBarDrawerToggle,这类帮我们解决了很多问题,因为我们要监听Drawer导航的打开和关闭事件,那么我们就要继承 DrawerLayout.DrawerListener,然后实现 onDrawerOpened() 和 onDrawerClosed()方法,因为ActionBarDrawerToggle继承了这个接口,所以我们只要覆写方法就行了,除了这个,它还可以顺道帮我们解决点击ActionBar上的Icon然后实现Drawer的打开和关闭,是不是很方便。

当然是用ActionBarDrawerToggle,一定不能忘记去覆写这两个方法onPostCreate()和onConfigurationChanged()。

mDrawerToggle = new ActionBarDrawerToggle(                this,                  /* host Activity */                mDrawerLayout,         /* DrawerLayout object */                R.drawable.ic_drawer,  /* nav drawer image to replace 'Up' caret */                R.string.drawer_open,  /* "open drawer" description for accessibility */                R.string.drawer_close  /* "close drawer" description for accessibility */                ) {            public void onDrawerClosed(View view) {                getActionBar().setTitle(mTitle);                invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()            }            public void onDrawerOpened(View drawerView) {                getActionBar().setTitle(mDrawerTitle);                invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()            }        };        mDrawerLayout.setDrawerListener(mDrawerToggle);

通过监听打开和关闭事件,完成了ActionBar改变标题的功能,除了这个,还有invalidateOptionsMenu()方法,它只要一出现,系统就会回调onPrepareOptionsMenu()方法,那有什么用呢,我们可以在这个里面,设置Action Item的可见性呀,效果提里的放大镜注意到了么,正常状态下它会显示,但是打开的时候它是看不见的。我们看看onPrepareOptionsMenu()的相关代码:

public boolean onPrepareOptionsMenu(Menu menu) {        // If the nav drawer is open, hide action items related to the content view        boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);        menu.findItem(R.id.action_websearch).setVisible(!drawerOpen);        return super.onPrepareOptionsMenu(menu);    }

上面提到的点击Icon打开和关闭Drawer导航怎么实现呢?ActionBarDrawerToggle会自动帮我们处理这个事件,它的内部已经封装好了怎么处理这个时间,我们只要在onOptionsItemSelected()方法里去捕捉到返回true,表示已处理就行了:

@Override    public boolean onOptionsItemSelected(MenuItem item) {         // The action bar home/up action should open or close the drawer.         // ActionBarDrawerToggle will take care of this.        if (mDrawerToggle.onOptionsItemSelected(item)) {            return true;        }

其实上面的代码已经注释的很好了,你完全可以直接看代码和注释,不用看我。

还有一个小细节,在这个例子里面Drawer是一个ListView,我们给它的监听器是这样的:

private class DrawerItemClickListener implements ListView.OnItemClickListener {        @Override        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {            selectItem(position);        }    }private void selectItem(int position) {        // update the main content by replacing fragments        Fragment fragment = new PlanetFragment();        Bundle args = new Bundle();        args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position);        fragment.setArguments(args);        FragmentManager fragmentManager = getFragmentManager();        fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();        // update selected item and title, then close the drawer        mDrawerList.setItemChecked(position, true);        setTitle(mPlanetTitles[position]);        mDrawerLayout.closeDrawer(mDrawerList);    }

通过selectItem()来实现点击显示相应的Fragment,但是selectItem()的最后三行不能忘,点击后通过 mDrawerList.setItemChecked(position, true); 来显示ListView里面Item的选中状态,更新ActionBar的标题,最后通过调用mDrawerLayout.closeDrawer(mDrawerList);关闭Drawer导航。


好了,今天的内容就是这么多

0 0
原创粉丝点击