ViewPager.html

ViewPager轮播Banner的坑

需要对ViewPager中的ViewContainer的加载机制更深入的学习

使用ViewPager实现轮播广告banner,功能为:

  1. banner可以自己向左或者向右自己轮播, 向右是无限的, 向左是到了adapter中item的最小值, (一般是0). 此时, 不应该可以继续向左划动. 如果需要向左划动, 本质上是把初始显示的item变为整个adapter的item的中间的一个.
  2. 点击跳转网页或者另一个activity
  3. 在banner的下面有可以配置的划动状态显示, 如小圆点显示当前展示的是娜一张.

实现思路:
如果通过ViewPager实现, 需要注意两点:

  1. 需要一个线程,或者定时器来定时让adapter中的currentItem更换. 这里我们使用了一个Runnable().
  2. 对于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中几种延迟处理事件的方法:

  1. 利用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);
  1. View中自带的postDelayed
    例如:
v.postDelayed(new Runnable(){ language-php
         public void run(){
             // ...
         }
     }, 30);
"> v.postDelayed(new Runnable(){
         public void run(){
             // ...
         }
     }, 30);
  1. 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