Android无限循环与自动播放ViewPager的简单实现广告栏
xhyt8248
8年前
<p>之前写过一个简单的ViewPager指示器,但是只能够展示指定数量的内容,没有实现无限循环和自动播放功能,今天来完整的把这几个功能写一下吧.当然还是用到之前写的简单的ViewPager指示器,并做一些小修改,来配合无限循环和自动播放.</p> <p>效果图如下:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/7b497c12e8efac69bb3c2eceb0868bbb.gif"></p> <h2>一、简单分析</h2> <p>首先考虑一下无限循环怎么实现,按照之前写的,只是指定了数据源的内容,ViewPager创建了数据源长度的子项个数,可滑动范围比较小.这样就导致拉到最后一页时,无法继续滑动,或者处于第一页的时候,无法向前滑动等情况,所以在设置ViewPager子项数量时,不能设置为数据源的个数.要设置一个比较大的值,才能使ViewPager的可滑动范围比较大,然后通过动态的修改ViewPager的位置,来实现无限循环的效果.</p> <p>接着考虑一下自动播放如何实现,自动播放,需要一个播放频率,每隔一段时间,去播放一次(也就是向后边滑动一页),这个用定时器Timer配合TimerTask就可以完成,每隔一定时间去执行一次.播放用Handler来做,在TimerTask中,发送一个消息,然后Handler接收到这个消息后,去做播放操作.当然方法有很多.</p> <h2>二、具体实现</h2> <p>通过分析,先来写一下适配器,定义一个比较大的数来规定ViewPager的项数:</p> <pre> <code class="language-java">/** * 默认轮播个数 */ public static final int FAKE_BANNER_SIZE = 10000; @Override public int getCount() { return FAKE_BANNER_SIZE; }</code></pre> <p>这样,ViewPager的滑动范围就不会受到限制,不过位置发生变化后,绑定数据时要对位置进行处理,不然会出现数据获取不到或者获取错误的问题,处理位置</p> <pre> <code class="language-java">@Override public Object instantiateItem(ViewGroup container, int position) { View view = LayoutInflater.from(mContext).inflate(R.layout.banner_item, container, false); ImageView imageView = (ImageView) view.findViewById(R.id.iv_banner_item); // 获取当前显示位置 position %= pictureList.size(); imageView.setImageResource(pictureList.get(position)); container.addView(view); return view; }</code></pre> <p>对position进行取余操作,这样就可以获取到符合数据源取值范围的位置,从而得到想要获取展示的数据.</p> <p>搞定了适配器,再来看一下之前写的简单指示器,如何做一下修改呢.</p> <p>首先新加入一个变量,来控制是否用于无限循环的ViewPager</p> <pre> <code class="language-java">/** * 是否是循环 */ private boolean isCirculate; public IndicatorView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); // 获取自定义属性 TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.IndicatorView); ... isCirculate = ta.getBoolean(R.styleable.IndicatorView_isCirculate, true); ta.recycle(); // 初始化 init(); }</code></pre> <p>接着对外提供一个新的设置ViewPager的方法,因为之前获取创建指示器个数的方法是获取ViewPager适配器中子项的个数,适配器已经做了修改,显然这个方法已经不再适用.想一下,指示器个数其实和数据源的个数是一致的,所以提供一个新的方法</p> <pre> <code class="language-java">/** * 设置vp * * @param childViewCount 需要显示指示器的个数 * @param viewpager vp */ public void setViewPager(int childViewCount, ViewPager viewpager) { if (null == viewpager) { return; } if (null == viewpager.getAdapter()) { throw new IllegalStateException("ViewPager does not have adapter."); } this.mViewPager = viewpager; this.mViewPager.addOnPageChangeListener(this); this.childViewCount = childViewCount; removeAllViews(); invalidate(); }</code></pre> <p>然后在修改指示器显示时,对位置也需要进行处理,方式同适配器中的处理相同</p> <pre> <code class="language-java">@Override public void onPageSelected(int position) { // 处理位置变化,防止指示器显示错误 if (isCirculate && 0 != childViewCount) { position %= childViewCount; } setIndicatorState(position); if (null != mListener) { mListener.onPageSelected(position); } }</code></pre> <p>这样ViewPager无限循环的问题基本就搞定了.下面来做下自动播放的</p> <p>部分.打算通过定时器配合Handle来完成.很简单.直接贴下代码</p> <p>在Activity中</p> <pre> <code class="language-java">/** * 轮播图自动轮播消息 */ public static final int AUTOBANNER_CODE = 0X1001; /** * 当前轮播图位置 */ private int mBannerPosition; /** * 自动轮播计时器 */ private Timer timer = new Timer(); /** * 自动轮播任务 */ private BannerTimerTask mBannerTimerTask; /** * 用户当前是否点击轮播图 */ private boolean mIsUserTouched = false; /** * 轮播图Handler */ Handler bannerHandler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { // 当用户点击时,不进行轮播 if (!mIsUserTouched) { // 获取当前的位置 mBannerPosition = mViewPager.getCurrentItem(); // 更换轮播图 mBannerPosition = (mBannerPosition + 1) % mBannerPagerAdapter.FAKE_BANNER_SIZE; mViewPager.setCurrentItem(mBannerPosition); } return true; } }); /** * 开始轮播 */ private void startBannerTimer() { if (timer == null) { timer = new Timer(); } if (mBannerTimerTask != null) { mBannerTimerTask.cancel(); } mBannerTimerTask = new BannerTimerTask(bannerHandler); if (timer != null && mBannerTimerTask != null) { // 循环5秒执行 timer.schedule(mBannerTimerTask, 5000, 5000); } } public class BannerTimerTask extends TimerTask { /** * handler */ Handler handler; public BannerTimerTask(Handler handler) { super(); this.handler = handler; } @Override public void run() { handler.sendEmptyMessage(MainActivity.AUTOBANNER_CODE); } }</code></pre> <p>创建一个定时器,每隔5秒钟发送一个消息,Handle接收到消息后,改变ViewPager的位置.不难理解.</p> <p>这样效果就达到了,一个简单的广告栏效果也就实现了.当然还可以再进一步去做一些封装和优化,这里就不做了,最重要的其实就是把位置搞清楚,其他的问题并不难.</p> <h2>三、完整代码</h2> <p>IndicatorView:</p> <pre> <code class="language-java">package com.example.junweiliu.simpleindicatorview; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.view.View; import android.widget.ImageView; import android.widget.LinearLayout; /** * 轮播图圆形指示器 * Created by junweiliu on 16/6/15. */ public class IndicatorView extends LinearLayout implements ViewPager.OnPageChangeListener { /** * 需要创建的指示器个数 */ private int childViewCount = 0; /** * 设置圆点间margin */ private int mInterval; /** * 当前选中的位置 */ private int mCurrentPostion = 0; /** * 普通显示的图片 */ private Bitmap normalBp; /** * 选中时显示的图片 */ private Bitmap selectBp; /** * 设置的轮播图Vp */ private ViewPager mViewPager; /** * 指示器单项宽度 */ private int mWidth; /** * 指示器单项高度 */ private int mHeight; /** * 圆点半径 */ private int mRadius; /** * 普通状态圆点颜色 */ private int normalColor; /** * 选中状态圆点颜色 */ private int selectColor; /** * 是否是循环 */ private boolean isCirculate; /** * 对外提供ViewPager的回调接口 */ interface OnPageChangeListener { public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels); public void onPageSelected(int position); public void onPageScrollStateChanged(int state); } /** * 回调接口 */ private OnPageChangeListener mListener; /** * 设置回调 * * @param listener */ public void setOnPageChangeListener(OnPageChangeListener listener) { this.mListener = listener; } public IndicatorView(Context context) { this(context, null); } public IndicatorView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public IndicatorView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); // 获取自定义属性 TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.IndicatorView); normalBp = drawableToBitamp(ta.getDrawable(R.styleable.IndicatorView_normalDrawable)); selectBp = drawableToBitamp(ta.getDrawable(R.styleable.IndicatorView_selectDrawable)); mInterval = ta.getDimensionPixelOffset(R.styleable.IndicatorView_indicatorInterval, 6); normalColor = ta.getColor(R.styleable.IndicatorView_normalColor, Color.GRAY); selectColor = ta.getColor(R.styleable.IndicatorView_selectColor, Color.RED); mRadius = ta.getInteger(R.styleable.IndicatorView_indicatorRadius, 6); isCirculate = ta.getBoolean(R.styleable.IndicatorView_isCirculate, true); ta.recycle(); // 初始化 init(); } /** * 初始化数据 */ private void init() { // 处理自定义属性 if (null == normalBp) { normalBp = makeIndicatorBp(normalColor); } if (null == selectBp) { selectBp = makeIndicatorBp(selectColor); } mWidth = normalBp.getWidth(); mHeight = normalBp.getWidth(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int sizeWidth = MeasureSpec.getSize(widthMeasureSpec); int sizeHeight = MeasureSpec.getSize(heightMeasureSpec); // 如果是wrap_content设置为图片宽高,否则设置为父容器宽高 setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? sizeWidth : (mWidth + mInterval) * childViewCount , (heightMode == MeasureSpec.EXACTLY) ? sizeHeight : mHeight); } /** * 重绘 * * @param canvas */ @Override protected void dispatchDraw(Canvas canvas) { // 创建指示器圆点 if (getChildCount() < childViewCount && getChildCount() == 0) { for (int i = 0; i < childViewCount; i++) { addView(makeIndicatorItem()); } // 设置默认选中指示器 setIndicatorState(mCurrentPostion); } super.dispatchDraw(canvas); } /** * 设置Vp * * @param viewpager */ public void setViewPager(ViewPager viewpager) { if (null == viewpager) { return; } if (null == viewpager.getAdapter()) { throw new IllegalStateException("ViewPager does not have adapter."); } this.mViewPager = viewpager; this.mViewPager.addOnPageChangeListener(this); this.childViewCount = viewpager.getAdapter().getCount(); invalidate(); } /** * 设置Vp * * @param viewpager * @param currposition 当前选中的位置 */ public void setViewPager(ViewPager viewpager, int currposition) { if (null == viewpager) { return; } if (null == viewpager.getAdapter()) { throw new IllegalStateException("ViewPager does not have adapter."); } this.mViewPager = viewpager; this.mViewPager.addOnPageChangeListener(this); this.childViewCount = viewpager.getAdapter().getCount(); this.mCurrentPostion = currposition; invalidate(); } /** * 设置vp * * @param childViewCount 需要显示指示器的个数 * @param viewpager vp */ public void setViewPager(int childViewCount, ViewPager viewpager) { if (null == viewpager) { return; } if (null == viewpager.getAdapter()) { throw new IllegalStateException("ViewPager does not have adapter."); } this.mViewPager = viewpager; this.mViewPager.addOnPageChangeListener(this); this.childViewCount = childViewCount; removeAllViews(); invalidate(); } /** * 创建指示器 * * @return */ private View makeIndicatorItem() { ImageView iv = new ImageView(getContext()); LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); lp.width = normalBp.getWidth(); lp.height = normalBp.getHeight(); lp.rightMargin = mInterval; iv.setImageBitmap(normalBp); iv.setLayoutParams(lp); return iv; } /** * 创建圆点指示器图片 * * @param color 创建不同颜色的指示器项 * @return */ private Bitmap makeIndicatorBp(int color) { Bitmap normalBp = Bitmap.createBitmap(mRadius * 2, mRadius * 2, Bitmap.Config.ARGB_8888); Paint paint = new Paint(); paint.setAntiAlias(true); paint.setColor(color); Canvas canvas = new Canvas(normalBp); canvas.drawCircle(mRadius, mRadius, mRadius, paint); return normalBp; } /** * drawable转bitmap * * @param drawable * @return */ private Bitmap drawableToBitamp(Drawable drawable) { if (null == drawable) { return null; } if (drawable instanceof BitmapDrawable) { BitmapDrawable bd = (BitmapDrawable) drawable; return bd.getBitmap(); } int w = drawable.getIntrinsicWidth(); int h = drawable.getIntrinsicHeight(); Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); drawable.setBounds(0, 0, w, h); drawable.draw(canvas); return bitmap; } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { if (null != mListener) { mListener.onPageScrolled(position, positionOffset, positionOffsetPixels); } } @Override public void onPageSelected(int position) { if (isCirculate && 0 != childViewCount) { position %= childViewCount; } setIndicatorState(position); if (null != mListener) { mListener.onPageSelected(position); } } @Override public void onPageScrollStateChanged(int state) { if (null != mListener) { mListener.onPageScrollStateChanged(state); } } /** * 设置指示器的状态 * * @param position */ public void setIndicatorState(int position) { for (int i = 0; i < getChildCount(); i++) { if (i == position) ((ImageView) getChildAt(i)).setImageBitmap(selectBp); else ((ImageView) getChildAt(i)).setImageBitmap(normalBp); } } }</code></pre> <p>适配器BannerPagerAdapter</p> <pre> <code class="language-java">package com.example.junweiliu.simpleindicatorview; import android.content.Context; import android.support.v4.view.PagerAdapter; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import java.util.ArrayList; import java.util.List; /** * Created by junweiliu on 16/6/14. * VP适配器 */ public class BannerPagerAdapter extends PagerAdapter { /** * 上下文 */ private Context mContext; /** * 图像列表 */ private List<Integer> pictureList = new ArrayList<>(); /** * 默认轮播个数 */ public static final int FAKE_BANNER_SIZE = 10000; public BannerPagerAdapter(Context context, List<Integer> pictureList) { this.mContext = context; this.pictureList = pictureList; } @Override public int getCount() { return FAKE_BANNER_SIZE; } @Override public boolean isViewFromObject(View view, Object object) { return view == object; } @Override public Object instantiateItem(ViewGroup container, int position) { View view = LayoutInflater.from(mContext).inflate(R.layout.banner_item, container, false); ImageView imageView = (ImageView) view.findViewById(R.id.iv_banner_item); // 获取当前显示位置 position %= pictureList.size(); imageView.setImageResource(pictureList.get(position)); container.addView(view); return view; } @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView((View) object); } }</code></pre> <p>MainActivity</p> <pre> <code class="language-java">package com.example.junweiliu.simpleindicatorview; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.v4.view.ViewPager; import android.support.v7.app.AppCompatActivity; import android.view.MotionEvent; import android.view.View; import java.util.ArrayList; import java.util.List; import java.util.Timer; public class MainActivity extends AppCompatActivity { /** * 轮播图 */ private ViewPager mViewPager; /** * 指示器 */ private IndicatorView mIndicatorView; /** * 适配器 */ private BannerPagerAdapter mBannerPagerAdapter; /** * 图片资源 */ private List<Integer> pictureList = new ArrayList<>(); /** * 轮播图自动轮播消息 */ public static final int AUTOBANNER_CODE = 0X1001; /** * 当前轮播图位置 */ private int mBannerPosition; /** * 自动轮播计时器 */ private Timer timer = new Timer(); /** * 自动轮播任务 */ private BannerTimerTask mBannerTimerTask; /** * 用户当前是否点击轮播图 */ private boolean mIsUserTouched = false; /** * 轮播图Handler */ Handler bannerHandler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { // 当用户点击时,不进行轮播 if (!mIsUserTouched) { // 获取当前的位置 mBannerPosition = mViewPager.getCurrentItem(); // 更换轮播图 mBannerPosition = (mBannerPosition + 1) % mBannerPagerAdapter.FAKE_BANNER_SIZE; mViewPager.setCurrentItem(mBannerPosition); } return true; } }); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initDatas(); initView(); } /** * 初始化数据 */ private void initDatas() { pictureList.add(R.drawable.pic_one); pictureList.add(R.drawable.pic_two); pictureList.add(R.drawable.pic_three); pictureList.add(R.drawable.pic_one); pictureList.add(R.drawable.pic_two); pictureList.add(R.drawable.pic_three); } /** * 初始化控件 */ private void initView() { mViewPager = (ViewPager) findViewById(R.id.vp_banner); mIndicatorView = (IndicatorView) findViewById(R.id.idv_banner); mBannerPagerAdapter = new BannerPagerAdapter(this, pictureList); mViewPager.setAdapter(mBannerPagerAdapter); mIndicatorView.setViewPager(pictureList.size(), mViewPager); // 设置默认起始位置,使开始可以向左边滑动 mViewPager.setCurrentItem(pictureList.size() * 100); mIndicatorView.setOnPageChangeListener(new IndicatorView.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { } @Override public void onPageScrollStateChanged(int state) { } }); mViewPager.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { int action = event.getAction(); if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_MOVE) { mIsUserTouched = true; } else if (action == MotionEvent.ACTION_UP) { mIsUserTouched = false; } return false; } }); startBannerTimer(); } /** * 开始轮播 */ private void startBannerTimer() { if (timer == null) { timer = new Timer(); } if (mBannerTimerTask != null) { mBannerTimerTask.cancel(); } mBannerTimerTask = new BannerTimerTask(bannerHandler); if (timer != null && mBannerTimerTask != null) { // 循环5秒执行 timer.schedule(mBannerTimerTask, 5000, 5000); } } /** * 销毁时,关闭任务,防止异常 */ @Override public void onDestroy() { super.onDestroy(); if (null != mBannerTimerTask) { mBannerTimerTask.cancel(); mBannerTimerTask = null; } } }</code></pre> <p>BannerTimerTask</p> <pre> <code class="language-java">package com.example.junweiliu.simpleindicatorview; import android.os.Handler; import java.util.TimerTask; /** * Created by junweiliu on 16/6/15. */ public class BannerTimerTask extends TimerTask { /** * handler */ Handler handler; public BannerTimerTask(Handler handler) { super(); this.handler = handler; } @Override public void run() { handler.sendEmptyMessage(MainActivity.AUTOBANNER_CODE); } }</code></pre> <p>attr</p> <pre> <code class="language-java"><?xml version="1.0" encoding="utf-8"?> <resources> <!--IndicatorView相关--> <!--普通指示器图片--> <attr name="normalDrawable" format="reference"/> <!--选中指示器图片--> <attr name="selectDrawable" format="reference"/> <!--指示器间隔--> <attr name="indicatorInterval" format="dimension"/> <!--普通指示器颜色--> <attr name="normalColor" format="color"/> <!--选中指示器颜色--> <attr name="selectColor" format="color"/> <!--圆点弧度--> <attr name="indicatorRadius" format="integer"/> <!--是否是循环--> <attr name="isCirculate" format="boolean"/> <declare-styleable name="IndicatorView"> <attr name="normalDrawable"/> <attr name="selectDrawable"/> <attr name="indicatorInterval"/> <attr name="normalColor"/> <attr name="selectColor"/> <attr name="indicatorRadius"/> <attr name="isCirculate"/> </declare-styleable> </resources></code></pre> <p>activity_main</p> <pre> <code class="language-java"><?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:idv="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="225dp" tools:context="com.example.junweiliu.simpleindicatorview.MainActivity"> <!--轮播图--> <android.support.v4.view.ViewPager android:id="@+id/vp_banner" android:layout_width="match_parent" android:layout_height="225dp" > </android.support.v4.view.ViewPager> <!--指示器--> <com.example.junweiliu.simpleindicatorview.IndicatorView android:id="@+id/idv_banner" android:layout_width="match_parent" android:layout_height="10dp" android:layout_alignParentBottom="true" android:gravity="center_horizontal" android:layout_marginTop="20dp" idv:indicatorInterval="10dp" idv:isCirculate="true" idv:normalDrawable="@mipmap/oval_indicator_grey" idv:selectDrawable="@mipmap/oval_indicator_green"> </com.example.junweiliu.simpleindicatorview.IndicatorView> </RelativeLayout></code></pre> <p>banner_item</p> <pre> <code class="language-java"><?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ImageView android:id="@+id/iv_banner_item" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="fitXY" /> </LinearLayout></code></pre> <p> </p> <p> </p> <p>来自:http://blog.csdn.net/u013904672/article/details/53743308</p> <p> </p>