当前位置: 移动技术网 > IT编程>移动开发>Android > 完美解决关于禁止ViewPager预加载的相关问题

完美解决关于禁止ViewPager预加载的相关问题

2019年07月24日  | 移动技术网IT编程  | 我要评论

春季育儿知识大全,张红亚,定襄在线论坛

我最近上班又遇到一个小难题了,就是如题所述:viewpager预加载的问题。相信用过viewpager的人大抵都有遇到过这种情况,网上的解决办法也就那么几个,终于在我自己不断试验之下,完美解决了(禁止了)viewpager的预加载。

好了,首先来说明一下,什么是viewpager的预加载:viewpager有一个 “预加载”的机制,默认会把viewpager当前位置的左右相邻页面预先初始化(俗称的预加载),它的默认值是 1,这样做的好处就是viewpager左右滑动会更加流畅。

可是我的情况很特殊,因为我 5 个fragment里有一个fragment是有surfaceview的,这样造成的问题就是,我viewpager滑动到其相邻页面时,含有surfaceview的页面就会被预先初始化,然后surfaceview就开始预览了,只是我们看不到而已。同样的,当我们从含有surfaceview的页面滑倒其相邻的页面时,surfaceview并不会回调其surfacedestory方法。于是这给我造成了极大的困扰。

ok,下面言归正传,到底该怎么禁止viewpager的这个预加载问题呢?

方案1:网上大多数说法是 懒加载,即让viewpager预加载初始化ui,而具体一些数据,网络访问请求等延迟加载。这是靠fragment里有一个setuservisiblehint(boolean isvisibletouser)的方法,我们可以在这个方法里做判断,当其true可见时(即切换到某一个具体fragment)时才去加载数据,这样可以省流量。但这里并不满足我的需求,因为某一个fragment并不会在viewpager滑动到其相邻的fragment时销毁。这个只可以解决部分人问题。

首先我们来深入了解下viewpager的预加载机制:

上文提到过,viewpager默认预加载的数量是1,这一点我们可以在viewpager源码里看到。

default_offscreen_pages 这里就定义了默认值是1, 所以网上 有种解决方案 说调用viewpager的setoffscreenpagelimit(int limit),来设置viewpager预加载的数量,但是这里很明确的告诉你,这种方案是不可行的,如下图viewpager源码:

我们可以看到,如果你调用该方法传进来的值小于1是无效的,会被强行的拽回1。而且default_offscreen_pages 这个值是private的,子类继承viewpager也是不可见的。

方案2:然后网上有第二种说法,自定义一个viewpager,把原生viewpager全拷过来,修改这个default_offscreen_pages 值为0。对,就是这种解决方案!!但是!!

但是!!!但是什么呢?但是我试过,没用。为什么呢?接下来就是本文的重点了。

因为现在android都6.0了,版本都老高了,其实android虽然每个版本都有v4包,但是这些v4包是有差异的。这就造成高版本v4包里的viewpager,即使你copy它,将其default_offscreen_pages的值改为0,还是不起作用的,其中的逻辑变了。具体哪里变了导致无效我也没有继续研究了,毕竟公司不会花时间让我研究这些啊。偷笑

完美解决方案:ok,所以关于禁止viewpager预加载的完美解决方案就是,使用低版本v4包里的viewpager,完全copy一份,将其中的default_offscreen_pages值改为0即可。博主亲测 api 14 即 android 4.0的v4包里viewpager 有效。

当然,谷歌既然有这么一种viewpager的机制肯定有它的道理,所以一般还是预加载的好。

最后,因为低版本的源码越来越少的人会去下载,这里直接把这个禁止了预加载的viewpager贴上来,需要的人就拿去吧。copy就能用了。

package com.plumcot.usb.view; 
 
/* 
 * copyright (c) 2011 the android open source project 
 * 
 * licensed under the apache license, version 2.0 (the "license"); 
 * you may not use this file except in compliance with the license. 
 * you may obtain a copy of the license at 
 * 
 *   http://www.apache.org/licenses/license-2.0 
 * 
 * unless required by applicable law or agreed to in writing, software 
 * distributed under the license is distributed on an "as is" basis, 
 * without warranties or conditions of any kind, either express or implied. 
 * see the license for the specific language governing permissions and 
 * limitations under the license. 
 */ 
 
 
import android.content.context; 
import android.database.datasetobserver; 
import android.graphics.canvas; 
import android.graphics.rect; 
import android.graphics.drawable.drawable; 
import android.os.parcel; 
import android.os.parcelable; 
import android.os.systemclock; 
import android.support.v4.os.parcelablecompat; 
import android.support.v4.os.parcelablecompatcreatorcallbacks; 
import android.support.v4.view.keyeventcompat; 
import android.support.v4.view.motioneventcompat; 
import android.support.v4.view.pageradapter; 
import android.support.v4.view.velocitytrackercompat; 
import android.support.v4.view.viewcompat; 
import android.support.v4.view.viewconfigurationcompat; 
import android.support.v4.widget.edgeeffectcompat; 
import android.util.attributeset; 
import android.util.log; 
import android.view.focusfinder; 
import android.view.keyevent; 
import android.view.motionevent; 
import android.view.soundeffectconstants; 
import android.view.velocitytracker; 
import android.view.view; 
import android.view.viewconfiguration; 
import android.view.viewgroup; 
import android.view.viewparent; 
import android.view.accessibility.accessibilityevent; 
import android.view.animation.interpolator; 
import android.widget.scroller; 
 
import java.util.arraylist; 
import java.util.collections; 
import java.util.comparator; 
 
/** 
 * layout manager that allows the user to flip left and right 
 * through pages of data. you supply an implementation of a 
 * {@link android.support.v4.view.pageradapter} to generate the pages that the view shows. 
 * 
 * <p>note this class is currently under early design and 
 * development. the api will likely change in later updates of 
 * the compatibility library, requiring changes to the source code 
 * of apps when they are compiled against the newer version.</p> 
 */ 
public class nopreloadviewpager extends viewgroup { 
  private static final string tag = "<span style="font-family:arial, helvetica, sans-serif;">nopreloadviewpager</span>"; 
  private static final boolean debug = false; 
 
  private static final boolean use_cache = false; 
 
  private static final int default_offscreen_pages = 0;//默认是1 
  private static final int max_settle_duration = 600; // ms 
 
  static class iteminfo { 
    object object; 
    int position; 
    boolean scrolling; 
  } 
 
  private static final comparator<iteminfo> comparator = new comparator<iteminfo>(){ 
    @override 
    public int compare(iteminfo lhs, iteminfo rhs) { 
      return lhs.position - rhs.position; 
    }}; 
 
  private static final interpolator sinterpolator = new interpolator() { 
    public float getinterpolation(float t) { 
      // _o(t) = t * t * ((tension + 1) * t + tension) 
      // o(t) = _o(t - 1) + 1 
      t -= 1.0f; 
      return t * t * t + 1.0f; 
    } 
  }; 
 
  private final arraylist<iteminfo> mitems = new arraylist<iteminfo>(); 
 
  private pageradapter madapter; 
  private int mcuritem;  // index of currently displayed page. 
  private int mrestoredcuritem = -1; 
  private parcelable mrestoredadapterstate = null; 
  private classloader mrestoredclassloader = null; 
  private scroller mscroller; 
  private pagerobserver mobserver; 
 
  private int mpagemargin; 
  private drawable mmargindrawable; 
 
  private int mchildwidthmeasurespec; 
  private int mchildheightmeasurespec; 
  private boolean minlayout; 
 
  private boolean mscrollingcacheenabled; 
 
  private boolean mpopulatepending; 
  private boolean mscrolling; 
  private int moffscreenpagelimit = default_offscreen_pages; 
 
