简介
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 {
// 定义一个 Viewpager 变量
private ViewPager mViewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// 实例化 ViewPager
mViewPager = findViewById(R.id.view_pager_inside);
}
}
接下来我们该干什么呢?当然是为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
。
现在这个类里空荡荡,让我们来填充一些有意思的内容。
- 关联
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
可以看看如下两位的文章:
这样就算是关联起来了,系统在创建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
-
感谢下列参考文章
接下来的文章会实现无限循环滑动、页面指示器,敬请期待~