您的位置:首页 > 其它

ViewPager实现是否预加载,解决Tablayout匹配使用

2016-09-27 17:41 549 查看
Tablayout可以更改下划线的长度和宽度,可根据字数的不同限定宽度,颜色,粗细都可以根据开发者的需要进行更改/**

*

* @author zhangmanyuan

* @date 2016/9/19

*/

public class SlidingTabView extends HorizontalScrollView {

public interface IconTabProvider {

public int getPageIconResId(int position);

}

// @formatter:off

private static final int[] ATTRS = new int[] {

android.R.attr.textSize, android.R.attr.textColor

};

// @formatter:on

private LinearLayout.LayoutParams defaultTabLayoutParams;

private LinearLayout.LayoutParams expandedTabLayoutParams;

private final PageListener pageListener = new PageListener();

public BaseViewPager.OnPageChangeListener delegatePageListener;

private LinearLayout tabsContainer;

private BaseViewPager pager;

private int tabCount;

private int currentPosition = 0;

private int selectedPosition = 0;

private float currentPositionOffset = 0f;

private Paint rectPaint;

private Paint dividerPaint;

private int indicatorColor = 0xFFdb2222; //选中item下划线颜色

private int underlineColor = 0xffeeeeee;//默认底部下划线背景

private int dividerColor = 0xffEBEBEB;//item之间分割线颜色

private boolean shouldExpand = false;

private boolean textAllCaps = true;

private int scrollOffset = 52;

private int indicatorHeight = 2; //选中item下划线高度

private int underlineHeight = 2;//默认下划线背景高度

private int dividerPadding = 12;//item分割线padding

private int tabPadding = 24;

private int dividerWidth = 0; //item分割线宽度

private int tabTextSize = 12; //默认字体大小

private int tabTextColor = 0xFF666666; //默认字体颜色

private int selectedTabTextSize = 12; //选中字体大小

private int selectedTabTextColor = 0xFFdb2222;//选中字体颜色

private Typeface tabTypeface = null;

private int tabTypefaceStyle = Typeface.NORMAL; //字体样式

private int itemWidth = 0;//默认item宽度

private int lastScrollX = 0;

private int tabBackgroundResId = Color.parseColor("#EBEBEB"); //item背景(此处有问题,最好在xml文件设置背景)

private Locale locale;

public SlidingTabView(Context context) {

this(context, null);

}

public SlidingTabView(Context context, AttributeSet attrs) {

this(context, attrs, 0);

}

public SlidingTabView(Context context, AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

setFillViewport(true);

setWillNotDraw(false);

tabsContainer = new LinearLayout(context);

tabsContainer.setOrientation(LinearLayout.HORIZONTAL);

tabsContainer.setLayoutParams(

new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));

addView(tabsContainer);

DisplayMetrics dm = getResources().getDisplayMetrics();

scrollOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, scrollOffset, dm);

indicatorHeight =

(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, indicatorHeight, dm);

underlineHeight =

(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, underlineHeight, dm);

dividerPadding =

(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dividerPadding, dm);

tabPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, tabPadding, dm);

dividerWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dividerWidth, dm);

tabTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, tabTextSize, dm);

TypedArray a = context.obtainStyledAttributes(attrs, ATTRS);

a.recycle();

rectPaint = new Paint();

rectPaint.setAntiAlias(true);

rectPaint.setStyle(Paint.Style.FILL);

dividerPaint = new Paint();

dividerPaint.setAntiAlias(true);

dividerPaint.setStrokeWidth(dividerWidth);

defaultTabLayoutParams =

new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);

expandedTabLayoutParams = new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f);

if (locale == null) {

locale = getResources().getConfiguration().locale;

}

}

/**

* 绑定viewpager

*/

public void setViewPager(BaseViewPager pager) {

this.pager = pager;

if (pager.getAdapter() == null) {

throw new IllegalStateException("ViewPager does not have adapter instance."
1a4d1
);

}

pager.setOnPageChangeListener(pageListener);

notifyDataSetChanged();

}

