1. 程式人生 > >ViewPager實現真正的左右無限迴圈滑動

ViewPager實現真正的左右無限迴圈滑動

最近由於專案需要需要實現類似於廣告那種左右無限滑動展示資訊的功能,因此第一反應就是使用viewpager,但是Android原生的viewpager不支援左右無限滑動,在網上也找了好多資料都不是很滿意,有的說在介面卡中把返回的大小設定為很大,然後初始化時再取中間位,因為一般人不會那麼無聊的一直向右或左滑到盡頭,但是我覺得這種方法不是很好,浪費資源,也容易報錯。因此就viewpager的特性想出了一個簡單而實用的方法,完美的實現了這一需求。

我們先看看效果再說原理:

1、這是初始化的第一張:


2、這是第二張(在第一張的基礎上再向做滑動的效果,到了最後一張):


3、這是最後一張:


4、這是最後一張再向右滑動(到了第一張):


是不是滿足了需求了,下面我們來看看實現的原理:

首先初始化圖片:比如我們要展示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真正的左右無限迴圈”,哈哈哈。

例項下載