重新写一个
LazyViewPager,就可以取消预加载了。
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
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;
/**
* 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 LazyViewPager
extends ViewGroup {
private static final String
TAG =
"LazyViewPager";
private static final boolean DEBUG =
false;
private static final boolean USE_CACHE =
false;
private static final int DEFAULT_OFFSCREEN_PAGES =
0;
// 默认的加载页面,ViewPager是1个,所以会加载两个Fragment
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 com.bxg.news.view.LazyViewPager.OnPageChangeListener}
* interface with stub implementations of each method. Extend this if you do
* not intend to override every method of
* {@link com.bxg.news.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 LazyViewPager(Context context) {
super(context);
initViewPager();
}
public LazyViewPager(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();
}
}
}