ViewPager实现炫酷的滑动缩放广告页

ViewPager是Android开发中一个重要的控件,其轻量级的多页展示功能简化了开发过程,使我们能够快速构建一个App的引导页。但是,如果只是用来做引导页,那就太浪费了,其实我们可以将它打造的更炫酷一点。

效果图

(制作的gif图不知道为什么不能显示,所以只能用一张静态图片代替了)

需求分析

  1. 页面滑动过程中,中间页向两边滑动的过程中进行缩小,两边页面向中间滑动的过程中进行放大,
  2. 页面滑动过程中底部显示选中页标志;
  3. 页面范围内都可以滑动;

实现

通过对上面的需求进行分析,我们先确定要实现的位置,需求一要求在缩放过程中进行缩放,那只能在ViewPager的滑动监听过程中实现了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

}

@Override
public void onPageSelected(int position) {

}

@Override
public void onPageScrollStateChanged(int i) {

}
});

ViewPager的滑动监听中有三个方法, onPageScrolled在滑动过程中被调用,onPageSelected当一个新的页面被选中时被调用(注意:动画此时不一定结束),onPageScrollStateChanged在滑动状态改变时被调用(i==1表示开始滑动,i==2表示当前页被选中,i==0表示滑动和动画都已结束,注意:i==2的状态后会迅速调用i==0的状态,所以滑动过程中i的变化过程为:i=1 --> i=2 –> i=0)。了解了滑动监听的过程,我们很容易看出,滑动过程的动画只能在onPageScrolled进行实现了。

onPageScrolled函数原型中的参数说明:

  • position:当前选中页的index, 当posistionOffset为非零时position++;
  • positionOffset:当前选中页的位置偏移量,取值范围[0, 1)。
  • positionOffsetPixels:当前选中页的偏移距离, 取值范围[0, viewpager的宽度+ViewPager中页面的间距)。

滑动过程中参数的变化:

  • 左滑过程中positionOffset和positionOffsetPixels逐渐变大,变到 最大后都又变成0,此时position++;
  • 右滑过程中positionOffset和positionOffsetPixels从最大值逐渐变为0,此过程中position始终是当前选中页的前一页的index;

了解了上面函数的参数含义后开始实现我们的需求一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// 虽然一屏只显示三页,但是当我们滑动的过程中需要设置四页的缩放效果,因为左边的页面被滑出去之后,页面上将显示的是后面的三页
View leftImage = activityMap.get(position - 1);
View currentImage = activityMap.get(position);
View rightImage = activityMap.get(position + 1);
View rightImage2 = activityMap.get(position + 2);
if (positionOffset > 0) {
float scaleAlpha1 = 1 - positionOffset / 4.0f;
float scaleAlpha2 = 0.75f + positionOffset / 4.0f;
// 设置页面滑动过程中的缩放效果
currentImage.setAlpha(scaleAlpha1);
currentImage.setScaleX(scaleAlpha1);
currentImage.setScaleY(scaleAlpha1);
if (leftImage != null) {
leftImage.setAlpha(scaleAlpha2);
leftImage.setScaleX(scaleAlpha2);
leftImage.setScaleY(scaleAlpha2);
}
if (rightImage != null) {
rightImage.setAlpha(scaleAlpha2);
rightImage.setScaleX(scaleAlpha2);
rightImage.setScaleY(scaleAlpha2);
}
if (rightImage2 != null) {
rightImage2.setAlpha(scaleAlpha1);
rightImage2.setScaleX(scaleAlpha1);
rightImage2.setScaleY(scaleAlpha1);
}
} else {
// 设置页面初次启动时的缩放效果
if (leftImage != null && currentImage.getAlpha() == 1) {
leftImage.setAlpha(0.75f);
leftImage.setScaleX(0.75f);
leftImage.setScaleY(0.75f);
}
if (rightImage != null && currentImage.getAlpha() == 1) {
rightImage.setAlpha(0.75f);
rightImage.setScaleX(0.75f);
rightImage.setScaleY(0.75f);
}
}
}

需求二其实简单,只需要在onPageSelected重新设置选中状态即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 @Override
public void onPageSelected(int position) {
selectCurrentDot(position);
}

/**
* 设置当前选中页的选中状态(只有一页时不显示选中状态)
* @param position 当前选中页的index
*/
private void selectCurrentDot(int position) {
if (imageList.size() > 1) {
for (int i = 0; i < indicatorIcon.length; i++) {
if (i == position) {
indicatorIcon[i].setBackgroundResource(R.drawable.indicator_selected);
} else {
indicatorIcon[i].setBackgroundResource(R.drawable.indicatorunselect);
}
}
}
}

到这一步,我们的滑动缩放广告栏看起来已经完成了,但是滑动的过程中发现只有ViewPager页面范围内可以滑动,两边的位置都不能滑动,看了下ViewPager的源码, 发现它重写了ViewGroup的dispatchTouchEvent函数,并且也是是公开的,那我们在ViewPager的父View的onTouch事件中返回ViewPager的dispatchTouchEvent应该就可以像ViewPager一样滑动了。

1
2
3
4
5
6
containLayout.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return viewPager.dispatchTouchEvent(event);
}
});

再次运行发现可以在ViewPager的父View范围内滑动了。至此,整个滑动过程就完成了。下面贴一下ViewPagerAdapter的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
 private class MyPagerAdapter extends PagerAdapter {
@Override
public int getCount() {
return imageList.size();
}

@Override
public boolean isViewFromObject(View view, Object obj) {
return view == obj;
}

@Override
public Object instantiateItem(ViewGroup container, final int position) {
RoundImageView imageView = new RoundImageView(context);
ViewGroup.LayoutParams params = container.getLayoutParams();
params.width = getScreenWidth() - dpToPx(140);
params.height = (int)(params.width * H_W);
imageView.setLayoutParams(params);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setImageDrawable(imageList.get(position));
imageView.setTag(position);
container.addView(imageView);
activityMap.put(position, imageView);

imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context, "触发了点击事件", Toast.LENGTH_SHORT).show();
}
});

return imageView;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {

}
}

源码

源码下载:可滑动缩放的ViewPager

遇到的问题

  1. ViewPager一屏不能显示三页;
  2. 只有ViewPager的位置可以滑动,两边不能滑动;
  3. ViewPager滑动过程中出现卡顿,多次滑动后App卡死;

解决方案

  1. 在ViewPager,ViewPager的父布局和ViewPager的根布局中添加属性android:clipChildren=”false”(注意:ViewPager不加此属性时有些手机会显示异常);
  2. 重写ViewPager父布局的onTouch事件;
  3. 先读取所有的图片内容,得到drawable或bitmap, 然后设置给ViewPager中的图片,同时设置ViewPager的预加载时需要预加载所有的Item,只有这样ViewPager滑动过程中才不会出现卡顿(具体原因有待进一步深究)。通过这种方案,加载了30页1080*540大小的图片没有出现卡顿情况。

如果有什么错误之处,欢迎指正!顺便问一下,你们都用什么软件来做gif截屏,尝试了一下感觉很麻烦,而且还不理想。

,