美文网首页
Android - 顶部滑动导航

Android - 顶部滑动导航

作者: 东方未曦 | 来源:发表于2017-05-18 11:37 被阅读653次

    一、概述

    在 Android 开发中,经常需要使用顶部或者底部的导航来切换当前显示的 Fragment。
    在很多应用中还添加了滑动切换的效果,大体效果如下:

    Pager滑动导航.gif

    这类程序分为两个部分。
    下方使用 ViewPager 实现多页滑动显示。滑动时,ViewPager 显示不同的 Fragment,我们可以为 ViewPager 设置适配器来实现这样的效果。
    上方的四个 TextView 的显示需要我们自己实现,主要是在 ViewPager 切换的时候进行文字颜色的设置以及下方横线的滑动。

    程序源码:PagerSlide

    二、Fragment

    ViewPager 本身是一个可以滑动的对象,我们可以在其中添加滑动的广告,或者是这里说的 Fragment 的切换。
    如果只是添加图片之类的控件,我们只需要设置相应的布局文件即可,但是添加 Fragment 却不是这么简单的。下面我们从 Fragment 生命周期开始讲起。

    1. Fragment 生命周期

    Fragment 生命周期.png

    Fragment 的生命周期很复杂,我们只看重点,Fragment 在 onCreateView() 中加载视图。经过 onActivityCreate() --> onStart() --> onResume() 后才真正显示。
    而在 Fragment 显示前,还有一个 onActivityCreate() 函数,我们可以在这里加载 Fragment 所需要的数据(这个例子没有数据,但在真正的项目里,这里一般加载联网数据)。

    2. BaseFragment

    我们创建一个继承自 Fragment(support.v4 包) 的抽象类 BaseFragment,在里面实现一些公共的方法。我们所有的自定义 Fragment 都将继承自 BaseFragment。

    BaseFragment 的子类必须都重写 initView() 方法(因为每个 Fragment 都需要加载布局),这个方法返回当前 Fragment 的 View 对象。
    而在 onActivityCreated() 方法中我们通过 initData() 加载数据,如果子类需要加载数据并重写了此方法,那么根据上面讲的生命周期,数据就会在 Fragment 显示前加载完毕。

    public abstract class BaseFragment extends Fragment {
    
        // 上下文对象
        protected Context mContext;
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mContext = getActivity();
        }
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            return initView();
        }
    
        @Override
        public void onActivityCreated(Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
            initData();
        }
    
        // 继承此类的子类必须重写此方法加载布局
        public abstract View initView();
    
        // 加载数据的方法
        public void initData() { }
    }
    

    3. 子 Fragment

    有了 BaseFragment,我们就可以自定义需要显示的 Fragment 了。Fragment 的布局文件随你乐意,这里我只加了一张图片。
    我们在 initView() 中加载并返回了 View 视图对象,在 initData() 中加载数据。这两个方法里都有 Log 日志打印,这个待会有用。

    public class Fragment1 extends BaseFragment {
    
        @Override
        public View initView() {
            Log.e("TAG", "Fragment1 --> initView");
            View view = View.inflate(mContext, R.layout.fragment1, null);
            return view;
        }
    
        @Override
        public void initData() {
            super.initData();
            // ......加载数据
            Log.e("TAG", "Fragment1 --> initData");
        }
    }
    

    之后再定义三个相似的 Fragment 即可。

    三、布局文件

    定义四个横向的 Textview 用于顶部导航。

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        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"
        android:orientation="vertical"
        tools:context="com.learn.lister.pagerslide.activity.MainActivity">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
    
            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:padding="10dp"
                android:gravity="center">
                <TextView
                    android:id="@+id/page_0"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="首页"
                    android:textSize="16sp"
                    android:textColor="@android:color/black"/>
            </LinearLayout>
    
            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:padding="10dp"
                android:gravity="center">
                <TextView
                    android:id="@+id/page_1"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="朋友"
                    android:textSize="16sp"
                    android:textColor="@android:color/black"/>
            </LinearLayout>
    
            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:padding="10dp"
                android:gravity="center">
                <TextView
                    android:id="@+id/page_2"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="动态"
                    android:textSize="16sp"
                    android:textColor="@android:color/black"/>
            </LinearLayout>
    
            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:padding="10dp"
                android:gravity="center">
                <TextView
                    android:id="@+id/page_3"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="附近"
                    android:textSize="16sp"
                    android:textColor="@android:color/black"/>
            </LinearLayout>
    
        </LinearLayout>
    
        <View
            android:layout_width="match_parent"
            android:layout_height="2dp"
            android:background="@android:color/darker_gray"/>
    
        <ImageView
            android:id="@+id/main_tab_line"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/slider"/>
    
        <android.support.v4.view.ViewPager
            android:id="@+id/main_pager"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        </android.support.v4.view.ViewPager>
    
    </LinearLayout>
    

    四、主要代码

    1. 适配器

    为了支持在 ViewPager 滑动时向其中添加不同的 Fragment,我们需要为 ViewPager 设置一个适配器。我们可以自定义一个继承于 FragmentPagerAdapter 的适配器。

    官方文档对 FragmentPagerAdapter 的解释大致如下:

    FragmentPagerAdapter 派生自 PagerAdapter,它是用来呈现Fragment页面的,这些Fragment页面会一直保存在fragment manager中,以便用户可以随时取用。
    这个适配器适用于有限个静态fragment页面的管理。尽管不可见的视图有时会被销毁,但用户所有访问过的fragment都会被保存在内存中。

    而继承自 FragmentPagerAdapter 的适配器也只需要重写 getCount() 和 getItem(int position) 两个方法。

    /**
     * Fragment 滑动适配器
     * BaseFragment 为自定义的 Fragment 基类。
     */
    public class PagerSlideAdapter extends FragmentPagerAdapter {
    
        private List<BaseFragment> mFragmentList;
    
        public PagerSlideAdapter(FragmentManager fm, List<BaseFragment> fragmentList) {
            super(fm);
            this.mFragmentList = fragmentList;
        }
    
        @Override
        public Fragment getItem(int position) {
            return mFragmentList.get(position);
        }
    
        @Override
        public int getCount() {
            return mFragmentList.size();
        }
    }
    

    从代码中我们可以看出,在构造函数中需要传入一个 Fragment 的合集并初始化,这些就是 ViewPager 中滑动的对象。

    2. MainActivity

    ViewPager 的滑动是设置适配器的效果,而滑动页面时文字的变化以及横条的移动就需要我们自己动手了。

    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
        @BindView(R.id.page_0) TextView text0;
        @BindView(R.id.page_1) TextView text1;
        @BindView(R.id.page_2) TextView text2;
        @BindView(R.id.page_3) TextView text3;
        @BindView(R.id.main_tab_line) ImageView tab_line;
        @BindView(R.id.main_pager) ViewPager mViewPager;
    
        private int screenWidth;
        private List<BaseFragment> mFragmentList = new ArrayList<>();
        private PagerSlideAdapter adapter;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            ButterKnife.bind(this);
    
            initData(); // 初始化数据
            initWidth(); // 初始化滑动横条的宽度
            setListener(); // 设置监听器
        }
    
        private void initData() {
            // 将我们自定义 Fragment 的对象添加到 List<BaseFragment> 中。
            mFragmentList.add(new Fragment1());
            mFragmentList.add(new Fragment2());
            mFragmentList.add(new Fragment3());
            mFragmentList.add(new Fragment4());
    
            // 新建适配器
            adapter = new PagerSlideAdapter(getSupportFragmentManager(), mFragmentList);
            // 为 ViewPager 设置适配器
            mViewPager.setAdapter(adapter);
    
            // 打开应用时 ViewPager 显示第一个 Fragment
            mViewPager.setCurrentItem(0);
            text0.setTextColor(Color.BLUE);
        }
    
        private void setListener() {
    
            mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
                /**
                 * This method will be invoked when the current page is scrolled, either as part
                 * of a programmatically initiated smooth scroll or a user initiated touch scroll.
                 *
                 * @param position Position index of the first page currently being displayed.
                 *                 Page position+1 will be visible if positionOffset is nonzero.
                 * @param positionOffset Value from [0, 1) indicating the offset from the page at position.
                 * @param positionOffsetPixels Value in pixels indicating the offset from position.
                 *                             这个参数的使用是为了在滑动页面时有文字下方横条的滑动效果
                 */
                @Override
                public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                    LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) tab_line.getLayoutParams();
                    lp.leftMargin = screenWidth/4*position + positionOffsetPixels/4;
                    tab_line.setLayoutParams(lp);
                }
    
                @Override
                public void onPageSelected(int position) {
                    // 在每次切换页面时重置 TextView 的颜色
                    resetTextView();
                    switch (position) {
                        case 0:
                            text0.setTextColor(Color.BLUE);
                            break;
                        case 1:
                            text1.setTextColor(Color.BLUE);
                            break;
                        case 2:
                            text2.setTextColor(Color.BLUE);
                            break;
                        case 3:
                            text3.setTextColor(Color.BLUE);
                            break;
                    }
                }
    
                @Override
                public void onPageScrollStateChanged(int state) {
                }
            });
            text0.setOnClickListener(this);
            text1.setOnClickListener(this);
            text2.setOnClickListener(this);
            text3.setOnClickListener(this);
    
        }
    
        private void resetTextView() {
            text0.setTextColor(Color.BLACK);
            text1.setTextColor(Color.BLACK);
            text2.setTextColor(Color.BLACK);
            text3.setTextColor(Color.BLACK);
        }
    
        // 初始化滑动横条的宽度
        private void initWidth() {
            DisplayMetrics dpMetrics = new DisplayMetrics();
            getWindow().getWindowManager().getDefaultDisplay().getMetrics(dpMetrics);
            screenWidth = dpMetrics.widthPixels;
            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) tab_line.getLayoutParams();
            lp.width = screenWidth / 4;
            tab_line.setLayoutParams(lp);
        }
    
        // 设置文字的点击事件,点击某个 TextView 就跳到相应页面
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.page_0:
                    mViewPager.setCurrentItem(0);
                    break;
                case R.id.page_1:
                    mViewPager.setCurrentItem(1);
                    break;
                case R.id.page_2:
                    mViewPager.setCurrentItem(2);
                    break;
                case R.id.page_3:
                    mViewPager.setCurrentItem(3);
                    break;
            }
        }
    }
    

    五、Fragment 的缓存

    到这里我们的程序已经可以运行了,但还记得我们之前在自定义 Fragment 类中的 Log 日志吗?运行程序,让我们看一下这个日志。
    程序刚运行时日志:

    E/TAG: Fragment1 --> initView
    E/TAG: Fragment1 --> initData
    E/TAG: Fragment2 --> initView
    E/TAG: Fragment2 --> initData
    

    程序刚打开时不是只显示一个 Fragment 吗?为什么会加载两个 Fragment 的资源?这时滑动到第二个 Fragment,你会发现日志是这样的:

    E/TAG: Fragment3 --> initView
    E/TAG: Fragment3 --> initData
    

    看起来适配器总是会预先加载一个页面,但是当你滑动到最后一个页面,再往前滑动时,日志是这样的:

    E/TAG: Fragment2 --> initView
    E/TAG: Fragment2 --> initData
    

    Fragment2 之前不是加载过了吗?怎么又来?
    其实是这样,适配器为你保存在内存中的 Fragment 时当前所显示的 Fragmen以及当前 Fragment 的前一个和后一个。在内存中最多只会缓存三个 Fragment。(刚打开时只缓存了两个)

    六、总结

    这里讲到了滑动 ViewPager 显示不同 Fragment,但是这里的 Fragment 都是静态的,如果要处理大量的页面切换,FragmentStatePagerAdapter 会更优秀,有兴趣的话就去学习一下吧。

    相关文章

      网友评论

          本文标题:Android - 顶部滑动导航

          本文链接:https://www.haomeiwen.com/subject/memgzttx.html