ViewPager 实现自动循环轮播 高度自适应 显示前后部分界面 点击事件

来源:互联网 发布:唯一网络怎么样 编辑:程序博客网 时间:2024/06/03 23:44

游民星空 3.0 界面大改之后,发现首页的轮播图很有特色,一直想着实现一下。先看一下原 app 的效果:

其实是可以自动轮播的,不过等的时间太长,我就动手帮了一把。

要实现这种效果无非需要考虑到以下几个问题:
1. ViewPager 可以显示前后的一部分界面;
2. 要在不同分辨率的手机上保持图片的长宽比例;
3. 实现自动循环轮播;
4. 注意 Activity 的生命周期和手指对 ViewPager 操作时对自动播放的影响;
5. 页面的点击事件的处理。

那么我们就来一步一步解决这些问题:

1.实现 ViewPager 显示前后部分界面与高度自适应

直接上 xml 文件的代码:

<?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"    tools:context="com.ayuhani.viewpagerdemo.MainActivity">    <LinearLayout        android:id="@+id/ll_parent"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:clipChildren="false"        android:orientation="vertical">        <android.support.v4.view.ViewPager            android:id="@+id/view_pager"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:clipChildren="false"></android.support.v4.view.ViewPager>    </LinearLayout></LinearLayout>

在 ViewPager 和它的父布局我们都添加了一行android:clipChildren="false",这句代码的意思是:是否限制子 View 在其范围内显示,默认为 true,是限制的,我们这里改成 false。然后在代码中放上五张图片,这里怎么自适应高度呢?

图片占用屏幕的高度 / 占用屏幕的宽度 = 原始图片高度 / 图片宽度

根据这个公式,我们就可以计算出 ViewPager 所需要的高度了。

DisplayMetrics metrics = getResources().getDisplayMetrics();ViewGroup.LayoutParams params = viewPager.getLayoutParams();params.width = (int) (metrics.widthPixels * 0.86); // 宽度设置成屏幕宽度的86%,这里根据自己喜好设置params.height = params.width * 240 / 386; // 利用已知图片的宽高比计算高度viewPager.setLayoutParams(params);

由于我们还需要展示前后的部分界面,所以不能完全占据屏幕宽度,再根据公式计算出高度,设置给 ViewPager 就好了。我们看一下效果:

等一下,这和我们想要的结果完全不一样啊!

这里发现了两个比较严重的问题:

1.图片不居中显示,B 区域不显示,只显示 C 区域

关于这个问题我首先想到的是大概由于 gravity 的原因,于是我在 ViewPager 的属性里面加上android:layout_gravity="center_horizontal"或者在它的父布局中加上android:gravity="center_horizontal"都可以解决这个问题。

2.手指放在 C 区域滑动时,无法滑到下一页,只有在 A 处可以正常滑动

这个问题我们想一下也可以知道,由于限制了 ViewPager 的宽度,所以 C 区域已经不属于 ViewPager 的当前界面,所以滑动是没有效果的,我们只需要重写父布局的OnTouchListener方法就好了:

// 这里处理触摸两端滑动无效果的问题parentLayout = (LinearLayout) findViewById(R.id.ll_parent);parentLayout.setOnTouchListener(new View.OnTouchListener() {    @Override    public boolean onTouch(View view, MotionEvent motionEvent) {        return viewPager.dispatchTouchEvent(motionEvent);    }});

注意:给父布局重写这个方法之后,在父布局的任意位置滑动都能带动 ViewPager 的滑动,所以要给 ViewPager 单独包一个父布局。

这两个问题解决后,已经可以正常显示 B 区域了,并且滑动 B 和 C 区域也有了效果,但从游民的效果可以看到 A B C 之间都是有一小块间隙的,这个很简单:

viewPager.setPageMargin(20);

这个方法用来设置 ViewPager 页面之间的间距,单位是 px。现在再来看一下效果,是不是已经基本成型了?

2.实现循环功能

从网上找了查了一些资料,发现大多数实现这个功能用的都是一种“障眼法”:

我们当前只有 5 幅图片,但是我们在集合的首尾再增加两个元素,第一个显示图片 5,最后一个显示图片 1。当我们向后滑动到页面 6 的时候,通过viewPager.setCurrentItem(1)方法跳转到页面 1;而向前滑动到页面 0 的时候,通过viewPager.setCurrentItem(5)方法跳转到页面 5。这样就给人一种无限循环滑动的感觉。当然初始化的时候,我们要先设置viewPager.setCurrentItem(1)来显示第一张图片。

viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {            @Override            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {            }            @Override            public void onPageSelected(int position) {                if (position == 0) {                    handler.postDelayed(new Runnable() {                        @Override                        public void run() {                            viewPager.setCurrentItem(adapter.getCount() - 2, false);                        }                    }, 250);                } else if (position == adapter.getCount() - 1) {                    handler.postDelayed(new Runnable() {                        @Override                        public void run() {                            viewPager.setCurrentItem(1, false);                        }                    }, 250);                }            }            @Override            public void onPageScrollStateChanged(int state) {            }        });

