ViewPager无限循环滑动+自动播放
最近在研究使用ViewPager实现banner广告,但是ViewPager本身并不能实现循环滑动。网上查找到两种方法:1. 将ViewPager设置一个足够大的整数Integer.MAX_VALUE,但这并不是真正的无限循环;2. 在实际展现的ViewPager前后分别插入一张图片,真正实现无限循环滑动,如下图所示
图中绿线代表向右滑动,蓝线代表向左滑动。红色字体代表的“C(额外增加)”和“A(额外增加)”只是起过渡作用。
C往右滑动到A:C–>A(额外增加)–>A;
A往左滑动到C:A–>C(额外增加)–>C;
- ViewPager实现无限循环滑动+自动播放的代码
- 代码思路详解
点击下载demo代码
一. 代码
- PagerAdapter适配器
import android.support.v4.view.PagerAdapter;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
/** ViewPager适配器
* Created by zxg on 2016/11/8.
* QQ:1092885570
*/
public class ViewPagerAdapter extends PagerAdapter {
private List<View> vp_views;
public ViewPagerAdapter(List<View> views) {
if (views == null){
vp_views = new ArrayList<View>();
}else {
vp_views = views;
}
}
@Override
public int getCount() {
return vp_views.size();
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
container.addView(vp_views.get(position));
return vp_views.get(position);
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView(vp_views.get(position));
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
}
2 . 对ViewPager封装,实现无限循环滑动和自动播放
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
/** banner无限循环滑动+自动播放
* Created by zxg on 2016/11/9.
* QQ:1092885570
*/
public class BannerView extends RelativeLayout {
private int mPosition = 1;
private Context mContext;
public ViewPager vp_banner;
private LinearLayout ll_indicator;
//Banner展示的view
private List<View> vp_views = new ArrayList<View>();
private List<ImageView> dot_imgs = new ArrayList<ImageView>();
//指示器图片资源
private int[] indicatorImgRes = {R.drawable.ic_indicator_on, R.drawable.ic_indicator_off};
private Timer mTimer;
private Handler mHandle = new Handler(){
@Override
public void handleMessage(Message msg) {
//实现循环自动播放的效果
vp_banner.setCurrentItem(mPosition+1);
}
};
public BannerView(Context context) {
super(context);
mContext = context;
initView();
}
public BannerView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
initView();
}
private void initView(){
/**
* 用Java代码创建布局
*/
vp_banner = new ViewPager(mContext);
LinearLayout.LayoutParams vp_param = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT);
vp_banner.setLayoutParams(vp_param);
ll_indicator = new LinearLayout(mContext);
RelativeLayout.LayoutParams dot_param = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
dot_param.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
dot_param.setMargins(0, 0, 0, 10);
ll_indicator.setLayoutParams(dot_param);
ll_indicator.setGravity(Gravity.CENTER_HORIZONTAL);
this.addView(vp_banner);
this.addView(ll_indicator);
}
/**
* 设置banner展示的view,指示器图片为默认图片
* @param views banner展示的view
*/
public void setView(List<View> views){
setView(views, null);
}
/**
* 设置banner展示的view和指示器图片
* @param views banner展示的view
* @param indicatorRes 指示器图片
*/
public void setView(List<View> views, int[] indicatorRes){
if (views != null){
vp_views = views;
}
vp_banner.setAdapter(new ViewPagerAdapter(vp_views));
vp_banner.setOffscreenPageLimit(1);
vp_banner.setCurrentItem(1);
vp_banner.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
//指示器跳转
mPosition = position;
int pageIndex = mPosition;
if(mPosition == 0){
pageIndex = dot_imgs.size();
}else if(mPosition == dot_imgs.size() + 1){
pageIndex = 1;
}
setIndicator(pageIndex);
}
@Override
public void onPageScrollStateChanged(int state) {
//ViewPager跳转
int pageIndex = mPosition;
if(mPosition == 0){
pageIndex = dot_imgs.size();
}else if(mPosition == dot_imgs.size() + 1){
pageIndex = 1;
}
if (pageIndex != mPosition) {
//无滑动动画,直接跳转
vp_banner.setCurrentItem(pageIndex, false);
return;
}
}
});
//加载指示器
if (indicatorRes!=null && indicatorRes.length>=2){
indicatorImgRes = indicatorRes;
}
for (int i = 0; i < vp_views.size()-2; i++) {
ImageView imageView = new ImageView(mContext);
if (i == 0){
imageView.setBackgroundResource(indicatorImgRes[0]);
} else {
imageView.setBackgroundResource(indicatorImgRes[1]);
}
dot_imgs.add(imageView);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
int margin_h = (int)(0.016 * mContext.getResources().getDisplayMetrics().widthPixels);
int margin_v = (int)(0.02 * mContext.getResources().getDisplayMetrics().widthPixels);
lp.setMargins(margin_h, margin_v, margin_h, margin_v);
ll_indicator.addView(imageView, lp);
}
}
/**
* 设置指示器的图片
* @param position 当前banner位置
*/
private void setIndicator(int position){
for (int i = 0; i < dot_imgs.size(); i++) {
if (position == i+1){
dot_imgs.get(i).setBackgroundResource(indicatorImgRes[0]);
} else {
dot_imgs.get(i).setBackgroundResource(indicatorImgRes[1]);
}
}
}
/**
* 开启自动轮播
* @param period banner轮播的周期
*/
public void startAutoPlay(long period){
//banner的vp_views数量大于1时,才允许自动轮播
if (vp_views.size()>1) {
mTimer = new Timer();
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
mHandle.sendEmptyMessage(1);
}
};
mTimer.schedule(timerTask, 2000, period);
}
}
/**
* 关闭自动轮播
*/
public void stopAutoPlay(){
if (mTimer != null){
mTimer.cancel();
}
}
3 . xml布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="banner.viewpager.viewpagerbannerdemo.MainActivity">
<banner.viewpager.viewpagerbannerdemo.BannerView
android:id="@+id/banner_view"
android:layout_width="match_parent"
android:layout_height="150dp"/>
<Button
android:id="@+id/btn_autoplay"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_margin="10dp"
android:layout_below="@id/banner_view"
android:text="自动轮播"/>
<Button
android:id="@+id/btn_stopplay"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_margin="10dp"
android:layout_below="@id/btn_autoplay"
android:text="停止轮播"/>
</RelativeLayout>
4 . MainActivity
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private static String TAG = MainActivity.class.getSimpleName();
private Context mContext;
private BannerView banner_view;
private Button btn_autoplay;
private Button btn_stopplay;
private List<View> bannerViews = new ArrayList<View>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = this;
initView();
initData();
}
private void initView(){
banner_view = (BannerView)findViewById(R.id.banner_view);
btn_autoplay = (Button) findViewById(R.id.btn_autoplay);
btn_autoplay.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
banner_view.startAutoPlay(1000);
}
});
btn_stopplay = (Button) findViewById(R.id.btn_stopplay);
btn_stopplay.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
banner_view.stopAutoPlay();
}
});
}
private void initData(){
int[] img_src = {R.drawable.test1, R.drawable.test2, R.drawable.test3};
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
for (int i = 0; i < img_src.length+2; i++) {
ImageView imageView = new ImageView(mContext);
imageView.setLayoutParams(lp);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
if (i == 0){
imageView.setImageBitmap(readBitmap(mContext, img_src[img_src.length-1]));
} else if (i == img_src.length+1) {
imageView.setImageBitmap(readBitmap(mContext, img_src[0]));
} else {
imageView.setImageBitmap(readBitmap(mContext, img_src[i - 1]));
}
bannerViews.add(imageView);
imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.i(TAG, "bannerview onclick listener");
}
});
}
banner_view.setView(bannerViews/*,new int[]{R.drawable.ic_indicator_off, R.drawable.ic_indicator_on}*/);
}
/**
* 以最小内存读取本地资源图片
* @param context
* @param bitmapResId
* @return
*/
public static Bitmap readBitmap(Context context, int bitmapResId){
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565;
opt.inPurgeable = true;
opt.inInputShareable = true;
InputStream is = context.getResources().openRawResource(bitmapResId);
return BitmapFactory.decodeStream(is, null, opt);
}
}
二. 代码思路详解
1 . ViewPager.OnPageChangeListener(), 其有三个接口方法需要实现:
关于OnPageChangeListener的用法可参考文章【2】
A. onPageScrollStateChanged(int state):scroller在状态改变时调用。state=0:闲置状态;state=1:正在滑动;state=0:滑动完成;
B. onPageScrolled(int position, float positionOffset, int positionOffsetPixels):滑动过程中调用若干次;
position:当前页面,及你点击滑动的页面
positionOffset:当前页面偏移的百分比
positionOffsetPixels:当前页面偏移的像素位置
C. onPageSelected(int position):页面跳转完成之后调用;
这三个方法的调用顺序:onPageScrollStateChanged(1),然后不断执行onPageScrolled,放手指的时候,直接立即执行一次onPageScrollStateChanged(2),然后立即执行一次onPageSelected,最后执行一次onPageScrollStateChanged(0)
2 . 自动播放,使用Timer、TimerTask和handle实现
关于定时器的实现可参考文章【3】
private Handler mHandle = new Handler(){
@Override
public void handleMessage(Message msg) {
//实现循环自动播放的效果
vp_banner.setCurrentItem(mPosition+1);
}
};
public void startAutoPlay(long period){
//banner的vp_views数量大于1时,才允许自动轮播
if (vp_views.size()>1) {
mTimer = new Timer();
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
mHandle.sendEmptyMessage(1);
}
};
mTimer.schedule(timerTask, 2000, period);
}
}
3 .ViewPager和指示器循环滑动处理
vp_banner.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
//指示器跳转
mPosition = position;
int pageIndex = mPosition;
if(mPosition == 0){
pageIndex = dot_imgs.size();
}else if(mPosition == dot_imgs.size() + 1){
pageIndex = 1;
}
setIndicator(pageIndex);
}
@Override
public void onPageScrollStateChanged(int state) {
//ViewPager跳转
int pageIndex = mPosition;
if(mPosition == 0){
pageIndex = dot_imgs.size();
}else if(mPosition == dot_imgs.size() + 1){
pageIndex = 1;
}
if (pageIndex != mPosition) {
//无滑动动画,直接跳转
vp_banner.setCurrentItem(pageIndex, false);
return;
}
}
});
C往右滑动到A:C–>A(额外增加)–>A
ViewPager翻页:C往右滑动的到A(额外增加)完成后,调用vp_banner.setCurrentItem(pageIndex, false)跳转到A。由于A(额外增加)和A放的是同一个页面,其A(额外增加)到A使用了无滑动动画直接跳转,视觉上感觉是直接从C跳转到A。如果放在将这段代码放在onPageSelected()方法中,则会出现C往右滑动的到A(额外增加)完成后调用onPageSelected()方法滑动Scroller同时就执行vp_banner.setCurrentItem(pageIndex, false)方法,此时Scroller的滑动还未完成而出现闪跳,显得不流畅。若放在onPageScrollStateChanged()方法中调用,则C往右滑动的到A(额外增加)完成后,先调用onPageSelected()方法完成C–>A(额外增加)滑动,再调用onPageScrollStateChanged(0)方法执行vp_banner.setCurrentItem(pageIndex, false)完成A(额外增加)到A的跳转,从而可以解决闪跳的问题。
指示器改变:如果将指示器改变的代码放在onPageScrollStateChanged()方法中,则会出现C到A(额外增加)滑动已完成页面切换后,再改变指示器状态,从而指示器会出现延时卡顿的效果。若放在onPageSelected()方法中调用,则C到A(额外增加),先调用onPageSelected()方法完成C–>A(额外增加)滑动过程中页面切换前,改变指示器状态,从而解决延时卡顿的问题。
同理可以得到A往左滑动到C。
花絮
最开始是想通过事件拦截onInterceptTouchEvent和onTouchEvent实现(关于Android事件拦截和事件分发参考文章【4】)。通过事件拦截,判断当前页是最后一页且往右翻页则直接跳转到第一页或当前页为第一页且往左翻页则直接跳转到最后一页。效果是正常情况(如A到B)往右滑动,动画是左出右进,而最后一页跳转到到第一页则刚好相反;正常情况往左滑动,动画是右出左进,而第一页跳转到到最后一页也刚好相反。这样能实现循环滑动,但是循环的时候跟正常情况滑动的效果不一致。
相关参考文章:
[1] http://www.cnblogs.com/xinye/archive/2013/06/09/3129140.html
[2] http://www.cnblogs.com/exmyth/p/4555814.html
[3] http://blog.csdn.net/yhm2046/article/details/8213629
[4] http://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=2649548149&idx=1&sn=709149df682c7d3a6e453c9ef0626a1f&chksm=f1180e08c66f871eb2e7e39e057a5b090214fd71adcd98aa36b3d7fcecf77ad5d08138c50131&mpshare=1&scene=1&srcid=1114VQC580nlkROWd6GMiVMa#rd