zoukankan      html  css  js  c++  java
  • Android 开发时使用 ViewPager 的问题及解决方案整理

    1. ViewPager 的页面重置问题

    当我们使用ViewPager控件时,假设我们的ViewPager有三页,当我们第一次启动ViewPager显示第一页的时候,ViewPager会预加载第二页,这样当我们向第二页滑动的时候就可以看见第二页的内容了,因为第二页的内容在第一页内容显示的时候就已经加载了。当我们滑动到第二页时,ViewPager会预加载第三页,这时ViewPager中已经保存了三页的内容:第一页,第二页和第三页,这样当我们在第二页向左或向右滑动ViewPager时能看到这三页的内容。

    我在使用ViewPager控件时遇到过这样的问题,像上面说明的那样,我在把ViewPager从第一页滑动到第三页的时候看到了这三页的内容,但是假设我在第一页中做了一些操作改变了第一页的内容,而我在第三页中也做了一些操作改变了第三页的内容,可是当我从第三页滑动到第一页时发现第一页的内容还原了,而当我再次从第一页滑动到第三页时发现第三页的内容也还原了。

    问题的根源在于ViewPage的加载页数,就是说你可以指定ViewPager预加载的页数,如果不指定,ViewPager默认加载一页。在这种情况下,ViewPage会加载当前显示页面相邻的还没有加载的页面。比如说,当前显示的是首页,因为首页的左边是没有页面的,所以ViewPager会加载第二页的内容;当ViewPager滑动到第二页的时候,因为第一页已经加载,所以不会重复加载,但是第三页还没有加载,所以ViewPager会加载第三页的内容;当ViewPager滑动到第三页的时候(假设ViewPager只有三页),因为第二页已经加载过了,而第三页的右边没有任何页面了,所以ViewPager不会加载任何界面。当从第三页滑动到第二页时,因为第三页已经加载过,所以不会重新加载,虽然第一页之前加载过,但是,ViewPager的默认加载为一页,而当ViewPager在第三页的时候第一页在默认加载之外,所以从第三页滑动到第二页的时候,ViewPager会重新加载第一页,而再从第一页滑动到第三页时是同样的道理,所以会出现我上面描述的情况。

    为了避免上述情况的发生我们可以通过调用ViewPager的setOffscreenPageLimit方法来指定加载的页数,从而得到我们想要的结果。

    2. 调用setCurrentItem方法无动画问题

    在使用setCurrentItem时,我们可以通过反射机制设置Viewpager的滑动过渡时间。

    具体的实现方式如下:

    /**
     * ViewPager 滚动速度设置
     * 
     */
    public class ViewPagerScroller extends Scroller {
        private int mScrollDuration = 2000;             // 滑动速度
     
        /**
         * 设置速度速度
         * @param duration
         */
        public void setScrollDuration(int duration){
            this.mScrollDuration = duration;
        }
         
        public ViewPagerScroller(Context context) {
            super(context);
        }
     
        public ViewPagerScroller(Context context, Interpolator interpolator) {
            super(context, interpolator);
        }
     
        public ViewPagerScroller(Context context, Interpolator interpolator, boolean flywheel) {
            super(context, interpolator, flywheel);
        }
     
        @Override
        public void startScroll(int startX, int startY, int dx, int dy, int duration) {
            super.startScroll(startX, startY, dx, dy, mScrollDuration);
        }
     
        @Override
        public void startScroll(int startX, int startY, int dx, int dy) {
            super.startScroll(startX, startY, dx, dy, mScrollDuration);
        }
         
        public void initViewPagerScroll(ViewPager viewPager) {
            try {
                Field mScroller = ViewPager.class.getDeclaredField("mScroller");
                mScroller.setAccessible(true);
                mScroller.set(viewPager, this);
            } catch(Exception e) {
                e.printStackTrace();
            }
        }
    }
     
    //这个是设置viewPager切换过度时间的类
    ViewPagerScroller scroller = new ViewPagerScroller(context);
    scroller.setScrollDuration(0);
    scroller.initViewPagerScroll(viewPager);  //这个是设置切换过渡时间为0毫秒
     
    ViewPagerScroller scroller = new ViewPagerScroller(context);
    scroller.setScrollDuration(2000);
    scroller.initViewPagerScroll(viewPager);//这个是设置切换过渡时间为2秒

    如果需要设置其他类型的动画效果,可以调用 ViewPager.setPageTransformer 来设置。

    3. ViewPager调用notifyDataSetChanged() 刷新问题解决方案

    问题说明:

    ViewPager控件很大程度上满足了开发者开发页面左右移动切换的功能,使用非常方便。但是使用中发现,在删除或者修改数据的时候,PagerAdapter无法像BaseAdapter那样仅通过notifyDataSetChanged方法通知刷新View。有人提出一种解决方案:给Viewpager重新设置一遍适配器adapter,来达到刷新数据的目的。但是这种方法在大多数情况下,是存在问题的。

    问题分析:

    为什么调用数据更新的方法,Viewpager却没有更新呢,我们跟进该方法的源代码看一下。

    /**
     * This method should be called by the application if the data backing this adapter has changed
     * and associated views should update.
     */
    public void notifyDataSetChanged() {
        mObservable.notifyChanged();
    }

    注释里说到,当附加在适配器上的数据发生变化时,应该调用该方法刷新数据。该方法调用了一个mObservable .notifyChanged():

    /**
     * Invokes {@link DataSetObserver#onChanged} on each observer.
     * Called when the contents of the data set have changed.  The recipient
     * will obtain the new contents the next time it queries the data set.
     */
    public void notifyChanged() {
        synchronized(mObservers ) {
             // since onChanged() is implemented by the app, it could do anything, including
             // removing itself from {@link mObservers} - and that could cause problems if
             // an iterator is used on the ArrayList {@link mObservers}.
             // to avoid such problems, just march thru the list in the reverse order.
             for (int i = mObservers .size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
             }
        }
    }

    这都不是重点,重点我们来看这个mObservers的类型是一个抽象类DataSetObserver,里面只有两个未实现的方法,都有谁使用了这个抽象类呢,快捷键 ctrl + alt + H ,在众多的调用者当中,我们发现了Viewpager的身影。进入Viewpager,我们终于找到了Viewpager中控制数据变更的重点方法dataSetChanged ,这个方法如下:

          void dataSetChanged () {
            // This method only gets called if our observer is attached, so mAdapter is non-null.
     
            boolean needPopulate = mItems .size() < mOffscreenPageLimit * 2 + 1 &&
                    mItems.size() < mAdapter.getCount();
            int newCurrItem = mCurItem ;
     
            boolean isUpdating = false;
            for (int i = 0; i < mItems.size(); i++) {
                final ItemInfo ii = mItems .get(i);
                final int newPos = mAdapter.getItemPosition(ii.object );
     
                if (newPos == PagerAdapter.POSITION_UNCHANGED ) {
                    continue;
                }
     
                if (newPos == PagerAdapter.POSITION_NONE) {
                    mItems.remove(i);
                    i--;
     
                    if (!isUpdating) {
                        mAdapter.startUpdate( this);
                        isUpdating = true;
                    }
     
                    mAdapter.destroyItem( this, ii.position , ii.object);
                    needPopulate = true;
     
                    if (mCurItem == ii.position ) {
                        // Keep the current item in the valid range
                        newCurrItem = Math. max(0, Math.min(mCurItem, mAdapter.getCount() - 1));
                        needPopulate = true;
                    }
                    continue;
                }
     
                if (ii.position != newPos) {
                    if (ii.position == mCurItem ) {
                        // Our current item changed position. Follow it.
                        newCurrItem = newPos;
                    }
     
                    ii. position = newPos;
                    needPopulate = true;
                }
            }
     
            if (isUpdating) {
                mAdapter.finishUpdate( this);
            }
     
            Collections. sort(mItems, COMPARATOR);
     
            if (needPopulate) {
                // Reset our known page widths; populate will recompute them.
                final int childCount = getChildCount();
                for (int i = 0; i < childCount; i++) {
                    final View child = getChildAt(i);
                    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                    if (!lp.isDecor ) {
                        lp. widthFactor = 0.f;
                    }
                }
     
                setCurrentItemInternal(newCurrItem, false, true);
                requestLayout();
            }
        }

     重点看这样一行代码:

    final int newPos = mAdapter.getItemPosition(ii.object);
    if (newPos == PagerAdapter.POSITION_UNCHANGED) {
        continue ;
    }

    到这里我们就找到了解决这个问题的核心方法:getItemPosition()。官方对getItemPosition()的解释是:

    Called when the host view is attempting to determine if an item’s position has changed.

    Returns POSITION_UNCHANGED if the position of the given item has not changed orPOSITION_NONE if the item is no longer present in the adapter.

    The default implementation assumes that items will never change position and always returns POSITION_UNCHANGED.

    意思是如果item的位置如果没有发生变化,则返回POSITION_UNCHANGED。如果返回了POSITION_NONE,表示该位置的item已经不存在了。默认的实现是假设item的位置永远不会发生变化,而返回POSITION_UNCHANGED。

    解决方案:

    根据上面的分析,我们可以尝试着修改适配器的写法,覆盖getItemPosition()方法,当调用notifyDataSetChanged时,让getItemPosition方法人为的返回POSITION_NONE,从而达到强制Viewpager重绘所有item的目的。

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

     参考资料:https://www.cnblogs.com/cheneasternsun/p/6017012.html

  • 相关阅读:
    uboot和内核分区的修改
    2440移植内核到uboot上,打印乱码
    启动新内核出现:No filesystem could mount root, tried: ext3 ext2 cramfs vfa
    启动新内核出现:Kernel panic
    移植最新版本3.4.2内核
    2017团体程序设计天梯赛大区赛 L3-3 球队“食物链”
    leetcode543 Diameter of Binary Tree
    CF599B Spongebob and Joke
    poj1930 Dead Fraction
    poj3040 Allowance
  • 原文地址:https://www.cnblogs.com/renhui/p/5884112.html
Copyright © 2011-2022 走看看