简介

文章发布于我的个人博客: 手动实现轮播图(一):ViewPager 上手

想看效果直接拉到最后~

Viewpager是 Android 提供的布局管理器,常被用来实现左右滑动的页面、视图。

在实际工程中,有许多都是用来实现轮播图功能的。

今天,我们从零开始造一个简易轮播图组件。

本系列文章面向的读者,是刚学完 Android 教材的初学者,旨在:

  • 简单介绍ViewPager原理并如何快速上手
  • 使用简单的代码结构,完成一个初级的轮播图组件

文章作者毕竟经验不多,水平有限,所以缺漏在所难免,希望路过读到本文的前辈们不吝赐教,谢谢~

接下来,我们就从Viewpager是什么开始,慢慢来了解他。

1. Viewpager 上手

官方开发文档:android.support.v4.view.ViewPager

  • ViewPager是一个布局管理器,可以作为根布局
  • 因为他继承于ViewGroup,常见的布局管理器还有FrameLayout, LinearLayout等
  • 当他作为根布局时,每一个页面都将占据整个布局
  • ViewPager该怎么使用
  • 在布局文件中添加一个<ViewPager>标签,此位置作为ViewPager容器主体所在
  • 创建一个新的布局文件,作为内嵌页面的布局
  • 如果使用fragment的话,我们只需要创建一个模板,之后所有内嵌页面都使用这个模板来生成即可
  • 如果单单使用布局文件,那么我们每一个页面项都要创建一个布局文件,之后手动添加ViewPager容器
  • 所以本文章均使用fragment来实现
  • 在activity中,实例化ViewPager
  • 为ViewPager设置Adapter
  • 类似于RecyclerView,我们也是使用Adapter来和ViewPager进行通信
  • 这样大大方便了我们使用

上述步骤中,前几步几乎是组件/布局实例化的常规操作,所以我们真正要做的其实非常少。

接下来我们开始动手来使用ViewPager。

创建 ViewPager 容器和子页面布局文件

我们新建一个项目之后,打开默认创建的activity_main.xml布局文件中,将内容改为以下代码:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <android.support.v4.view.ViewPager
        android:id="@+id/view_pager_inside"
        android:layout_width="400dp"
        android:layout_height="400dp"
        android:background="@android:color/darker_gray"
        android:layout_centerInParent="true">
    </android.support.v4.view.ViewPager>
</RelativeLayout>复制代码

可以看到,布局文件中仅有一个根布局RelativeLayout和一个ViewPager。

这里的ViewPager就是容器主体所在。

接着我们创建嵌入的页面布局文件:

新建一个view_pager_fragment.xml文件,内容如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:gravity="center"
    android:orientation="vertical">

    <android.support.v7.widget.CardView
        android:id="@+id/card_view"
        android:layout_width="300dp"
        android:layout_height="300dp"
        app:cardCornerRadius="10dp"
        android:elevation="5dp">
        <TextView
            android:id="@+id/text_view_fragment"
            android:layout_gravity="center"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </android.support.v7.widget.CardView>

</LinearLayout>复制代码

这里面是常规布局,有一个卡片CardView和内藏一个的TextView。

到时候,滑动的每一个页面的布局模板都来自这个文件,我们只需要在代码里稍微修改,就可以生成特定的页面了。

现在,我们回到MainActivity.java文件中,实例化我们刚刚的ViewPager。

public class MainActivity extends AppCompatActivity {
    private ViewPager mViewPager;   // 定义一个 Viewpager 变量
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        mViewPager = findViewById(R.id.view_pager_inside);  // 实例化 ViewPager
    }
}复制代码

接下来我们该干什么呢?当然是为ViewPager添加页面了。

那么页面从哪里来呢?当然是我们之前创建的那个布局view_page_fragment.xml了。

  • 我们的ViewPager主体位于activity_main.xml布局中
  • 我们在MainActivity.java中使用setContentView(R.layout.activity_main); 设置两者关联
  • 然后我们可以在MainActivity.java里面实例化ViewPager并使用它
  • 同理,我们要创建一个Fragment,将它和view_page_fragment.xml关联起来,并在它里面实例化页面的布局

不理解Fragment的同学,可以看一下文档里的 片段 哦。

创建一个PageFragment.java类,继承于android.support.v4.app.Fragment,这里特别注意要使用v4包里的Fragment。

现在这个类里空荡荡,让我们来填充一些有意思的内容。

  1. 关联PageFragment.java和view_page_fragment.xml

使用Alt + Insert,选择Override Methods,然后重写onCreateView如下:

private TextView mTextView;
    private CardView mCardView;    

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.view_pager_fragment, container, false);
        
        mTextView = view.findViewById(R.id.text_view_fragment);
        mCardView = view.findViewById(R.id.card_view);
        return view;
    }复制代码

我们使用了LayoutInflater来将view_page_fragment.xml加载为代码里的View对象,然后再从view对象里,找到我们放置的两个组件:CardView和TextView。

如果不理解LayoutInflater可以看看如下两位的文章:

  • Android LayoutInflater原理分析,带你一步步深入了解View(一)
  • 理解Android中的LayoutInflater

这样就算是关联起来了,系统在创建PageFrament.java对象的时候,就会实例化view_page_fragment.xml布局了。