该方法的第二个参数是一个布尔类型,代表是否要平滑地滚动到指定的位置,如果 true 的话,会有一段动画的效果,具体可以自己实现看一下。这里写 false,代表立刻跳转。但是onPageSelected()这个方法在下个页面还没有完全显示完成的时候就执行了,所以给人一种很突兀的感觉,我在这里写了 250 毫秒的延迟。

注意:这种方法在 ViewPager 的页面完全显示的时候是没有问题的,但是我们要显示前后的部分,所以给人的视觉效果不是很好。比如说当前滑动到了页面 6,这是时候是要跳转到页面 1 的,但由于我们写了 250 毫秒的延迟,导致图片 2 也被延迟加载,所以显示页面 6 的时候会有短暂的时间右边显示一小部分空白,然后才加载出图片 2。目前想到的办法就是在页面 6 之后再放一个页面 7 用来显示图片 2,这样能暂时解决问题,但是又进一步消耗了资源,不是太好的处理办法。

3.实现自动播放

利用viewPager.setCurrentItem(viewPager.getCurrentItem() + 1)方法与定时功能实现。

private void autoPlay() {        handler.postDelayed(runnable, 2000);    }class PlayRunnable implements Runnable {    @Override    public void run() {        viewPager.setCurrentItem(viewPager.getCurrentItem() + 1);        autoPlay();    }}

只需要调用autoPlay()方法,便可以自动轮播了。

4.生命周期的管理与触摸管理

为了减少内存的开销,一般都是在 Activity 暂停的时候停止自动播放,在恢复时开始自动播放,在销毁时移除 handler 的回调。

    private void autoPlay() {        handler.postDelayed(runnable, 2000);    }    private void stopAutoPlay(){        handler.removeCallbacks(runnable);    }    @Override    protected void onResume() {        super.onResume();        autoPlay();    }    @Override    protected void onPause() {        super.onPause();        stopAutoPlay();    }    @Override    protected void onDestroy() {        super.onDestroy();        // 页面被销毁时,移除所有的callbacks和messages        handler.removeCallbacksAndMessages(null);    }

而当用户手动滑动 ViewPager 时也要暂停播放,松手时再继续播放,所以需要重写ViewPager的OnTouchListener()方法:

viewPager.setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View view, MotionEvent motionEvent) {                switch (motionEvent.getAction()) {                    case MotionEvent.ACTION_DOWN:                    case MotionEvent.ACTION_MOVE:                        stopAutoPlay();                        break;                    case MotionEvent.ACTION_UP:                        autoPlay();                        break;                    default:                        break;                }                return false;            }        });

5.点击事件的处理

我直接给 ImageView 设置了setOnClickListener()方法,结果发现它拦截了 ViewPager 的 onTouchEvent 事件。尝试了解决这个问题,但是一时又没有找到好的办法,于是想着能不能用别的什么方案替代 ViewPager 的触摸管理或者点击事件,在同事的帮助下,做了如下改变:
不再利用 ViewPager 的OnTouchEvent()方法进行手势的处理,而是利用onPageScrollStateChanged(int state)这个方法:

@Overridepublic void onPageScrollStateChanged(int state) {     //state: 0 空闲,1 是滑行中,2 加载完毕     if (state == 1 && isRunning) {         // 当处于滑动中并且正在自动播放,则停止滑动         // 也就是自动播放的时候用手指去滑动,会停止播放         stopAutoPlay();     } else if (state == 0 && !isRunning) {         // 当ViewPager处于空闲状态并且没有在自动播放的时候,才开始自动播放         // 也就是当手指离开屏幕时,再次启动自动播放         autoPlay();     }}private void autoPlay() {    Log.e("isRunning", "true");    isRunning = true;    handler.postDelayed(runnable, 3000);}private void stopAutoPlay() {    Log.e("isRunning", "false");    isRunning = false;    handler.removeCallbacks(runnable);}

这样处理之后基本解决了问题,用手指滑动时会停止自动播放,松手时又会继续,同时也可以响应点击事件。最终的效果:

6.遗留的两个问题

1.上文提到过的,首尾相互跳转的时候,导致相邻图片加载延迟问题;

2.利用onPageScrollStateChanged(int state)方法之后,仅仅是触摸而不滑动 ViewPager 的话,自动播放不会暂停,不知这是否符合交互体验?(游民这个 app 只是触摸也不会暂停自动播放)

代码写的比较乱,其实完全可以写个类继承自 ViewPager,把需要用到的方法写在这个类里,这样更便于管理和拓展,也能减少 Activity 的代码量。如果大家有更好的实现方法或者思路,欢迎指教。

->->->点击下载源码<-<-<-

阅读全文
1 0
原创粉丝点击