最近由于项目需要需要实现类似于广告那种左右无限滑动展示信息的功能,因此第一反应就是使用viewpager,但是Android原生的viewpager不支持左右无限滑动,在网上也找了好多资料都不是很满意,有的说在适配器中把返回的大小设置为很大,然后初始化时再取中间位,因为一般人不会那么无聊的一直向右或左滑到尽头,但是我觉得这种方法不是很好,浪费资源,也容易报错。因此就viewpager的特性想出了一个简单而实用的方法,完美的实现了这一需求。

我们先看看效果再说原理:

1、这是初始化的第一张:

android 取消viewpager切换的时候滑动动画_ide

2、这是第二张(在第一张的基础上再向左滑动的效果,到了最后一张):

android 取消viewpager切换的时候滑动动画_android_02

3、这是最后一张:

android 取消viewpager切换的时候滑动动画_ide_03

4、这是最后一张再向右滑动(到了第一张):

android 取消viewpager切换的时候滑动动画_导航_04

是不是满足了需求了,下面我们来看看实现的原理:

首先初始化图片:比如我们要展示A B C D四张图片,为了滑到第一张或左后一张还能继续滑动,所以我们需要在原来的图片前后各再构造一张图片即:D A B C D A。第一张图片A再向左滑动,应该是图片D展示出来;最后一张D图片再向右滑动,应该是图片A展示出来。这样就实现了滑到第一张和左后一张时都还可以再向左或向右滑动。

然后重点来了:当滑到A前面的D图片时,此时再向左滑动是不能滑动的,因为到了viewpager的第一张(索引为0),按照逻辑如果还能滑动的话,再向左滑动的下一张会是C,因此我们需要重写方法onPageScrolled(int arg0, float arg1, int arg2),当从第一张A完全滑动到D图片时,参数arg1为0.0,此时表示滑动已经完成,这时为了还能继续向左滑动,是不是应该把viewpager的当前页设置到倒数第二个D的位置?关键点就是这样的,此时用方法setCurrentItem(int, boolean)设置viewpager跳转到倒数第二个D那一页,其中boolean参数设置为false,viewpager就会瞬间跳转到倒数第二个D,在我们的视觉效果来看,完全看不出任何变化,所以此时的页面实际就是倒数第二个D那一页,所以再向左边滑动时,B就显示出来了,给人的感觉就是,可以无限向左滑动,就这样实现了无限左右滑动。以此类推,向右滑动也一样,到了最后一个A的时候,跳转到正数第二个A,然后继续向右滑动就会显示B,也实现了无限向右滑动。此时向左向右无限循环滑动已经实现了。

下面是示例代码(已经写了详细的注释):

布局文件(activity_main.xml):

 

<pre name="code" class="html"><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"
    android:orientation="vertical"
    tools:context="${relativePackage}.${activityClass}" >

    <LinearLayout 
        android:layout_width="match_parent"
        android:layout_height="250dip"
        android:orientation="vertical">
        <android.support.v4.view.ViewPager
            android:id="@+id/circlulate_pager"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            />
    </LinearLayout>
    <TextView 
        android:id="@+id/page"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"/>
</LinearLayout>

ViewPager适配器(CirculatePagerAdapter.java):

 


 


</pre><pre name="code" class="java">package com.ywl5320.circulateviewpager;

import android.content.Context;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;

/**
 * viewpager适配器
 * @author ywl
 *
 */
public class CirculatePagerAdapter extends PagerAdapter {
	
	private ImageView[] imageViews;
	private int size;
	private Context context;
	
	
	public CirculatePagerAdapter(Context context, ImageView[] imageViews) {
		this.context = context;
		this.imageViews = imageViews;
		size = imageViews.length;
	}

	@Override
	public int getCount() {
		return imageViews.length;
	}

	@Override
	public boolean isViewFromObject(View arg0, Object arg1) {
		return arg0 == arg1;
	}
	
	@Override
	public void destroyItem(ViewGroup container, int position, Object object) {
		((ViewPager) container).removeView((View) object);// 完全溢出view,避免数据多时出现重复现象
	}
	