接着我们为PageFragment.java创建一个静态生成器(方法)。为什么要静态生产类呢?

因为我们每生成一个页面,其实就是创建一个PageFragment.java的对象,然后我们还要向这个对象传递数据。

为了不做重复的工作,我们写一个静态生成器,这样每次外部类只要调用这个静态生成器,就可以很简单地创建对象了。

看看代码:

public class PageFragment extends Fragment {
   
    public static Fragment newInstance(){
       
        return new PageFragment();
    }
    public View onCreateView ...
}复制代码

如果你写过静态Intent生成方法,相信这个类生成器也很容易理解了。

上面代码就是在返回时,先创建一个PageFragment对象再返回去。就这一句代码,有必要写一个静态方法吗?

当然有,因为我们还没有把他真正的用处挖掘出来呢!

前面说到的,我们之所以只需要创建一个布局模板文件,而不需要每一个页面就定制一个,就是我们要在代码里动态定制页面。

我们这里子页面模板里,只有一个TextView可以写东西,所以我们用它来作为区分页面的标志,比如T1、T2这样。

那问题就是,我们如何动态定制页面呢?

我们来看看现在的情况吧:

可以看到,这是典型的 MVC 结构,在这里面呢,PageFragment唯一地通过MainActivity.java来创建,虽然我们还没有实现这一步。

也即是说,我们要在这一步里,向PageFragment传递定制化的数据,比如页面一传递T1,页面二传递T2这样子。

接着在PageFragment只需要使用同一套代码就可以生成不同的页面了。

问题的难点在于如何向一个Fragment传递数据。当然,这样的文章已经写了很多了,相信你稍微搜索一下,就知道我们即将使用的是Fragment Arguement的方法。其实就是在fragment对象上附加一个参数。

这种方法是不是很像Intent的附加参数呢?

下面是实现代码:

public class PageFragment extends Fragment {
   
    private static final String ARGS_TITLE = "argsTitle";
    private CardView mCardView;
    private TextView mTextView;

    public static Fragment newInstance(String title){
        Bundle args = new Bundle();
        args.putString(ARGS_TITLE, title);
        PageFragment pageFragment = new PageFragment();
        pageFragment.setArguments(args);
        return pageFragment;
    }
    public View onCreateView ...
}复制代码

在这里面,我们使用了Bundle对象来存储要传递的数据,然后使用setArguement()方法来把参数附加到新建的pageFragment对象里面。

最后返回这个对象即可。

接下来我们就可以在MainActivity.java里面使用这个静态生成器。(我只列出了新增的代码哦)

public class MainActivity extends AppCompatActivity {
    ...
        
    private String[] mStringList = {
            "T1", "T2", "T3", "T4", "T5"
    };
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        FragmentManager fm = getSupportFragmentManager();      
        mViewPager.setAdapter(new FragmentPagerAdapter(fm) {
         
            @Override
            public Fragment getItem(int position) {
                
                String title = mStringList[position];
                
                return PageFragment.newInstance(title);
            }

            @Override
            public int getCount() {
                return mStringList.length;
            }


        });
    }
}复制代码

这里我们先定义了一个字符串数组,来存储文字字符串。也就是之前图片的【模型】区域。

FragmentManager fm = getSupportFragmentManager(); 复制代码

这句代码是获取一个FragmentManager,也就是fragment的管理器。接下来在ViewPager中需要用这个管理器来管理fragment。(别担心,这里系统已经帮你做好了,你只要传入一个管理器就行。

mViewPager.setAdapter(new FragmentPagerAdapter(fm) {
    ...
});复制代码

这一句也好理解,前面说了,ViewPager也需要一个对应的Adapter来和他通信,幸运的是系统已经为我们提供了两个非常好用的Fragment的Adapter。

  • FragmentPagerAdapter
  • 会提前自动创建:前中后,三个页面
  • 适合页面布局简单的情况
  • FragmentStatePagerAdapter
  • 只会创建一个页面
  • 适合页面布局复杂的情况

所以我们这里使用了FragmentPagerAdapter咯。

使用这个FragmentPagerAdapter,最少只需要重写两个方法:

  • getItem()
  • 通过position参数,返回一个创建好的页面
  • 我们就要在这里面做页面的创建工作哦
  • getCount()
  • 要创建的页面的数量

理解到这里,我们只需要在这段代码后面,轻轻加上一句:

mViewPager.setCurrentItem(0);复制代码

然后构建、运行,这个 Demo 就做好啦!

快试试效果吧~

试完了吗?是不是感觉哪里不对劲?

TextView 呢?

对啊,因为你虽然在newInstance里存放了数据,但是你并没有取出来呀~

来到PageFragment里取出来吧。

@Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.view_pager_fragment, container, false);
        mTextView = view.findViewById(R.id.text_view_fragment);
        mCardView = view.findViewById(R.id.card_view);

        String title = getArguments().getString(ARGS_TITLE);
        mTextView.setText(title);
        
        return view;
    }复制代码

现在不就可以了嘛~

什么?你嫌一个TextView太单调?...

那你干嘛不加一个ImageView进去啊,然后传入一些令人心旷神怡的图片还不是美滋滋?

  • 本项目地址ViewPagerDemo
  • 感谢下列参考文章
  • Android ViewPager 无限循环左右滑动(可自动) 实现
  • Android ViewPager实现循环滚动