• Android使用ViewPager做轮播


    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 {
    
        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) {
        start();
    } else if (state == SCROLL_STATE_DRAGGING) {
        stop();
    }
    

    这里当划动的时候就先stop(), 将自动轮播的效果去除, 而在划动结束后重新将play函数加载.所以不会出现手划后马上轮播的情况.

    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() {
                @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
    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
    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
    public boolean isViewFromObject(View view, Object object) {
        return view == object;
    }
    

    由于我们这里已经在instantiateItem函数中将多余的item去除掉了, 这里不需要再额外去除.

    @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(){
           public void run(){
               Message msg = new Message();
               msg.what = 1;
               handler.sendMessage(msg);
           }
       }
      

      然后定义出handler

       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();
       timer.schedule(task, 50);
      
    2. View中自带的postDelayed
      例如:

       v.postDelayed(new Runnable(){
               public void run(){
                   // ...
               }
           }, 30);
      
    3. handler中的postDelayed
      例如:

       hanler.postDelayed(new Runnable(){
               public void run(){
                   // ...
               }
           }, 30);
      

    另外, 在Android中动态改变layoutParams需要得到要改变控件的父布局的参数, 然后对该控件设置.

    generated by haroopad

  • 相关阅读:
    oracle性能调优
    oracle常用函数
    plsql的安装与使用
    WSAIoctl
    SQL中大概有这么几种JOIN
    如何取分组最大值记录
    having
    MSSQL—按照某一列分组后取前N条记录
    sql之left join、right join、inner join的区别
    delphi 接收心跳包怎么写
  • 原文地址:https://www.cnblogs.com/putuotingchan/p/6135518.html
Copyright © 2020-2023  润新知