Android 轮播图的实现 自动+手动滑动+指示+点击事件
·
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%3D200/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();
}
}
}
更多推荐
已为社区贡献4条内容
所有评论(0)