ViewPager.html
ViewPager轮播Banner的坑
需要对ViewPager中的ViewContainer的加载机制更深入的学习
使用ViewPager实现轮播广告banner,功能为:
- banner可以自己向左或者向右自己轮播, 向右是无限的, 向左是到了adapter中item的最小值, (一般是0). 此时, 不应该可以继续向左划动. 如果需要向左划动, 本质上是把初始显示的item变为整个adapter的item的中间的一个.
- 点击跳转网页或者另一个activity
- 在banner的下面有可以配置的划动状态显示, 如小圆点显示当前展示的是娜一张.
实现思路:
如果通过ViewPager实现, 需要注意两点:
- 需要一个线程,或者定时器来定时让adapter中的currentItem更换. 这里我们使用了一个Runnable().
- 对于ViewPager的更换item的机制需要理解, 在ViewPager中需要了解Adapter的重写的四大函数.
具体实现:
需要定义一个继承自ViewPager的View, 并定义其对应的Adapter, 直接上代码.
public class AutoPlayBanner extends ViewPager { language-php
public enum ShowDirection {
LEFT,
RIGHT
}
public AutoPlayBanner(Context context) {
super(context);
}
public AutoPlayBanner(Context context, AttributeSet attrs) {
super(context, attrs);
}
private long showTime = 3 * 1000;
private ShowDirection direction = ShowDirection.LEFT;
public void setShowTime(long showTimeMillis) {
showTime = showTimeMillis;
}
public void setShowDirection(ShowDirection direction) {
this.direction = direction;
}
public void start() {
stop();
postDelayed(player, showTime);
}
public void stop() {
removeCallbacks(player);
}
private Runnable player = new Runnable() {
@Override
public void run() {
play(direction);
}
};
private synchronized void play(ShowDirection direction1) {
PagerAdapter adapter = getAdapter();
if (adapter != null) {
int count = adapter.getCount();
int currentItem = getCurrentItem();
switch (direction1) {
case LEFT:
currentItem++;
if (currentItem > count) {
currentItem = 0;
}
break;
case RIGHT:
currentItem--;
if (currentItem < 0) {
currentItem = count;
}
break;
}
setCurrentItem(currentItem);
}
start();
}
public void previous() {
if (direction == ShowDirection.LEFT) {
play(ShowDirection.RIGHT);
} else {
play(ShowDirection.LEFT);
}
}
public void next() {
play(direction);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
addOnPageChangeListener(new OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
}
@Override
public void onPageScrollStateChanged(int state) {
if (state == SCROLL_STATE_IDLE) {
start();
} else if (state == SCROLL_STATE_DRAGGING) {
stop();
}
}
});
}
public static class AutoPlayBannerAdapter extends PagerAdapter {
private Context mContext;
private int itemsCount;
private List<ImageView> imageList;
public AutoPlayBannerAdapter(Context context) {
mContext = context;
}
// Should call this to init imageList
public void InitItems(List<ImageView> imageArray) {
itemsCount = imageArray.size();
imageList = imageArray;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (imageList.size() >= 4){
// container.removeView((View) object);
container.removeView(imageList.get(position % imageList.size()));
}
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
ImageView imageView;
if ((imageList == null) || (imageList.size() == 0)) {
imageView = new ImageView(mContext);
imageView.setBackgroundResource(android.R.color.white);
} else {
imageView = imageList.get(position % imageList.size());
}
//ViewGroup parent = (ViewGroup) imageView.getParent();
if (container != null) {
container.removeView(imageView);
}
container.addView(imageView);
return imageView;
}
@Override
public int getCount() {
return imageList == null ? 0 : Integer.MAX_VALUE;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
}
}
">public class AutoPlayBanner extends ViewPager {
public enum ShowDirection {
LEFT,
RIGHT
}
public AutoPlayBanner(Context context) {
super(context);
}
public AutoPlayBanner(Context context, AttributeSet attrs) {
super(context, attrs);
}
private long showTime = 3 * 1000;
private ShowDirection direction = ShowDirection.LEFT;
public void setShowTime(long showTimeMillis) {
showTime = showTimeMillis;
}
public void setShowDirection(ShowDirection direction) {
this.direction = direction;
}
public void start() {
stop();
postDelayed(player, showTime);
}
public void stop() {
removeCallbacks(player);
}
private Runnable player = new Runnable() {
@Override
public void run() {
play(direction);
}
};
private synchronized void play(ShowDirection direction1) {
PagerAdapter adapter = getAdapter();
if (adapter != null) {
int count = adapter.getCount();
int currentItem = getCurrentItem();
switch (direction1) {
case LEFT:
currentItem++;
if (currentItem > count) {
currentItem = 0;
}
break;
case RIGHT:
currentItem--;
if (currentItem < 0) {
currentItem = count;
}
break;
}
setCurrentItem(currentItem);
}
start();
}
public void previous() {
if (direction == ShowDirection.LEFT) {
play(ShowDirection.RIGHT);
} else {
play(ShowDirection.LEFT);
}
}
public void next() {
play(direction);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
addOnPageChangeListener(new OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
}
@Override
public void onPageScrollStateChanged(int state) {
if (state == SCROLL_STATE_IDLE) {
start();
} else if (state == SCROLL_STATE_DRAGGING) {
stop();
}
}
});
}
public static class AutoPlayBannerAdapter extends PagerAdapter {
private Context mContext;
private int itemsCount;
private List<ImageView> imageList;
public AutoPlayBannerAdapter(Context context) {
mContext = context;
}
// Should call this to init imageList
public void InitItems(List<ImageView> imageArray) {
itemsCount = imageArray.size();
imageList = imageArray;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (imageList.size() >= 4){
// container.removeView((View) object);
container.removeView(imageList.get(position % imageList.size()));
}
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
ImageView imageView;
if ((imageList == null) || (imageList.size() == 0)) {
imageView = new ImageView(mContext);
imageView.setBackgroundResource(android.R.color.white);
} else {
imageView = imageList.get(position % imageList.size());
}
//ViewGroup parent = (ViewGroup) imageView.getParent();
if (container != null) {
container.removeView(imageView);
}
container.addView(imageView);
return imageView;
}
@Override
public int getCount() {
return imageList == null ? 0 : Integer.MAX_VALUE;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
}
}
以前不懂的地方:
如果需要在xml布局文件中直接引用该控件, 不仅需要实现带一个Context参数的构造函数, 还需要实现带两个参数(另一个是AttributeSet)的构造函数.
实现自动播放的关键是play()函数, 原理也比较简单, 就是根据方向的不同对currentItem的加减.
坑1. ViewPager在内部实现的时候增加新的item是根据Adapter的count来一个一个增加的, 如果setCurrentItem(1000), 就会在ViewPager中增加1000个item, 所以在这里如果真的切换会有很长的切换动画, 会从最后一个一个个跳回到第一个item, 并不是直接跳.
坑2. 如坑1, 如果在刚开始就setCurrentItem(x), 可以做到左右划动, 但是如果x特别大, 会有加载卡死问题, 因为ViewPager会创建之前所有的item.
实现手动划动的关键在于OnPageChangeListener()的回调, 这里在onFinishInflate().
OnPageChangeListener中有三个函数: onPageScrolled(), onPageSelected(), onPageScrollStateChanged(). 这里在第三个函数中, 根据划动是否结束来规避手动划动和自动播放的冲突.
if (state == SCROLL_STATE_IDLE) { language-php
start();
} else if (state == SCROLL_STATE_DRAGGING) {
stop();
}
">if (state == SCROLL_STATE_IDLE) {
start();
} else if (state == SCROLL_STATE_DRAGGING) {
stop();
}
这里当划动的时候就先stop(), 将自动轮播的效果去除, 而在划动结束后重新将play函数加载.所以不会出现手划后马上轮播的情况.
public void start() { language-php
stop();
postDelayed(player, showTime);
}
public void stop() {
removeCallbacks(player);
}
">public void start() {
stop();
postDelayed(player, showTime);
}
public void stop() {
removeCallbacks(player);
}
onPageSelected()是当前展示页面的Callback, 可以将banner下部的小圆点标识符切换放在这里, 也可以放在ViewPager的外面, 因为现在的ViewPager可以接受多个onPageChangeListener()的绑定, 小圆点和banner的唯一联系是, 计算当前展示的item都以position % list.length得到.
banner.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { language-php
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
if (position >= dotsViews.length) {
position = (position % dotsViews.length);
}
for (int i = 0; i < dotsViews.length; i++) {
dotsViews[position]
.setBackgroundResource(R.drawable.banner_dot_focused);
if (position != i) {
dotsViews[i]
.setBackgroundResource(R.drawable.banner_dot_normal);
}
}
}
@Override
public void onPageScrollStateChanged(int state) {
}
}
">banner.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
if (position >= dotsViews.length) {
position = (position % dotsViews.length);
}
for (int i = 0; i < dotsViews.length; i++) {
dotsViews[position]
.setBackgroundResource(R.drawable.banner_dot_focused);
if (position != i) {
dotsViews[i]
.setBackgroundResource(R.drawable.banner_dot_normal);
}
}
}
@Override
public void onPageScrollStateChanged(int state) {
}
}
Adapter的问题, 四大函数:
从最简单的来, getCount(), 就是返回所有的应当在ViewPage中的展示的item数. 所以这里设为Integer的最大值, 可以向右无限划动.
@Override language-php
public int getCount() {
return imageList == null ? 0 : Integer.MAX_VALUE;
}
">@Override
public int getCount() {
return imageList == null ? 0 : Integer.MAX_VALUE;
}
instantiateItem是增加新的item是来初始化新的item的. 这里需要对ViewPager的缓存机制有所了解, ViewPager中有一个int变量DEFAULT_OFFSCREEN_PAGES = 1, 是在当前展示的item的两侧, 一共缓存了多少个Pages(Items), 一般不应超过(左右)三个.
这里我们要加入ViewPager中的item是ImageView, 所以在Adapter内部维护了一个ImageView的List.
注意, 这里是先removeView, 再addView, 这里是因为根据position得到循环加入pager的item, 当item加入到pager中时, 如果该view已经有parent, 则应先解除之前的包含关系, 其实在这里parent就是ViewPager本身, 也即container.
该开始的时候parent和container不相等, 是因为第一次将item加入的时候parent是null.
@Override language-php
public Object instantiateItem(ViewGroup container, int position) {
ImageView imageView;
if ((imageList == null) || (imageList.size() == 0)) {
imageView = new ImageView(mContext);
imageView.setBackgroundResource(android.R.color.white);
} else {
imageView = imageList.get(position % imageList.size());
}
//ViewGroup parent = (ViewGroup) imageView.getParent();
if (container != null) {
container.removeView(imageView);
}
container.addView(imageView);
return imageView;
}
">@Override
public Object instantiateItem(ViewGroup container, int position) {
ImageView imageView;
if ((imageList == null) || (imageList.size() == 0)) {
imageView = new ImageView(mContext);
imageView.setBackgroundResource(android.R.color.white);
} else {
imageView = imageList.get(position % imageList.size());
}
//ViewGroup parent = (ViewGroup) imageView.getParent();
if (container != null) {
container.removeView(imageView);
}
container.addView(imageView);
return imageView;
}
ViewPager中每一个item会有一个key与其对应, 也是找到该item的唯一标识. 可以直接使用view本身作为key, 也可以用类似position的标识. isViewFromObject()函数就是表示标识和真正的view的关系的函数, 我们这里用的时view做key, 那么object就是view.
@Override language-php
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
">@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
由于我们这里已经在instantiateItem函数中将多余的item去除掉了, 这里不需要再额外去除.
@Override language-php
public void destroyItem(ViewGroup container, int position, Object object) {
if (imageList.size() >= 4){
// container.removeView((View) object);
container.removeView(imageList.get(position % imageList.size()));
}
}
">@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (imageList.size() >= 4){
// container.removeView((View) object);
container.removeView(imageList.get(position % imageList.size()));
}
}
特殊地,这里给出Android中几种延迟处理事件的方法:
- 利用TimerTask和Handler
首先定义一个TimerTask
TimerTask task = new TimerTask(){ language-php
public void run(){
Message msg = new Message();
msg.what = 1;
handler.sendMessage(msg);
}
}
"> TimerTask task = new TimerTask(){
public void run(){
Message msg = new Message();
msg.what = 1;
handler.sendMessage(msg);
}
}
然后定义出handler
Handler handler = new Handler(){ language-php
public void handleMessage(Message msg){
switch(msg.what){
case 1:
break;
default:
break;
}
super.handleMessage(msg);
}
}
"> Handler handler = new Handler(){
public void handleMessage(Message msg){
switch(msg.what){
case 1:
break;
default:
break;
}
super.handleMessage(msg);
}
}
最后用Timer调用,
Timer timer = new Timer(); language-php
timer.schedule(task, 50);
"> Timer timer = new Timer();
timer.schedule(task, 50);
- View中自带的postDelayed
例如:
v.postDelayed(new Runnable(){ language-php
public void run(){
// ...
}
}, 30);
"> v.postDelayed(new Runnable(){
public void run(){
// ...
}
}, 30);
- handler中的postDelayed
例如:
hanler.postDelayed(new Runnable(){ language-php
public void run(){
// ...
}
}, 30);
"> hanler.postDelayed(new Runnable(){
public void run(){
// ...
}
}, 30);
另外, 在Android中动态改变layoutParams需要得到要改变控件的父布局的参数, 然后对该控件设置.
generated by haroopad