public void setOnPageChangeListener(BaseViewPager.OnPageChangeListener listener) {

this.delegatePageListener = listener;

}

/**

* 刷新tab

*/

public void notifyDataSetChanged() {

tabsContainer.removeAllViews();

tabCount = pager.getAdapter().getCount();

for (int i = 0; i < tabCount; i++) {

if (pager.getAdapter() instanceof IconTabProvider) {

addIconTab(i, ((IconTabProvider) pager.getAdapter()).getPageIconResId(i));

} else {

addTextTab(i, pager.getAdapter().getPageTitle(i).toString());

}

}

updateTabStyles();

getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

@Override public void onGlobalLayout() {

getViewTreeObserver().removeGlobalOnLayoutListener(this);

currentPosition = pager.getCurrentItem();

scrollToChild(currentPosition, 0);

}

});

}

/**

* 添加tab(内部默认textview)

*

* @param position 添加位置

* @param title textview标题

*/

private void addTextTab(final int position, String title) {

TextView tab = new TextView(getContext());

tab.setText(title);

tab.setGravity(Gravity.CENTER);

tab.setSingleLine();

addTab(position, tab);

}

/**

* 添加tab(默认imagebutton)

*

* @param position 添加位置

* @param resId 图片资源

*/

private void addIconTab(final int position, int resId) {

ImageButton tab = new ImageButton(getContext());

tab.setImageResource(resId);

addTab(position, tab);

}

/**

* 添加tab外层控件(默认LinearLayout)

*

* @param position 添加位置

* @param tab 子布局

*/

private void addTab(final int position, View tab) {

tab.setFocusable(true);

tab.setOnClickListener(new OnClickListener() {

@Override public void onClick(View v) {

pager.setCurrentItem(position);

}

});

tab.setPadding(tabPadding, 0, tabPadding, 0);

tabsContainer.addView(tab, position,

shouldExpand ? expandedTabLayoutParams : defaultTabLayoutParams);

}

/**

* 刷新tab

*/

private void updateTabStyles() {

for (int i = 0; i < tabCount; i++) {

View v = tabsContainer.getChildAt(i);

if (getItemWidth() != 0) {//判断使用默认宽度还是自定义宽度

ViewGroup.LayoutParams params = v.getLayoutParams();

params.width = getItemWidth() / tabCount;//设置item宽度

v.setLayoutParams(params);

}

//v.setBackgroundResource(tabBackgroundResId);

if (v instanceof TextView) {

TextView tab = (TextView) v;

tab.setTextSize(TypedValue.COMPLEX_UNIT_PX, tabTextSize);

tab.setTypeface(tabTypeface, tabTypefaceStyle);

tab.setTextColor(tabTextColor);

// setAllCaps() is only available from API 14, so the upper case is made manually if we are on a

// pre-ICS-build

if (textAllCaps) {

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {

tab.setAllCaps(true);

} else {

tab.setText(tab.getText().toString().toUpperCase(locale));

}

}

if (i == selectedPosition) {//判断是否选中

tab.setTextColor(selectedTabTextColor);

tab.setTextSize(selectedTabTextSize);

}

}

}

}

private void scrollToChild(int position, int offset) {

if (tabCount == 0) {

return;

}

int newScrollX = tabsContainer.getChildAt(position).getLeft() + offset;

if (position > 0 || offset > 0) {

newScrollX -= scrollOffset;

}

if (newScrollX != lastScrollX) {

lastScrollX = newScrollX;

scrollTo(newScrollX, 0);

}

}

@Override protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

if (isInEditMode() || tabCount == 0) {

return;

}

final int height = getHeight();

//默认底部下划线颜色

rectPaint.setColor(underlineColor);

canvas.drawRect(0, height - underlineHeight, tabsContainer.getWidth(), height, rectPaint);

//选中item下划线颜色

rectPaint.setColor(indicatorColor);

// default: line below current tab

View currentTab = tabsContainer.getChildAt(currentPosition);

float lineLeft = currentTab.getLeft();