  private boolean misbeingdragged; 
  private boolean misunabletodrag; 
  private int mtouchslop; 
  private float minitialmotionx; 
  /** 
   * position of the last motion event. 
   */ 
  private float mlastmotionx; 
  private float mlastmotiony; 
  /** 
   * id of the active pointer. this is used to retain consistency during 
   * drags/flings if multiple pointers are used. 
   */ 
  private int mactivepointerid = invalid_pointer; 
  /** 
   * sentinel value for no current active pointer. 
   * used by {@link #mactivepointerid}. 
   */ 
  private static final int invalid_pointer = -1; 
 
  /** 
   * determines speed during touch scrolling 
   */ 
  private velocitytracker mvelocitytracker; 
  private int mminimumvelocity; 
  private int mmaximumvelocity; 
  private float mbaselineflingvelocity; 
  private float mflingvelocityinfluence; 
 
  private boolean mfakedragging; 
  private long mfakedragbegintime; 
 
  private edgeeffectcompat mleftedge; 
  private edgeeffectcompat mrightedge; 
 
  private boolean mfirstlayout = true; 
 
  private onpagechangelistener monpagechangelistener; 
 
  /** 
   * indicates that the pager is in an idle, settled state. the current page 
   * is fully in view and no animation is in progress. 
   */ 
  public static final int scroll_state_idle = 0; 
 
  /** 
   * indicates that the pager is currently being dragged by the user. 
   */ 
  public static final int scroll_state_dragging = 1; 
 
  /** 
   * indicates that the pager is in the process of settling to a final position. 
   */ 
  public static final int scroll_state_settling = 2; 
 
  private int mscrollstate = scroll_state_idle; 
 
  /** 
   * callback interface for responding to changing state of the selected page. 
   */ 
  public interface onpagechangelistener { 
 
    /** 
     * this method will be invoked when the current page is scrolled, either as part 
     * of a programmatically initiated smooth scroll or a user initiated touch scroll. 
     * 
     * @param position position index of the first page currently being displayed. 
     *         page position+1 will be visible if positionoffset is nonzero. 
     * @param positionoffset value from [0, 1) indicating the offset from the page at position. 
     * @param positionoffsetpixels value in pixels indicating the offset from position. 
     */ 
    public void onpagescrolled(int position, float positionoffset, int positionoffsetpixels); 
 
    /** 
     * this method will be invoked when a new page becomes selected. animation is not 
     * necessarily complete. 
     * 
     * @param position position index of the new selected page. 
     */ 
    public void onpageselected(int position); 
 
    /** 
     * called when the scroll state changes. useful for discovering when the user 
     * begins dragging, when the pager is automatically settling to the current page, 
     * or when it is fully stopped/idle. 
     * 
     * @param state the new scroll state. 
     * @see android.support.v4.view.viewpager#scroll_state_idle 
     * @see android.support.v4.view.viewpager#scroll_state_dragging 
     * @see android.support.v4.view.viewpager#scroll_state_settling 
     */ 
    public void onpagescrollstatechanged(int state); 
  } 
 
  /** 
   * simple implementation of the {@link android.support.v4.view.lazyviewpager.onpagechangelistener} interface with stub 
   * implementations of each method. extend this if you do not intend to override 
   * every method of {@link android.support.v4.view.lazyviewpager.onpagechangelistener}. 
   */ 
  public static class simpleonpagechangelistener implements onpagechangelistener { 
    @override 
    public void onpagescrolled(int position, float positionoffset, int positionoffsetpixels) { 
      // this space for rent 
    } 
 
    @override 
    public void onpageselected(int position) { 
      // this space for rent 
    } 
 
    @override 
    public void onpagescrollstatechanged(int state) { 
      // this space for rent 
    } 
  } 
 
  public nopreloadviewpager(context context) { 
    super(context); 
    initviewpager(); 
  } 
 
  public nopreloadviewpager(context context, attributeset attrs) { 
    super(context, attrs); 
    initviewpager(); 
  } 
 
  void initviewpager() { 
    setwillnotdraw(false); 
    setdescendantfocusability(focus_after_descendants); 
    setfocusable(true); 
    final context context = getcontext(); 
    mscroller = new scroller(context, sinterpolator); 
    final viewconfiguration configuration = viewconfiguration.get(context); 
    mtouchslop = viewconfigurationcompat.getscaledpagingtouchslop(configuration); 
    mminimumvelocity = configuration.getscaledminimumflingvelocity(); 
    mmaximumvelocity = configuration.getscaledmaximumflingvelocity(); 
    mleftedge = new edgeeffectcompat(context); 
    mrightedge = new edgeeffectcompat(context); 
 
    float density = context.getresources().getdisplaymetrics().density; 
    mbaselineflingvelocity = 2500.0f * density; 
    mflingvelocityinfluence = 0.4f; 
  } 
 
  private void setscrollstate(int newstate) { 
    if (mscrollstate == newstate) { 
      return; 
    } 
 
    mscrollstate = newstate; 
    if (monpagechangelistener != null) { 
      monpagechangelistener.onpagescrollstatechanged(newstate); 
    } 
  } 
 
  public void setadapter(pageradapter adapter) { 
    if (madapter != null) { 
//      madapter.unregisterdatasetobserver(mobserver); 
      madapter.startupdate(this); 
      for (int i = 0; i < mitems.size(); i++) { 
        final iteminfo ii = mitems.get(i); 
        madapter.destroyitem(this, ii.position, ii.object); 
      } 
      madapter.finishupdate(this); 
      mitems.clear(); 
      removeallviews(); 
      mcuritem = 0; 
      scrollto(0, 0); 
    } 
 
    madapter = adapter; 
 
    if (madapter != null) { 
      if (mobserver == null) { 
        mobserver = new pagerobserver(); 
      } 
//      madapter.registerdatasetobserver(mobserver); 
      mpopulatepending = false; 
      if (mrestoredcuritem >= 0) { 
        madapter.restorestate(mrestoredadapterstate, mrestoredclassloader); 
        setcurrentiteminternal(mrestoredcuritem, false, true); 
        mrestoredcuritem = -1; 
        mrestoredadapterstate = null; 
        mrestoredclassloader = null; 
      } else { 
        populate(); 
      } 
    } 
  } 
 
  public pageradapter getadapter() { 
    return madapter; 
  } 
 
  /** 
   * set the currently selected page. if the viewpager has already been through its first 
   * layout there will be a smooth animated transition between the current item and the 
   * specified item. 
   * 
   * @param item item index to select 
   */ 
  public void setcurrentitem(int item) { 
    mpopulatepending = false; 
    setcurrentiteminternal(item, !mfirstlayout, false); 
  } 
 
  /** 
   * set the currently selected page. 
   * 
   * @param item item index to select 
   * @param smoothscroll true to smoothly scroll to the new item, false to transition immediately 
   */ 
  public void setcurrentitem(int item, boolean smoothscroll) { 
    mpopulatepending = false; 
    setcurrentiteminternal(item, smoothscroll, false); 
  } 
 
  public int getcurrentitem() { 
    return mcuritem; 
  } 
 
  void setcurrentiteminternal(int item, boolean smoothscroll, boolean always) { 
    setcurrentiteminternal(item, smoothscroll, always, 0); 
  } 
 
  void setcurrentiteminternal(int item, boolean smoothscroll, boolean always, int velocity) { 
    if (madapter == null || madapter.getcount() <= 0) { 
      setscrollingcacheenabled(false); 
      return; 
    } 
    if (!always && mcuritem == item && mitems.size() != 0) { 
      setscrollingcacheenabled(false); 
      return; 
    } 
    if (item < 0) { 
      item = 0; 
    } else if (item >= madapter.getcount()) { 
      item = madapter.getcount() - 1; 
    } 
    final int pagelimit = moffscreenpagelimit; 
    if (item > (mcuritem + pagelimit) || item < (mcuritem - pagelimit)) { 
      // we are doing a jump by more than one page. to avoid 
      // glitches, we want to keep all current pages in the view 
      // until the scroll ends. 
      for (int i=0; i<mitems.size(); i++) { 
        mitems.get(i).scrolling = true; 
      } 
    } 
 
    final boolean dispatchselected = mcuritem != item; 
    mcuritem = item; 
    populate(); 
    final int destx = (getwidth() + mpagemargin) * item; 
    if (smoothscroll) { 
      smoothscrollto(destx, 0, velocity); 
      if (dispatchselected && monpagechangelistener != null) { 
        monpagechangelistener.onpageselected(item); 
      } 
    } else { 
      if (dispatchselected && monpagechangelistener != null) { 
        monpagechangelistener.onpageselected(item); 
      } 
      completescroll(); 
      scrollto(destx, 0); 
    } 
  } 
 
