Android 轮播图的实现 自动+手动滑动+指示+点击事件

    xiaoxiao2021-03-25  8

    1.图片加载框架

    compile 'com.github.bumptech.glide:glide:3.5.2'

    2.一个先写一个布局 

    <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="180dp" > <com.hzq.xiaoqiang.widget.banner.loopviewpager.AutoLoopViewPager android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/alvp_viewpager" /> <!--该indicator的高度必须指定,否则圆形显示不全--> <com.hzq.xiaoqiang.widget.banner.indicator.AnimatorCircleIndicator android:layout_marginTop="160dp" android:layout_width="match_parent" android:layout_height="24dp" android:id="@+id/anim_indicator" /> </RelativeLayout>3.创建一个 Activity

    public class MainActivity extends AppCompatActivity { //模拟数据 private String[] mViewList = {"http://img2.imgtn.bdimg.com/it/u=3093785514,1341050958&fm=21&gp=0.jpg", "http://img2.3lian.com/2014/f2/37/d/40.jpg", "http://img2.3lian.com/2014/f2/37/d/39.jpg", "http://www.8kmm.com/UploadFiles/2012/8/201208140920132659.jpg", "http://f.hiphotos.baidu.com/image/h=200/sign=1478eb74d5a20cf45990f9df460b4b0c/d058ccbf6c81800a5422e5fdb43533fa838b4779.jpg", "http://f.hiphotos.baidu.com/image/pic/item/09fa513d269759ee50f1971ab6fb43166c22dfba.jpg"}; private AutoLoopViewPager mViewPager; private AnimatorCircleIndicator animindicator; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); this.animindicator = (AnimatorCircleIndicator) findViewById(anim_indicator); this.mViewPager = (AutoLoopViewPager) findViewById(R.id.alvp_viewpager); //创建一个适配器 把模拟数据传过去 BannerAdapter mAdapter = new BannerAdapter(this, mViewList); //在adapter中必须要复写getItemPosition方法,使用Fragment的话必须使用FragmentStatePagerAdapter mViewPager.setAdapter(mAdapter); //设置滚动间隔时间 mViewPager.setInterval(2000); //开始滚动 mViewPager.startAutoScroll(); //indicator与viewpager关联 animindicator.setViewPager(mViewPager); //轮播图的点击事件 mViewPager.setOnTouchListener(new View.OnTouchListener() { int flage = 0 ; @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: flage = 0 ; break ; case MotionEvent.ACTION_MOVE: flage = 1 ; break ; case MotionEvent.ACTION_UP : if (flage == 0) { int item = mViewPager.getCurrentItem(); if (item == 0) { Intent intent = new Intent(this, LoginActivity.class); startActivity(intent); } else if (item == 1) { Intent intent = new Intent(this, LoginActivity.class); startActivity(intent); } else if (item == 2) { Intent intent = new Intent(this, LoginActivity.class); startActivity(intent); }else if (item == 3) { Intent intent = new Intent(this, LoginActivity.class); startActivity(intent); } } break ; } return false; } }); } }

    4.创建一个Adapter

    public class BannerAdapter extends PagerAdapter { private Context context; String[] mViewList; public BannerAdapter(Context context, String[] viewList) { this.context = context; this.mViewList = viewList; } @Override public int getCount() { return mViewList == null? 0 : mViewList.length; } @Override public boolean isViewFromObject(View view, Object object) { return view == object; } @Override public Object instantiateItem(ViewGroup container, int position) { View view = View.inflate(context, R.layout.item_banner_image, null); ImageView iv = (ImageView) view.findViewById(R.id.iv_image); Glide.with(context).load(mViewList[position]).error(R.mipmap.ic_launcher).into(iv); container.addView(view); return view; } @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView((View) object); } //这一步必须有!直接照抄 @Override public int getItemPosition(Object object) { return POSITION_NONE; } }

    5.Adapter布局 item_banner_image

    <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/iv_image" android:scaleType="centerCrop" /> </LinearLayout>

    6.在res文件下的values下的attrs.xml添加一下代码

    <declare-styleable name="LoopViewPager"> <attr format="float|reference" name="scale"/> </declare-styleable> <declare-styleable name="SimpleCircleIndicator"> <attr name="dot_interval" format="dimension|reference"/> <attr name="selected_radius" format="dimension|reference"/> <attr name="unselected_radius" format="dimension|reference"/> <attr name="selected_color" format="color|reference"/> <attr name="unselected_color" format="color|reference"/> <attr name="selected_strokeWidth" format="dimension|reference"/> <attr name="unselected_strokeWidth" format="dimension|reference"/> <attr name="selectedStroke" format="boolean"/> <attr name="unselectedStroke" format="boolean"/> </declare-styleable> <declare-styleable name="LinePageIndicator"> <!-- Whether or not the indicators should be centered. --> <attr format="boolean" name="centered"/> <!-- Color of the unselected lines that represent the pages. --> <attr format="color|reference" name="unselectedColor"/> <!-- Color of the selected line that represents the current page. --> <attr format="color|reference" name="selectedColor"/> <!-- Width of each indicator line. --> <attr format="dimension" name="lineWidth"/> <!-- Width of each indicator line's stroke. --> <attr format="dimension|reference" name="strokeWidth"/> <!-- Width of the gap between each indicator line. --> <attr format="dimension|reference" name="gapWidth"/> <!-- View background --> <attr name="android:background"/> </declare-styleable> <declare-styleable name="AnotherCircleIndicator"> <attr format="dimension" name="another_ci_radius"/> <attr format="dimension" name="another_ci_margin"/> <attr format="color|integer" name="another_ci_background"/> <attr format="color|integer" name="another_ci_selected_background"/> <attr name="another_ci_gravity"> <enum name="left" value="0"/> <enum name="center" value="1"/> <enum name="right" value="2"/> </attr> <attr name="another_ci_mode"> <enum name="inside" value="0"/> <enum name="outside" value="1"/> <enum name="solo" value="2"/> </attr> </declare-styleable> <declare-styleable name="CircleIndicator"> <attr format="dimension" name="ci_width"/> <attr format="dimension" name="ci_height"/> <attr format="dimension" name="ci_margin"/> <attr format="reference" name="ci_animator"/> <attr format="reference" name="ci_animator_reverse"/> <attr format="reference" name="ci_drawable"/> <attr format="reference" name="ci_drawable_unselected"/> </declare-styleable>7.创建一个文件夹( indicator)复制以下俩个类的代码 

    AnimatorCircleIndicator类

    public class AnimatorCircleIndicator extends LinearLayout implements IPageIndicator { private final static int DEFAULT_INDICATOR_WIDTH = 5; private AutoLoopViewPager mViewPager; private int mIndicatorMargin = -1; private int mIndicatorWidth = -1; private int mIndicatorHeight = -1; private int mAnimatorResId = R.animator.scale_with_alpha; private int mAnimatorReverseResId = 0; private int mIndicatorBackgroundResId = R.drawable.white_radius; private int mIndicatorUnselectedBackgroundResId = R.drawable.white_radius; private Animator mAnimatorOut; private Animator mAnimatorIn; private Animator mImmediateAnimatorOut; private Animator mImmediateAnimatorIn; private int mLastPosition = -1; public AnimatorCircleIndicator(Context context) { super(context); init(context, null); } public AnimatorCircleIndicator(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } private void init(Context context, AttributeSet attrs) { setOrientation(LinearLayout.HORIZONTAL); setGravity(Gravity.CENTER); handleTypedArray(context, attrs); checkIndicatorConfig(context); } private void handleTypedArray(Context context, AttributeSet attrs) { if (attrs == null) { return; } TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleIndicator); mIndicatorWidth = typedArray.getDimensionPixelSize(R.styleable.CircleIndicator_ci_width, -1); mIndicatorHeight = typedArray.getDimensionPixelSize(R.styleable.CircleIndicator_ci_height, -1); mIndicatorMargin = typedArray.getDimensionPixelSize(R.styleable.CircleIndicator_ci_margin, -1); mAnimatorResId = typedArray.getResourceId(R.styleable.CircleIndicator_ci_animator, R.animator.scale_with_alpha); mAnimatorReverseResId = typedArray.getResourceId(R.styleable.CircleIndicator_ci_animator_reverse, 0); mIndicatorBackgroundResId = typedArray.getResourceId(R.styleable.CircleIndicator_ci_drawable, R.drawable.white_radius); mIndicatorUnselectedBackgroundResId = typedArray.getResourceId(R.styleable.CircleIndicator_ci_drawable_unselected, mIndicatorBackgroundResId); typedArray.recycle(); } /** * Create and configure Indicator in Java code. */ public void configureIndicator(int indicatorWidth, int indicatorHeight, int indicatorMargin) { configureIndicator(indicatorWidth, indicatorHeight, indicatorMargin, R.animator.scale_with_alpha, 0, R.drawable.white_radius, R.drawable.white_radius); } public void configureIndicator(int indicatorWidth, int indicatorHeight, int indicatorMargin, int animatorId, int animatorReverseId, int indicatorBackgroundId, int indicatorUnselectedBackgroundId) { mIndicatorWidth = indicatorWidth; mIndicatorHeight = indicatorHeight; mIndicatorMargin = indicatorMargin; mAnimatorResId = animatorId; mAnimatorReverseResId = animatorReverseId; mIndicatorBackgroundResId = indicatorBackgroundId; mIndicatorUnselectedBackgroundResId = indicatorUnselectedBackgroundId; checkIndicatorConfig(getContext()); } private void checkIndicatorConfig(Context context) { mIndicatorWidth = (mIndicatorWidth < 0) ? dip2px(DEFAULT_INDICATOR_WIDTH) : mIndicatorWidth; mIndicatorHeight = (mIndicatorHeight < 0) ? dip2px(DEFAULT_INDICATOR_WIDTH) : mIndicatorHeight; mIndicatorMargin = (mIndicatorMargin < 0) ? dip2px(DEFAULT_INDICATOR_WIDTH) : mIndicatorMargin; mAnimatorResId = (mAnimatorResId == 0) ? R.animator.scale_with_alpha : mAnimatorResId; mAnimatorOut = createAnimatorOut(context); mImmediateAnimatorOut = createAnimatorOut(context); mImmediateAnimatorOut.setDuration(0); mAnimatorIn = createAnimatorIn(context); mImmediateAnimatorIn = createAnimatorIn(context); mImmediateAnimatorIn.setDuration(0); mIndicatorBackgroundResId = (mIndicatorBackgroundResId == 0) ? R.drawable.white_radius : mIndicatorBackgroundResId; mIndicatorUnselectedBackgroundResId = (mIndicatorUnselectedBackgroundResId == 0) ? mIndicatorBackgroundResId : mIndicatorUnselectedBackgroundResId; } private Animator createAnimatorOut(Context context) { return AnimatorInflater.loadAnimator(context, mAnimatorResId); } private Animator createAnimatorIn(Context context) { Animator animatorIn; if (mAnimatorReverseResId == 0) { animatorIn = AnimatorInflater.loadAnimator(context, mAnimatorResId); animatorIn.setInterpolator(new ReverseInterpolator()); } else { animatorIn = AnimatorInflater.loadAnimator(context, mAnimatorReverseResId); } return animatorIn; } @Override public void setViewPager(AutoLoopViewPager viewPager) { if (viewPager == null || viewPager.getAdapter() == null) { throw new IllegalStateException("you must initial the viewpager with adapter"); } int initialPosition; viewPager.removeOnPageChangeListener(this); viewPager.addOnPageChangeListener(this); initialPosition = viewPager.getCurrentItem(); this.mViewPager = viewPager; createIndicators(initialPosition); setCurrentItem(initialPosition); } @Override public void setCurrentItem(int item) { onPageSelected(item); } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { if (mViewPager.getAdapter() == null || mViewPager.getAdapter() .getCount() <= 0) { return; } position = position % getRealCount(); if (mAnimatorIn.isRunning()) { mAnimatorIn.end(); mAnimatorIn.cancel(); } if (mAnimatorOut.isRunning()) { mAnimatorOut.end(); mAnimatorOut.cancel(); } if (mLastPosition >= 0) { View currentIndicator = getChildAt(mLastPosition); if (currentIndicator != null) { currentIndicator.setBackgroundResource(mIndicatorUnselectedBackgroundResId); mAnimatorIn.setTarget(currentIndicator); mAnimatorIn.start(); } } View selectedIndicator = getChildAt(position); if (selectedIndicator != null) { selectedIndicator.setBackgroundResource(mIndicatorBackgroundResId); mAnimatorOut.setTarget(selectedIndicator); mAnimatorOut.start(); } mLastPosition = position; } @Override public void onPageScrollStateChanged(int state) { } private void createIndicators(int initialPosition) { removeAllViews(); int count = getRealCount(); for (int i = 0; i < count; i++) { if (initialPosition == i) { addIndicator(mIndicatorBackgroundResId, mImmediateAnimatorOut); } else { addIndicator(mIndicatorUnselectedBackgroundResId, mImmediateAnimatorIn); } } } private void addIndicator(@DrawableRes int backgroundDrawableId, Animator animator) { if (animator.isRunning()) { animator.end(); animator.cancel(); } View Indicator = new View(getContext()); Indicator.setBackgroundResource(backgroundDrawableId); addView(Indicator, mIndicatorWidth, mIndicatorHeight); LayoutParams lp = (LayoutParams) Indicator.getLayoutParams(); lp.leftMargin = mIndicatorMargin; lp.rightMargin = mIndicatorMargin; Indicator.setLayoutParams(lp); animator.setTarget(Indicator); animator.start(); } private class ReverseInterpolator implements Interpolator { @Override public float getInterpolation(float value) { return Math.abs(1.0f - value); } } public int dip2px(float dpValue) { final float scale = getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } private int getRealCount() { if (mViewPager == null) { return 0; } try { PagerAdapter adapter = mViewPager.getAdapter(); if (adapter instanceof IRealAdapter) { return ((IRealAdapter) adapter).getRealCount(); } return adapter.getCount(); } catch (Exception e) { return 0; } } public void notifyDataSetChanged() { int newCount = getRealCount(); int currentCount = getChildCount(); if (newCount == currentCount) { // No change return; } else if (mLastPosition < newCount) { mLastPosition = mViewPager.getCurrentItem(); } else { mLastPosition = -1; } createIndicators(mLastPosition); } }IPageIndicator接口:

    /** * auther: 小强 * time: 17/3/24 27 17:12 * description: indicator接口,可实现该接口写自己的indicator */ public interface IPageIndicator extends LoopViewPager.OnPageChangeListener { void setViewPager(AutoLoopViewPager viewPager); void setCurrentItem(int item); void notifyDataSetChanged(); } 8.创建loopviewpager文件夹复制一下几个类的代码

    AutoLoopViewPager类

    /** * auther: 小强 * time: 17/3/24 27 17:13 * description: indicator接口,可实现该接口写自己的indicator * <p> * 1. 添加自定义属性,可以控制宽高 * 2. to be continued */ public class AutoLoopViewPager extends AutoScrollViewPager { /** * 默认的宽高比,用于宽高都是wrap_content时 */ private static final float DEFAULT_SCALE = 0.5F; private float mScale = DEFAULT_SCALE; public AutoLoopViewPager(Context context) { this(context, null); } public AutoLoopViewPager(Context context, AttributeSet attrs) { super(context, attrs); TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LoopViewPager); mScale = typedArray.getFloat(R.styleable.LoopViewPager_scale, DEFAULT_SCALE); typedArray.recycle(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width_mode = View.MeasureSpec.getMode(widthMeasureSpec); int width_size = View.MeasureSpec.getSize(widthMeasureSpec); int height_mode = View.MeasureSpec.getMode(heightMeasureSpec); int height_size = View.MeasureSpec.getSize(heightMeasureSpec); int width_result = width_size; int height_result = height_size; width_result = width_size;//宽度wrap_content时,size由父控件决定.总是等于parent_size,即屏幕宽度. if (height_mode == View.MeasureSpec.EXACTLY) { height_result = height_size; } else { height_result = (int) (width_result * mScale + 0.5); } int measureSpecWidth = View.MeasureSpec.makeMeasureSpec(width_result, View.MeasureSpec.EXACTLY); int measureSpecHeight = View.MeasureSpec.makeMeasureSpec(height_result, View.MeasureSpec.EXACTLY); super.onMeasure(measureSpecWidth, measureSpecHeight); } }AutoScrollViewPager类

    public class AutoScrollViewPager extends LoopViewPager { public static final int DEFAULT_INTERVAL = 1500; public static final int LEFT = 0; public static final int RIGHT = 1; /** * do nothing when sliding at the last or first item **/ public static final int SLIDE_BORDER_MODE_NONE = 0; /** * cycle when sliding at the last or first item **/ public static final int SLIDE_BORDER_MODE_CYCLE = 1; /** * deliver event to parent when sliding at the last or first item **/ public static final int SLIDE_BORDER_MODE_TO_PARENT = 2; /** * auto scroll time in milliseconds, default is {@link #DEFAULT_INTERVAL} **/ private long interval = DEFAULT_INTERVAL; /** * auto scroll direction, default is {@link #RIGHT} **/ private int direction = RIGHT; /** * whether automatic cycle when auto scroll reaching the last or first item, default is true. * 设置是否轮播 **/ private boolean isCycle = true; /** * whether stop auto scroll when touching, default is true **/ private boolean stopScrollWhenTouch = true; /** * how to process when sliding at the last or first item, default is {@link #SLIDE_BORDER_MODE_NONE} **/ private int slideBorderMode = SLIDE_BORDER_MODE_NONE; /** * whether animating when auto scroll at the last or first item **/ private boolean isBorderAnimation = true; /** * scroll factor for auto scroll animation, default is 1.0 **/ private double autoScrollFactor = 1.0; /** * scroll factor for swipe scroll animation, default is 1.0 **/ private double swipeScrollFactor = 1.0; private Handler handler; private boolean isAutoScroll = false; private boolean isStopByTouch = false; private float touchX = 0f, downX = 0f; private CustomDurationScroller scroller = null; public static final int SCROLL_WHAT = 0; public AutoScrollViewPager(Context paramContext) { super(paramContext); init(); } public AutoScrollViewPager(Context paramContext, AttributeSet paramAttributeSet) { super(paramContext, paramAttributeSet); init(); } private void init() { handler = new MyHandler(this); setViewPagerScroller(); } /** * start auto scroll, first scroll delay time is {@link #getInterval()} * 开始滑动 */ public void startAutoScroll() { isAutoScroll = true; sendScrollMessage((long) (interval + scroller.getDuration() / autoScrollFactor * swipeScrollFactor)); } /** * start auto scroll * * @param delayTimeInMills first scroll delay time */ public void startAutoScroll(int delayTimeInMills) { isAutoScroll = true; sendScrollMessage(delayTimeInMills); } /** * stop auto scroll */ public void stopAutoScroll() { isAutoScroll = false; handler.removeMessages(SCROLL_WHAT); } /** * set the factor by which the duration of sliding animation will change while swiping */ public void setSwipeScrollDurationFactor(double scrollFactor) { swipeScrollFactor = scrollFactor; } /** * set the factor by which the duration of sliding animation will change while auto scrolling. * the bigger the slower. */ public void setAutoScrollDurationFactor(double scrollFactor) { autoScrollFactor = scrollFactor; } private void sendScrollMessage(long delayTimeInMills) { /** remove messages before, keeps one message is running at most **/ handler.removeMessages(SCROLL_WHAT); handler.sendEmptyMessageDelayed(SCROLL_WHAT, delayTimeInMills); } /** * set ViewPager scroller to change animation duration when sliding */ private void setViewPagerScroller() { try { Field scrollerField = ViewPager.class.getDeclaredField("mScroller"); scrollerField.setAccessible(true); Field interpolatorField = ViewPager.class.getDeclaredField("sInterpolator"); interpolatorField.setAccessible(true); scroller = new CustomDurationScroller(getContext(), (Interpolator) interpolatorField.get(null)); scrollerField.set(this, scroller); } catch (Exception e) { e.printStackTrace(); } } /** * scroll only once */ public void scrollOnce() { PagerAdapter adapter = getAdapter(); int currentItem = getCurrentItem(); int totalCount; if (adapter == null || (totalCount = adapter.getCount()) <= 1) { return; } int nextItem = (direction == LEFT) ? --currentItem : ++currentItem; if (nextItem < 0) { if (isCycle) { setCurrentItem(totalCount - 1, isBorderAnimation); } } else if (nextItem == totalCount) { if (isCycle) { setCurrentItem(0, isBorderAnimation); } } else { setCurrentItem(nextItem, true); } } /** * <ul> * if stopScrollWhenTouch is true * <li>if event is down, stop auto scroll.</li> * <li>if event is up, start auto scroll again.</li> * </ul> */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { int action = MotionEventCompat.getActionMasked(ev); if (stopScrollWhenTouch) { if ((action == MotionEvent.ACTION_DOWN) && isAutoScroll) { isStopByTouch = true; stopAutoScroll(); } else if (ev.getAction() == MotionEvent.ACTION_UP && isStopByTouch) { //action_up时,直接开始滚动. startAutoScroll(); } } if (slideBorderMode == SLIDE_BORDER_MODE_TO_PARENT || slideBorderMode == SLIDE_BORDER_MODE_CYCLE) { touchX = ev.getX(); if (ev.getAction() == MotionEvent.ACTION_DOWN) { downX = touchX; } int currentItem = getCurrentItem(); PagerAdapter adapter = getAdapter(); int pageCount = adapter == null ? 0 : adapter.getCount(); /** * current index is first one and slide to right or current index is last one and slide to left.<br/> * if slide border mode is to parent, then requestDisallowInterceptTouchEvent false.<br/> * else scroll to last one when current item is first one, scroll to first one when current item is last * one. */ if ((currentItem == 0 && downX <= touchX) || (currentItem == pageCount - 1 && downX >= touchX)) { if (slideBorderMode == SLIDE_BORDER_MODE_TO_PARENT) { getParent().requestDisallowInterceptTouchEvent(false); } else { if (pageCount > 1) { setCurrentItem(pageCount - currentItem - 1, isBorderAnimation); } getParent().requestDisallowInterceptTouchEvent(true); } return super.dispatchTouchEvent(ev); } } getParent().requestDisallowInterceptTouchEvent(true); return super.dispatchTouchEvent(ev); } private static class MyHandler extends Handler { private final WeakReference<AutoScrollViewPager> autoScrollViewPager; public MyHandler(AutoScrollViewPager autoScrollViewPager) { this.autoScrollViewPager = new WeakReference<AutoScrollViewPager>(autoScrollViewPager); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case SCROLL_WHAT: AutoScrollViewPager pager = this.autoScrollViewPager.get(); if (pager != null) { pager.scroller.setScrollDurationFactor(pager.autoScrollFactor); pager.scrollOnce(); pager.scroller.setScrollDurationFactor(pager.swipeScrollFactor); pager.sendScrollMessage(pager.interval + pager.scroller.getDuration()); } break; default: break; } } } /** * get auto scroll time in milliseconds, default is {@link #DEFAULT_INTERVAL} * * @return the interval */ public long getInterval() { return interval; } /** * set auto scroll time in milliseconds, default is {@link #DEFAULT_INTERVAL} * 多久触发自动滑动. * * @param interval the interval to set */ public void setInterval(long interval) { this.interval = interval; } /** * get auto scroll direction * * @return {@link #LEFT} or {@link #RIGHT}, default is {@link #RIGHT} */ public int getDirection() { return (direction == LEFT) ? LEFT : RIGHT; } /** * set auto scroll direction * 设置滑动方法,从左到右还是从右到左 * * @param direction {@link #LEFT} or {@link #RIGHT}, default is {@link #RIGHT} */ public void setDirection(int direction) { this.direction = direction; } /** * whether automatic cycle when auto scroll reaching the last or first item, default is true * * @return the isCycle */ public boolean isCycle() { return isCycle; } /** * set whether automatic cycle when auto scroll reaching the last or first item, default is true * * @param isCycle the isCycle to set */ public void setCycle(boolean isCycle) { this.isCycle = isCycle; } /** * whether stop auto scroll when touching, default is true * * @return the stopScrollWhenTouch */ public boolean isStopScrollWhenTouch() { return stopScrollWhenTouch; } /** * set whether stop auto scroll when touching, default is true * * @param stopScrollWhenTouch */ public void setStopScrollWhenTouch(boolean stopScrollWhenTouch) { this.stopScrollWhenTouch = stopScrollWhenTouch; } /** * get how to process when sliding at the last or first item * * @return the slideBorderMode {@link #SLIDE_BORDER_MODE_NONE}, {@link #SLIDE_BORDER_MODE_TO_PARENT}, * {@link #SLIDE_BORDER_MODE_CYCLE}, default is {@link #SLIDE_BORDER_MODE_NONE} */ public int getSlideBorderMode() { return slideBorderMode; } /** * set how to process when sliding at the last or first item * * @param slideBorderMode {@link #SLIDE_BORDER_MODE_NONE}, {@link #SLIDE_BORDER_MODE_TO_PARENT}, * {@link #SLIDE_BORDER_MODE_CYCLE}, default is {@link #SLIDE_BORDER_MODE_NONE} */ public void setSlideBorderMode(int slideBorderMode) { this.slideBorderMode = slideBorderMode; } /** * whether animating when auto scroll at the last or first item, default is true * * @return */ public boolean isBorderAnimation() { return isBorderAnimation; } /** * set whether animating when auto scroll at the last or first item, default is true * 设置滑动到最后一页时是否显示回到第一页的动画,默认显示,即会经过中间页. */ public void setBorderAnimation(boolean isBorderAnimation) { this.isBorderAnimation = isBorderAnimation; } @Override protected void onDetachedFromWindow() { if (handler != null) { handler.removeCallbacksAndMessages(null); } super.onDetachedFromWindow(); } }CustomDurationScroller类

    public class CustomDurationScroller extends Scroller { /** * 影响滑动速度的因子 */ private double scrollFactor = 1; public CustomDurationScroller(Context context) { super(context); } public CustomDurationScroller(Context context, Interpolator interpolator) { super(context, interpolator); } /** * Set the factor by which the duration will change */ public void setScrollDurationFactor(double scrollFactor) { this.scrollFactor = scrollFactor; } @Override public void startScroll(int startX, int startY, int dx, int dy, int duration) { //修改duration来控制速度.一定的路程内,时间越长,滑的越慢 super.startScroll(startX, startY, dx, dy, (int) (duration * scrollFactor)); } } IRealAdapter接口

    /** * author: 小强 * date: on 17/3/24 17:17 * description: */ public interface IRealAdapter { int getRealCount(); } LoopViewPager类

    public class LoopViewPager extends ViewGroup { private static final String TAG = "LoopViewPager"; private static final boolean DEBUG = false; private static final boolean USE_CACHE = false; private static final int DEFAULT_OFFSCREEN_PAGES = 1; private static final int MAX_SETTLE_DURATION = 600; // ms private static final int MIN_DISTANCE_FOR_FLING = 25; // dips private static final int DEFAULT_GUTTER_SIZE = 16; // dips private static final int[] LAYOUT_ATTRS = new int[] { android.R.attr.layout_gravity }; static class ItemInfo { Object object; int position; boolean scrolling; float widthFactor; float offset; } 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) { t -= 1.0f; return t * t * t * t * t + 1.0f; } }; private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>(); private final ItemInfo mTempItem = new ItemInfo(); private final Rect mTempRect = new Rect(); 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 mTopPageBounds; private int mBottomPageBounds; // Offsets of the first and last items, if known. // Set during population, used to determine if we are at the beginning // or end of the pager data set during touch scrolling. private float mFirstOffset = -Float.MAX_VALUE; private float mLastOffset = Float.MAX_VALUE; private int mChildWidthMeasureSpec; private int mChildHeightMeasureSpec; private boolean mInLayout; private boolean mScrollingCacheEnabled; private boolean mPopulatePending; private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES; private boolean mIsBeingDragged; private boolean mIsUnableToDrag; private boolean mIgnoreGutter; private int mDefaultGutterSize; private int mGutterSize; 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 int mFlingDistance; private int mCloseEnough; // If the pager is at least this close to its final position, complete the // scroll // on touch down and let the user interact with the content inside instead // of // "catching" the flinging pager. private static final int CLOSE_ENOUGH = 2; // dp private boolean mFakeDragging; private long mFakeDragBeginTime; private EdgeEffectCompat mLeftEdge; private EdgeEffectCompat mRightEdge; private boolean mFirstLayout = true; private boolean mNeedCalculatePageOffsets = false; private boolean mCalledSuper; private int mDecorChildCount; private List<OnPageChangeListener> mOnPageChangeListeners; private OnPageChangeListener mOnPageChangeListener; private OnPageChangeListener mInternalPageChangeListener; private OnAdapterChangeListener mAdapterChangeListener; /** * 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 LoopViewPager#SCROLL_STATE_IDLE * @see LoopViewPager#SCROLL_STATE_DRAGGING * @see LoopViewPager#SCROLL_STATE_SETTLING */ public void onPageScrollStateChanged(int state); } /** * Simple implementation of the {@link OnPageChangeListener} interface with * stub implementations of each method. Extend this if you do not intend to * override every method of {@link 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 } } /** * Used internally to monitor when adapters are switched. */ interface OnAdapterChangeListener { public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter); } /** * Used internally to tag special types of child views that should be added * as pager decorations by default. */ interface Decor { } public LoopViewPager(Context context) { super(context); initViewPager(); } public LoopViewPager(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); final float density = context.getResources().getDisplayMetrics().density; mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density); mCloseEnough = (int) (CLOSE_ENOUGH * density); mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density); ViewCompat.setAccessibilityDelegate(this, new MyAccessibilityDelegate()); if (ViewCompat.getImportantForAccessibility(this) == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); } } private void setScrollState(int newState) { if (mScrollState == newState) { return; } mScrollState = newState; if (mOnPageChangeListener != null) { mOnPageChangeListener.onPageScrollStateChanged(newState); } if (mOnPageChangeListeners != null) { for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) { OnPageChangeListener listener = mOnPageChangeListeners.get(i); if (listener != null) { listener.onPageScrollStateChanged(newState); } } } } /** * Set a PagerAdapter that will supply views for this pager as needed. * * @param adapter * Adapter to use */ 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); if (ii.position < 0 && ii.object == null) { // 左側のダミーページ continue; } mAdapter.destroyItem(this, ii.position, ii.object); } // 左側のダミーページを削除 if (mAdapter.getCount() == 2) { for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); if (isDummy(child)) { removeView(child); } } } mAdapter.finishUpdate(this); mItems.clear(); removeNonDecorViews(); mCurItem = 0; scrollTo(0, 0); } final PagerAdapter oldAdapter = mAdapter; mAdapter = adapter; if (mAdapter != null) { if (mObserver == null) { mObserver = new PagerObserver(); } mAdapter.registerDataSetObserver(mObserver); mPopulatePending = false; mFirstLayout = true; if (mRestoredCurItem >= 0) { mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader); setCurrentItemInternal(mRestoredCurItem, false, true); mRestoredCurItem = -1; mRestoredAdapterState = null; mRestoredClassLoader = null; } else { populate(); } } if (mAdapterChangeListener != null && oldAdapter != adapter) { mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter); } } private void removeNonDecorViews() { for (int i = 0; i < getChildCount(); i++) { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (!lp.isDecor) { removeViewAt(i); i--; } } } /** * Retrieve the current adapter supplying pages. * * @return The currently registered PagerAdapter */ public PagerAdapter getAdapter() { return mAdapter; } void setOnAdapterChangeListener(OnAdapterChangeListener listener) { mAdapterChangeListener = listener; } /** * Set the currently selected page. If the ViewPager has already been * through its first layout with its current adapter 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; } final int pageLimit = mOffscreenPageLimit; final int N = mAdapter.getCount(); if (N != 2) { if (item < 0) { item = 0; } else if (item >= N) { item = N - 1; } // TODO CHANGE 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; // CHANGE int orgItem = item; if (N == 2) { if (item < 0) { item = mCurItem == 0 ? 1 : 0; } else if (item >= N) { item = N - 1; } } // CHANGE int oldPosition = mCurItem; populate(item); // CHANGE final ItemInfo curInfo = infoForPosition(orgItem); int destX = 0; if (curInfo != null) { final int width = getWidth(); destX = (int) (width * Math.max(mFirstOffset, Math.min(curInfo.offset, mLastOffset))); } // CHANGE if (mPopulatePending == false && oldPosition != item) { if (N == 2 && item == 1) { oldPosition = -1; } final ItemInfo oldInfo = infoForPosition(oldPosition); if (oldInfo != null) { final int width = getWidth(); int x = (int) (width * Math.max(mFirstOffset, Math.min(oldInfo.offset, mLastOffset))); scrollTo(x, 0); } } if (smoothScroll) { smoothScrollTo(destX, 0, velocity); if (dispatchSelected && mOnPageChangeListener != null) { mOnPageChangeListener.onPageSelected(item); } if (mOnPageChangeListeners != null) { for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) { OnPageChangeListener listener = mOnPageChangeListeners.get(i); if (listener != null) { listener.onPageSelected(item); } } } if (dispatchSelected && mInternalPageChangeListener != null) { mInternalPageChangeListener.onPageSelected(item); } } else { if (dispatchSelected && mOnPageChangeListener != null) { mOnPageChangeListener.onPageSelected(item); } if (mOnPageChangeListeners != null) { for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) { OnPageChangeListener listener = mOnPageChangeListeners.get(i); if (listener != null) { listener.onPageSelected(item); } } } if (dispatchSelected && mInternalPageChangeListener != null) { mInternalPageChangeListener.onPageSelected(item); } completeScroll(); scrollTo(destX, 0); } } /** * Set a listener that will be invoked whenever the page changes or is incrementally * scrolled. See {@link OnPageChangeListener}. * * @param listener Listener to set * * @deprecated Use {@link #addOnPageChangeListener(OnPageChangeListener)} * and {@link #removeOnPageChangeListener(OnPageChangeListener)} instead. */ @Deprecated public void setOnPageChangeListener(OnPageChangeListener listener) { mOnPageChangeListener = listener; } /** * Add a listener that will be invoked whenever the page changes or is incrementally * scrolled. See {@link OnPageChangeListener}. * * <p>Components that add a listener should take care to remove it when finished. * Other components that take ownership of a view may call {@link #clearOnPageChangeListeners()} * to remove all attached listeners.</p> * * @param listener listener to add */ public void addOnPageChangeListener(OnPageChangeListener listener) { if (mOnPageChangeListeners == null) { mOnPageChangeListeners = new ArrayList<>(); } mOnPageChangeListeners.add(listener); } /** * Remove a listener that was previously added via * {@link #addOnPageChangeListener(OnPageChangeListener)}. * * @param listener listener to remove */ public void removeOnPageChangeListener(OnPageChangeListener listener) { if (mOnPageChangeListeners != null) { mOnPageChangeListeners.remove(listener); } } /** * Remove all listeners that are notified of any changes in scroll state or position. */ public void clearOnPageChangeListeners() { if (mOnPageChangeListeners != null) { mOnPageChangeListeners.clear(); } } /** * Set a separate OnPageChangeListener for internal use by the support * library. * * @param listener * Listener to set * @return The old listener that was set, if any. */ OnPageChangeListener setInternalPageChangeListener(OnPageChangeListener listener) { OnPageChangeListener oldListener = mInternalPageChangeListener; mInternalPageChangeListener = listener; return oldListener; } /** * 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) { 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(); populate(); setScrollState(SCROLL_STATE_IDLE); return; } setScrollingCacheEnabled(true); setScrollState(SCROLL_STATE_SETTLING); final int width = getWidth(); final int halfWidth = width / 2; final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width); final float distance = halfWidth + halfWidth * distanceInfluenceForSnapDuration(distanceRatio); int duration = 0; velocity = Math.abs(velocity); if (velocity > 0) { duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); } else { final float pageWidth = width * mAdapter.getPageWidth(mCurItem); final float pageDelta = (float) Math.abs(dx) / (pageWidth + mPageMargin); duration = (int) ((pageDelta + 1) * 100); } duration = Math.min(duration, MAX_SETTLE_DURATION); mScroller.startScroll(sx, sy, dx, dy, duration); ViewCompat.postInvalidateOnAnimation(this); } ItemInfo addNewItem(int position, int index) { ItemInfo ii = new ItemInfo(); ii.position = position; ii.object = mAdapter.instantiateItem(this, position); ii.widthFactor = mAdapter.getPageWidth(position); if (index < 0 || index >= mItems.size()) { // CHANGE if (index < 0) { mItems.add(0, ii); } else { mItems.add(ii); } } else { mItems.add(index, ii); } return ii; } void dataSetChanged() { // This method only gets called if our observer is attached, so mAdapter // is non-null. boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 && mItems.size() < mAdapter.getCount(); int newCurrItem = mCurItem; boolean isUpdating = false; for (int i = 0; i < mItems.size(); i++) { final ItemInfo ii = mItems.get(i); final int newPos = mAdapter.getItemPosition(ii.object); if (newPos == PagerAdapter.POSITION_UNCHANGED) { continue; } if (newPos == PagerAdapter.POSITION_NONE) { mItems.remove(i); i--; if (!isUpdating) { mAdapter.startUpdate(this); isUpdating = true; } if (ii.position < 0 && ii.object == null) { // 左側のダミーページを } else { mAdapter.destroyItem(this, ii.position, ii.object); } needPopulate = true; if (mCurItem == ii.position) { // Keep the current item in the valid range newCurrItem = Math.max(0, Math.min(mCurItem, mAdapter.getCount() - 1)); needPopulate = true; } continue; } if (ii.position != newPos) { if (ii.position == mCurItem) { // Our current item changed position. Follow it. newCurrItem = newPos; } ii.position = newPos; needPopulate = true; } } if (isUpdating) { mAdapter.finishUpdate(this); } Collections.sort(mItems, COMPARATOR); if (needPopulate) { // Reset our known page widths; populate will recompute them. final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); // 左側のダミーページを削除 if (isDummy(child)) { removeView(child); continue; } final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (!lp.isDecor) { lp.widthFactor = 0.f; } } setCurrentItemInternal(newCurrItem, false, true); requestLayout(); } } void populate() { populate(mCurItem); } // CHANGE boolean mRequestLayoutNeed = false; void populate(int newCurrentItem) { // CHANGE int oldCurrentItem = mCurItem; int oldItemsSize = mItems.size(); ItemInfo oldCurInfo = null; if (mCurItem != newCurrentItem) { oldCurInfo = infoForPosition(mCurItem); mCurItem = newCurrentItem; mRequestLayoutNeed = true; } 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) { 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 N = mAdapter.getCount(); final int pageLimit = mOffscreenPageLimit; // CHANGE final int startPos = (mCurItem + N - pageLimit) % N; // final int startPos = Math.max(0, mCurItem - pageLimit); final int endPos = (mCurItem + pageLimit) % N; // final int endPos = Math.min(N - 1, mCurItem + pageLimit); // Locate the currently focused item or add it if needed. int curIndex = -1; ItemInfo curItem = null; for (curIndex = 0; curIndex < mItems.size(); curIndex++) { final ItemInfo ii = mItems.get(curIndex); // CHANGE if (ii.position == mCurItem) { curItem = ii; break; } } // CHANGE if (curIndex >= mItems.size() && mItems.size() > 0 && oldCurInfo != null) { int oldPosition = oldCurInfo.position; int f = oldPosition < mCurItem ? oldPosition + N : oldPosition; int l = oldPosition > mCurItem ? oldPosition - N : oldPosition; int fDiff = f - mCurItem; int lDiff = mCurItem - l; if (fDiff < lDiff) { curIndex = 0; } else if (lDiff < fDiff) { curIndex = mItems.size(); } else { curIndex = mCurItem < oldPosition ? 0 : mItems.size(); } } if (curItem == null && N > 0) { curItem = addNewItem(mCurItem, curIndex); } // Fill 3x the available width or up to the number of offscreen // pages requested to either side, whichever is larger. // If we have no current item we have no work to do. if (curItem != null) { if (N > 1) { // ANALYZE 左側のページ float extraWidthLeft = 0.f; int itemIndex = curIndex - 1; ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; final float leftWidthNeeded = 2.f - curItem.widthFactor; for (int i = 0; i < N; i++) { final int pos = ((mCurItem - 1 - i) + N) % N; // CHANGE ItemInfo firstInfo = mItems.get(0); if (pos == firstInfo.position && firstInfo.scrolling) { break; } // CHANGE if (extraWidthLeft >= leftWidthNeeded && (pos < startPos || pos > endPos || itemIndex < 0)) { if (ii == null) { break; } // ANALYZE 右にスクロールして、左側のページを削除 if (pos == ii.position && !ii.scrolling && N > 3) { mItems.remove(itemIndex); mAdapter.destroyItem(this, pos, ii.object); itemIndex--; curIndex--; ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; } } else if (ii != null && pos == ii.position) { extraWidthLeft += ii.widthFactor; itemIndex--; ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; } else { // CHANGE ItemInfo lastInfo = mItems.get(mItems.size() - 1); if (pos == lastInfo.position) { ii = lastInfo; mItems.remove(lastInfo); mItems.add(itemIndex + 1, lastInfo); } else { // 左側のページを追加 ii = addNewItem(pos, itemIndex + 1); } curIndex++; extraWidthLeft += ii.widthFactor; ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; } } // ANALYZE 右側のページ float extraWidthRight = curItem.widthFactor; itemIndex = curIndex + 1; if (extraWidthRight < 2.f) { ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; for (int i = 0; i < N; i++) { final int pos = ((mCurItem + 1 + i) + N) % N; // CHANGE if (extraWidthRight >= 2.f && (pos > endPos || pos < startPos || itemIndex >= mItems.size())) { if (ii == null) { break; } // ANALYZE 左にスクロールして、右側のページを削除 // CHANGE if (pos == ii.position) { if (!ii.scrolling) { mItems.remove(itemIndex); mAdapter.destroyItem(this, pos, ii.object); ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; } else { itemIndex++; ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; } } } else if (ii != null && pos == ii.position) { extraWidthRight += ii.widthFactor; itemIndex++; ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; } else { // CHANGE ItemInfo firstInfo = mItems.get(0); if (firstInfo.position == -1 && mItems.size() > 1) { firstInfo = mItems.get(1); } if (pos == firstInfo.position && N > 1) { ii = firstInfo; mItems.remove(firstInfo); mItems.add(itemIndex - 1, firstInfo); curIndex--; } else { // 右側のページを追加 ii = addNewItem(pos, itemIndex); itemIndex++; } extraWidthRight += ii.widthFactor; ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; } } } } // CHANGE if (N == 2) { if (mItems.size() < 3) { ItemInfo lastInfo = mItems.get(mItems.size() - 1); // 左側のダミーページを追加 ItemInfo ii = new ItemInfo(); ii.position = -1; ii.object = null; ii.widthFactor = lastInfo.widthFactor; ImageView dummyView = new ImageView(getContext()); dummyView.setTag(-1); this.addView(dummyView); mItems.add(0, ii); curIndex++; } } calculatePageOffsets(curItem, curIndex, oldCurInfo); if (N > 3) { if (oldItemsSize > 3) { if (mItems.size() > 3) { if (mRequestLayoutNeed) { requestLayout(); mRequestLayoutNeed = false; } } } } } if (DEBUG) { for (int i = 0; i < mItems.size(); i++) { } } mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null); mAdapter.finishUpdate(this); // Check width measurement of current pages. Update LayoutParams as // needed. final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (!lp.isDecor && lp.widthFactor == 0.f) { // 0 means requery the adapter for this, it doesn't have a valid // width. final ItemInfo ii = infoForChild(child); if (ii != null) { lp.widthFactor = ii.widthFactor; } } } 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; } } } } } } private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) { final int N = mAdapter.getCount(); final int width = getWidth(); final float marginOffset = width > 0 ? (float) mPageMargin / width : 0; // Fix up offsets for later layout. if (oldCurInfo != null) { final int oldCurPosition = oldCurInfo.position; // Base offsets off of oldCurInfo. if (oldCurPosition < curItem.position) { // ANALYZE 右に移動した。以前のページ + 1から新しいページまでの各ページのオフセットを計算 int itemIndex = 0; ItemInfo ii = null; float offset = oldCurInfo.offset + oldCurInfo.widthFactor + marginOffset; for (int pos = oldCurPosition + 1; pos <= curItem.position && itemIndex < mItems.size(); pos++) { ii = mItems.get(itemIndex); // ANALYZE position が pos より大きくなる ItemInfo を mItems を下から探す while (pos > ii.position && itemIndex < mItems.size() - 1) { itemIndex++; ii = mItems.get(itemIndex); } // ANALYZE position が pos より大きい = position が pos と同じになる // ItemInfo がなかった while (pos < ii.position) { // We don't have an item populated for this, // ask the adapter for an offset. offset += mAdapter.getPageWidth(pos) + marginOffset; pos++; } ii.offset = offset; offset += ii.widthFactor + marginOffset; } } else if (oldCurPosition > curItem.position) { // ANALYZE 左に移動した。以前のページ - 1 から新しいページまでの各ページのオフセットを計算 int itemIndex = mItems.size() - 1; ItemInfo ii = null; float offset = oldCurInfo.offset; for (int pos = oldCurPosition - 1; pos >= curItem.position && itemIndex >= 0; pos--) { ii = mItems.get(itemIndex); while (pos < ii.position && itemIndex > 0) { itemIndex--; ii = mItems.get(itemIndex); } while (pos > ii.position) { // We don't have an item populated for this, // ask the adapter for an offset. offset -= mAdapter.getPageWidth(pos) + marginOffset; pos--; } offset -= ii.widthFactor + marginOffset; ii.offset = offset; } } } // Base all offsets off of curItem. final int itemCount = mItems.size(); // CHANGE curItem.offset = 0; mFirstOffset = /* curItem.position == 0 ? curItem.offset : */-Float.MAX_VALUE; mLastOffset = /* * curItem.position == N - 1 ? curItem.offset + * curItem.widthFactor - 1 : */Float.MAX_VALUE; float offset = curItem.offset; int pos = curItem.position - 1; // Previous pages for (int i = curIndex - 1; i >= 0; i--, pos--) { final ItemInfo ii = mItems.get(i); // CHANGE while (pos > ii.position && N != 2) { offset -= mAdapter.getPageWidth(pos--) + marginOffset; } offset -= ii.widthFactor + marginOffset; ii.offset = offset; if (ii.position == 0) mFirstOffset = offset; } offset = curItem.offset + curItem.widthFactor + marginOffset; pos = curItem.position + 1; // Next pages for (int i = curIndex + 1; i < itemCount; i++, pos++) { final ItemInfo ii = mItems.get(i); while (pos < ii.position) { offset += mAdapter.getPageWidth(pos++) + marginOffset; } if (ii.position == N - 1) { mLastOffset = offset + ii.widthFactor - 1; } ii.offset = offset; offset += ii.widthFactor + marginOffset; } // CHANGE if (N <= 3) { requestLayout(); } mNeedCalculatePageOffsets = false; } /** * This is the persistent state that is saved by ViewPager. Only needed if * you are creating a sublass of ViewPager that must save its own state, in * which case it should implement a subclass of this which contains that * state. */ 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, ViewGroup.LayoutParams params) { if (!checkLayoutParams(params)) { params = generateLayoutParams(params); } final LayoutParams lp = (LayoutParams) params; lp.isDecor |= child instanceof Decor; if (mInLayout) { if (lp != null && lp.isDecor) { throw new IllegalStateException("Cannot add pager decor view during layout"); } lp.needsMeasure = true; addViewInLayout(child, index, params); } else { super.addView(child, index, params); } if (USE_CACHE) { if (child.getVisibility() != GONE) { child.setDrawingCacheEnabled(mScrollingCacheEnabled); } else { child.setDrawingCacheEnabled(false); } } } private boolean isDummy(View child) { if (!(child instanceof ImageView)) { return false; } Object tag = child.getTag(); if (tag == null) { return false; } if (!(tag instanceof Integer)) { return false; } int index = (Integer) tag; return index == -1; } ItemInfo infoForChild(View child) { // CHANGE final int N = mAdapter.getCount(); boolean isDummy = isDummy(child); for (int i = 0; i < mItems.size(); i++) { ItemInfo ii = mItems.get(i); // CHANGE if (isDummy && ii.position == -1) { return ii; } if (ii.position != -1 && 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); } ItemInfo infoForPosition(int position) { for (int i = 0; i < mItems.size(); i++) { ItemInfo ii = mItems.get(i); if (ii.position == position) { return ii; } } return null; } @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)); final int measuredWidth = getMeasuredWidth(); final int maxGutterSize = measuredWidth / 10; mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize); // Children are just made to fill our space. int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight(); int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom(); /* * Make sure all children have been properly measured. Decor views * first. Right now we cheat and make this less complicated by assuming * decor views won't intersect. We will pin to edges based on gravity. */ int size = getChildCount(); for (int i = 0; i < size; ++i) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (lp != null && lp.isDecor) { final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK; int widthMode = MeasureSpec.AT_MOST; int heightMode = MeasureSpec.AT_MOST; boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM; boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT; if (consumeVertical) { widthMode = MeasureSpec.EXACTLY; } else if (consumeHorizontal) { heightMode = MeasureSpec.EXACTLY; } int widthSize = childWidthSize; int heightSize = childHeightSize; if (lp.width != LayoutParams.WRAP_CONTENT) { widthMode = MeasureSpec.EXACTLY; if (lp.width != LayoutParams.FILL_PARENT) { widthSize = lp.width; } } if (lp.height != LayoutParams.WRAP_CONTENT) { heightMode = MeasureSpec.EXACTLY; if (lp.height != LayoutParams.FILL_PARENT) { heightSize = lp.height; } } final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode); final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode); child.measure(widthSpec, heightSpec); if (consumeVertical) { childHeightSize -= child.getMeasuredHeight(); } else if (consumeHorizontal) { childWidthSize -= child.getMeasuredWidth(); } } } } mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY); mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY); // Make sure we have created all fragments that we need to have shown. mInLayout = true; populate(); mInLayout = false; // Page views next. size = getChildCount(); for (int i = 0; i < size; ++i) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (lp == null || !lp.isDecor) { final int widthSpec = MeasureSpec.makeMeasureSpec((int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY); child.measure(widthSpec, 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) { if (oldWidth > 0 && !mItems.isEmpty()) { final int widthWithMargin = width + margin; final int oldWidthWithMargin = oldWidth + oldMargin; final int xpos = getScrollX(); final float pageOffset = (float) xpos / oldWidthWithMargin; final int newOffsetPixels = (int) (pageOffset * widthWithMargin); scrollTo(newOffsetPixels, getScrollY()); if (!mScroller.isFinished()) { // We now return to your regularly scheduled scroll, already in // progress. final int newDuration = mScroller.getDuration() - mScroller.timePassed(); ItemInfo targetInfo = infoForPosition(mCurItem); mScroller.startScroll(newOffsetPixels, 0, (int) (targetInfo.offset * width), 0, newDuration); } } else { final ItemInfo ii = infoForPosition(mCurItem); final float scrollOffset = ii != null ? Math.min(ii.offset, mLastOffset) : 0; final int scrollPos = (int) (scrollOffset * width); 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(); int width = r - l; int height = b - t; int paddingLeft = getPaddingLeft(); int paddingTop = getPaddingTop(); int paddingRight = getPaddingRight(); int paddingBottom = getPaddingBottom(); final int scrollX = getScrollX(); int decorCount = 0; // First pass - decor views. We need to do this in two passes so that // we have the proper offsets for non-decor views later. for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); int childLeft = 0; int childTop = 0; if (lp.isDecor) { final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK; switch (hgrav) { default: childLeft = paddingLeft; break; case Gravity.LEFT: childLeft = paddingLeft; paddingLeft += child.getMeasuredWidth(); break; case Gravity.CENTER_HORIZONTAL: childLeft = Math.max((width - child.getMeasuredWidth()) / 2, paddingLeft); break; case Gravity.RIGHT: childLeft = width - paddingRight - child.getMeasuredWidth(); paddingRight += child.getMeasuredWidth(); break; } switch (vgrav) { default: childTop = paddingTop; break; case Gravity.TOP: childTop = paddingTop; paddingTop += child.getMeasuredHeight(); break; case Gravity.CENTER_VERTICAL: childTop = Math.max((height - child.getMeasuredHeight()) / 2, paddingTop); break; case Gravity.BOTTOM: childTop = height - paddingBottom - child.getMeasuredHeight(); paddingBottom += child.getMeasuredHeight(); break; } childLeft += scrollX; child.layout(childLeft, childTop, childLeft + child.getMeasuredWidth(), childTop + child.getMeasuredHeight()); decorCount++; } } } // Page views. Do this once we have the right padding offsets from // above. for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); ItemInfo ii; if (!lp.isDecor && (ii = infoForChild(child)) != null) { int loff = (int) (width * ii.offset); int childLeft = paddingLeft + loff; int childTop = paddingTop; if (lp.needsMeasure) { // This was added during layout and needs measurement. // Do it now that we know what we're working with. lp.needsMeasure = false; final int widthSpec = MeasureSpec.makeMeasureSpec( (int) ((width - paddingLeft - paddingRight) * lp.widthFactor), MeasureSpec.EXACTLY); final int heightSpec = MeasureSpec.makeMeasureSpec((int) (height - paddingTop - paddingBottom), MeasureSpec.EXACTLY); child.measure(widthSpec, heightSpec); } child.layout(childLeft, childTop, childLeft + child.getMeasuredWidth(), childTop + child.getMeasuredHeight()); } } } // CHANGE scrollTo(0, 0); mTopPageBounds = paddingTop; mBottomPageBounds = height - paddingBottom; mDecorChildCount = decorCount; mFirstLayout = false; } @Override public void computeScroll() { if (!mScroller.isFinished() && mScroller.computeScrollOffset()) { int oldX = getScrollX(); int oldY = getScrollY(); int x = mScroller.getCurrX(); int y = mScroller.getCurrY(); if (oldX != x || oldY != y) { scrollTo(x, y); if (!pageScrolled(x)) { mScroller.abortAnimation(); scrollTo(0, y); } } // Keep on drawing until the animation has finished. ViewCompat.postInvalidateOnAnimation(this); return; } // Done with scroll, clean up state. completeScroll(); } private boolean pageScrolled(int xpos) { if (mItems.size() == 0) { mCalledSuper = false; onPageScrolled(0, 0, 0); if (!mCalledSuper) { throw new IllegalStateException("onPageScrolled did not call superclass implementation"); } return false; } final ItemInfo ii = infoForCurrentScrollPosition(); final int width = getWidth(); final int widthWithMargin = width + mPageMargin; final float marginOffset = (float) mPageMargin / width; final int currentPage = ii.position; final float pageOffset = (((float) xpos / width) - ii.offset) / (ii.widthFactor + marginOffset); final int offsetPixels = (int) (pageOffset * widthWithMargin); mCalledSuper = false; onPageScrolled(currentPage, pageOffset, offsetPixels); if (!mCalledSuper) { throw new IllegalStateException("onPageScrolled did not call superclass implementation"); } return true; } /** * 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. If you override this method you must call through to the * superclass implementation (e.g. super.onPageScrolled(position, offset, * offsetPixels)) before onPageScrolled returns. * * @param position * Position index of the first page currently being displayed. * Page position+1 will be visible if positionOffset is nonzero. * @param offset * Value from [0, 1) indicating the offset from the page at * position. * @param offsetPixels * Value in pixels indicating the offset from position. */ protected void onPageScrolled(int position, float offset, int offsetPixels) { // Offset any decor views if needed - keep them on-screen at all times. if (mDecorChildCount > 0) { final int scrollX = getScrollX(); int paddingLeft = getPaddingLeft(); int paddingRight = getPaddingRight(); final int width = getWidth(); final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (!lp.isDecor) continue; final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; int childLeft = 0; switch (hgrav) { default: childLeft = paddingLeft; break; case Gravity.LEFT: childLeft = paddingLeft; paddingLeft += child.getWidth(); break; case Gravity.CENTER_HORIZONTAL: childLeft = Math.max((width - child.getMeasuredWidth()) / 2, paddingLeft); break; case Gravity.RIGHT: childLeft = width - paddingRight - child.getMeasuredWidth(); paddingRight += child.getMeasuredWidth(); break; } childLeft += scrollX; final int childOffset = childLeft - child.getLeft(); if (childOffset != 0) { child.offsetLeftAndRight(childOffset); } } } if (mOnPageChangeListener != null) { mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels); } if (mOnPageChangeListeners != null) { for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) { OnPageChangeListener listener = mOnPageChangeListeners.get(i); if (listener != null) { listener.onPageScrolled(position, offset, offsetPixels); } } } if (mInternalPageChangeListener != null) { mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels); } mCalledSuper = true; } private void completeScroll() { boolean needPopulate = mScrollState == SCROLL_STATE_SETTLING; 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; for (int i = 0; i < mItems.size(); i++) { ItemInfo ii = mItems.get(i); if (ii.scrolling) { needPopulate = true; ii.scrolling = false; } } if (needPopulate) { populate(); } } private boolean isGutterDrag(float x, float dx) { return (x < mGutterSize && dx > 0) || (x > getWidth() - mGutterSize && dx < 0); } @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. mIsBeingDragged = false; mIsUnableToDrag = false; mActivePointerId = INVALID_POINTER; if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } return false; } // Nothing more to do here if we have decided whether or not we // are dragging. if (action != MotionEvent.ACTION_DOWN) { if (mIsBeingDragged) { return true; } if (mIsUnableToDrag) { 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); if (dx != 0 && !isGutterDrag(mLastMotionX, dx) && 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; mIsUnableToDrag = true; return false; } if (xDiff > mTouchSlop && xDiff > yDiff) { mIsBeingDragged = true; setScrollState(SCROLL_STATE_DRAGGING); // CHANGE if (mAdapter.getCount() == 2) { setupDummyView(); } mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop : mInitialMotionX - mTouchSlop; 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. mIsUnableToDrag = true; } } if (mIsBeingDragged) { // Scroll to follow the motion event if (performDrag(x)) { ViewCompat.postInvalidateOnAnimation(this); } } 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); mIsUnableToDrag = false; mScroller.computeScrollOffset(); if (mScrollState == SCROLL_STATE_SETTLING && Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) { // Let the user 'catch' the pager as it animates. mScroller.abortAnimation(); mPopulatePending = false; populate(); mIsBeingDragged = true; setScrollState(SCROLL_STATE_DRAGGING); } else { completeScroll(); mIsBeingDragged = false; } break; } case MotionEventCompat.ACTION_POINTER_UP: onSecondaryPointerUp(ev); break; } if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(ev); /* * The only time we want to intercept motion events is if we are in the * drag mode. */ return mIsBeingDragged; } private void setupDummyView() { int childCount = getChildCount(); ImageView dummyView = null; View targetView = null; ItemInfo targetInfo = mItems.get(2); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); if (child instanceof ImageView) { Object tag = child.getTag(); if (tag != null && tag instanceof Integer) { int index = (Integer) tag; if (index == -1) { dummyView = (ImageView) child; continue; } } } if (mAdapter.isViewFromObject(child, targetInfo.object)) { targetView = child; } } if (targetView != null && dummyView != null) { Bitmap bitmap = getViewBitmap(targetView); dummyView.setImageBitmap(bitmap); } } @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: { mScroller.abortAnimation(); mPopulatePending = false; populate(); mIsBeingDragged = true; setScrollState(SCROLL_STATE_DRAGGING); // CHANGE if (mAdapter.getCount() == 2) { setupDummyView(); } // 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 (xDiff > mTouchSlop && xDiff > yDiff) { mIsBeingDragged = true; mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop : mInitialMotionX - mTouchSlop; setScrollState(SCROLL_STATE_DRAGGING); setScrollingCacheEnabled(true); } } // Not else! Note that mIsBeingDragged can be set above. if (mIsBeingDragged) { // Scroll to follow the motion event final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); final float x = MotionEventCompat.getX(ev, activePointerIndex); needsInvalidate |= performDrag(x); } 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 width = getWidth(); final int scrollX = getScrollX(); final ItemInfo ii = infoForCurrentScrollPosition(); final int currentPage = ii.position; final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor; final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); final float x = MotionEventCompat.getX(ev, activePointerIndex); final int totalDelta = (int) (x - mInitialMotionX); int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity, totalDelta); 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) { ViewCompat.postInvalidateOnAnimation(this); } return true; } private boolean performDrag(float x) { boolean needsInvalidate = false; final float deltaX = mLastMotionX - x; mLastMotionX = x; float oldScrollX = getScrollX(); float scrollX = oldScrollX + deltaX; final int width = getWidth(); float leftBound = width * mFirstOffset; float rightBound = width * mLastOffset; boolean leftAbsolute = true; boolean rightAbsolute = true; final ItemInfo firstItem = mItems.get(0); final ItemInfo lastItem = mItems.get(mItems.size() - 1); if (firstItem.position != 0) { leftAbsolute = false; leftBound = firstItem.offset * width; } if (lastItem.position != mAdapter.getCount() - 1) { rightAbsolute = false; rightBound = lastItem.offset * width; } if (scrollX < leftBound) { if (leftAbsolute) { float over = leftBound - scrollX; needsInvalidate = mLeftEdge.onPull(Math.abs(over) / width); } scrollX = leftBound; } else if (scrollX > rightBound) { if (rightAbsolute) { float over = scrollX - rightBound; needsInvalidate = mRightEdge.onPull(Math.abs(over) / width); } scrollX = rightBound; } // Don't lose the rounded component mLastMotionX += scrollX - (int) scrollX; scrollTo((int) scrollX, getScrollY()); pageScrolled((int) scrollX); return needsInvalidate; } /** * @return Info about the page at the current scroll position. This can be * synthetic for a missing middle page; the 'object' field can be * null. */ private ItemInfo infoForCurrentScrollPosition() { final int width = getWidth(); final float scrollOffset = width > 0 ? (float) getScrollX() / width : 0; final float marginOffset = width > 0 ? (float) mPageMargin / width : 0; int lastPos = -1; float lastOffset = 0.f; float lastWidth = 0.f; boolean first = true; ItemInfo lastItem = null; for (int i = 0; i < mItems.size(); i++) { ItemInfo ii = mItems.get(i); float offset; // CHANGE if (!first && ii.position != lastPos + 1 && mAdapter.getCount() != 2) { // Create a synthetic item for a missing page. ii = mTempItem; ii.offset = lastOffset + lastWidth + marginOffset; ii.position = lastPos + 1; ii.widthFactor = mAdapter.getPageWidth(ii.position); i--; } offset = ii.offset; final float leftBound = offset; final float rightBound = offset + ii.widthFactor + marginOffset; if (first || scrollOffset >= leftBound) { if (scrollOffset < rightBound || i == mItems.size() - 1) { return ii; } } else { return lastItem; } first = false; lastPos = ii.position; lastOffset = offset; lastWidth = ii.widthFactor; lastItem = ii; } return lastItem; } private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaX) { int targetPage; if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) { targetPage = velocity > 0 ? currentPage : currentPage + 1; } else { if (currentPage + pageOffset + 0.5f >= 0f) { targetPage = (int) (currentPage + pageOffset + 0.5f); } else { targetPage = (int) (currentPage + pageOffset + 0.5f - 1f); } } final int N = mAdapter.getCount(); // CHANGE if (N == 2) { // ページ数2 if (mCurItem == 1) { // -1 1 0 if (currentPage == -1 && targetPage == 0) { targetPage = 1; } if (targetPage == 2) { targetPage = 0; } } } else if (mItems.size() > 0) { final ItemInfo firstItem = mItems.get(0); final ItemInfo lastItem = mItems.get(mItems.size() - 1); // CHANGE if (firstItem.position > lastItem.position) { // 端にいる if (targetPage >= N) { targetPage = targetPage % N; } } } return targetPage; } @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(); final int width = getWidth(); canvas.rotate(270); canvas.translate(-height + getPaddingTop(), mFirstOffset * width); mLeftEdge.setSize(height, width); 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(); canvas.rotate(90); canvas.translate(-getPaddingTop(), -(mLastOffset + 1) * width); mRightEdge.setSize(height, width); needsInvalidate |= mRightEdge.draw(canvas); canvas.restoreToCount(restoreCount); } } else { mLeftEdge.finish(); mRightEdge.finish(); } if (needsInvalidate) { // Keep animating ViewCompat.postInvalidateOnAnimation(this); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // Draw the margin drawable between pages if needed. if (mPageMargin > 0 && mMarginDrawable != null && mItems.size() > 0 && mAdapter != null) { final int scrollX = getScrollX(); final int width = getWidth(); final float marginOffset = (float) mPageMargin / width; int itemIndex = 0; ItemInfo ii = mItems.get(0); float offset = ii.offset; final int itemCount = mItems.size(); final int firstPos = ii.position; final int lastPos = mItems.get(itemCount - 1).position; for (int pos = firstPos; pos < lastPos; pos++) { while (pos > ii.position && itemIndex < itemCount) { ii = mItems.get(++itemIndex); } float drawAt; if (pos == ii.position) { drawAt = (ii.offset + ii.widthFactor) * width; offset = ii.offset + ii.widthFactor + marginOffset; } else { float widthFactor = mAdapter.getPageWidth(pos); drawAt = (offset + widthFactor) * width; offset += widthFactor + marginOffset; } if (drawAt + mPageMargin > scrollX) { mMarginDrawable.setBounds((int) drawAt, mTopPageBounds, (int) (drawAt + mPageMargin + 0.5f), mBottomPageBounds); mMarginDrawable.draw(canvas); } if (drawAt > scrollX + width) { break; // No more visible, no sense in continuing } } } } // CHANGE Bitmap getViewBitmap(View v) { v.clearFocus(); v.setPressed(false); // もともとの設定値を保持しておく boolean willNotCache = v.willNotCacheDrawing(); int color = v.getDrawingCacheBackgroundColor(); // 描画をキャッシュしないようにセット v.setWillNotCacheDrawing(false); // 描画キャッシュをクリアして、背景を透明にセット v.setDrawingCacheBackgroundColor(0); // 以前の描画キャッシュを破棄 if (color != 0) { v.destroyDrawingCache(); } // 新しく描画キャッシュを作成 v.buildDrawingCache(); // キャッシュ用の Bitmap を取得 Bitmap cacheBitmap = v.getDrawingCache(); if (cacheBitmap == null) { return null; } // キャッシュ用の Bitmap からドラッグ中の画像に使うための Bitmap を作成 Bitmap bitmap = Bitmap.createBitmap(cacheBitmap); // もともとセットされていた描画キャッシュに戻す v.destroyDrawingCache(); v.setWillNotCacheDrawing(willNotCache); v.setDrawingCacheBackgroundColor(color); return bitmap; } /** * 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.getXVelocity(velocityTracker, mActivePointerId); mPopulatePending = true; final int width = getWidth(); final int scrollX = getScrollX(); final ItemInfo ii = infoForCurrentScrollPosition(); final int currentPage = ii.position; final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor; final int totalDelta = (int) (mLastMotionX - mInitialMotionX); int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity, totalDelta); setCurrentItemInternal(nextPage, true, true, initialVelocity); 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 oldScrollX = getScrollX(); float scrollX = oldScrollX - xOffset; final int width = getWidth(); float leftBound = width * mFirstOffset; float rightBound = width * mLastOffset; final ItemInfo firstItem = mItems.get(0); final ItemInfo lastItem = mItems.get(mItems.size() - 1); if (firstItem.position != 0) { leftBound = firstItem.offset * width; } if (lastItem.position != mAdapter.getCount() - 1) { rightBound = lastItem.offset * width; } 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()); pageScrolled((int) scrollX); // 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 (Build.VERSION.SDK_INT >= 11) { // The focus finder had a bug handling FOCUS_FORWARD and // FOCUS_BACKWARD // before Android 3.0. Ignore the tab key on those // devices. 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. final int nextLeft = getChildRectInPagerCoordinates(mTempRect, nextFocused).left; final int currLeft = getChildRectInPagerCoordinates(mTempRect, currentFocused).left; if (currentFocused != null && nextLeft >= currLeft) { 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. final int nextLeft = getChildRectInPagerCoordinates(mTempRect, nextFocused).left; final int currLeft = getChildRectInPagerCoordinates(mTempRect, currentFocused).left; if (currentFocused != null && nextLeft <= currLeft) { 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; } private Rect getChildRectInPagerCoordinates(Rect outRect, View child) { if (outRect == null) { outRect = new Rect(); } if (child == null) { outRect.set(0, 0, 0, 0); return outRect; } outRect.left = child.getLeft(); outRect.right = child.getRight(); outRect.top = child.getTop(); outRect.bottom = child.getBottom(); ViewParent parent = child.getParent(); while (parent instanceof ViewGroup && parent != this) { final ViewGroup group = (ViewGroup) parent; outRect.left += group.getLeft(); outRect.right += group.getRight(); outRect.top += group.getTop(); outRect.bottom += group.getBottom(); parent = group.getParent(); } return outRect; } 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; } @Override protected ViewGroup.LayoutParams generateDefaultLayoutParams() { return new LayoutParams(); } @Override protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { return generateDefaultLayoutParams(); } @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof LayoutParams && super.checkLayoutParams(p); } @Override public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { return new LayoutParams(getContext(), attrs); } class MyAccessibilityDelegate extends AccessibilityDelegateCompat { @Override public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { super.onInitializeAccessibilityEvent(host, event); event.setClassName(LoopViewPager.class.getName()); } @Override public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { super.onInitializeAccessibilityNodeInfo(host, info); info.setClassName(LoopViewPager.class.getName()); info.setScrollable(mAdapter != null && mAdapter.getCount() > 1); if (mAdapter != null && mCurItem >= 0 && mCurItem < mAdapter.getCount() - 1) { info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD); } if (mAdapter != null && mCurItem > 0 && mCurItem < mAdapter.getCount()) { info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD); } } @Override public boolean performAccessibilityAction(View host, int action, Bundle args) { if (super.performAccessibilityAction(host, action, args)) { return true; } switch (action) { case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: { if (mAdapter != null && mCurItem >= 0 && mCurItem < mAdapter.getCount() - 1) { setCurrentItem(mCurItem + 1); return true; } } return false; case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: { if (mAdapter != null && mCurItem > 0 && mCurItem < mAdapter.getCount()) { setCurrentItem(mCurItem - 1); return true; } } return false; } return false; } } private class PagerObserver extends DataSetObserver { @Override public void onChanged() { dataSetChanged(); } @Override public void onInvalidated() { dataSetChanged(); } } /** * Layout parameters that should be supplied for views added to a ViewPager. */ public static class LayoutParams extends ViewGroup.LayoutParams { /** * true if this view is a decoration on the pager itself and not a view * supplied by the adapter. */ public boolean isDecor; /** * Gravity setting for use on decor views only: Where to position the * view page within the overall ViewPager container; constants are * defined in {@link Gravity}. */ public int gravity; /** * Width as a 0-1 multiplier of the measured pager width */ public float widthFactor = 0.f; /** * true if this view was added during layout and needs to be measured * before being positioned. */ public boolean needsMeasure; public LayoutParams() { super(MATCH_PARENT, MATCH_PARENT); } public LayoutParams(Context context, AttributeSet attrs) { super(context, attrs); final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS); gravity = a.getInteger(0, Gravity.TOP); a.recycle(); } } }

    转载请注明原文地址: https://ju.6miu.com/read-156107.html

    最新回复(0)