前言
好记性不如烂笔头,学习的知识总要记录下来,通过本文来加深对 ViewPager 方方面面的理解:
- ViewPager 的基础介绍
- PagerAdapter + FragmentPagerAdapter&FragmentStatePagerAdapter
- 与 Fragment + TabLayout 的联动使用
- Banner 轮播图
- 自定义切换动画
- 首次登录引导界面
闲话少说,下面进入正题。
基础介绍
ViewPager 是Android support v4 包中的类,官方文档对其描述如下:
Layout manager that allows the user to flip left and right through pages of data.
意思是说,其本身是一个布局管理器,允许我们左右滑动来切换不同的数据页面。
它直接继承自 ViewGroup 类,说明它是一个容器类,可以在其中添加其他View,实际上我们也就是这么用的。
在使用时,直接在布局中加入 ViewPager 即可,相信大家都会,至于其中的属性,就只有一个 android:clipChildren
需要注意一下,我们后面会说,其他都和一般的 ViewGroup 没什么区别(其实这个clipChildren属性也是源自 ViewGroup 的~)。
这里提一下几个动态设置方法,能不能实现 漂亮花哨的效果,基本就靠这几个方法:
-
setAdapter(PagerAdapter adapter)
设置适配器 -
setOffscreenPageLimit(int limit)
设置缓存的页面个数,默认是 1 -
setCurrentItem(int item)
跳转到特定的页面 -
addOnPageChangeListener(..)
设置页面滑动时的监听器 -
setPageTransformer(..PageTransformer)
设置页面切换时的动画效果 -
setPageMargin(int marginPixels)
设置不同页面之间的间隔 -
setPageMarginDrawable(..)
设置不同页面间隔之间的装饰图也就是 divide ,要想显示设置的图片,需要同时设置setPageMargin()
同时它需要实现一个 PagerAdapter 适配器,和 ListView,RecyclerView 类似,适配器用来提供数据,填充页面。
ViewPager 适配器 - PagerAdapter
PagerAdapter 是一个抽象类,因此我们只能使用它的实现类,官方为我们提供了两个直接子类 FragmentPagerAdapter 和 FragmentStatePagerAdapter ,基本都是ViewPager + Fragment 搭配时使用的。
但是,我们使用ViewPager显然不是只为了和 Fragment 打交道的,比如实现后面会讲到的轮播图,因此我们仍要按需实现合适的适配器,现在先看看如何去实现一个PagerAdapter子类,主要就是以下4个方法(必须实现):
-
int getCount()
:获取页面数。 -
boolean isViewFromObject(View view, Object object)
:判断页面视图是否和instantiateItem()
方法返回的对象相关联,总之通常直接返回return view == object;
-
Object instantiateItem(View container, int position)
:作用是对要显示或缓存的界面,进行布局的初始化。 -
void destroyItem(ViewGroup container, int position, Object object)
: 销毁页面。
我们来看一下源码中对 ViewPager执行流程的解释,来加深理解。
ViewPager associates each page with a key Object instead of working with Views directly. This key is used to track and uniquely identify a given page independent of its position in the adapter.
ViewPager 并不是直接处理视图,而是将每个页面与一个key Object(没错就是instantiateItem()
返回的东西)关联起来,这个 key Object 跟踪并且唯一标识一个给定的页面。
A very simple PagerAdapter may choose to use the page Views themselves as key objects, returning them from {@link #instantiateItem(ViewGroup, int)} after creation and adding them to the parent ViewGroup. A matching {@link #destroyItem(ViewGroup, int, Object)} implementation would remove the View from the parent ViewGroup and {@link #isViewFromObject(View, Object)} could be implemented as
return view == object;
.
最通常的PagerAdapter实现(也就是只实现上面的4个方法),是将页面视图本身作为key Object,在创建后通过instantiateItem()
方法返回,并将它们添加到父容器ViewGroup 中,当我们不需要某视图或者缓存达到上限时,destroyItem()
方法被调用,会将该视图从父ViewGroup中移除。最后Google建议我们直接在isViewFromObject()
方法中直接返回return view == object;
更多关于ViewPager的处理逻辑,建议直接看源码中的注释,涉及到其他的各种方法,此处就不再多说了。
ViewPager + TabLayout + Fragment
理论
Google 官方文档中 Creating swipe views with tabs 这一节中,介绍的是 ViewPgaer + Fragment + Action bar tabs/ PagerTitleStrip 实现导航页,三者联动使用。但是随着 Material Design 中 TabLayout 的推出,直接秒杀上述tabs或PagerTitleStrip(其实从效果上来看差不太多,但是 TabLayout 可以一行代码外加一个方法搞定和ViewPager 的联动,比前者方便太多),所以本文就直接介绍和 TabLayout 的配合使用。
前面我们也提到了官方为我们提供了两个PagerAdapter的直接子类:FragmentPagerAdapter和FragmentStatePagerAdapter,不知道在座的读者你们是什么感觉,我是觉得很奇怪,为什么针对 Fragment 要搞两个子类出来?
这种时候,看看源码就清楚了,主要区别主要在destroyItem()
方法:
//FragmentPagerAdapter.java
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
+ " v=" + ((Fragment)object).getView());
mCurTransaction.detach((Fragment)object);
}
//FragmentStatePagerAdapter.java
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment) object;
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
+ " v=" + ((Fragment)object).getView());
while (mSavedState.size() <= position) {
mSavedState.add(null);
}
mSavedState.set(position, fragment.isAdded()
? mFragmentManager.saveFragmentInstanceState(fragment) : null);
mFragments.set(position, null);
mCurTransaction.remove(fragment);
}
源码解释的很清楚,FragmentPagerAdapter 只是将 销毁视图,而不是销毁Fragment 实例,而FragmentStatePagerAdapter 则是彻底将 Fragment 从当前的 FragmentManager中溢出,但是会保存 Fragment 的状态信息(也就是名字中State的意义),等到需要重建(切换回该页面)时,通过状态信息进行恢复创建。
官方(源码)建议我们使用这二者的场景如下:
FragmentPagerAdapter:适合用于展示静态的fragment,主页面等,类似几个tabs。此时,不会占有太大的内存,同时避免因反复销毁创建浪费时间。
FragmentStatePagerAdapter:类似ListView,需要展示大量页面时,由于大量页面对用户不可见,当Fragment被销毁时,我们只会保存其状态信息,这样会节省大量的内存。
emmm...好像说的有点远了,下面介绍如何使用。
二者从使用上来看是毫无区别的,实现两个方法:
-
public Fragment getItem(int position)
返回对应 Fragment 实例,一般我们在使用时,会通过构造传入一个要显示的 Fragment 的集合,我们只要在这里把对应的 Fragment 返回就行了 -
public int getCount()
返回的是页面的个数,我们只要返回传入 Fragment 集合的长度就行了。
嗯,下面就到如何实现 TabLayout 和 ViewPager 的联动了,等我下面介绍完,我相信你会惊讶于它怎么会如此简单的,只要两个步骤:
- 初始化后调用
TabLayout.setupWithViewPager(ViewPager)
方法,将二者绑定到一起。 - 重写 PagerAdapter 的
public CharSequence getPageTitle(int position)
方法。 TabLayout 会通过setupWithViewPager()
方法底部会调用 PagerAdapter 中的getPageTitle()
方法来获取 title 并更新自己的 tab 的。
在网上看到一篇文章说到
setupWithViewPager()
方法存在三个坑,看了下好像的确有些道理,大家可以自行了解一下。https://www.jianshu.com/p/896b149aaa43
理论知识暂时告一段落,下面进入实践时间。
实例
先放上最终的效果图:(顶部绿色导航基于 TabLayout 实现,而下方的蓝(青?)色的是 PagerTitleStrip 的默认效果,只是为了凸显二者的区别)
嗯。。还是挺简单的,直接上代码吧:
TabActivity.java
public class TabActivity extends AppCompatActivity {
private ViewPager mViewPager;
private TabLayout mTabLayout;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tabfragment);
mViewPager = findViewById(R.id.view_pager_tab);
mViewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
private String[] titles = new String[]{"Deemo", "Cytus", "兰空", "万向物语", "绝地求生", "魔女之泉"};
@Override
public Fragment getItem(int position) {
return PageFragment.newinstance(position);
}
@Override
public int getCount() {
return titles.length;
}
@Nullable
@Override
public CharSequence getPageTitle(int position) {
return titles[position];
}
});
mTabLayout = findViewById(R.id.tablayout);
mTabLayout.setupWithViewPager(mViewPager);
//设置标签摆放方式
//默认为MODE_FIXED,固定模式
//mTabLayout.setTabMode(TabLayout.MODE_FIXED);
//滑动模式
mTabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
}
}
PageFragment.java
public class PageFragment extends Fragment {
public static final String ARGS = "PageFragment";
private int curPage;
public static PageFragment newinstance(int curPage) {
Bundle args = new Bundle();
args.putInt(ARGS, curPage);
PageFragment fragment = new PageFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
curPage = getArguments().getInt(ARGS);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_page, container, false);
TextView textView = view.findViewById(R.id.text_view);
textView.setText("Page :" + curPage);
return view;
}
}
activity_tabfragment.xml
fragment_page.xml
这个组合还是挺常用的,尤其是MD风格的APP中尤为常见,建议还是要能够熟练使用(虽然我才入坑不久,但是菜就不能提建议了么~)
ViewPager 轮播
首先盗个图~
从构成元素来讲,就这么几个:标题&指示器、切换动画、自动轮播、首位循环无限轮播。(页面本身用一个 ImageView 填充,应该不需要在额外强调什么吧~)
标题&指示器
比较常见的写法是在ViewPager所在布局中,声明指示器和标题布局:
acctivity_banner.xml
可能有童鞋要问:为什么不直接把 标题和指示器 放到 Banner 的 Item 里面呢,这样我们只要复写 instantiateItem()
不就可以直接完成初始化了?嗯,关于这点,只是为了切换效果好一点,仅此而已,没有什么额外的用意。
然后需要注意我们上面 小圆点 指示器使用了一个 LinearLayout,这是因为某些情况下,我们预先可能不知道会有多少个页面,所以我们干脆直接用一个 LinearLayout,在代码中动态加载指示器的 view 添加进来。
现在我们有了标题和指示器,下面就要考虑如何让这二者与页面联动了。
这就用到了 addOnPageChangeListener()
这个方法,该方法会设置一个OnPageChangeListener监听器,用来监听页面的变化。其中有三个回调方法:
-
onPageScrolled()
:当前页面发生滑动时调用 -
onPageSelected()
:页面滑动结束,选定页面时调用。需要注意的是,该方法调用时,动画未必完成 -
onPageScrollStateChanged()
:当滑动状态改变时调用,即处理何时开始滑动,或何时滑动停止。
于是乎,我们只需要回调onPageSelected()
方法即可,在此方法中设置标题和指示器跟随变化即可。
实例如下:
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
//处理指示器(小圆点)的显示逻辑
for (int i = 0; i < dotsList.size(); i++) {
if (position % dotsList.size() == i) {
dotsList.get(i).setImageResource(R.drawable.indicator_focus);
} else {
dotsList.get(i).setImageResource(R.drawable.indicator_normal);
}
}
//设置标题
bannerTitle.setText(titles[position]);
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
关于页面本身的加载,就只是用一个 ArrayListimageView.setImageResource(R.drawable.XXXX);
时模拟器卡的动不了,主要还是图片资源太大了。= =),以下是实现 PagerAdapter 子类填充页面的部分代码。
mViewPager.setAdapter(new PagerAdapter() {
@Override
public int getCount() {
return imgs.length;
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view == object;
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
container.addView(mList.get(position));
return mList.get(position);
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView(mList.get(position));
}
});
切换动画
切换动画,主要是用到setPageTransformer(boolean .. ,PageTransformer ...)
方法来设置动画,该方法会接收一个 PageTransfromer 参数,这就是动画的核心关键所在了。
PageTransformer 实际上是一个接口,内部只有一个方法 void transformPage(@NonNull View page, float position);
,该方法接收两个参数,一个 View 显然就是我们的页面了,当然这个 页面 涵盖了当前显示的页面、即将滑出的页面、即将滑入的页面以及隐藏的页面,而这么多页面,如何区分呢?这就第二个参数 position 的作用了。首先,千万不要和 ViewPager 下标的 position 混淆了(float 类型你告诉我是下标?),源码中对 position 的解释如下:
View 的 position 和 ViewPager 当前的中心位置有关,当前选中的页面 position 是 0,前一个页面是 -1,后一个页面是 1。
但是有同学指出:
前后 item position 为 -1 和 1 的前提是你没有给 ViewPager 设置 pageMargin。如果你设置了 pageMargin,前后 item 的 position 需要分别加上(或减去,前减后加)一个偏移量(偏移量的计算方式为 pageMargin / pageWidth)。
嗯,然后当我们页面滑动的时候,position 是动态变化的,transformPage()
会根据 position 的值来对页面进行属性变换,position 的变化规律如下:(不考虑pageMargin,方便讲解)
- position 分为三段:(-∞,-1)[-1,1](1,∞)
- 对于左右两个,多数时是不可见的,因此只需要分析以下[-1,1]区间
- 以第一页->第二页(左滑)为例:
- 页1的position:0->-1
- 页1的position:1->0
- 根据上述,我们就可以通过
setAlpha()
等方法设置属性,以此达到自定义切换动画的效果。(实际和属性动画有那么一点点类似)
实例嘛,见这节结束的实例就好了,此处不多搞了。
切换动画,可塑性实在是太高了,基本只有你想不到,没有它做不到的,于是后面我们会再扩充几种切换动画来加深理解。
自动轮播
自动轮播,听起来高大上,原理简单的离谱:每隔一定时间给它一个事件,告诉它“嘿,你该切换页面了”。嗯,说到这,不就是调用Handler.sendEmptyMessageDelayed(int what, long delayMillis)
的小事了么~
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//mViewPager.setCurrentItem(mViewPager.getCurrentItem() + 1);//当实现首尾循环无限轮播时的第一种方案时会这么设置,后面再说。
mViewPager.setCurrentItem((mViewPager.getCurrentItem() + 1) % mList.size());
this.sendEmptyMessageDelayed(MSG_WHAT, 2000);
}
};
有了上述代码,我们只需要在初始化 ViewPager 之后调用依次Handler.sendEmptyMessageDelayed(int what, long delayMillis)
就ok了。
当然实践中,我们可能需要对自动轮播进一步处理,譬如判断滑动手势暂停轮播,我们总不会希望“我错过了一个感兴趣的广告,然后把页面滑动回去,结果很快页面又!自动滑动回来了”,这种体验估计就很差。我在此处就不加以实现了,大家可以自行尝试一下,毕竟我只是讲解向~~(其实只是手势判断还没接触 ~~)。
首尾循环无限轮播
关于首尾无限轮播,指的是在第一个页面时向左滑动能够连贯的滑动到最后一页,而在最后一页向右滑动时,能顺畅的滑动到第一页。
起初我是没有注意到有什么坑的,但是当我按照上面的代码运行之后,发现首尾十分的不连贯,会连续滑过中间的所有页面,显然并不能满足我们的需求。
对于首尾循环的轮播,我也是参考网上的思路,就简单介绍一下:
- 设置 ViewPager 展示的个数为Inreger.MAX_VALUE,初始化时,将当前页面设置为n*mList.size(),除非闲得蛋疼,不然没什么人有毅力滑个Integer.MAX_VALUE次吧,所以说通常是没什么问题的。
- 在首尾分别加入最后一页和当前一页,比如 原来是 a,b,c 现在变为 c,a,b,c,a,当从末尾的c滑动到a时,将页面切换为第一个a。同理在第一个a左滑动到c时,将页面切换到第二个c。缺点可能就时可能会有短暂的延时?
贴出来参考的文章
https://blog.csdn.net/zhiyuan0932/article/details/52673169
https://blog.csdn.net/anyfive/article/details/52525262
实例
效果图呈上:
BannerActivity.java
public class BannerActivity extends AppCompatActivity {
private static final int MSG_WHAT = 0;
private int[] imgs;
private ViewPager mViewPager;
private List mList = new ArrayList<>();
private String[] titles;
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//mViewPager.setCurrentItem(mViewPager.getCurrentItem() + 1);//无限轮播时
mViewPager.setCurrentItem((mViewPager.getCurrentItem() + 1) % mList.size());
this.sendEmptyMessageDelayed(MSG_WHAT, 2000);
}
};
private LinearLayout mLinearLayout;
private ArrayList dotsList;
private TextView bannerTitle;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_banner);
imgs = new int[]{R.drawable.a, R.drawable.b, R.drawable.c, R.drawable.e, R.drawable.f};
titles = new String[]{"To think as great minds, to do as idiots","One Step Closer To The Hell","Knowing Everything of Something","Nothing For Nothing","No Royal Road To Anything"};
bannerTitle = findViewById(R.id.banner_title);
mLinearLayout = findViewById(R.id.indicator);
init();
initDots();
mViewPager = findViewById(R.id.view_pager);
mViewPager.setOffscreenPageLimit(3);//设置缓存页面数量
mViewPager.setPageTransformer(true, new BannerPageTransformer());
mViewPager.setAdapter(new PagerAdapter() {
@Override
public int getCount() {
return imgs.length;
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view == object;
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
container.addView(mList.get(position));
return mList.get(position);
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView(mList.get(position));
}
});
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
for (int i = 0; i < dotsList.size(); i++) {
if (position % dotsList.size() == i) {
dotsList.get(i).setImageResource(R.drawable.indicator_focus);
} else {
dotsList.get(i).setImageResource(R.drawable.indicator_normal);
}
}
bannerTitle.setText(titles[position]);
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
mHandler.sendEmptyMessageDelayed(MSG_WHAT, 2000);
}
private void init() {
for (int img : imgs) {
ImageView imageView = new ImageView(getApplicationContext());
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
//imageView.setImageResource(imgid);
Glide.with(getApplicationContext()).load(img).into(imageView);
mList.add(imageView);
}
}
private void initDots() {
dotsList = new ArrayList<>();
for (int i = 0; i < imgs.length; i++) {
ImageView imageView = new ImageView(getApplicationContext());
if (i == 0) {
imageView.setImageResource(R.drawable.indicator_focus);
} else {
imageView.setImageResource(R.drawable.indicator_normal);
}
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(16, 16);
params.setMargins(5, 0, 5, 0);
mLinearLayout.addView(imageView, params);
dotsList.add(imageView);
}
}
}
BannerPageTransformer.java
public class BannerPageTransformer implements ViewPager.PageTransformer {
@Override
public void transformPage(@NonNull View page, float position) {
int width = page.getWidth();
if (position < -1) {
page.setScrollX((int) (width * 0.75 * -1));
} else if (position <= 1) {
page.setScrollX((int) (width * 0.75 * position));
} else {
page.setScrollX((int) (width * 0.75));
}
}
}
activity_banner.xml
见前几节。
ViewPager 切换动画扩充
ZoomOutPageTransformer
RotateDownPageTransformer
注意,为了再ViewPager中可以同时显示多个页面,我们需要再布局中 设置 ViewPager 及其父容器的 clipChildren 属性为 false。
activity_trans.xml
嗯,其他的好像没什么可说得了(毕竟我的这两个切换效果一个是摘自Google官方,一个摘自 鸿洋 大佬。。),就推荐一个两个开源库吧
- GitHub上比较火的广告轮播控件,虽然是几年前的东西,但还是很值得参考的:AndroidImageSlider
- 一个看起来还不错的切换效果合辑 PageTransformerHelp
另外,给出 鸿洋 大佬关于自定义切换效果的文章,大佬的文章还是很值得学习的。
巧用ViewPager 打造不一样的广告轮播切换效果
View Pager + Fragment + SharedPreferences 首次登录引导界面
还是先将效果图放出来吧(图片和上面相同的资源,毕竟只是讲解思路嘛~ 丑点就丑点吧~)
(为了图省事,直接从CSDN把图扒过来,然后又图省事,在线压缩gif,结果就来了两重水印。。蛋碎了一地。)
实际上和上面也没有什么本质上的区别,所以在此就只介绍一下思路吧。
只是利用 SharedPreferences 来记录当前是否为第一次登录,指示器和上述实现一致,同时加入两个按钮,右上角 skip(始终存在),指示器上方 got it(当滑动到最后一页时出现),二者点击时都会启动主页面。
除此之外,该模式可以有很多变型:
- 右上角 skip 倒计时,倒计时完成后自动启动主页面,也可点击进入主页面
- 左右滑动的页面可以设置为 自动轮播,播放到最后一页时 自动进入主页面
- 不给 skip ,强制观看完所有引导页之后,才能通过弹出的got it 进入主页面
- …………
代码如下:(其实你会发现,代码和上面的代码 差别很小~)
WelcomeActivity.java
public class WelcomeActivity extends AppCompatActivity {
private ViewPager mViewPager;
private AppCompatButton btn_got;
private AppCompatButton btn_skip;
private LinearLayout mLinearLayout;
private ArrayList dotsList;
private int[] imgs;
private List mList = new ArrayList<>();
private SharedPreferences mPreferences;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
imgs = new int[]{R.drawable.a, R.drawable.b, R.drawable.c, R.drawable.e, R.drawable.f};
if (mPreferences.getBoolean("FirstLaunch", true)) {
setContentView(R.layout.activity_welcome);
mLinearLayout = findViewById(R.id.indicator_welcome);
initView();
initDots();
mViewPager.setAdapter(new PagerAdapter() {
@Override
public int getCount() {
return imgs.length;
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view == object;
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
container.addView(mList.get(position));
return mList.get(position);
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView(mList.get(position));
}
});
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
for (int i = 0; i < dotsList.size(); i++) {
if (position % dotsList.size() == i) {
dotsList.get(i).setImageResource(R.drawable.indicator_focus);
} else {
dotsList.get(i).setImageResource(R.drawable.indicator_normal);
}
}
btn_got.setVisibility(position == mList.size()-1?View.VISIBLE:View.GONE);
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
btn_got.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
recordFirstLaunch();
notFirstLaunch();
}
});
btn_skip.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
recordFirstLaunch();
notFirstLaunch();
}
});
} else {
notFirstLaunch();
finish();
}
}
private void recordFirstLaunch() {
SharedPreferences.Editor editor = mPreferences.edit();
editor.putBoolean("FirstLaunch", false);
editor.apply();
notFirstLaunch();
}
private void notFirstLaunch() {
startActivity(new Intent(this, MainActivity.class));
}
private void initView() {
mViewPager = findViewById(R.id.view_pager);
btn_got = findViewById(R.id.btn_got);
btn_skip = findViewById(R.id.skip);
for (int img : imgs) {
ImageView imageView = new ImageView(getApplicationContext());
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
//imageView.setImageResource(imgid);
Glide.with(getApplicationContext()).load(img).into(imageView);
mList.add(imageView);
}
}
private void initDots() {
dotsList = new ArrayList<>();
for (int i = 0; i < imgs.length; i++) {
ImageView imageView = new ImageView(getApplicationContext());
if (i == 0) {
imageView.setImageResource(R.drawable.indicator_focus);
} else {
imageView.setImageResource(R.drawable.indicator_normal);
}
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(16, 16);
params.setMargins(5, 0, 5, 0);
mLinearLayout.addView(imageView, params);
dotsList.add(imageView);
}
}
}
activity_welcome.xml
总结
本文针对 ViewPager 尽可能的介绍各种使用方法,涵盖如下:
- 基础介绍
- PagerAdapter + FragmentPagerAdapter&FragmentStatePagerAdapter
- 与 Fragment + TabLayout 的联动使用
- Banner 轮播图
- 自定义切换动画
- 首次登录引导界面
放上源码地址,可以下载下来配合学习。
源码地址:https://github.com/whdalive/Demo-ViewPager
洋洋洒洒写了这么多,最后愿本文对大家有所帮助。互勉。