  public void setonpagechangelistener(onpagechangelistener listener) { 
    monpagechangelistener = listener; 
  } 
 
  /** 
   * returns the number of pages that will be retained to either side of the 
   * current page in the view hierarchy in an idle state. defaults to 1. 
   * 
   * @return how many pages will be kept offscreen on either side 
   * @see #setoffscreenpagelimit(int) 
   */ 
  public int getoffscreenpagelimit() { 
    return moffscreenpagelimit; 
  } 
 
  /** 
   * set the number of pages that should be retained to either side of the 
   * current page in the view hierarchy in an idle state. pages beyond this 
   * limit will be recreated from the adapter when needed. 
   * 
   * <p>this is offered as an optimization. if you know in advance the number 
   * of pages you will need to support or have lazy-loading mechanisms in place 
   * on your pages, tweaking this setting can have benefits in perceived smoothness 
   * of paging animations and interaction. if you have a small number of pages (3-4) 
   * that you can keep active all at once, less time will be spent in layout for 
   * newly created view subtrees as the user pages back and forth.</p> 
   * 
   * <p>you should keep this limit low, especially if your pages have complex layouts. 
   * this setting defaults to 1.</p> 
   * 
   * @param limit how many pages will be kept offscreen in an idle state. 
   */ 
  public void setoffscreenpagelimit(int limit) { 
    if (limit < default_offscreen_pages) { 
      log.w(tag, "requested offscreen page limit " + limit + " too small; defaulting to " + 
          default_offscreen_pages); 
      limit = default_offscreen_pages; 
    } 
    if (limit != moffscreenpagelimit) { 
      moffscreenpagelimit = limit; 
      populate(); 
    } 
  } 
 
  /** 
   * set the margin between pages. 
   * 
   * @param marginpixels distance between adjacent pages in pixels 
   * @see #getpagemargin() 
   * @see #setpagemargindrawable(android.graphics.drawable.drawable) 
   * @see #setpagemargindrawable(int) 
   */ 
  public void setpagemargin(int marginpixels) { 
    final int oldmargin = mpagemargin; 
    mpagemargin = marginpixels; 
 
    final int width = getwidth(); 
    recomputescrollposition(width, width, marginpixels, oldmargin); 
 
    requestlayout(); 
  } 
 
  /** 
   * return the margin between pages. 
   * 
   * @return the size of the margin in pixels 
   */ 
  public int getpagemargin() { 
    return mpagemargin; 
  } 
 
  /** 
   * set a drawable that will be used to fill the margin between pages. 
   * 
   * @param d drawable to display between pages 
   */ 
  public void setpagemargindrawable(drawable d) { 
    mmargindrawable = d; 
    if (d != null) refreshdrawablestate(); 
    setwillnotdraw(d == null); 
    invalidate(); 
  } 
 
  /** 
   * set a drawable that will be used to fill the margin between pages. 
   * 
   * @param resid resource id of a drawable to display between pages 
   */ 
  public void setpagemargindrawable(int resid) { 
    setpagemargindrawable(getcontext().getresources().getdrawable(resid)); 
  } 
 
  @override 
  protected boolean verifydrawable(drawable who) { 
    return super.verifydrawable(who) || who == mmargindrawable; 
  } 
 
  @override 
  protected void drawablestatechanged() { 
    super.drawablestatechanged(); 
    final drawable d = mmargindrawable; 
    if (d != null && d.isstateful()) { 
      d.setstate(getdrawablestate()); 
    } 
  } 
 
  // we want the duration of the page snap animation to be influenced by the distance that 
  // the screen has to travel, however, we don't want this duration to be effected in a 
  // purely linear fashion. instead, we use this method to moderate the effect that the distance 
  // of travel has on the overall snap duration. 
  float distanceinfluenceforsnapduration(float f) { 
    f -= 0.5f; // center the values about 0. 
    f *= 0.3f * math.pi / 2.0f; 
    return (float) math.sin(f); 
  } 
 
  /** 
   * like {@link android.view.view#scrollby}, but scroll smoothly instead of immediately. 
   * 
   * @param x the number of pixels to scroll by on the x axis 
   * @param y the number of pixels to scroll by on the y axis 
   */ 
  void smoothscrollto(int x, int y) { 
    smoothscrollto(x, y, 0); 
  } 
 
  /** 
   * like {@link android.view.view#scrollby}, but scroll smoothly instead of immediately. 
   * 
   * @param x the number of pixels to scroll by on the x axis 
   * @param y the number of pixels to scroll by on the y axis 
   * @param velocity the velocity associated with a fling, if applicable. (0 otherwise) 
   */ 
  void smoothscrollto(int x, int y, int velocity) { 
    if (getchildcount() == 0) { 
      // nothing to do. 
      setscrollingcacheenabled(false); 
      return; 
    } 
    int sx = getscrollx(); 
    int sy = getscrolly(); 
    int dx = x - sx; 
    int dy = y - sy; 
    if (dx == 0 && dy == 0) { 
      completescroll(); 
      setscrollstate(scroll_state_idle); 
      return; 
    } 
 
    setscrollingcacheenabled(true); 
    mscrolling = true; 
    setscrollstate(scroll_state_settling); 
 
    final float pagedelta = (float) math.abs(dx) / (getwidth() + mpagemargin); 
    int duration = (int) (pagedelta * 100); 
 
    velocity = math.abs(velocity); 
    if (velocity > 0) { 
      duration += (duration / (velocity / mbaselineflingvelocity)) * mflingvelocityinfluence; 
    } else { 
      duration += 100; 
    } 
    duration = math.min(duration, max_settle_duration); 
 
    mscroller.startscroll(sx, sy, dx, dy, duration); 
    invalidate(); 
  } 
 
  void addnewitem(int position, int index) { 
    iteminfo ii = new iteminfo(); 
    ii.position = position; 
    ii.object = madapter.instantiateitem(this, position); 
    if (index < 0) { 
      mitems.add(ii); 
    } else { 
      mitems.add(index, ii); 
    } 
  } 
 