	@Override
	public Object instantiateItem(ViewGroup container, int position) {
		container.addView((ImageView)imageViews[position], 0);
		return (ImageView)imageViews[position];
	}
	/*
	 * 这个方法可用来异步获取网路图片时,更新viewpager中的图片(此处没用,使用时需格式化一下代码) public void refresh()
	 * { if(imageViews[size - 2].getDrawable() != null &&
	 * imageViews[0].getDrawable() == null) { ImageView imageView = new
	 * ImageView(context); imageView.setBackgroundDrawable(imageViews[size -
	 * 2].getDrawable()); imageViews[0] = imageView; }
	 * if(imageViews[1].getDrawable() != null && imageViews[size -
	 * 1].getDrawable() == null) { ImageView imageView = new ImageView(context);
	 * imageView.setBackgroundDrawable(imageViews[1].getDrawable());
	 * imageViews[size - 1] = imageView; } notifyDataSetChanged(); }
	 */

}

主Activity(MainActivity.java):

 

 

package com.ywl5320.circulateviewpager;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout.LayoutParams;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity implements OnPageChangeListener{

	private ViewPager mvPager;
	private TextView mTextView;
	private ImageView[] mImageViews;// 要循环显示的图片资源(也可以是其他的view)
	private CirculatePagerAdapter mCirculatePagerAdapter;// viewpager适配器
	private static int [] imgs = {R.drawable.zx,R.drawable.tm,R.drawable.kp,R.drawable.js};//要显示的图片资源
	private int imgsize = 0;
	private ScheduledExecutorService scheduledExecutorService;// 定时周期执行指定任务
	private int currentIndex = 0;// (自动播放时)定时周期要显示的图片的索引(viewpager中的图片位置)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mvPager = (ViewPager) findViewById(R.id.circlulate_pager);
        mTextView = (TextView) findViewById(R.id.page);
        initImg();
        setAdapter();
        scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
		scheduledExecutorService.scheduleWithFixedDelay(new ViewPagerTask(),
				10, 10, TimeUnit.SECONDS);
    }
    
	/**
	 * 初始化图片资源 为数组里的每一个Imageview分配内存
	 */
    public void initImg()
    {
    	LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    	int length = imgs.length + 2;
    	mImageViews = new ImageView[length];
    	for(int i = 0; i < length; i++)
    	{
    		ImageView imageView = new ImageView(this);
			imageView.setLayoutParams(params);
			imageView.setScaleType(ScaleType.FIT_XY);
			mImageViews[i] = imageView;
    	}
    	setImg(length);
    }
    
	/**
	 * 除去第一个和最后一个imageview,其他的imageview依次设置相应的图片,顺序与imgs里的一样
	 * 最后再把第一个imageview的图片设置为imgs中的最后一张图片
	 * ,把最后一个imageview的图片设置为imgs中的第一张图片(因为向左滑动到第一张时
	 * ,再向左滑动就到了最后一张;向右滑动一样,到了最后一张,再向右滑动就到了第一张) 比如:要显示的图片为: A B C
	 * D四张图片,此时我们要把它们构造成: D {A B C D}
	 * A中间大括号里的就是要显示的图片,第一个D和最后一个A就是滑动到头时继续再滑动时逻辑上要展示的图片
	 * 
	 * @param length
	 */
    public void setImg(int length)
    {
    	imgsize = length;
    	for(int i = 0; i < length; i++)
    	{
			if(i < length - 2)
			{
				final int index = i;
				mImageViews[i + 1].setBackgroundDrawable(getResources().getDrawable(imgs[i]));
				mImageViews[i + 1].setOnClickListener(new OnClickListener() {
					
					@Override
					public void onClick(View v) {
						// TODO Auto-generated method stub
						// 为每一张图片添加点击事件
						if(index + 1 == 1)
						{
							Toast.makeText(MainActivity.this, "赵信", Toast.LENGTH_SHORT).show();
						}
						else if(index + 1 == 2)
						{
							Toast.makeText(MainActivity.this, "提莫", Toast.LENGTH_SHORT).show();
						}
						else if(index + 1 == 3)
						{
							Toast.makeText(MainActivity.this, "卡牌", Toast.LENGTH_SHORT).show();
						}
						else if(index + 1 == 4)
						{
							Toast.makeText(MainActivity.this, "剑圣", Toast.LENGTH_SHORT).show();
						}
					}
				});
			}
    	}
    	mImageViews[0].setBackgroundDrawable(getResources().getDrawable(imgs[imgs.length - 1]));
    	mImageViews[length - 1].setBackgroundDrawable(getResources().getDrawable(imgs[0]));
    }
    
	/**
	 * 初始化和给viewpager添加适配器
	 */
    public void setAdapter()
    {
    	mCirculatePagerAdapter = new CirculatePagerAdapter(this, mImageViews);
    	mvPager.setAdapter(mCirculatePagerAdapter);
    	mvPager.setOnPageChangeListener(this);
    	mvPager.setCurrentItem(1);//初始化时设置显示第一页(ViewPager中索引为1)
    }

	@Override
	public void onPageScrollStateChanged(int arg0) {
		// TODO Auto-generated method stub
		
	}

	/**
	 * 这是重点: 此方法会监听viewpager的滑动过程,可获取滑动的百分比(arg1)参数。
	 * 这里判断的方法是:当滑动到第索引为0的那一页时(即:在逻辑上是到了第一张图片
	 * (即赵信那张图片)后,再向左滑动,此时viewpager会显示索引为0的那张图片
	 * (即在视觉效果上是左后一张的图片,因为,第一张过了再向左滑动,就是最后一张
	 * )。如果再这里不做相应的处理,再向左滑动就滑不动了,因为已经到了viewpager的第一张
	 * (索引为0),此时我们就要依靠参数arg1的值来判断是否已经完成了滑动到第一张
	 * ,当arg1的值为0.0时,即已经滑动完成,此时我们就把viewpager的页面跳转到viewpager的倒数第二张页面上
	 * ,使用setcurrentItem
	 * (Int,boolean)方法,当Boolean取值为FALSE时,就没有滑动效果,直接跳转过去,由于当前页的图片和要跳转到的页面一样
	 * ,所以在视觉效果上看不出闪烁
	 * ,这样就很自然的跳转到了倒数第二张,然后继续向左滑动,就可以继续滑动了。给人的感觉就是能一直无限向左滑动。)向右滑动也类似
	 * ,当滑动到最后一张时就跳到索引为1的那张,然后继续向右还可以滑动,这样就实现了左右循环无限滑动的效果。哈哈
	 */
	@Override
	public void onPageScrolled(int arg0, float arg1, int arg2) {
		// TODO Auto-generated method stub
		if(arg1 == 0.0)
		{
			if(arg0 == 0)
			{
				mvPager.setCurrentItem(imgsize - 2, false);
				System.out.println("ok");
			}
			else if(arg0 == imgsize - 1)
			{
				mvPager.setCurrentItem(1, false);
			}
		}
	}

	/**
	 * 显示滑到了第几张(不是必须的)
	 */
	@Override
	public void onPageSelected(int arg0) {
		// TODO Auto-generated method stub
		currentIndex = arg0;// 把当前页的索引记住,方便跳转到下一页(这是必须的)
		if(arg0 == 1)
		{
			mTextView.setText(arg0 + "/" + (imgsize - 2));
		}
		else if(arg0 == 2)
		{
			mTextView.setText(arg0 + "/" + (imgsize - 2));
		}
		else if(arg0 == 3)
		{
			mTextView.setText(arg0 + "/" + (imgsize - 2));
		}
		else if(arg0 == 4)
		{
			mTextView.setText(arg0 + "/" + (imgsize - 2));
		}
	}
	
	/**
	 * 继承runnable一个新线程,来定时播放图片
	 * 
	 * @author ywl
	 *
	 */
	private class ViewPagerTask implements Runnable
	{

		@Override
		public void run() {
			// TODO Auto-generated method stub
			currentIndex++;
			handler.obtainMessage().sendToTarget();
		}
	}
	
	private Handler handler = new Handler(){
		
		public void handleMessage(Message msg) 
		{
			// 使viewpager跳转到指定页(true:带有滑动效果)
			mvPager.setCurrentItem(currentIndex, true);
		};
	};
}

 

 

至此就实现了“ViewPager真正的左右无限循环”,哈哈哈。

 

实例下载