float lineRight = currentTab.getRight();

float textLength = 0f;

if (currentTab instanceof TextView) {

TextView tab = (TextView) currentTab;

textLength = tab.length() * tab.getTextSize();

}

//Log.e("++++", currentTab.getWidth() + "---" + lineLeft + "----" + lineRight);

// if there is an offset, start interpolating left and right coordinates between current and next tab

if (currentPositionOffset > 0f && currentPosition < tabCount - 1) {

View nextTab = tabsContainer.getChildAt(currentPosition + 1);

final float nextTabLeft = nextTab.getLeft();

final float nextTabRight = nextTab.getRight();

lineLeft = (currentPositionOffset * nextTabLeft + (1f - currentPositionOffset) * lineLeft);

lineRight = (currentPositionOffset * nextTabRight + (1f - currentPositionOffset) * lineRight);

}

//选中底部下划线描绘

canvas.drawRect(lineLeft + currentTab.getWidth() / 2 - textLength, height - indicatorHeight,

lineRight - currentTab.getWidth() / 2 + textLength, height, rectPaint);

Log.e("+++", lineLeft + currentTab.getWidth() / 2 - textLength + "");

Log.e("++----+", lineRight - currentTab.getWidth() / 2 + textLength + "");

//item之间分割线描绘

dividerPaint.setColor(dividerColor);

for (int i = 0; i < tabCount - 1; i++) {

View tab = tabsContainer.getChildAt(i);

canvas.drawLine(tab.getRight(), dividerPadding, tab.getRight(), height - dividerPadding,

dividerPaint);

}

}

private class PageListener implements BaseViewPager.OnPageChangeListener {

@Override

public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

currentPosition = position;

currentPositionOffset = positionOffset;

scrollToChild(position,

(int) (positionOffset * tabsContainer.getChildAt(position).getWidth()));

invalidate();

if (delegatePageListener != null) {

delegatePageListener.onPageScrolled(position, positionOffset, positionOffsetPixels);

}

}

@Override public void onPageScrollStateChanged(int state) {

if (state == BaseViewPager.SCROLL_STATE_IDLE) {

scrollToChild(pager.getCurrentItem(), 0);

}

if (delegatePageListener != null) {

delegatePageListener.onPageScrollStateChanged(state);

}

}

@Override public void onPageSelected(int position) {

selectedPosition = position;

updateTabStyles();

if (delegatePageListener != null) {

delegatePageListener.onPageSelected(position);

}

}

}

public void setItemWidth(int width) {

itemWidth = width;

}

private int getItemWidth() {

return itemWidth;

}

public void setIndicatorColor(int indicatorColor) {

this.indicatorColor = indicatorColor;

invalidate();

}

public void setIndicatorColorResource(int resId) {

this.indicatorColor = getResources().getColor(resId);

invalidate();

}

public int getIndicatorColor() {

return this.indicatorColor;

}

public void setIndicatorHeight(int indicatorLineHeightPx) {

this.indicatorHeight = indicatorLineHeightPx;

invalidate();

}

public int getIndicatorHeight() {

return indicatorHeight;

}

public void setUnderlineColor(int underlineColor) {

this.underlineColor = underlineColor;

invalidate();

}

public void setUnderlineColorResource(int resId) {

this.underlineColor = getResources().getColor(resId);

invalidate();

}

public int getUnderlineColor() {

return underlineColor;

}

public void setDividerColor(int dividerColor) {

this.dividerColor = dividerColor;

invalidate();

}

public void setDividerColorResource(int resId) {

this.dividerColor = getResources().getColor(resId);

invalidate();

}

public int getDividerColor() {

return dividerColor;

}

public void setUnderlineHeight(int underlineHeightPx) {

this.underlineHeight = underlineHeightPx;

invalidate();

}

public int getUnderlineHeight() {

return underlineHeight;

}

public void setDividerPadding(int dividerPaddingPx) {

this.dividerPadding = dividerPaddingPx;

invalidate();

}

public int getDividerPadding() {

return dividerPadding;

}