  void datasetchanged() { 
    // this method only gets called if our observer is attached, so madapter is non-null. 
 
    boolean needpopulate = mitems.size() < 3 && mitems.size() < madapter.getcount(); 
    int newcurritem = -1; 
 
    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--; 
        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)); 
        } 
        continue; 
      } 
 
      if (ii.position != newpos) { 
        if (ii.position == mcuritem) { 
          // our current item changed position. follow it. 
          newcurritem = newpos; 
        } 
 
        ii.position = newpos; 
        needpopulate = true; 
      } 
    } 
 
    collections.sort(mitems, comparator); 
 
    if (newcurritem >= 0) { 
      // todo this currently causes a jump. 
      setcurrentiteminternal(newcurritem, false, true); 
      needpopulate = true; 
    } 
    if (needpopulate) { 
      populate(); 
      requestlayout(); 
    } 
  } 
 
  void populate() { 
    if (madapter == null) { 
      return; 
    } 
 
    // bail now if we are waiting to populate. this is to hold off 
    // on creating views from the time the user releases their finger to 
    // fling to a new position until we have finished the scroll to 
    // that position, avoiding glitches from happening at that point. 
    if (mpopulatepending) { 
      if (debug) log.i(tag, "populate is pending, skipping for now..."); 
      return; 
    } 
 
    // also, don't populate until we are attached to a window. this is to 
    // avoid trying to populate before we have restored our view hierarchy 
    // state and conflicting with what is restored. 
    if (getwindowtoken() == null) { 
      return; 
    } 
 
    madapter.startupdate(this); 
 
    final int pagelimit = moffscreenpagelimit; 
    final int startpos = math.max(0, mcuritem - pagelimit); 
    final int n = madapter.getcount(); 
    final int endpos = math.min(n-1, mcuritem + pagelimit); 
 
    if (debug) log.v(tag, "populating: startpos=" + startpos + " endpos=" + endpos); 
 
    // add and remove pages in the existing list. 
    int lastpos = -1; 
    for (int i=0; i<mitems.size(); i++) { 
      iteminfo ii = mitems.get(i); 
      if ((ii.position < startpos || ii.position > endpos) && !ii.scrolling) { 
        if (debug) log.i(tag, "removing: " + ii.position + " @ " + i); 
        mitems.remove(i); 
        i--; 
        madapter.destroyitem(this, ii.position, ii.object); 
      } else if (lastpos < endpos && ii.position > startpos) { 
        // the next item is outside of our range, but we have a gap 
        // between it and the last item where we want to have a page 
        // shown. fill in the gap. 
        lastpos++; 
        if (lastpos < startpos) { 
          lastpos = startpos; 
        } 
        while (lastpos <= endpos && lastpos < ii.position) { 
          if (debug) log.i(tag, "inserting: " + lastpos + " @ " + i); 
          addnewitem(lastpos, i); 
          lastpos++; 
          i++; 
        } 
      } 
      lastpos = ii.position; 
    } 
 
    // add any new pages we need at the end. 
    lastpos = mitems.size() > 0 ? mitems.get(mitems.size()-1).position : -1; 
    if (lastpos < endpos) { 
      lastpos++; 
      lastpos = lastpos > startpos ? lastpos : startpos; 
      while (lastpos <= endpos) { 
        if (debug) log.i(tag, "appending: " + lastpos); 
        addnewitem(lastpos, -1); 
        lastpos++; 
      } 
    } 
 
    if (debug) { 
      log.i(tag, "current page list:"); 
      for (int i=0; i<mitems.size(); i++) { 
        log.i(tag, "#" + i + ": page " + mitems.get(i).position); 
      } 
    } 
 
    iteminfo curitem = null; 
    for (int i=0; i<mitems.size(); i++) { 
      if (mitems.get(i).position == mcuritem) { 
        curitem = mitems.get(i); 
        break; 
      } 
    } 
    madapter.setprimaryitem(this, mcuritem, curitem != null ? curitem.object : null); 
 
    madapter.finishupdate(this); 
 
    if (hasfocus()) { 
      view currentfocused = findfocus(); 
      iteminfo ii = currentfocused != null ? infoforanychild(currentfocused) : null; 
      if (ii == null || ii.position != mcuritem) { 
        for (int i=0; i<getchildcount(); i++) { 
          view child = getchildat(i); 
          ii = infoforchild(child); 
          if (ii != null && ii.position == mcuritem) { 
            if (child.requestfocus(focus_forward)) { 
              break; 
            } 
          } 
        } 
      } 
    } 
  } 
 
  public static class savedstate extends basesavedstate { 
    int position; 
    parcelable adapterstate; 
    classloader loader; 
 
    public savedstate(parcelable superstate) { 
      super(superstate); 
    } 
 
    @override 
    public void writetoparcel(parcel out, int flags) { 
      super.writetoparcel(out, flags); 
      out.writeint(position); 
      out.writeparcelable(adapterstate, flags); 
    } 
 
    @override 
    public string tostring() { 
      return "fragmentpager.savedstate{" 
          + integer.tohexstring(system.identityhashcode(this)) 
          + " position=" + position + "}"; 
    } 
 
    public static final creator<savedstate> creator 
        = parcelablecompat.newcreator(new parcelablecompatcreatorcallbacks<savedstate>() { 
          @override 
          public savedstate createfromparcel(parcel in, classloader loader) { 
            return new savedstate(in, loader); 
          } 
          @override 
          public savedstate[] newarray(int size) { 
            return new savedstate[size]; 
          } 
        }); 
 
    savedstate(parcel in, classloader loader) { 
      super(in); 
      if (loader == null) { 
        loader = getclass().getclassloader(); 
      } 
      position = in.readint(); 
      adapterstate = in.readparcelable(loader); 
      this.loader = loader; 
    } 
  } 
 
  @override 
  public parcelable onsaveinstancestate() { 
    parcelable superstate = super.onsaveinstancestate(); 
    savedstate ss = new savedstate(superstate); 
    ss.position = mcuritem; 
    if (madapter != null) { 
      ss.adapterstate = madapter.savestate(); 
    } 
    return ss; 
  } 
 
  @override 
  public void onrestoreinstancestate(parcelable state) { 
    if (!(state instanceof savedstate)) { 
      super.onrestoreinstancestate(state); 
      return; 
    } 
 
    savedstate ss = (savedstate)state; 
    super.onrestoreinstancestate(ss.getsuperstate()); 
 
    if (madapter != null) { 
      madapter.restorestate(ss.adapterstate, ss.loader); 
      setcurrentiteminternal(ss.position, false, true); 
    } else { 
      mrestoredcuritem = ss.position; 
      mrestoredadapterstate = ss.adapterstate; 
      mrestoredclassloader = ss.loader; 
    } 
  } 
 
  @override 
  public void addview(view child, int index, layoutparams params) { 
    if (minlayout) { 
      addviewinlayout(child, index, params); 
      child.measure(mchildwidthmeasurespec, mchildheightmeasurespec); 
    } else { 
      super.addview(child, index, params); 
    } 
 
    if (use_cache) { 
      if (child.getvisibility() != gone) { 
        child.setdrawingcacheenabled(mscrollingcacheenabled); 
      } else { 
        child.setdrawingcacheenabled(false); 
      } 
    } 
  } 
 
  iteminfo infoforchild(view child) { 
    for (int i=0; i<mitems.size(); i++) { 
      iteminfo ii = mitems.get(i); 
      if (madapter.isviewfromobject(child, ii.object)) { 
        return ii; 
      } 
    } 
    return null; 
  } 
 
  iteminfo infoforanychild(view child) { 
    viewparent parent; 
    while ((parent=child.getparent()) != this) { 
      if (parent == null || !(parent instanceof view)) { 
        return null; 
      } 
      child = (view)parent; 
    } 
    return infoforchild(child); 
  } 
 
  @override 
  protected void onattachedtowindow() { 
    super.onattachedtowindow(); 
    mfirstlayout = true; 
  } 
 
  @override 
  protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { 
    // for simple implementation, or internal size is always 0. 
    // we depend on the container to specify the layout size of 
    // our view. we can't really know what it is since we will be 
    // adding and removing different arbitrary views and do not 
    // want the layout to change as this happens. 
    setmeasureddimension(getdefaultsize(0, widthmeasurespec), 
        getdefaultsize(0, heightmeasurespec)); 
 
    // children are just made to fill our space. 
    mchildwidthmeasurespec = measurespec.makemeasurespec(getmeasuredwidth() - 
        getpaddingleft() - getpaddingright(), measurespec.exactly); 
    mchildheightmeasurespec = measurespec.makemeasurespec(getmeasuredheight() - 
        getpaddingtop() - getpaddingbottom(), measurespec.exactly); 
 
    // make sure we have created all fragments that we need to have shown. 
    minlayout = true; 
    populate(); 
    minlayout = false; 
 
    // make sure all children have been properly measured. 
    final int size = getchildcount(); 
    for (int i = 0; i < size; ++i) { 
      final view child = getchildat(i); 
      if (child.getvisibility() != gone) { 
        if (debug) log.v(tag, "measuring #" + i + " " + child 
        + ": " + mchildwidthmeasurespec); 
        child.measure(mchildwidthmeasurespec, mchildheightmeasurespec); 
      } 
    } 
  } 
 
  @override 
  protected void onsizechanged(int w, int h, int oldw, int oldh) { 
    super.onsizechanged(w, h, oldw, oldh); 
 
    // make sure scroll position is set correctly. 
    if (w != oldw) { 
      recomputescrollposition(w, oldw, mpagemargin, mpagemargin); 
    } 
  } 
 
  private void recomputescrollposition(int width, int oldwidth, int margin, int oldmargin) { 
    final int widthwithmargin = width + margin; 
    if (oldwidth > 0) { 
      final int oldscrollpos = getscrollx(); 
      final int oldwwm = oldwidth + oldmargin; 
      final int oldscrollitem = oldscrollpos / oldwwm; 
      final float scrolloffset = (float) (oldscrollpos % oldwwm) / oldwwm; 
      final int scrollpos = (int) ((oldscrollitem + scrolloffset) * widthwithmargin); 
      scrollto(scrollpos, getscrolly()); 
      if (!mscroller.isfinished()) { 
        // we now return to your regularly scheduled scroll, already in progress. 
        final int newduration = mscroller.getduration() - mscroller.timepassed(); 
        mscroller.startscroll(scrollpos, 0, mcuritem * widthwithmargin, 0, newduration); 
      } 
    } else { 
      int scrollpos = mcuritem * widthwithmargin; 
      if (scrollpos != getscrollx()) { 
        completescroll(); 
        scrollto(scrollpos, getscrolly()); 
      } 
    } 
  } 
 
  @override 
  protected void onlayout(boolean changed, int l, int t, int r, int b) { 
    minlayout = true; 
    populate(); 
    minlayout = false; 
 
    final int count = getchildcount(); 
    final int width = r-l; 
 
    for (int i = 0; i < count; i++) { 
      view child = getchildat(i); 
      iteminfo ii; 
      if (child.getvisibility() != gone && (ii=infoforchild(child)) != null) { 
        int loff = (width + mpagemargin) * ii.position; 
        int childleft = getpaddingleft() + loff; 
        int childtop = getpaddingtop(); 
        if (debug) log.v(tag, "positioning #" + i + " " + child + " f=" + ii.object 
        + ":" + childleft + "," + childtop + " " + child.getmeasuredwidth() 
        + "x" + child.getmeasuredheight()); 
        child.layout(childleft, childtop, 
            childleft + child.getmeasuredwidth(), 
            childtop + child.getmeasuredheight()); 
      } 
    } 
    mfirstlayout = false; 
  } 
 
  @override 
  public void computescroll() { 
    if (debug) log.i(tag, "computescroll: finished=" + mscroller.isfinished()); 
    if (!mscroller.isfinished()) { 
      if (mscroller.computescrolloffset()) { 
        if (debug) log.i(tag, "computescroll: still scrolling"); 
        int oldx = getscrollx(); 
        int oldy = getscrolly(); 
        int x = mscroller.getcurrx(); 
        int y = mscroller.getcurry(); 
 
        if (oldx != x || oldy != y) { 
          scrollto(x, y); 
        } 
 
        if (monpagechangelistener != null) { 
          final int widthwithmargin = getwidth() + mpagemargin; 
          final int position = x / widthwithmargin; 
          final int offsetpixels = x % widthwithmargin; 
          final float offset = (float) offsetpixels / widthwithmargin; 
          monpagechangelistener.onpagescrolled(position, offset, offsetpixels); 
        } 
 
        // keep on drawing until the animation has finished. 
        invalidate(); 
        return; 
      } 
    } 
 
    // done with scroll, clean up state. 
    completescroll(); 
  } 
 
  private void completescroll() { 
    boolean needpopulate = mscrolling; 
    if (needpopulate) { 
      // done with scroll, no longer want to cache view drawing. 
      setscrollingcacheenabled(false); 
      mscroller.abortanimation(); 
      int oldx = getscrollx(); 
      int oldy = getscrolly(); 
      int x = mscroller.getcurrx(); 
      int y = mscroller.getcurry(); 
      if (oldx != x || oldy != y) { 
        scrollto(x, y); 
      } 
      setscrollstate(scroll_state_idle); 
    } 
    mpopulatepending = false; 
    mscrolling = false; 
    for (int i=0; i<mitems.size(); i++) { 
      iteminfo ii = mitems.get(i); 
      if (ii.scrolling) { 
        needpopulate = true; 
        ii.scrolling = false; 
      } 
    } 
    if (needpopulate) { 
      populate(); 
    } 
  } 
 
  @override 
  public boolean onintercepttouchevent(motionevent ev) { 
    /* 
     * this method just determines whether we want to intercept the motion. 
     * if we return true, onmotionevent will be called and we do the actual 
     * scrolling there. 
     */ 
 
    final int action = ev.getaction() & motioneventcompat.action_mask; 
 
    // always take care of the touch gesture being complete. 
    if (action == motionevent.action_cancel || action == motionevent.action_up) { 
      // release the drag. 
      if (debug) log.v(tag, "intercept done!"); 
      misbeingdragged = false; 
      misunabletodrag = false; 
      mactivepointerid = invalid_pointer; 
      return false; 
    } 
 
    // nothing more to do here if we have decided whether or not we 
    // are dragging. 
    if (action != motionevent.action_down) { 
      if (misbeingdragged) { 
        if (debug) log.v(tag, "intercept returning true!"); 
        return true; 
      } 
      if (misunabletodrag) { 
        if (debug) log.v(tag, "intercept returning false!"); 
        return false; 
      } 
    } 
 
    switch (action) { 
      case motionevent.action_move: { 
        /* 
         * misbeingdragged == false, otherwise the shortcut would have caught it. check 
         * whether the user has moved far enough from his original down touch. 
         */ 
 
        /* 
        * locally do absolute value. mlastmotiony is set to the y value 
        * of the down event. 
        */ 
        final int activepointerid = mactivepointerid; 
        if (activepointerid == invalid_pointer) { 
          // if we don't have a valid id, the touch down wasn't on content. 
          break; 
        } 
 
        final int pointerindex = motioneventcompat.findpointerindex(ev, activepointerid); 
        final float x = motioneventcompat.getx(ev, pointerindex); 
        final float dx = x - mlastmotionx; 
        final float xdiff = math.abs(dx); 
        final float y = motioneventcompat.gety(ev, pointerindex); 
        final float ydiff = math.abs(y - mlastmotiony); 
        final int scrollx = getscrollx(); 
        final boolean atedge = (dx > 0 && scrollx == 0) || (dx < 0 && madapter != null && 
            scrollx >= (madapter.getcount() - 1) * getwidth() - 1); 
        if (debug) log.v(tag, "moved x to " + x + "," + y + " diff=" + xdiff + "," + ydiff); 
 
        if (canscroll(this, false, (int) dx, (int) x, (int) y)) { 
          // nested view has scrollable area under this point. let it be handled there. 
          minitialmotionx = mlastmotionx = x; 
          mlastmotiony = y; 
          return false; 
        } 
        if (xdiff > mtouchslop && xdiff > ydiff) { 
          if (debug) log.v(tag, "starting drag!"); 
          misbeingdragged = true; 
          setscrollstate(scroll_state_dragging); 
          mlastmotionx = x; 
          setscrollingcacheenabled(true); 
        } else { 
          if (ydiff > mtouchslop) { 
            // the finger has moved enough in the vertical 
            // direction to be counted as a drag... abort 
            // any attempt to drag horizontally, to work correctly 
            // with children that have scrolling containers. 
            if (debug) log.v(tag, "starting unable to drag!"); 
            misunabletodrag = true; 
          } 
        } 
        break; 
      } 
 
      case motionevent.action_down: { 
        /* 
         * remember location of down touch. 
         * action_down always refers to pointer index 0. 
         */ 
        mlastmotionx = minitialmotionx = ev.getx(); 
        mlastmotiony = ev.gety(); 
        mactivepointerid = motioneventcompat.getpointerid(ev, 0); 
 
        if (mscrollstate == scroll_state_settling) { 
          // let the user 'catch' the pager as it animates. 
          misbeingdragged = true; 
          misunabletodrag = false; 
          setscrollstate(scroll_state_dragging); 
        } else { 
          completescroll(); 
          misbeingdragged = false; 
          misunabletodrag = false; 
        } 
 
        if (debug) log.v(tag, "down at " + mlastmotionx + "," + mlastmotiony 
            + " misbeingdragged=" + misbeingdragged 
            + "misunabletodrag=" + misunabletodrag); 
        break; 
      } 
 
      case motioneventcompat.action_pointer_up: 
        onsecondarypointerup(ev); 
        break; 
    } 
 
    /* 
    * the only time we want to intercept motion events is if we are in the 
    * drag mode. 
    */ 
    return misbeingdragged; 
  } 
 
  @override 
  public boolean ontouchevent(motionevent ev) { 
    if (mfakedragging) { 
      // a fake drag is in progress already, ignore this real one 
      // but still eat the touch events. 
      // (it is likely that the user is multi-touching the screen.) 
      return true; 
    } 
 
    if (ev.getaction() == motionevent.action_down && ev.getedgeflags() != 0) { 
      // don't handle edge touches immediately -- they may actually belong to one of our 
      // descendants. 
      return false; 
    } 
 
    if (madapter == null || madapter.getcount() == 0) { 
      // nothing to present or scroll; nothing to touch. 
      return false; 
    } 
 
    if (mvelocitytracker == null) { 
      mvelocitytracker = velocitytracker.obtain(); 
    } 
    mvelocitytracker.addmovement(ev); 
 
    final int action = ev.getaction(); 
    boolean needsinvalidate = false; 
 
    switch (action & motioneventcompat.action_mask) { 
      case motionevent.action_down: { 
        /* 
         * if being flinged and user touches, stop the fling. isfinished 
         * will be false if being flinged. 
         */ 
        completescroll(); 
 
        // remember where the motion event started 
        mlastmotionx = minitialmotionx = ev.getx(); 
        mactivepointerid = motioneventcompat.getpointerid(ev, 0); 
        break; 
      } 
      case motionevent.action_move: 
        if (!misbeingdragged) { 
          final int pointerindex = motioneventcompat.findpointerindex(ev, mactivepointerid); 
          final float x = motioneventcompat.getx(ev, pointerindex); 
          final float xdiff = math.abs(x - mlastmotionx); 
          final float y = motioneventcompat.gety(ev, pointerindex); 
          final float ydiff = math.abs(y - mlastmotiony); 
          if (debug) log.v(tag, "moved x to " + x + "," + y + " diff=" + xdiff + "," + ydiff); 
          if (xdiff > mtouchslop && xdiff > ydiff) { 
            if (debug) log.v(tag, "starting drag!"); 
            misbeingdragged = true; 
            mlastmotionx = x; 
            setscrollstate(scroll_state_dragging); 
            setscrollingcacheenabled(true); 
          } 
        } 
        if (misbeingdragged) { 
          // scroll to follow the motion event 
          final int activepointerindex = motioneventcompat.findpointerindex( 
              ev, mactivepointerid); 
          final float x = motioneventcompat.getx(ev, activepointerindex); 
          final float deltax = mlastmotionx - x; 
          mlastmotionx = x; 
          float oldscrollx = getscrollx(); 
          float scrollx = oldscrollx + deltax; 
          final int width = getwidth(); 
          final int widthwithmargin = width + mpagemargin; 
 
          final int lastitemindex = madapter.getcount() - 1; 
          final float leftbound = math.max(0, (mcuritem - 1) * widthwithmargin); 
          final float rightbound = 
              math.min(mcuritem + 1, lastitemindex) * widthwithmargin; 
          if (scrollx < leftbound) { 
            if (leftbound == 0) { 
              float over = -scrollx; 
              needsinvalidate = mleftedge.onpull(over / width); 
            } 
            scrollx = leftbound; 
          } else if (scrollx > rightbound) { 
            if (rightbound == lastitemindex * widthwithmargin) { 
              float over = scrollx - rightbound; 
              needsinvalidate = mrightedge.onpull(over / width); 
            } 
            scrollx = rightbound; 
          } 
          // don't lose the rounded component 
          mlastmotionx += scrollx - (int) scrollx; 
          scrollto((int) scrollx, getscrolly()); 
          if (monpagechangelistener != null) { 
            final int position = (int) scrollx / widthwithmargin; 
            final int positionoffsetpixels = (int) scrollx % widthwithmargin; 
            final float positionoffset = (float) positionoffsetpixels / widthwithmargin; 
            monpagechangelistener.onpagescrolled(position, positionoffset, 
                positionoffsetpixels); 
          } 
        } 
        break; 
      case motionevent.action_up: 
        if (misbeingdragged) { 
          final velocitytracker velocitytracker = mvelocitytracker; 
          velocitytracker.computecurrentvelocity(1000, mmaximumvelocity); 
          int initialvelocity = (int) velocitytrackercompat.getxvelocity( 
              velocitytracker, mactivepointerid); 
          mpopulatepending = true; 
          final int widthwithmargin = getwidth() + mpagemargin; 
          final int scrollx = getscrollx(); 
          final int currentpage = scrollx / widthwithmargin; 
          int nextpage = initialvelocity > 0 ? currentpage : currentpage + 1; 
          setcurrentiteminternal(nextpage, true, true, initialvelocity); 
 
          mactivepointerid = invalid_pointer; 
          enddrag(); 
          needsinvalidate = mleftedge.onrelease() | mrightedge.onrelease(); 
        } 
        break; 
      case motionevent.action_cancel: 
        if (misbeingdragged) { 
          setcurrentiteminternal(mcuritem, true, true); 
          mactivepointerid = invalid_pointer; 
          enddrag(); 
          needsinvalidate = mleftedge.onrelease() | mrightedge.onrelease(); 
        } 
        break; 
      case motioneventcompat.action_pointer_down: { 
        final int index = motioneventcompat.getactionindex(ev); 
        final float x = motioneventcompat.getx(ev, index); 
        mlastmotionx = x; 
        mactivepointerid = motioneventcompat.getpointerid(ev, index); 
        break; 
      } 
      case motioneventcompat.action_pointer_up: 
        onsecondarypointerup(ev); 
        mlastmotionx = motioneventcompat.getx(ev, 
            motioneventcompat.findpointerindex(ev, mactivepointerid)); 
        break; 
    } 
    if (needsinvalidate) { 
      invalidate(); 
    } 
    return true; 
  } 
 
  @override 
  public void draw(canvas canvas) { 
    super.draw(canvas); 
    boolean needsinvalidate = false; 
 
    final int overscrollmode = viewcompat.getoverscrollmode(this); 
    if (overscrollmode == viewcompat.over_scroll_always || 
        (overscrollmode == viewcompat.over_scroll_if_content_scrolls && 
            madapter != null && madapter.getcount() > 1)) { 
      if (!mleftedge.isfinished()) { 
        final int restorecount = canvas.save(); 
        final int height = getheight() - getpaddingtop() - getpaddingbottom(); 
 
        canvas.rotate(270); 
        canvas.translate(-height + getpaddingtop(), 0); 
        mleftedge.setsize(height, getwidth()); 
        needsinvalidate |= mleftedge.draw(canvas); 
        canvas.restoretocount(restorecount); 
      } 
      if (!mrightedge.isfinished()) { 
        final int restorecount = canvas.save(); 
        final int width = getwidth(); 
        final int height = getheight() - getpaddingtop() - getpaddingbottom(); 
        final int itemcount = madapter != null ? madapter.getcount() : 1; 
 
        canvas.rotate(90); 
        canvas.translate(-getpaddingtop(), 
            -itemcount * (width + mpagemargin) + mpagemargin); 
        mrightedge.setsize(height, width); 
        needsinvalidate |= mrightedge.draw(canvas); 
        canvas.restoretocount(restorecount); 
      } 
    } else { 
      mleftedge.finish(); 
      mrightedge.finish(); 
    } 
 
    if (needsinvalidate) { 
      // keep animating 
      invalidate(); 
    } 
  } 
 
  @override 
  protected void ondraw(canvas canvas) { 
    super.ondraw(canvas); 
 
    // draw the margin drawable if needed. 
    if (mpagemargin > 0 && mmargindrawable != null) { 
      final int scrollx = getscrollx(); 
      final int width = getwidth(); 
      final int offset = scrollx % (width + mpagemargin); 
      if (offset != 0) { 
        // pages fit completely when settled; we only need to draw when in between 
        final int left = scrollx - offset + width; 
        mmargindrawable.setbounds(left, 0, left + mpagemargin, getheight()); 
        mmargindrawable.draw(canvas); 
      } 
    } 
  } 
 
  /** 
   * start a fake drag of the pager. 
   * 
   * <p>a fake drag can be useful if you want to synchronize the motion of the viewpager 
   * with the touch scrolling of another view, while still letting the viewpager 
   * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.) 
   * call {@link #fakedragby(float)} to simulate the actual drag motion. call 
   * {@link #endfakedrag()} to complete the fake drag and fling as necessary. 
   * 
   * <p>during a fake drag the viewpager will ignore all touch events. if a real drag 
   * is already in progress, this method will return false. 
   * 
   * @return true if the fake drag began successfully, false if it could not be started. 
   * 
   * @see #fakedragby(float) 
   * @see #endfakedrag() 
   */ 
  public boolean beginfakedrag() { 
    if (misbeingdragged) { 
      return false; 
    } 
    mfakedragging = true; 
    setscrollstate(scroll_state_dragging); 
    minitialmotionx = mlastmotionx = 0; 
    if (mvelocitytracker == null) { 
      mvelocitytracker = velocitytracker.obtain(); 
    } else { 
      mvelocitytracker.clear(); 
    } 
    final long time = systemclock.uptimemillis(); 
    final motionevent ev = motionevent.obtain(time, time, motionevent.action_down, 0, 0, 0); 
    mvelocitytracker.addmovement(ev); 
    ev.recycle(); 
    mfakedragbegintime = time; 
    return true; 
  } 
 
  /** 
   * end a fake drag of the pager. 
   * 
   * @see #beginfakedrag() 
   * @see #fakedragby(float) 
   */ 
  public void endfakedrag() { 
    if (!mfakedragging) { 
      throw new illegalstateexception("no fake drag in progress. call beginfakedrag first."); 
    } 
 
    final velocitytracker velocitytracker = mvelocitytracker; 
    velocitytracker.computecurrentvelocity(1000, mmaximumvelocity); 
    int initialvelocity = (int)velocitytrackercompat.getyvelocity( 
        velocitytracker, mactivepointerid); 
    mpopulatepending = true; 
    if ((math.abs(initialvelocity) > mminimumvelocity) 
        || math.abs(minitialmotionx-mlastmotionx) >= (getwidth()/3)) { 
      if (mlastmotionx > minitialmotionx) { 
        setcurrentiteminternal(mcuritem-1, true, true); 
      } else { 
        setcurrentiteminternal(mcuritem+1, true, true); 
      } 
    } else { 
      setcurrentiteminternal(mcuritem, true, true); 
    } 
    enddrag(); 
 
    mfakedragging = false; 
  } 
 
  /** 
   * fake drag by an offset in pixels. you must have called {@link #beginfakedrag()} first. 
   * 
   * @param xoffset offset in pixels to drag by. 
   * @see #beginfakedrag() 
   * @see #endfakedrag() 
   */ 
  public void fakedragby(float xoffset) { 
    if (!mfakedragging) { 
      throw new illegalstateexception("no fake drag in progress. call beginfakedrag first."); 
    } 
 
    mlastmotionx += xoffset; 
    float scrollx = getscrollx() - xoffset; 
    final int width = getwidth(); 
    final int widthwithmargin = width + mpagemargin; 
 
    final float leftbound = math.max(0, (mcuritem - 1) * widthwithmargin); 
    final float rightbound = 
        math.min(mcuritem + 1, madapter.getcount() - 1) * widthwithmargin; 
    if (scrollx < leftbound) { 
      scrollx = leftbound; 
    } else if (scrollx > rightbound) { 
      scrollx = rightbound; 
    } 
    // don't lose the rounded component 
    mlastmotionx += scrollx - (int) scrollx; 
    scrollto((int) scrollx, getscrolly()); 
    if (monpagechangelistener != null) { 
      final int position = (int) scrollx / widthwithmargin; 
      final int positionoffsetpixels = (int) scrollx % widthwithmargin; 
      final float positionoffset = (float) positionoffsetpixels / widthwithmargin; 
      monpagechangelistener.onpagescrolled(position, positionoffset, 
          positionoffsetpixels); 
    } 
 
    // synthesize an event for the velocitytracker. 
    final long time = systemclock.uptimemillis(); 
    final motionevent ev = motionevent.obtain(mfakedragbegintime, time, motionevent.action_move, 
        mlastmotionx, 0, 0); 
    mvelocitytracker.addmovement(ev); 
    ev.recycle(); 
  } 
 
  /** 
   * returns true if a fake drag is in progress. 
   * 
   * @return true if currently in a fake drag, false otherwise. 
   * 
   * @see #beginfakedrag() 
   * @see #fakedragby(float) 
   * @see #endfakedrag() 
   */ 
  public boolean isfakedragging() { 
    return mfakedragging; 
  } 
 
  private void onsecondarypointerup(motionevent ev) { 
    final int pointerindex = motioneventcompat.getactionindex(ev); 
    final int pointerid = motioneventcompat.getpointerid(ev, pointerindex); 
    if (pointerid == mactivepointerid) { 
      // this was our active pointer going up. choose a new 
      // active pointer and adjust accordingly. 
      final int newpointerindex = pointerindex == 0 ? 1 : 0; 
      mlastmotionx = motioneventcompat.getx(ev, newpointerindex); 
      mactivepointerid = motioneventcompat.getpointerid(ev, newpointerindex); 
      if (mvelocitytracker != null) { 
        mvelocitytracker.clear(); 
      } 
    } 
  } 
 
  private void enddrag() { 
    misbeingdragged = false; 
    misunabletodrag = false; 
 
    if (mvelocitytracker != null) { 
      mvelocitytracker.recycle(); 
      mvelocitytracker = null; 
    } 
  } 
 
  private void setscrollingcacheenabled(boolean enabled) { 
    if (mscrollingcacheenabled != enabled) { 
      mscrollingcacheenabled = enabled; 
      if (use_cache) { 
        final int size = getchildcount(); 
        for (int i = 0; i < size; ++i) { 
          final view child = getchildat(i); 
          if (child.getvisibility() != gone) { 
            child.setdrawingcacheenabled(enabled); 
          } 
        } 
      } 
    } 
  } 
 
  /** 
   * tests scrollability within child views of v given a delta of dx. 
   * 
   * @param v view to test for horizontal scrollability 
   * @param checkv whether the view v passed should itself be checked for scrollability (true), 
   *        or just its children (false). 
   * @param dx delta scrolled in pixels 
   * @param x x coordinate of the active touch point 
   * @param y y coordinate of the active touch point 
   * @return true if child views of v can be scrolled by delta of dx. 
   */ 
  protected boolean canscroll(view v, boolean checkv, int dx, int x, int y) { 
    if (v instanceof viewgroup) { 
      final viewgroup group = (viewgroup) v; 
      final int scrollx = v.getscrollx(); 
      final int scrolly = v.getscrolly(); 
      final int count = group.getchildcount(); 
      // count backwards - let topmost views consume scroll distance first. 
      for (int i = count - 1; i >= 0; i--) { 
        // todo: add versioned support here for transformed views. 
        // this will not work for transformed views in honeycomb+ 
        final view child = group.getchildat(i); 
        if (x + scrollx >= child.getleft() && x + scrollx < child.getright() && 
            y + scrolly >= child.gettop() && y + scrolly < child.getbottom() && 
            canscroll(child, true, dx, x + scrollx - child.getleft(), 
                y + scrolly - child.gettop())) { 
          return true; 
        } 
      } 
    } 
 
    return checkv && viewcompat.canscrollhorizontally(v, -dx); 
  } 
 
  @override 
  public boolean dispatchkeyevent(keyevent event) { 
    // let the focused view and/or our descendants get the key first 
    return super.dispatchkeyevent(event) || executekeyevent(event); 
  } 
 
  /** 
   * you can call this function yourself to have the scroll view perform 
   * scrolling from a key event, just as if the event had been dispatched to 
   * it by the view hierarchy. 
   * 
   * @param event the key event to execute. 
   * @return return true if the event was handled, else false. 
   */ 
  public boolean executekeyevent(keyevent event) { 
    boolean handled = false; 
    if (event.getaction() == keyevent.action_down) { 
      switch (event.getkeycode()) { 
        case keyevent.keycode_dpad_left: 
          handled = arrowscroll(focus_left); 
          break; 
        case keyevent.keycode_dpad_right: 
          handled = arrowscroll(focus_right); 
          break; 
        case keyevent.keycode_tab: 
          if (keyeventcompat.hasnomodifiers(event)) { 
            handled = arrowscroll(focus_forward); 
          } else if (keyeventcompat.hasmodifiers(event, keyevent.meta_shift_on)) { 
            handled = arrowscroll(focus_backward); 
          } 
          break; 
      } 
    } 
    return handled; 
  } 
 
  public boolean arrowscroll(int direction) { 
    view currentfocused = findfocus(); 
    if (currentfocused == this) currentfocused = null; 
 
    boolean handled = false; 
 
    view nextfocused = focusfinder.getinstance().findnextfocus(this, currentfocused, 
        direction); 
    if (nextfocused != null && nextfocused != currentfocused) { 
      if (direction == view.focus_left) { 
        // if there is nothing to the left, or this is causing us to 
        // jump to the right, then what we really want to do is page left. 
        if (currentfocused != null && nextfocused.getleft() >= currentfocused.getleft()) { 
          handled = pageleft(); 
        } else { 
          handled = nextfocused.requestfocus(); 
        } 
      } else if (direction == view.focus_right) { 
        // if there is nothing to the right, or this is causing us to 
        // jump to the left, then what we really want to do is page right. 
        if (currentfocused != null && nextfocused.getleft() <= currentfocused.getleft()) { 
          handled = pageright(); 
        } else { 
          handled = nextfocused.requestfocus(); 
        } 
      } 
    } else if (direction == focus_left || direction == focus_backward) { 
      // trying to move left and nothing there; try to page. 
      handled = pageleft(); 
    } else if (direction == focus_right || direction == focus_forward) { 
      // trying to move right and nothing there; try to page. 
      handled = pageright(); 
    } 
    if (handled) { 
      playsoundeffect(soundeffectconstants.getcontantforfocusdirection(direction)); 
    } 
    return handled; 
  } 
 
  boolean pageleft() { 
    if (mcuritem > 0) { 
      setcurrentitem(mcuritem-1, true); 
      return true; 
    } 
    return false; 
  } 
 
  boolean pageright() { 
    if (madapter != null && mcuritem < (madapter.getcount()-1)) { 
      setcurrentitem(mcuritem+1, true); 
      return true; 
    } 
    return false; 
  } 
 
  /** 
   * we only want the current page that is being shown to be focusable. 
   */ 
  @override 
  public void addfocusables(arraylist<view> views, int direction, int focusablemode) { 
    final int focusablecount = views.size(); 
 
    final int descendantfocusability = getdescendantfocusability(); 
 
    if (descendantfocusability != focus_block_descendants) { 
      for (int i = 0; i < getchildcount(); i++) { 
        final view child = getchildat(i); 
        if (child.getvisibility() == visible) { 
          iteminfo ii = infoforchild(child); 
          if (ii != null && ii.position == mcuritem) { 
            child.addfocusables(views, direction, focusablemode); 
          } 
        } 
      } 
    } 
 
    // we add ourselves (if focusable) in all cases except for when we are 
    // focus_after_descendants and there are some descendants focusable. this is 
    // to avoid the focus search finding layouts when a more precise search 
    // among the focusable children would be more interesting. 
    if ( 
      descendantfocusability != focus_after_descendants || 
        // no focusable descendants 
        (focusablecount == views.size())) { 
      // note that we can't call the superclass here, because it will 
      // add all views in. so we need to do the same thing view does. 
      if (!isfocusable()) { 
        return; 
      } 
      if ((focusablemode & focusables_touch_mode) == focusables_touch_mode && 
          isintouchmode() && !isfocusableintouchmode()) { 
        return; 
      } 
      if (views != null) { 
        views.add(this); 
      } 
    } 
  } 
 
  /** 
   * we only want the current page that is being shown to be touchable. 
   */ 
  @override 
  public void addtouchables(arraylist<view> views) { 
    // note that we don't call super.addtouchables(), which means that 
    // we don't call view.addtouchables(). this is okay because a viewpager 
    // is itself not touchable. 
    for (int i = 0; i < getchildcount(); i++) { 
      final view child = getchildat(i); 
      if (child.getvisibility() == visible) { 
        iteminfo ii = infoforchild(child); 
        if (ii != null && ii.position == mcuritem) { 
          child.addtouchables(views); 
        } 
      } 
    } 
  } 
 
  /** 
   * we only want the current page that is being shown to be focusable. 
   */ 
  @override 
  protected boolean onrequestfocusindescendants(int direction, 
      rect previouslyfocusedrect) { 
    int index; 
    int increment; 
    int end; 
    int count = getchildcount(); 
    if ((direction & focus_forward) != 0) { 
      index = 0; 
      increment = 1; 
      end = count; 
    } else { 
      index = count - 1; 
      increment = -1; 
      end = -1; 
    } 
    for (int i = index; i != end; i += increment) { 
      view child = getchildat(i); 
      if (child.getvisibility() == visible) { 
        iteminfo ii = infoforchild(child); 
        if (ii != null && ii.position == mcuritem) { 
          if (child.requestfocus(direction, previouslyfocusedrect)) { 
            return true; 
          } 
        } 
      } 
    } 
    return false; 
  } 
 
  @override 
  public boolean dispatchpopulateaccessibilityevent(accessibilityevent event) { 
    // viewpagers should only report accessibility info for the current page, 
    // otherwise things get very confusing. 
 
    // todo: should this note something about the paging container? 
 
    final int childcount = getchildcount(); 
    for (int i = 0; i < childcount; i++) { 
      final view child = getchildat(i); 
      if (child.getvisibility() == visible) { 
        final iteminfo ii = infoforchild(child); 
        if (ii != null && ii.position == mcuritem && 
            child.dispatchpopulateaccessibilityevent(event)) { 
          return true; 
        } 
      } 
    } 
 
    return false; 
  } 
 
  private class pagerobserver extends datasetobserver { 
 
    @override 
    public void onchanged() { 
      datasetchanged(); 
    } 
 
    @override 
    public void oninvalidated() { 
      datasetchanged(); 
    } 
  } 
} 

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网