public void setScrollOffset(int scrollOffsetPx) {

this.scrollOffset = scrollOffsetPx;

invalidate();

}

public int getScrollOffset() {

return scrollOffset;

}

public void setShouldExpand(boolean shouldExpand) {

this.shouldExpand = shouldExpand;

notifyDataSetChanged();

}

public boolean getShouldExpand() {

return shouldExpand;

}

public boolean isTextAllCaps() {

return textAllCaps;

}

public void setAllCaps(boolean textAllCaps) {

this.textAllCaps = textAllCaps;

}

public void setTextSize(int textSizePx) {

this.tabTextSize = textSizePx;

updateTabStyles();

}

public int getTextSize() {

return tabTextSize;

}

public void setTextColor(int textColor) {

this.tabTextColor = textColor;

updateTabStyles();

}

public void setTextColorResource(int resId) {

this.tabTextColor = getResources().getColor(resId);

updateTabStyles();

}

public int getTextColor() {

return tabTextColor;

}

public void setSelectedTextColor(int textColor) {

this.selectedTabTextColor = textColor;

updateTabStyles();

}

public void setSelectedTextColorResource(int resId) {

this.selectedTabTextColor = getResources().getColor(resId);

updateTabStyles();

}

public int getSelectedTextColor() {

return selectedTabTextColor;

}

public void setTypeface(Typeface typeface, int style) {

this.tabTypeface = typeface;

this.tabTypefaceStyle = style;

updateTabStyles();

}

public void setTabBackground(int resId) {

this.tabBackgroundResId = resId;

updateTabStyles();

}

public int getTabBackground() {

return tabBackgroundResId;

}

public void setTabPaddingLeftRight(int paddingPx) {

this.tabPadding = paddingPx;

updateTabStyles();

}

public int getTabPaddingLeftRight() {

return tabPadding;

}

@Override public void onRestoreInstanceState(Parcelable state) {

SavedState savedState = (SavedState) state;

super.onRestoreInstanceState(savedState.getSuperState());

currentPosition = savedState.currentPosition;

requestLayout();

}

@Override public Parcelable onSaveInstanceState() {

Parcelable superState = super.onSaveInstanceState();

SavedState savedState = new SavedState(superState);

savedState.currentPosition = currentPosition;

return savedState;

}

static class SavedState extends BaseSavedState {

int currentPosition;

public SavedState(Parcelable superState) {

super(superState);

}

private SavedState(Parcel in) {

super(in);

currentPosition = in.readInt();

}

@Override public void writeToParcel(Parcel dest, int flags) {

super.writeToParcel(dest, flags);

dest.writeInt(currentPosition);

}

public static final Parcelable.Creator<SavedState> CREATOR =

new Parcelable.Creator<SavedState>() {

@Override public SavedState createFromParcel(Parcel in) {

return new SavedState(in);

}

@Override public SavedState[] newArray(int size) {

return new SavedState[size];

}

};

}

}

viewpager可实现预加载

备注:这个viewpager为什么能实现预加载,其实在viewpager的源码里面对预加载有个属性设置,DEFAULT_OFFSCREEN_PAGES这个参数可以控制用户是否采用预加载,在这里,我将此参数可做修改,默认为1,则实现预加载,0为禁止预加载,但此功能在android4.0以上版本不能实现,因为当你设置此参数小于1时,将会自动修改为1,所以不能禁止预加载,此次修改目的是让就是改变原有的逻辑,实现禁止预加载的功能。

/**

* Description: 重写v4包下的ViewPager 取消预加载功能

* @author zhangmanyuan

* @date 2016/9/19

*/

public class BaseViewPager extends ViewGroup {

private static final String TAG = "NoPreLoadViewPager";

private static final boolean DEBUG = false;

private static final boolean USE_CACHE = false;

//此变量设置为0时为禁止预加载,其他则为实现预加载

public 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);

}

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 BaseViewPager(Context context) {

super(context);

initViewPager();

}

public BaseViewPager(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(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 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 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();

}

} }
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: