n rel="diff-5fcb712c66f51f4414ecca75fb28fb973d63135cR95">95
t -= 1.0f;
|
|
|
96
|
return t * t * t * t * t + 1.0f;
|
|
|
97
|
}
|
|
|
98
|
};
|
|
|
99
|
|
|
|
100
|
private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
|
|
|
101
|
private final ItemInfo mTempItem = new ItemInfo();
|
|
|
102
|
|
|
|
103
|
private final Rect mTempRect = new Rect();
|
|
|
104
|
|
|
|
105
|
private PagerAdapter mAdapter;
|
|
|
106
|
private int mCurItem; // Index of currently displayed page.
|
|
|
107
|
private int mRestoredCurItem = -1;
|
|
|
108
|
private Parcelable mRestoredAdapterState = null;
|
|
|
109
|
private ClassLoader mRestoredClassLoader = null;
|
|
|
110
|
private Scroller mScroller;
|
|
|
111
|
private PagerObserver mObserver;
|
|
|
112
|
|
|
|
113
|
private int mPageMargin;
|
|
|
114
|
private Drawable mMarginDrawable;
|
|
|
115
|
private int mLeftPageBounds;
|
|
|
116
|
private int mRightPageBounds;
|
|
|
117
|
|
|
|
118
|
// Offsets of the first and last items, if known.
|
|
|
119
|
// Set during population, used to determine if we are at the beginning
|
|
|
120
|
// or end of the pager data set during touch scrolling.
|
|
|
121
|
private float mFirstOffset = -Float.MAX_VALUE;
|
|
|
122
|
private float mLastOffset = Float.MAX_VALUE;
|
|
|
123
|
|
|
|
124
|
private int mChildWidthMeasureSpec;
|
|
|
125
|
private int mChildHeightMeasureSpec;
|
|
|
126
|
private boolean mInLayout;
|
|
|
127
|
|
|
|
128
|
private boolean mScrollingCacheEnabled;
|
|
|
129
|
|
|
|
130
|
private boolean mPopulatePending;
|
|
|
131
|
private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;
|
|
|
132
|
|
|
|
133
|
private boolean mIsBeingDragged;
|
|
|
134
|
private boolean mIsUnableToDrag;
|
|
|
135
|
private boolean mIgnoreGutter;
|
|
|
136
|
private int mDefaultGutterSize;
|
|
|
137
|
private int mGutterSize;
|
|
|
138
|
private int mTouchSlop;
|
|
|
139
|
/**
|
|
|
140
|
* Position of the last motion event.
|
|
|
141
|
*/
|
|
|
142
|
private float mLastMotionX;
|
|
|
143
|
private float mLastMotionY;
|
|
|
144
|
private float mInitialMotionX;
|
|
|
145
|
private float mInitialMotionY;
|
|
|
146
|
/**
|
|
|
147
|
* ID of the active pointer. This is used to retain consistency during
|
|
|
148
|
* drags/flings if multiple pointers are used.
|
|
|
149
|
*/
|
|
|
150
|
private int mActivePointerId = INVALID_POINTER;
|
|
|
151
|
/**
|
|
|
152
|
* Sentinel value for no current active pointer.
|
|
|
153
|
* Used by {@link #mActivePointerId}.
|
|
|
154
|
*/
|
|
|
155
|
private static final int INVALID_POINTER = -1;
|
|
|
156
|
|
|
|
157
|
/**
|
|
|
158
|
* Determines speed during touch scrolling
|
|
|
159
|
*/
|
|
|
160
|
private VelocityTracker mVelocityTracker;
|
|
|
161
|
private int mMinimumVelocity;
|
|
|
162
|
private int mMaximumVelocity;
|
|
|
163
|
private int mFlingDistance;
|
|
|
164
|
private int mCloseEnough;
|
|
|
165
|
|
|
|
166
|
// If the pager is at least this close to its final position, complete the scroll
|
|
|
167
|
// on touch down and let the user interact with the content inside instead of
|
|
|
168
|
// "catching" the flinging pager.
|
|
|
169
|
private static final int CLOSE_ENOUGH = 2; // dp
|
|
|
170
|
|
|
|
171
|
private boolean mFakeDragging;
|
|
|
172
|
private long mFakeDragBeginTime;
|
|
|
173
|
|
|
|
174
|
private EdgeEffectCompat mTopEdge;
|
|
|
175
|
private EdgeEffectCompat mBottomEdge;
|
|
|
176
|
|
|
|
177
|
private boolean mFirstLayout = true;
|
|
|
178
|
private boolean mNeedCalculatePageOffsets = false;
|
|
|
179
|
private boolean mCalledSuper;
|
|
|
180
|
private int mDecorChildCount;
|
|
|
181
|
|
|
|
182
|
private ViewPager.OnPageChangeListener mOnPageChangeListener;
|
|
|
183
|
private ViewPager.OnPageChangeListener mInternalPageChangeListener;
|
|
|
184
|
private OnAdapterChangeListener mAdapterChangeListener;
|
|
|
185
|
private ViewPager.PageTransformer mPageTransformer;
|
|
|
186
|
private Method mSetChildrenDrawingOrderEnabled;
|
|
|
187
|
|
|
|
188
|
private static final int DRAW_ORDER_DEFAULT = 0;
|
|
|
189
|
private static final int DRAW_ORDER_FORWARD = 1;
|
|
|
190
|
private static final int DRAW_ORDER_REVERSE = 2;
|
|
|
191
|
private int mDrawingOrder;
|
|
|
192
|
private ArrayList<View> mDrawingOrderedChildren;
|
|
|
193
|
private static final ViewPositionComparator sPositionComparator = new ViewPositionComparator();
|
|
|
194
|
|
|
|
195
|
/**
|
|
|
196
|
* Indicates that the pager is in an idle, settled state. The current page
|
|
|
197
|
* is fully in view and no animation is in progress.
|
|
|
198
|
*/
|
|
|
199
|
public static final int SCROLL_STATE_IDLE = 0;
|
|
|
200
|
|
|
|
201
|
/**
|
|
|
202
|
* Indicates that the pager is currently being dragged by the user.
|
|
|
203
|
*/
|
|
|
204
|
public static final int SCROLL_STATE_DRAGGING = 1;
|
|
|
205
|
|
|
|
206
|
/**
|
|
|
207
|
* Indicates that the pager is in the process of settling to a final position.
|
|
|
208
|
*/
|
|
|
209
|
public static final int SCROLL_STATE_SETTLING = 2;
|
|
|
210
|
|
|
|
211
|
private final Runnable mEndScrollRunnable = new Runnable() {
|
|
|
212
|
@Override
|
|
|
213
|
public void run() {
|
|
|
214
|
setScrollState(SCROLL_STATE_IDLE);
|
|
|
215
|
populate();
|
|
|
216
|
}
|
|
|
217
|
};
|
|
|
218
|
|
|
|
219
|
private int mScrollState = SCROLL_STATE_IDLE;
|
|
|
220
|
|
|
|
221
|
/**
|
|
|
222
|
* Used internally to monitor when adapters are switched.
|
|
|
223
|
*/
|
|
|
224
|
interface OnAdapterChangeListener {
|
|
|
225
|
public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter);
|
|
|
226
|
}
|
|
|
227
|
|
|
|
228
|
/**
|
|
|
229
|
* Used internally to tag special types of child views that should be added as
|
|
|
230
|
* pager decorations by default.
|
|
|
231
|
*/
|
|
|
232
|
interface Decor {
|
|
|
233
|
}
|
|
|
234
|
|
|
|
235
|
public VerticalViewPager(Context context) {
|
|
|
236
|
super(context);
|
|
|
237
|
initViewPager();
|
|
|
238
|
}
|
|
|
239
|
|
|
|
240
|
public VerticalViewPager(Context context, AttributeSet attrs) {
|
|
|
241
|
super(context, attrs);
|
|
|
242
|
initViewPager();
|
|
|
243
|
}
|
|
|
244
|
|
|
|
245
|
void initViewPager() {
|
|
|
246
|
setWillNotDraw(false);
|
|
|
247
|
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
|
|
|
248
|
setFocusable(true);
|
|
|
249
|
final Context context = getContext();
|
|
|
250
|
mScroller = new Scroller(context, sInterpolator);
|
|
|
251
|
final ViewConfiguration configuration = ViewConfiguration.get(context);
|
|
|
252
|
final float density = context.getResources().getDisplayMetrics().density;
|
|
|
253
|
|
|
|
254
|
mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
|
|
|
255
|
mMinimumVelocity = (int) (MIN_FLING_VELOCITY * density);
|
|
|
256
|
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
|
|
|
257
|
mTopEdge = new EdgeEffectCompat(context);
|
|
|
258
|
mBottomEdge = new EdgeEffectCompat(context);
|
|
|
259
|
|
|
|
260
|
mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);
|
|
|
261
|
mCloseEnough = (int) (CLOSE_ENOUGH * density);
|
|
|
262
|
mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density);
|
|
|
263
|
|
|
|
264
|
ViewCompat.setAccessibilityDelegate(this, new MyAccessibilityDelegate());
|
|
|
265
|
|
|
|
266
|
if (ViewCompat.getImportantForAccessibility(this)
|
|
|
267
|
== ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
|
|
|
268
|
ViewCompat.setImportantForAccessibility(this,
|
|
|
269
|
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
|
|
|
270
|
}
|
|
|
271
|
}
|
|
|
272
|
|
|
|
273
|
@Override
|
|
|
274
|
protected void onDetachedFromWindow() {
|
|
|
275
|
removeCallbacks(mEndScrollRunnable);
|
|
|
276
|
super.onDetachedFromWindow();
|
|
|
277
|
}
|
|
|
278
|
|
|
|
279
|
private void setScrollState(int newState) {
|
|
|
280
|
if (mScrollState == newState) {
|
|
|
281
|
return;
|
|
|
282
|
}
|
|
|
283
|
|
|
|
284
|
mScrollState = newState;
|
|
|
285
|
if (mPageTransformer != null) {
|
|
|
286
|
// PageTransformers can do complex things that benefit from hardware layers.
|
|
|
287
|
enableLayers(newState != SCROLL_STATE_IDLE);
|
|
|
288
|
}
|
|
|
289
|
if (mOnPageChangeListener != null) {
|
|
|
290
|
mOnPageChangeListener.onPageScrollStateChanged(newState);
|
|
|
291
|
}
|
|
|
292
|
}
|
|
|
293
|
|
|
|
294
|
/**
|
|
|
295
|
* Set a PagerAdapter that will supply views for this pager as needed.
|
|
|
296
|
*
|
|
|
297
|
* @param adapter Adapter to use
|
|
|
298
|
*/
|
|
|
299
|
public void setAdapter(PagerAdapter adapter) {
|
|
|
300
|
if (mAdapter != null) {
|
|
|
301
|
mAdapter.unregisterDataSetObserver(mObserver);
|
|
|
302
|
mAdapter.startUpdate(this);
|
|
|
303
|
for (int i = 0; i < mItems.size(); i++) {
|
|
|
304
|
final ItemInfo ii = mItems.get(i);
|
|
|
305
|
mAdapter.destroyItem(this, ii.position, ii.object);
|
|
|
306
|
}
|
|
|
307
|
mAdapter.finishUpdate(this);
|
|
|
308
|
mItems.clear();
|
|
|
309
|
removeNonDecorViews();
|
|
|
310
|
mCurItem = 0;
|
|
|
311
|
scrollTo(0, 0);
|
|
|
312
|
}
|
|
|
313
|
|
|
|
314
|
final PagerAdapter oldAdapter = mAdapter;
|
|
|
315
|
mAdapter = adapter;
|
|
|
316
|
mExpectedAdapterCount = 0;
|
|
|
317
|
|
|
|
318
|
if (mAdapter != null) {
|
|
|
319
|
if (mObserver == null) {
|
|
|
320
|
mObserver = new PagerObserver();
|
|
|
321
|
}
|
|
|
322
|
mAdapter.registerDataSetObserver(mObserver);
|
|
|
323
|
mPopulatePending = false;
|
|
|
324
|
final boolean wasFirstLayout = mFirstLayout;
|
|
|
325
|
mFirstLayout = true;
|
|
|
326
|
mExpectedAdapterCount = mAdapter.getCount();
|
|
|
327
|
if (mRestoredCurItem >= 0) {
|
|
|
328
|
mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
|
|
|
329
|
setCurrentItemInternal(mRestoredCurItem, false, true);
|
|
|
330
|
mRestoredCurItem = -1;
|
|
|
331
|
mRestoredAdapterState = null;
|
|
|
332
|
mRestoredClassLoader = null;
|
|
|
333
|
} else if (!wasFirstLayout) {
|
|
|
334
|
populate();
|
|
|
335
|
} else {
|
|
|
336
|
requestLayout();
|
|
|
337
|
}
|
|
|
338
|
}
|
|
|
339
|
|
|
|
340
|
if (mAdapterChangeListener != null && oldAdapter != adapter) {
|
|
|
341
|
mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter);
|
|
|
342
|
}
|
|
|
343
|
}
|
|
|
344
|
|
|
|
345
|
private void removeNonDecorViews() {
|
|
|
346
|
for (int i = 0; i < getChildCount(); i++) {
|
|
|
347
|
final View child = getChildAt(i);
|
|
|
348
|
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
|
|
|
349
|
if (!lp.isDecor) {
|
|
|
350
|
removeViewAt(i);
|
|
|
351
|
i--;
|
|
|
352
|
}
|
|
|
353
|
}
|
|
|
354
|
}
|
|
|
355
|
|
|
|
356
|
/**
|
|
|
357
|
* Retrieve the current adapter supplying pages.
|
|
|
358
|
*
|
|
|
359
|
* @return The currently registered PagerAdapter
|
|
|
360
|
*/
|
|
|
361
|
public PagerAdapter getAdapter() {
|
|
|
362
|
return mAdapter;
|
|
|
363
|
}
|
|
|
364
|
|
|
|
365
|
void setOnAdapterChangeListener(OnAdapterChangeListener listener) {
|
|
|
366
|
mAdapterChangeListener = listener;
|
|
|
367
|
}
|
|
|
368
|
|
|
|
369
|
// private int getClientWidth() {
|
|
|
370
|
// return getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
|
|
|
371
|
// }
|
|
|
372
|
|
|
|
373
|
private int getClientHeight() {
|
|
|
374
|
return getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
|
|
|
375
|
}
|
|
|
376
|
|
|
|
377
|
|
|
|
378
|
/**
|
|
|
379
|
* Set the currently selected page. If the ViewPager has already been through its first
|
|
|
380
|
* layout with its current adapter there will be a smooth animated transition between
|
|
|
381
|
* the current item and the specified item.
|
|
|
382
|
*
|
|
|
383
|
* @param item Item index to select
|
|
|
384
|
*/
|
|
|
385
|
public void setCurrentItem(int item) {
|
|
|
386
|
mPopulatePending = false;
|
|
|
387
|
setCurrentItemInternal(item, !mFirstLayout, false);
|
|
|
388
|
}
|
|
|
389
|
|
|
|
390
|
/**
|
|
|
391
|
* Set the currently selected page.
|
|
|
392
|
*
|
|
|
393
|
* @param item Item index to select
|
|
|
394
|
* @param smoothScroll True to smoothly scroll to the new item, false to transition immediately
|
|
|
395
|
*/
|
|
|
396
|
public void setCurrentItem(int item, boolean smoothScroll) {
|
|
|
397
|
mPopulatePending = false;
|
|
|
398
|
setCurrentItemInternal(item, smoothScroll, false);
|
|
|
399
|
}
|
|
|
400
|
|
|
|
401
|
public int getCurrentItem() {
|
|
|
402
|
return mCurItem;
|
|
|
403
|
}
|
|
|
404
|
|
|
|
405
|
void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
|
|
|
406
|
setCurrentItemInternal(item, smoothScroll, always, 0);
|
|
|
407
|
}
|
|
|
408
|
|
|
|
409
|
void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
|
|
|
410
|
if (mAdapter == null || mAdapter.getCount() <= 0) {
|
|
|
411
|
setScrollingCacheEnabled(false);
|
|
|
412
|
return;
|
|
|
413
|
}
|
|
|
414
|
if (!always && mCurItem == item && mItems.size() != 0) {
|
|
|
415
|
setScrollingCacheEnabled(false);
|
|
|
416
|
return;
|
|
|
417
|
}
|
|
|
418
|
|
|
|
419
|
if (item < 0) {
|
|
|
420
|
item = 0;
|
|
|
421
|
} else if (item >= mAdapter.getCount()) {
|
|
|
422
|
item = mAdapter.getCount() - 1;
|
|
|
423
|
}
|
|
|
424
|
final int pageLimit = mOffscreenPageLimit;
|
|
|
425
|
if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
|
|
|
426
|
// We are doing a jump by more than one page. To avoid
|
|
|
427
|
// glitches, we want to keep all current pages in the view
|
|
|
428
|
// until the scroll ends.
|
|
|
429
|
for (int i = 0; i < mItems.size(); i++) {
|
|
|
430
|
mItems.get(i).scrolling = true;
|
|
|
431
|
}
|
|
|
432
|
}
|
|
|
433
|
final boolean dispatchSelected = mCurItem != item;
|
|
|
434
|
|
|
|
435
|
if (mFirstLayout) {
|
|
|
436
|
// We don't have any idea how big we are yet and shouldn't have any pages either.
|
|
|
437
|
// Just set things up and let the pending layout handle things.
|
|
|
438
|
mCurItem = item;
|
|
|
439
|
if (dispatchSelected && mOnPageChangeListener != null) {
|
|
|
440
|
mOnPageChangeListener.onPageSelected(item);
|
|
|
441
|
}
|
|
|
442
|
if (dispatchSelected && mInternalPageChangeListener != null) {
|
|
|
443
|
mInternalPageChangeListener.onPageSelected(item);
|
|
|
444
|
}
|
|
|
445
|
requestLayout();
|
|
|
446
|
} else {
|
|
|
447
|
populate(item);
|
|
|
448
|
scrollToItem(item, smoothScroll, velocity, dispatchSelected);
|
|
|
449
|
}
|
|
|
450
|
}
|
|
|
451
|
|
|
|
452
|
private void scrollToItem(int item, boolean smoothScroll, int velocity,
|
|
|
453
|
boolean dispatchSelected) {
|
|
|
454
|
final ItemInfo curInfo = infoForPosition(item);
|
|
|
455
|
int destY = 0;
|
|
|
456
|
if (curInfo != null) {
|
|
|
457
|
final int height = getClientHeight();
|
|
|
458
|
destY = (int) (height * Math.max(mFirstOffset,
|
|
|
459
|
Math.min(curInfo.offset, mLastOffset)));
|
|
|
460
|
}
|
|
|
461
|
if (smoothScroll) {
|
|
|
462
|
smoothScrollTo(0, destY, velocity);
|
|
|
463
|
if (dispatchSelected && mOnPageChangeListener != null) {
|
|
|
464
|
mOnPageChangeListener.onPageSelected(item);
|
|
|
465
|
}
|
|
|
466
|
if (dispatchSelected && mInternalPageChangeListener != null) {
|
|
|
467
|
mInternalPageChangeListener.onPageSelected(item);
|
|
|
468
|
}
|
|
|
469
|
} else {
|
|
|
470
|
if (dispatchSelected && mOnPageChangeListener != null) {
|
|
|
471
|
mOnPageChangeListener.onPageSelected(item);
|
|
|
472
|
}
|
|
|
473
|
if (dispatchSelected && mInternalPageChangeListener != null) {
|
|
|
474
|
mInternalPageChangeListener.onPageSelected(item);
|
|
|
475
|
}
|
|
|
476
|
completeScroll(false);
|
|
|
477
|
scrollTo(0, destY);
|
|
|
478
|
pageScrolled(destY);
|
|
|
479
|
}
|
|
|
480
|
}
|
|
|
481
|
|
|
|
482
|
/**
|
|
|
483
|
* Set a listener that will be invoked whenever the page changes or is incrementally
|
|
|
484
|
* scrolled. See {@link ViewPager.OnPageChangeListener}.
|
|
|
485
|
*
|
|
|
486
|
* @param listener Listener to set
|
|
|
487
|
*/
|
|
|
488
|
public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
|
|
|
489
|
mOnPageChangeListener = listener;
|
|
|
490
|
}
|
|
|
491
|
|
|
|
492
|
/**
|
|
|
493
|
* Set a {@link ViewPager.PageTransformer} that will be called for each attached page whenever
|
|
|
494
|
* the scroll position is changed. This allows the application to apply custom property
|
|
|
495
|
* transformations to each page, overriding the default sliding look and feel.
|
|
|
496
|
* <p/>
|
|
|
497
|
* <p><em>Note:</em> Prior to Android 3.0 the property animation APIs did not exist.
|
|
|
498
|
* As a result, setting a PageTransformer prior to Android 3.0 (API 11) will have no effect.</p>
|
|
|
499
|
*
|
|
|
500
|
* @param reverseDrawingOrder true if the supplied PageTransformer requires page views
|
|
|
501
|
* to be drawn from last to first instead of first to last.
|
|
|
502
|
* @param transformer PageTransformer that will modify each page's animation properties
|
|
|
503
|
*/
|
|
|
504
|
public void setPageTransformer(boolean reverseDrawingOrder, ViewPager.PageTransformer transformer) {
|
|
|
505
|
if (Build.VERSION.SDK_INT >= 11) {
|
|
|
506
|
final boolean hasTransformer = transformer != null;
|
|
|
507
|
final boolean needsPopulate = hasTransformer != (mPageTransformer != null);
|
|
|
508
|
mPageTransformer = transformer;
|
|
|
509
|
setChildrenDrawingOrderEnabledCompat(hasTransformer);
|
|
|
510
|
if (hasTransformer) {
|
|
|
511
|
mDrawingOrder = reverseDrawingOrder ? DRAW_ORDER_REVERSE : DRAW_ORDER_FORWARD;
|
|
|
512
|
} else {
|
|
|
513
|
mDrawingOrder = DRAW_ORDER_DEFAULT;
|
|
|
514
|
}
|
|
|
515
|
if (needsPopulate) populate();
|
|
|
516
|
}
|
|
|
517
|
}
|
|
|
518
|
|
|
|
519
|
void setChildrenDrawingOrderEnabledCompat(boolean enable) {
|
|
|
520
|
if (Build.VERSION.SDK_INT >= 7) {
|
|
|
521
|
if (mSetChildrenDrawingOrderEnabled == null) {
|
|
|
522
|
try {
|
|
|
523
|
mSetChildrenDrawingOrderEnabled = ViewGroup.class.getDeclaredMethod(
|
|
|
524
|
"setChildrenDrawingOrderEnabled", new Class[]{Boolean.TYPE});
|
|
|
525
|
} catch (NoSuchMethodException e) {
|
|
|
526
|
Log.e(TAG, "Can't find setChildrenDrawingOrderEnabled", e);
|
|
|
527
|
}
|
|
|
528
|
}
|
|
|
529
|
try {
|
|
|
530
|
mSetChildrenDrawingOrderEnabled.invoke(this, enable);
|
|
|
531
|
} catch (Exception e) {
|
|
|
532
|
Log.e(TAG, "Error changing children drawing order", e);
|
|
|
533
|
}
|
|
|
534
|
}
|
|
|
535
|
}
|
|
|
536
|
|
|
|
537
|
@Override
|
|
|
538
|
protected int getChildDrawingOrder(int childCount, int i) {
|
|
|
539
|
final int index = mDrawingOrder == DRAW_ORDER_REVERSE ? childCount - 1 - i : i;
|
|
|
540
|
final int result = ((LayoutParams) mDrawingOrderedChildren.get(index).getLayoutParams()).childIndex;
|
|
|
541
|
return result;
|
|
|
542
|
}
|
|
|
543
|
|
|
|
544
|
/**
|
|
|
545
|
* Set a separate OnPageChangeListener for internal use by the support library.
|
|
|
546
|
*
|
|
|
547
|
* @param listener Listener to set
|
|
|
548
|
* @return The old listener that was set, if any.
|
|
|
549
|
*/
|
|
|
550
|
ViewPager.OnPageChangeListener setInternalPageChangeListener(ViewPager.OnPageChangeListener listener) {
|
|
|
551
|
ViewPager.OnPageChangeListener oldListener = mInternalPageChangeListener;
|
|
|
552
|
mInternalPageChangeListener = listener;
|
|
|
553
|
return oldListener;
|
|
|
554
|
}
|
|
|
555
|
|
|
|
556
|
/**
|
|
|
557
|
* Returns the number of pages that will be retained to either side of the
|
|
|
558
|
* current page in the view hierarchy in an idle state. Defaults to 1.
|
|
|
559
|
*
|
|
|
560
|
* @return How many pages will be kept offscreen on either side
|
|
|
561
|
* @see #setOffscreenPageLimit(int)
|
|
|
562
|
*/
|
|
|
563
|
public int getOffscreenPageLimit() {
|
|
|
564
|
return mOffscreenPageLimit;
|
|
|
565
|
}
|
|
|
566
|
|
|
|
567
|
/**
|
|
|
568
|
* Set the number of pages that should be retained to either side of the
|
|
|
569
|
* current page in the view hierarchy in an idle state. Pages beyond this
|
|
|
570
|
* limit will be recreated from the adapter when needed.
|
|
|
571
|
* <p/>
|
|
|
572
|
* <p>This is offered as an optimization. If you know in advance the number
|
|
|
573
|
* of pages you will need to support or have lazy-loading mechanisms in place
|
|
|
574
|
* on your pages, tweaking this setting can have benefits in perceived smoothness
|
|
|
575
|
* of paging animations and interaction. If you have a small number of pages (3-4)
|
|
|
576
|
* that you can keep active all at once, less time will be spent in layout for
|
|
|
577
|
* newly created view subtrees as the user pages back and forth.</p>
|
|
|
578
|
* <p/>
|
|
|
579
|
* <p>You should keep this limit low, especially if your pages have complex layouts.
|
|
|
580
|
* This setting defaults to 1.</p>
|
|
|
581
|
*
|
|
|
582
|
* @param limit How many pages will be kept offscreen in an idle state.
|
|
|
583
|
*/
|
|
|
584
|
public void setOffscreenPageLimit(int limit) {
|
|
|
585
|
if (limit < DEFAULT_OFFSCREEN_PAGES) {
|
|
|
586
|
Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
|
|
|
587
|
DEFAULT_OFFSCREEN_PAGES);
|
|
|
588
|
limit = DEFAULT_OFFSCREEN_PAGES;
|
|
|
589
|
}
|
|
|
590
|
if (limit != mOffscreenPageLimit) {
|
|
|
591
|
mOffscreenPageLimit = limit;
|
|
|
592
|
populate();
|
|
|
593
|
}
|
|
|
594
|
}
|
|
|
595
|
|
|
|
596
|
/**
|
|
|
597
|
* Set the margin between pages.
|
|
|
598
|
*
|
|
|
599
|
* @param marginPixels Distance between adjacent pages in pixels
|
|
|
600
|
* @see #getPageMargin()
|
|
|
601
|
* @see #setPageMarginDrawable(Drawable)
|
|
|
602
|
* @see #setPageMarginDrawable(int)
|
|
|
603
|
*/
|
|
|
604
|
public void setPageMargin(int marginPixels) {
|
|
|
605
|
final int oldMargin = mPageMargin;
|
|
|
606
|
mPageMargin = marginPixels;
|
|
|
607
|
|
|
|
608
|
final int height = getHeight();
|
|
|
609
|
recomputeScrollPosition(height, height, marginPixels, oldMargin);
|
|
|
610
|
|
|
|
611
|
requestLayout();
|
|
|
612
|
}
|
|
|
613
|
|
|
|
614
|
/**
|
|
|
615
|
* Return the margin between pages.
|
|
|
616
|
*
|
|
|
617
|
* @return The size of the margin in pixels
|
|
|
618
|
*/
|
|
|
619
|
public int getPageMargin() {
|
|
|
620
|
return mPageMargin;
|
|
|
621
|
}
|
|
|
622
|
|
|
|
623
|
/**
|
|
|
624
|
* Set a drawable that will be used to fill the margin between pages.
|
|
|
625
|
*
|
|
|
626
|
* @param d Drawable to display between pages
|
|
|
627
|
*/
|
|
|
628
|
public void setPageMarginDrawable(Drawable d) {
|
|
|
629
|
mMarginDrawable = d;
|
|
|
630
|
if (d != null) refreshDrawableState();
|
|
|
631
|
setWillNotDraw(d == null);
|
|
|
632
|
invalidate();
|
|
|
633
|
}
|
|
|
634
|
|
|
|
635
|
/**
|
|
|
636
|
* Set a drawable that will be used to fill the margin between pages.
|
|
|
637
|
*
|
|
|
638
|
* @param resId Resource ID of a drawable to display between pages
|
|
|
639
|
*/
|
|
|
640
|
public void setPageMarginDrawable(int resId) {
|
|
|
641
|
setPageMarginDrawable(getContext().getResources().getDrawable(resId));
|
|
|
642
|
}
|
|
|
643
|
|
|
|
644
|
@Override
|
|
|
645
|
protected boolean verifyDrawable(Drawable who) {
|
|
|
646
|
return super.verifyDrawable(who) || who == mMarginDrawable;
|
|
|
647
|
}
|
|
|
648
|
|
|
|
649
|
@Override
|
|
|
650
|
protected void drawableStateChanged() {
|
|
|
651
|
super.drawableStateChanged();
|
|
|
652
|
final Drawable d = mMarginDrawable;
|
|
|
653
|
if (d != null && d.isStateful()) {
|
|
|
654
|
d.setState(getDrawableState());
|
|
|
655
|
}
|
|
|
656
|
}
|
|
|
657
|
|
|
|
658
|
// We want the duration of the page snap animation to be influenced by the distance that
|
|
|
659
|
// the screen has to travel, however, we don't want this duration to be effected in a
|
|
|
660
|
// purely linear fashion. Instead, we use this method to moderate the effect that the distance
|
|
|
661
|
// of travel has on the overall snap duration.
|
|
|
662
|
float distanceInfluenceForSnapDuration(float f) {
|
|
|
663
|
f -= 0.5f; // center the values about 0.
|
|
|
664
|
f *= 0.3f * Math.PI / 2.0f;
|
|
|
665
|
return (float) Math.sin(f);
|
|
|
666
|
}
|
|
|
667
|
|
|
|
668
|
/**
|
|
|
669
|
* Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
|
|
|
670
|
*
|
|
|
671
|
* @param x the number of pixels to scroll by on the X axis
|
|
|
672
|
* @param y the number of pixels to scroll by on the Y axis
|
|
|
673
|
*/
|
|
|
674
|
void smoothScrollTo(int x, int y) {
|
|
|
675
|
smoothScrollTo(x, y, 0);
|
|
|
676
|
}
|
|
|
677
|
|
|
|
678
|
/**
|
|
|
679
|
* Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
|
|
|
680
|
*
|
|
|
681
|
* @param x the number of pixels to scroll by on the X axis
|
|
|
682
|
* @param y the number of pixels to scroll by on the Y axis
|
|
|
683
|
* @param velocity the velocity associated with a fling, if applicable. (0 otherwise)
|
|
|
684
|
*/
|
|
|
685
|
void smoothScrollTo(int x, int y, int velocity) {
|
|
|
686
|
if (getChildCount() == 0) {
|
|
|
687
|
// Nothing to do.
|
|
|
688
|
setScrollingCacheEnabled(false);
|
|
|
689
|
return;
|
|
|
690
|
}
|
|
|
691
|
int sx = getScrollX();
|
|
|
692
|
int sy = getScrollY();
|
|
|
693
|
int dx = x - sx;
|
|
|
694
|
int dy = y - sy;
|
|
|
695
|
if (dx == 0 && dy == 0) {
|
|
|
696
|
completeScroll(false);
|
|
|
697
|
populate();
|
|
|
698
|
setScrollState(SCROLL_STATE_IDLE);
|
|
|
699
|
return;
|
|
|
700
|
}
|
|
|
701
|
|
|
|
702
|
setScrollingCacheEnabled(true);
|
|
|
703
|
setScrollState(SCROLL_STATE_SETTLING);
|
|
|
704
|
|
|
|
705
|
final int height = getClientHeight();
|
|
|
706
|
final int halfHeight = height / 2;
|
|
|
707
|
final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / height);
|
|
|
708
|
final float distance = halfHeight + halfHeight *
|
|
|
709
|
distanceInfluenceForSnapDuration(distanceRatio);
|
|
|
710
|
|
|
|
711
|
int duration = 0;
|
|
|
712
|
velocity = Math.abs(velocity);
|
|
|
713
|
if (velocity > 0) {
|
|
|
714
|
duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
|
|
|
715
|
} else {
|
|
|
716
|
final float pageHeight = height * mAdapter.getPageWidth(mCurItem);
|
|
|
717
|
final float pageDelta = (float) Math.abs(dx) / (pageHeight + mPageMargin);
|
|
|
718
|
duration = (int) ((pageDelta + 1) * 100);
|
|
|
719
|
}
|
|
|
720
|
duration = Math.min(duration, MAX_SETTLE_DURATION);
|
|
|
721
|
|
|
|
722
|
mScroller.startScroll(sx, sy, dx, dy, duration);
|
|
|
723
|
ViewCompat.postInvalidateOnAnimation(this);
|
|
|
724
|
}
|
|
|
725
|
|
|
|
726
|
ItemInfo addNewItem(int position, int index) {
|
|
|
727
|
ItemInfo ii = new ItemInfo();
|
|
|
728
|
ii.position = position;
|
|
|
729
|
ii.object = mAdapter.instantiateItem(this, position);
|
|
|
730
|
ii.heightFactor = mAdapter.getPageWidth(position);
|
|
|
731
|
if (index < 0 || index >= mItems.size()) {
|
|
|
732
|
mItems.add(ii);
|
|
|
733
|
} else {
|
|
|
734
|
mItems.add(index, ii);
|
|
|
735
|
}
|
|
|
736
|
return ii;
|
|
|
737
|
}
|
|
|
738
|
|
|
|
739
|
void dataSetChanged() {
|
|
|
740
|
// This method only gets called if our observer is attached, so mAdapter is non-null.
|
|
|
741
|
|
|
|
742
|
final int adapterCount = mAdapter.getCount();
|
|
|
743
|
mExpectedAdapterCount = adapterCount;
|
|
|
744
|
boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 &&
|
|
|
745
|
mItems.size() < adapterCount;
|
|
|
746
|
int newCurrItem = mCurItem;
|
|
|
747
|
|
|
|
748
|
boolean isUpdating = false;
|
|
|
749
|
for (int i = 0; i < mItems.size(); i++) {
|
|
|
750
|
final ItemInfo ii = mItems.get(i);
|
|
|
751
|
final int newPos = mAdapter.getItemPosition(ii.object);
|
|
|
752
|
|
|
|
753
|
if (newPos == PagerAdapter.POSITION_UNCHANGED) {
|
|
|
754
|
continue;
|
|
|
755
|
}
|
|
|
756
|
|
|
|
757
|
if (newPos == PagerAdapter.POSITION_NONE) {
|
|
|
758
|
mItems.remove(i);
|
|
|
759
|
i--;
|
|
|
760
|
|
|
|
761
|
if (!isUpdating) {
|
|
|
762
|
mAdapter.startUpdate(this);
|
|
|
763
|
isUpdating = true;
|
|
|
764
|
}
|
|
|
765
|
|
|
|
766
|
mAdapter.destroyItem(this, ii.position, ii.object);
|
|
|
767
|
needPopulate = true;
|
|
|
768
|
|
|
|
769
|
if (mCurItem == ii.position) {
|
|
|
770
|
// Keep the current item in the valid range
|
|
|
771
|
newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1));
|
|
|
772
|
needPopulate = true;
|
|
|
773
|
}
|
|
|
774
|
continue;
|
|
|
775
|
}
|
|
|
776
|
|
|
|
777
|
if (ii.position != newPos) {
|
|
|
778
|
if (ii.position == mCurItem) {
|
|
|
779
|
// Our current item changed position. Follow it.
|
|
|
780
|
newCurrItem = newPos;
|
|
|
781
|
}
|
|
|
782
|
|
|
|
783
|
ii.position = newPos;
|
|
|
784
|
needPopulate = true;
|
|
|
785
|
}
|
|
|
786
|
}
|
|
|
787
|
|
|
|
788
|
if (isUpdating) {
|
|
|
789
|
mAdapter.finishUpdate(this);
|
|
|
790
|
}
|
|
|
791
|
|
|
|
792
|
Collections.sort(mItems, COMPARATOR);
|
|
|
793
|
|
|
|
794
|
if (needPopulate) {
|
|
|
795
|
// Reset our known page widths; populate will recompute them.
|
|
|
796
|
final int childCount = getChildCount();
|
|
|
797
|
for (int i = 0; i < childCount; i++) {
|
|
|
798
|
final View child = getChildAt(i);
|
|
|
799
|
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
|
|
|
800
|
if (!lp.isDecor) {
|
|
|
801
|
lp.heightFactor = 0.f;
|
|
|
802
|
}
|
|
|
803
|
}
|
|
|
804
|
|
|
|
805
|
setCurrentItemInternal(newCurrItem, false, true);
|
|
|
806
|
requestLayout();
|
|
|
807
|
}
|
|
|
808
|
}
|
|
|
809
|
|
|
|
810
|
void populate() {
|
|
|
811
|
populate(mCurItem);
|
|
|
812
|
}
|
|
|
813
|
|
|
|
814
|
void populate(int newCurrentItem) {
|
|
|
815
|
ItemInfo oldCurInfo = null;
|
|
|
816
|
int focusDirection = View.FOCUS_FORWARD;
|
|
|
817
|
if (mCurItem != newCurrentItem) {
|
|
|
818
|
focusDirection = mCurItem < newCurrentItem ? View.FOCUS_DOWN : View.FOCUS_UP;
|
|
|
819
|
oldCurInfo = infoForPosition(mCurItem);
|
|
|
820
|
mCurItem = newCurrentItem;
|
|
|
821
|
}
|
|
|
822
|
|
|
|
823
|
if (mAdapter == null) {
|
|
|
824
|
sortChildDrawingOrder();
|
|
|
825
|
return;
|
|
|
826
|
}
|
|
|
827
|
|
|
|
828
|
// Bail now if we are waiting to populate. This is to hold off
|
|
|
829
|
// on creating views from the time the user releases their finger to
|
|
|
830
|
// fling to a new position until we have finished the scroll to
|
|
|
831
|
// that position, avoiding glitches from happening at that point.
|
|
|
832
|
if (mPopulatePending) {
|
|
|
833
|
if (DEBUG) {
|
|
|
834
|
Log.i(TAG, "populate is pending, skipping for now...");
|
|
|
835
|
}
|
|
|
836
|
sortChildDrawingOrder();
|
|
|
837
|
return;
|
|
|
838
|
}
|
|
|
839
|
|
|
|
840
|
// Also, don't populate until we are attached to a window. This is to
|
|
|
841
|
// avoid trying to populate before we have restored our view hierarchy
|
|
|
842
|
// state and conflicting with what is restored.
|
|
|
843
|
if (getWindowToken() == null) {
|
|
|
844
|
return;
|
|
|
845
|
}
|
|
|
846
|
|
|
|
847
|
mAdapter.startUpdate(this);
|
|
|
848
|
|
|
|
849
|
final int pageLimit = mOffscreenPageLimit;
|
|
|
850
|
final int startPos = Math.max(0, mCurItem - pageLimit);
|
|
|
851
|
final int N = mAdapter.getCount();
|
|
|
852
|
final int endPos = Math.min(N - 1, mCurItem + pageLimit);
|
|
|
853
|
|
|
|
854
|
if (N != mExpectedAdapterCount) {
|
|
|
855
|
String resName;
|
|
|
856
|
try {
|
|
|
857
|
resName = getResources().getResourceName(getId());
|
|
|
858
|
} catch (Resources.NotFoundException e) {
|
|
|
859
|
resName = Integer.toHexString(getId());
|
|
|
860
|
}
|
|
|
861
|
throw new IllegalStateException("The application's PagerAdapter changed the adapter's" +
|
|
|
862
|
" contents without calling PagerAdapter#notifyDataSetChanged!" +
|
|
|
863
|
" Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N +
|
|
|
864
|
" Pager id: " + resName +
|
|
|
865
|
" Pager class: " + getClass() +
|
|
|
866
|
" Problematic adapter: " + mAdapter.getClass());
|
|
|
867
|
}
|
|
|
868
|
|
|
|
869
|
// Locate the currently focused item or add it if needed.
|
|
|
870
|
int curIndex = -1;
|
|
|
871
|
ItemInfo curItem = null;
|
|
|
872
|
for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
|
|
|
873
|
final ItemInfo ii = mItems.get(curIndex);
|
|
|
874
|
if (ii.position >= mCurItem) {
|
|
|
875
|
if (ii.position == mCurItem) curItem = ii;
|
|
|
876
|
break;
|
|
|
877
|
}
|
|
|
878
|
}
|
|
|
879
|
|
|
|
880
|
if (curItem == null && N > 0) {
|
|
|
881
|
curItem = addNewItem(mCurItem, curIndex);
|
|
|
882
|
}
|
|
|
883
|
|
|
|
884
|
// Fill 3x the available width or up to the number of offscreen
|
|
|
885
|
// pages requested to either side, whichever is larger.
|
|
|
886
|
// If we have no current item we have no work to do.
|
|
|
887
|
if (curItem != null) {
|
|
|
888
|
float extraHeightTop = 0.f;
|
|
|
889
|
int itemIndex = curIndex - 1;
|
|
|
890
|
ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
|
|
|
891
|
final int clientHeight = getClientHeight();
|
|
|
892
|
final float topHeightNeeded = clientHeight <= 0 ? 0 :
|
|
|
893
|
2.f - curItem.heightFactor + (float) getPaddingLeft() / (float) clientHeight;
|
|
|
894
|
for (int pos = mCurItem - 1; pos >= 0; pos--) {
|
|
|
895
|
if (extraHeightTop >= topHeightNeeded && pos < startPos) {
|
|
|
896
|
if (ii == null) {
|
|
|
897
|
break;
|
|
|
898
|
}
|
|
|
899
|
if (pos == ii.position && !ii.scrolling) {
|
|
|
900
|
mItems.remove(itemIndex);
|
|
|
901
|
mAdapter.destroyItem(this, pos, ii.object);
|
|
|
902
|
if (DEBUG) {
|
|
|
903
|
Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
|
|
|
904
|
" view: " + ((View) ii.object));
|
|
|
905
|
}
|
|
|
906
|
itemIndex--;
|
|
|
907
|
curIndex--;
|
|
|
908
|
ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
|
|
|
909
|
}
|
|
|
910
|
} else if (ii != null && pos == ii.position) {
|
|
|
911
|
extraHeightTop += ii.heightFactor;
|
|
|
912
|
itemIndex--;
|
|
|
913
|
ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
|
|
|
914
|
} else {
|
|
|
915
|
ii = addNewItem(pos, itemIndex + 1);
|
|
|
916
|
extraHeightTop += ii.heightFactor;
|
|
|
917
|
curIndex++;
|
|
|
918
|
ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
|
|
|
919
|
}
|
|
|
920
|
}
|
|
|
921
|
|
|
|
922
|
float extraHeightBottom = curItem.heightFactor;
|
|
|
923
|
itemIndex = curIndex + 1;
|
|
|
924
|
if (extraHeightBottom < 2.f) {
|
|
|
925
|
ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
|
|
|
926
|
final float bottomHeightNeeded = clientHeight <= 0 ? 0 :
|
|
|
927
|
(float) getPaddingRight() / (float) clientHeight + 2.f;
|
|
|
928
|
for (int pos = mCurItem + 1; pos < N; pos++) {
|
|
|
929
|
if (extraHeightBottom >= bottomHeightNeeded && pos > endPos) {
|
|
|
930
|
if (ii == null) {
|
|
|
931
|
break;
|
|
|
932
|
}
|
|
|
933
|
if (pos == ii.position && !ii.scrolling) {
|
|
|
934
|
mItems.remove(itemIndex);
|
|
|
935
|
mAdapter.destroyItem(this, pos, ii.object);
|
|
|
936
|
if (DEBUG) {
|
|
|
937
|
Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
|
|
|
938
|
" view: " + ((View) ii.object));
|
|
|
939
|
}
|
|
|
940
|
ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
|
|
|
941
|
}
|
|
|
942
|
} else if (ii != null && pos == ii.position) {
|
|
|
943
|
extraHeightBottom += ii.heightFactor;
|
|
|
944
|
itemIndex++;
|
|
|
945
|
ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
|
|
|
946
|
} else {
|
|
|
947
|
ii = addNewItem(pos, itemIndex);
|
|
|
948
|
itemIndex++;
|
|
|
949
|
extraHeightBottom += ii.heightFactor;
|
|
|
950
|
ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
|
|
|
951
|
}
|
|
|
952
|
}
|
|
|
953
|
}
|
|
|
954
|
|
|
|
955
|
calculatePageOffsets(curItem, curIndex, oldCurInfo);
|
|
|
956
|
}
|
|
|
957
|
|
|
|
958
|
if (DEBUG) {
|
|
|
959
|
Log.i(TAG, "Current page list:");
|
|
|
960
|
for (int i = 0; i < mItems.size(); i++) {
|
|
|
961
|
Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);
|
|
|
962
|
}
|
|
|
963
|
}
|
|
|
964
|
|
|
|
965
|
mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
|
|
|
966
|
|
|
|
967
|
mAdapter.finishUpdate(this);
|
|
|
968
|
|
|
|
969
|
// Check width measurement of current pages and drawing sort order.
|
|
|
970
|
// Update LayoutParams as needed.
|
|
|
971
|
final int childCount = getChildCount();
|
|
|
972
|
for (int i = 0; i < childCount; i++) {
|
|
|
973
|
final View child = getChildAt(i);
|
|
|
974
|
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
|
|
|
975
|
lp.childIndex = i;
|
|
|
976
|
if (!lp.isDecor && lp.heightFactor == 0.f) {
|
|
|
977
|
// 0 means requery the adapter for this, it doesn't have a valid width.
|
|
|
978
|
final ItemInfo ii = infoForChild(child);
|
|
|
979
|
if (ii != null) {
|
|
|
980
|
lp.heightFactor = ii.heightFactor;
|
|
|
981
|
lp.position = ii.position;
|
|
|
982
|
}
|
|
|
983
|
}
|
|
|
984
|
}
|
|
|
985
|
sortChildDrawingOrder();
|
|
|
986
|
|
|
|
987
|
if (hasFocus()) {
|
|
|
988
|
View currentFocused = findFocus();
|
|
|
989
|
ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;
|
|
|
990
|
if (ii == null || ii.position != mCurItem) {
|
|
|
991
|
for (int i = 0; i < getChildCount(); i++) {
|
|
|
992
|
View child = getChildAt(i);
|
|
|
993
|
ii = infoForChild(child);
|
|
|
994
|
if (ii != null && ii.position == mCurItem) {
|
|
|
995
|
if (child.requestFocus(focusDirection)) {
|
|
|
996
|
break;
|
|
|
997
|
}
|
|
|
998
|
}
|
|
|
999
|
}
|
|
|
1000
|
}
|
|
|
1001
|
}
|
|
|
1002
|
}
|
|
|
1003
|
|
|
|
1004
|
private void sortChildDrawingOrder() {
|
|
|
1005
|
if (mDrawingOrder != DRAW_ORDER_DEFAULT) {
|
|
|
1006
|
if (mDrawingOrderedChildren == null) {
|
|
|
1007
|
mDrawingOrderedChildren = new ArrayList<View>();
|
|
|
1008
|
} else {
|
|
|
1009
|
mDrawingOrderedChildren.clear();
|
|
|
1010
|
}
|
|
|
1011
|
final int childCount = getChildCount();
|
|
|
1012
|
for (int i = 0; i < childCount; i++) {
|
|
|
1013
|
final View child = getChildAt(i);
|
|
|
1014
|
mDrawingOrderedChildren.add(child);
|
|
|
1015
|
}
|
|
|
1016
|
Collections.sort(mDrawingOrderedChildren, sPositionComparator);
|
|
|
1017
|
}
|
|
|
1018
|
}
|
|
|
1019
|
|
|
|
1020
|
private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) {
|
|
|
1021
|
final int N = mAdapter.getCount();
|
|
|
1022
|
final int height = getClientHeight();
|
|
|
1023
|
final float marginOffset = height > 0 ? (float) mPageMargin / height : 0;
|
|
|
1024
|
// Fix up offsets for later layout.
|
|
|
1025
|
if (oldCurInfo != null) {
|
|
|
1026
|
final int oldCurPosition = oldCurInfo.position;
|
|
|
1027
|
// Base offsets off of oldCurInfo.
|
|
|
1028
|
if (oldCurPosition < curItem.position) {
|
|
|
1029
|
int itemIndex = 0;
|
|
|
1030
|
ItemInfo ii = null;
|
|
|
1031
|
float offset = oldCurInfo.offset + oldCurInfo.heightFactor + marginOffset;
|
|
|
1032
|
for (int pos = oldCurPosition + 1;
|
|
|
1033
|
pos <= curItem.position && itemIndex < mItems.size(); pos++) {
|
|
|
1034
|
ii = mItems.get(itemIndex);
|
|
|
1035
|
while (pos > ii.position && itemIndex < mItems.size() - 1) {
|
|
|
1036
|
itemIndex++;
|
|
|
1037
|
ii = mItems.get(itemIndex);
|
|
|
1038
|
}
|
|
|
1039
|
while (pos < ii.position) {
|
|
|
1040
|
// We don't have an item populated for this,
|
|
|
1041
|
// ask the adapter for an offset.
|
|
|
1042
|
offset += mAdapter.getPageWidth(pos) + marginOffset;
|
|
|
1043
|
pos++;
|
|
|
1044
|
}
|
|
|
1045
|
ii.offset = offset;
|
|
|
1046
|
offset += ii.heightFactor + marginOffset;
|
|
|
1047
|
}
|
|
|
1048
|
} else if (oldCurPosition > curItem.position) {
|
|
|
1049
|
int itemIndex = mItems.size() - 1;
|
|
|
1050
|
ItemInfo ii = null;
|
|
|
1051
|
float offset = oldCurInfo.offset;
|
|
|
1052
|
for (int pos = oldCurPosition - 1;
|
|
|
1053
|
pos >= curItem.position && itemIndex >= 0; pos--) {
|
|
|
1054
|
ii = mItems.get(itemIndex);
|
|
|
1055
|
while (pos < ii.position && itemIndex > 0) {
|
|
|
1056
|
itemIndex--;
|
|
|
1057
|
ii = mItems.get(itemIndex);
|
|
|
1058
|
}
|
|
|
1059
|
while (pos > ii.position) {
|
|
|
1060
|
// We don't have an item populated for this,
|
|
|
1061
|
// ask the adapter for an offset.
|
|
|
1062
|
offset -= mAdapter.getPageWidth(pos) + marginOffset;
|
|
|
1063
|
pos--;
|
|
|
1064
|
}
|
|
|
1065
|
offset -= ii.heightFactor + marginOffset;
|
|
|
1066
|
ii.offset = offset;
|
|
|
1067
|
}
|
|
|
1068
|
}
|
|
|
1069
|
}
|
|
|
1070
|
|
|
|
1071
|
// Base all offsets off of curItem.
|
|
|
1072
|
final int itemCount = mItems.size();
|
|
|
1073
|
float offset = curItem.offset;
|
|
|
1074
|
int pos = curItem.position - 1;
|
|
|
1075
|
mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE;
|
|
|
1076
|
mLastOffset = curItem.position == N - 1 ?
|
|
|
1077
|
curItem.offset + curItem.heightFactor - 1 : Float.MAX_VALUE;
|
|
|
1078
|
// Previous pages
|
|
|
1079
|
for (int i = curIndex - 1; i >= 0; i--, pos--) {
|
|
|
1080
|
final ItemInfo ii = mItems.get(i);
|
|
|
1081
|
while (pos > ii.position) {
|
|
|
1082
|
offset -= mAdapter.getPageWidth(pos--) + marginOffset;
|
|
|
1083
|
}
|
|
|
1084
|
offset -= ii.heightFactor + marginOffset;
|
|
|
1085
|
ii.offset = offset;
|
|
|
1086
|
if (ii.position == 0) {
|
|
|
1087
|
mFirstOffset = offset;
|
|
|
1088
|
}
|
|
|
1089
|
}
|
|
|
1090
|
offset = curItem.offset + curItem.heightFactor + marginOffset;
|
|
|
1091
|
pos = curItem.position + 1;
|
|
|
1092
|
// Next pages
|
|
|
1093
|
for (int i = curIndex + 1; i < itemCount; i++, pos++) {
|
|
|
1094
|
final ItemInfo ii = mItems.get(i);
|
|
|
1095
|
while (pos < ii.position) {
|
|
|
1096
|
offset += mAdapter.getPageWidth(pos++) + marginOffset;
|
|
|
1097
|
}
|
|
|
1098
|
if (ii.position == N - 1) {
|
|
|
1099
|
mLastOffset = offset + ii.heightFactor - 1;
|
|
|
1100
|
}
|
|
|
1101
|
ii.offset = offset;
|
|
|
1102
|
offset += ii.heightFactor + marginOffset;
|
|
|
1103
|
}
|
|
|
1104
|
|
|
|
1105
|
mNeedCalculatePageOffsets = false;
|
|
|
1106
|
}
|
|
|
1107
|
|
|
|
1108
|
/**
|
|
|
1109
|
* This is the persistent state that is saved by ViewPager. Only needed
|
|
|
1110
|
* if you are creating a sublass of ViewPager that must save its own
|
|
|
1111
|
* state, in which case it should implement a subclass of this which
|
|
|
1112
|
* contains that state.
|
|
|
1113
|
*/
|
|
|
1114
|
public static class SavedState extends BaseSavedState {
|
|
|
1115
|
int position;
|
|
|
1116
|
Parcelable adapterState;
|
|
|
1117
|
ClassLoader loader;
|
|
|
1118
|
|
|
|
1119
|
public SavedState(Parcelable superState) {
|
|
|
1120
|
super(superState);
|
|
|
1121
|
}
|
|
|
1122
|
|
|
|
1123
|
@Override
|
|
|
1124
|
public void writeToParcel(Parcel out, int flags) {
|
|
|
1125
|
super.writeToParcel(out, flags);
|
|
|
1126
|
out.writeInt(position);
|
|
|
1127
|
out.writeParcelable(adapterState, flags);
|
|
|
1128
|
}
|
|
|
1129
|
|
|
|
1130
|
@Override
|
|
|
1131
|
public String toString() {
|
|
|
1132
|
return "FragmentPager.SavedState{"
|
|
|
1133
|
+ Integer.toHexString(System.identityHashCode(this))
|
|
|
1134
|
+ " position=" + position + "}";
|
|
|
1135
|
}
|
|
|
1136
|
|
|
|
1137
|
public static final Creator<SavedState> CREATOR
|
|
|
1138
|
= ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {
|
|
|
1139
|
@Override
|
|
|
1140
|
public SavedState createFromParcel(Parcel in, ClassLoader loader) {
|
|
|
1141
|
return new SavedState(in, loader);
|
|
|
1142
|
}
|
|
|
1143
|
|
|
|
1144
|
@Override
|
|
|
1145
|
public SavedState[] newArray(int size) {
|
|
|
1146
|
return new SavedState[size];
|
|
|
1147
|
}
|
|
|
1148
|
});
|
|
|
1149
|
|
|
|
1150
|
SavedState(Parcel in, ClassLoader loader) {
|
|
|
1151
|
super(in);
|
|
|
1152
|
if (loader == null) {
|
|
|
1153
|
loader = getClass().getClassLoader();
|
|
|
1154
|
}
|
|
|
1155
|
position = in.readInt();
|
|
|
1156
|
adapterState = in.readParcelable(loader);
|
|
|
1157
|
this.loader = loader;
|
|
|
1158
|
}
|
|
|
1159
|
}
|
|
|
1160
|
|
|
|
1161
|
@Override
|
|
|
1162
|
public Parcelable onSaveInstanceState() {
|
|
|
1163
|
Parcelable superState = super.onSaveInstanceState();
|
|
|
1164
|
SavedState ss = new SavedState(superState);
|
|
|
1165
|
ss.position = mCurItem;
|
|
|
1166
|
if (mAdapter != null) {
|
|
|
1167
|
ss.adapterState = mAdapter.saveState();
|
|
|
1168
|
}
|
|
|
1169
|
return ss;
|
|
|
1170
|
}
|
|
|
1171
|
|
|
|
1172
|
@Override
|
|
|
1173
|
public void onRestoreInstanceState(Parcelable state) {
|
|
|
1174
|
if (!(state instanceof SavedState)) {
|
|
|
1175
|
super.onRestoreInstanceState(state);
|
|
|
1176
|
return;
|
|
|
1177
|
}
|
|
|
1178
|
|
|
|
1179
|
SavedState ss = (SavedState) state;
|
|
|
1180
|
super.onRestoreInstanceState(ss.getSuperState());
|
|
|
1181
|
|
|
|
1182
|
if (mAdapter != null) {
|
|
|
1183
|
mAdapter.restoreState(ss.adapterState, ss.loader);
|
|
|
1184
|
setCurrentItemInternal(ss.position, false, true);
|
|
|
1185
|
} else {
|
|
|
1186
|
mRestoredCurItem = ss.position;
|
|
|
1187
|
mRestoredAdapterState = ss.adapterState;
|
|
|
1188
|
mRestoredClassLoader = ss.loader;
|
|
|
1189
|
}
|
|
|
1190
|
}
|
|
|
1191
|
|
|
|
1192
|
@Override
|
|
|
1193
|
public void addView(View child, int index, ViewGroup.LayoutParams params) {
|
|
|
1194
|
if (!checkLayoutParams(params)) {
|
|
|
1195
|
params = generateLayoutParams(params);
|
|
|
1196
|
}
|
|
|
1197
|
final LayoutParams lp = (LayoutParams) params;
|
|
|
1198
|
lp.isDecor |= child instanceof Decor;
|
|
|
1199
|
if (mInLayout) {
|
|
|
1200
|
if (lp != null && lp.isDecor) {
|
|
|
1201
|
throw new IllegalStateException("Cannot add pager decor view during layout");
|
|
|
1202
|
}
|
|
|
1203
|
lp.needsMeasure = true;
|
|
|
1204
|
addViewInLayout(child, index, params);
|
|
|
1205
|
} else {
|
|
|
1206
|
super.addView(child, index, params);
|
|
|
1207
|
}
|
|
|
1208
|
|
|
|
1209
|
if (USE_CACHE) {
|
|
|
1210
|
if (child.getVisibility() != GONE) {
|
|
|
1211
|
child.setDrawingCacheEnabled(mScrollingCacheEnabled);
|
|
|
1212
|
} else {
|
|
|
1213
|
child.setDrawingCacheEnabled(false);
|
|
|
1214
|
}
|
|
|
1215
|
}
|
|
|
1216
|
}
|
|
|
1217
|
|
|
|
1218
|
@Override
|
|
|
1219
|
public void removeView(View view) {
|
|
|
1220
|
if (mInLayout) {
|
|
|
1221
|
removeViewInLayout(view);
|
|
|
1222
|
} else {
|
|
|
1223
|
super.removeView(view);
|
|
|
1224
|
}
|
|
|
1225
|
}
|
|
|
1226
|
|
|
|
1227
|
ItemInfo infoForChild(View child) {
|
|
|
1228
|
for (int i = 0; i < mItems.size(); i++) {
|
|
|
1229
|
ItemInfo ii = mItems.get(i);
|
|
|
1230
|
if (mAdapter.isViewFromObject(child, ii.object)) {
|
|
|
1231
|
return ii;
|
|
|
1232
|
}
|
|
|
1233
|
}
|
|
|
1234
|
return null;
|
|
|
1235
|
}
|
|
|
1236
|
|
|
|
1237
|
ItemInfo infoForAnyChild(View child) {
|
|
|
1238
|
ViewParent parent;
|
|
|
1239
|
while ((parent = child.getParent()) != this) {
|
|
|
1240
|
if (parent == null || !(parent instanceof View)) {
|
|
|
1241
|
return null;
|
|
|
1242
|
}
|
|
|
1243
|
child = (View) parent;
|
|
|
1244
|
}
|
|
|
1245
|
return infoForChild(child);
|
|
|
1246
|
}
|
|
|
1247
|
|
|
|
1248
|
ItemInfo infoForPosition(int position) {
|
|
|
1249
|
for (int i = 0; i < mItems.size(); i++) {
|
|
|
1250
|
ItemInfo ii = mItems.get(i);
|
|
|
1251
|
if (ii.position == position) {
|
|
|
1252
|
return ii;
|
|
|
1253
|
}
|
|
|
1254
|
}
|
|
|
1255
|
return null;
|
|
|
1256
|
}
|
|
|
1257
|
|
|
|
1258
|
@Override
|
|
|
1259
|
protected void onAttachedToWindow() {
|
|
|
1260
|
super.onAttachedToWindow();
|
|
|
1261
|
mFirstLayout = true;
|
|
|
1262
|
}
|
|
|
1263
|
|
|
|
1264
|
@Override
|
|
|
1265
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
|
1266
|
// For simple implementation, our internal size is always 0.
|
|
|
1267
|
// We depend on the container to specify the layout size of
|
|
|
1268
|
// our view. We can't really know what it is since we will be
|
|
|
1269
|
// adding and removing different arbitrary views and do not
|
|
|
1270
|
// want the layout to change as this happens.
|
|
|
1271
|
setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
|
|
|
1272
|
getDefaultSize(0, heightMeasureSpec));
|
|
|
1273
|
|
|
|
1274
|
final int measuredHeight = getMeasuredHeight();
|
|
|
1275
|
final int maxGutterSize = measuredHeight / 10;
|
|
|
1276
|
mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);
|
|
|
1277
|
|
|
|
1278
|
// Children are just made to fill our space.
|
|
|
1279
|
int childWidthSize = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
|
|
|
1280
|
int childHeightSize = measuredHeight - getPaddingTop() - getPaddingBottom();
|
|
|
1281
|
|
|
|
1282
|
/*
|
|
|
1283
|
* Make sure all children have been properly measured. Decor views first.
|
|
|
1284
|
* Right now we cheat and make this less complicated by assuming decor
|
|
|
1285
|
* views won't intersect. We will pin to edges based on gravity.
|
|
|
1286
|
*/
|
|
|
1287
|
int size = getChildCount();
|
|
|
1288
|
for (int i = 0; i < size; ++i) {
|
|
|
1289
|
final View child = getChildAt(i);
|
|
|
1290
|
if (child.getVisibility() != GONE) {
|
|
|
1291
|
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
|
|
|
1292
|
if (lp != null && lp.isDecor) {
|
|
|
1293
|
final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
|
|
|
1294
|
final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
|
|
|
1295
|
int widthMode = MeasureSpec.AT_MOST;
|
|
|
1296
|
int heightMode = MeasureSpec.AT_MOST;
|
|
|
1297
|
boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
|
|
|
1298
|
boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;
|
|
|
1299
|
|
|
|
1300
|
if (consumeVertical) {
|
|
|
1301
|
widthMode = MeasureSpec.EXACTLY;
|
|
|
1302
|
} else if (consumeHorizontal) {
|
|
|
1303
|
heightMode = MeasureSpec.EXACTLY;
|
|
|
1304
|
}
|
|
|
1305
|
|
|
|
1306
|
int widthSize = childWidthSize;
|
|
|
1307
|
int heightSize = childHeightSize;
|
|
|
1308
|
if (lp.width != LayoutParams.WRAP_CONTENT) {
|
|
|
1309
|
widthMode = MeasureSpec.EXACTLY;
|
|
|
1310
|
if (lp.width != LayoutParams.FILL_PARENT) {
|
|
|
1311
|
widthSize = lp.width;
|
|
|
1312
|
}
|
|
|
1313
|
}
|
|
|
1314
|
if (lp.height != LayoutParams.WRAP_CONTENT) {
|
|
|
1315
|
heightMode = MeasureSpec.EXACTLY;
|
|
|
1316
|
if (lp.height != LayoutParams.FILL_PARENT) {
|
|
|
1317
|
heightSize = lp.height;
|
|
|
1318
|
}
|
|
|
1319
|
}
|
|
|
1320
|
final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
|
|
|
1321
|
final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
|
|
|
1322
|
child.measure(widthSpec, heightSpec);
|
|
|
1323
|
|
|
|
1324
|
if (consumeVertical) {
|
|
|
1325
|
childHeightSize -= child.getMeasuredHeight();
|
|
|
1326
|
} else if (consumeHorizontal) {
|
|
|
1327
|
childWidthSize -= child.getMeasuredWidth();
|
|
|
1328
|
}
|
|
|
1329
|
}
|
|
|
1330
|
}
|
|
|
1331
|
}
|
|
|
1332
|
|
|
|
1333
|
mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
|
|
|
1334
|
mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);
|
|
|
1335
|
|
|
|
1336
|
// Make sure we have created all fragments that we need to have shown.
|
|
|
1337
|
mInLayout = true;
|
|
|
1338
|
populate();
|
|
|
1339
|
mInLayout = false;
|
|
|
1340
|
|
|
|
1341
|
// Page views next.
|
|
|
1342
|
size = getChildCount();
|
|
|
1343
|
for (int i = 0; i < size; ++i) {
|
|
|
1344
|
final View child = getChildAt(i);
|
|
|
1345
|
if (child.getVisibility() != GONE) {
|
|
|
1346
|
if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child
|
|
|
1347
|
+ ": " + mChildWidthMeasureSpec);
|
|
|
1348
|
|
|
|
1349
|
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
|
|
|
1350
|
if (lp == null || !lp.isDecor) {
|
|
|
1351
|
final int heightSpec = MeasureSpec.makeMeasureSpec(
|
|
|
1352
|
(int) (childHeightSize * lp.heightFactor), MeasureSpec.EXACTLY);
|
|
|
1353
|
child.measure(mChildWidthMeasureSpec, heightSpec);
|
|
|
1354
|
}
|
|
|
1355
|
}
|
|
|
1356
|
}
|
|
|
1357
|
}
|
|
|
1358
|
|
|
|
1359
|
@Override
|
|
|
1360
|
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
|
|
1361
|
super.onSizeChanged(w, h, oldw, oldh);
|
|
|
1362
|
|
|
|
1363
|
// Make sure scroll position is set correctly.
|
|
|
1364
|
if (h != oldh) {
|
|
|
1365
|
recomputeScrollPosition(h, oldh, mPageMargin, mPageMargin);
|
|
|
1366
|
}
|
|
|
1367
|
}
|
|
|
1368
|
|
|
|
1369
|
private void recomputeScrollPosition(int height, int oldHeight, int margin, int oldMargin) {
|
|
|
1370
|
if (oldHeight > 0 && !mItems.isEmpty()) {
|
|
|
1371
|
final int heightWithMargin = height - getPaddingTop() - getPaddingBottom() + margin;
|
|
|
1372
|
final int oldHeightWithMargin = oldHeight - getPaddingTop() - getPaddingBottom()
|
|
|
1373
|
+ oldMargin;
|
|
|
1374
|
final int ypos = getScrollY();
|
|
|
1375
|
final float pageOffset = (float) ypos / oldHeightWithMargin;
|
|
|
1376
|
final int newOffsetPixels = (int) (pageOffset * heightWithMargin);
|
|
|
1377
|
|
|
|
1378
|
scrollTo(getScrollX(), newOffsetPixels);
|
|
|
1379
|
if (!mScroller.isFinished()) {
|
|
|
1380
|
// We now return to your regularly scheduled scroll, already in progress.
|
|
|
1381
|
final int newDuration = mScroller.getDuration() - mScroller.timePassed();
|
|
|
1382
|
ItemInfo targetInfo = infoForPosition(mCurItem);
|
|
|
1383
|
mScroller.startScroll(0, newOffsetPixels,
|
|
|
1384
|
0, (int) (targetInfo.offset * height), newDuration);
|
|
|
1385
|
}
|
|
|
1386
|
} else {
|
|
|
1387
|
final ItemInfo ii = infoForPosition(mCurItem);
|
|
|
1388
|
final float scrollOffset = ii != null ? Math.min(ii.offset, mLastOffset) : 0;
|
|
|
1389
|
final int scrollPos = (int) (scrollOffset *
|
|
|
1390
|
(height - getPaddingTop() - getPaddingBottom()));
|
|
|
1391
|
if (scrollPos != getScrollY()) {
|
|
|
1392
|
completeScroll(false);
|
|
|
1393
|
scrollTo(getScrollX(), scrollPos);
|
|
|
1394
|
}
|
|
|
1395
|
}
|
|
|
1396
|
}
|
|
|
1397
|
|
|
|
1398
|
@Override
|
|
|
1399
|
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
|
|
1400
|
final int count = getChildCount();
|
|
|
1401
|
int width = r - l;
|
|
|
1402
|
int height = b - t;
|
|
|
1403
|
int paddingLeft = getPaddingLeft();
|
|
|
1404
|
int paddingTop = getPaddingTop();
|
|
|
1405
|
int paddingRight = getPaddingRight();
|
|
|
1406
|
int paddingBottom = getPaddingBottom();
|
|
|
1407
|
final int scrollY = getScrollY();
|
|
|
1408
|
|
|
|
1409
|
int decorCount = 0;
|
|
|
1410
|
|
|
|
1411
|
// First pass - decor views. We need to do this in two passes so that
|
|
|
1412
|
// we have the proper offsets for non-decor views later.
|
|
|
1413
|
for (int i = 0; i < count; i++) {
|
|
|
1414
|
final View child = getChildAt(i);
|
|
|
1415
|
if (child.getVisibility() != GONE) {
|
|
|
1416
|
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
|
|
|
1417
|
int childLeft = 0;
|
|
|
1418
|
int childTop = 0;
|
|
|
1419
|
if (lp.isDecor) {
|
|
|
1420
|
final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
|
|
|
1421
|
final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
|
|
|
1422
|
switch (hgrav) {
|
|
|
1423
|
default:
|
|
|
1424
|
childLeft = paddingLeft;
|
|
|
1425
|
break;
|
|
|
1426
|
case Gravity.LEFT:
|
|
|
1427
|
childLeft = paddingLeft;
|
|
|
1428
|
paddingLeft += child.getMeasuredWidth();
|
|
|
1429
|
break;
|
|
|
1430
|
case Gravity.CENTER_HORIZONTAL:
|
|
|
1431
|
childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
|
|
|
1432
|
paddingLeft);
|
|
|
1433
|
break;
|
|
|
1434
|
case Gravity.RIGHT:
|
|
|
1435
|
childLeft = width - paddingRight - child.getMeasuredWidth();
|
|
|
1436
|
paddingRight += child.getMeasuredWidth();
|
|
|
1437
|
break;
|
|
|
1438
|
}
|
|
|
1439
|
switch (vgrav) {
|
|
|
1440
|
default:
|
|
|
1441
|
childTop = paddingTop;
|
|
|
1442
|
break;
|
|
|
1443
|
case Gravity.TOP:
|
|
|
1444
|
childTop = paddingTop;
|
|
|
1445
|
paddingTop += child.getMeasuredHeight();
|
|
|
1446
|
break;
|
|
|
1447
|
case Gravity.CENTER_VERTICAL:
|
|
|
1448
|
childTop = Math.max((height - child.getMeasuredHeight()) / 2,
|
|
|
1449
|
paddingTop);
|
|
|
1450
|
break;
|
|
|
1451
|
case Gravity.BOTTOM:
|
|
|
1452
|
childTop = height - paddingBottom - child.getMeasuredHeight();
|
|
|
1453
|
paddingBottom += child.getMeasuredHeight();
|
|
|
1454
|
break;
|
|
|
1455
|
}
|
|
|
1456
|
childTop += scrollY;
|
|
|
1457
|
child.layout(childLeft, childTop,
|
|
|
1458
|
childLeft + child.getMeasuredWidth(),
|
|
|
1459
|
childTop + child.getMeasuredHeight());
|
|
|
1460
|
decorCount++;
|
|
|
1461
|
}
|
|
|
1462
|
}
|
|
|
1463
|
}
|
|
|
1464
|
|
|
|
1465
|
final int childHeight = height - paddingTop - paddingBottom;
|
|
|
1466
|
// Page views. Do this once we have the right padding offsets from above.
|
|
|
1467
|
for (int i = 0; i < count; i++) {
|
|
|
1468
|
final View child = getChildAt(i);
|
|
|
1469
|
if (child.getVisibility() != GONE) {
|
|
|
1470
|
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
|
|
|
1471
|
ItemInfo ii;
|
|
|
1472
|
if (!lp.isDecor && (ii = infoForChild(child)) != null) {
|
|
|
1473
|
int toff = (int) (childHeight * ii.offset);
|
|
|
1474
|
int childLeft = paddingLeft;
|
|
|
1475
|
int childTop = paddingTop + toff;
|
|
|
1476
|
if (lp.needsMeasure) {
|
|
|
1477
|
// This was added during layout and needs measurement.
|
|
|
1478
|
// Do it now that we know what we're working with.
|
|
|
1479
|
lp.needsMeasure = false;
|
|
|
1480
|
final int widthSpec = MeasureSpec.makeMeasureSpec(
|
|
|
1481
|
(int) (width - paddingLeft - paddingRight),
|
|
|
1482
|
MeasureSpec.EXACTLY);
|
|
|
1483
|
final int heightSpec = MeasureSpec.makeMeasureSpec(
|
|
|
1484
|
(int) (childHeight * lp.heightFactor),
|
|
|
1485
|
MeasureSpec.EXACTLY);
|
|
|
1486
|
child.measure(widthSpec, heightSpec);
|
|
|
1487
|
}
|
|
|
1488
|
if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
|
|
|
1489
|
+ ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
|
|
|
1490
|
+ "x" + child.getMeasuredHeight());
|
|
|
1491
|
child.layout(childLeft, childTop,
|
|
|
1492
|
childLeft + child.getMeasuredWidth(),
|
|
|
1493
|
childTop + child.getMeasuredHeight());
|
|
|
1494
|
}
|
|
|
1495
|
}
|
|
|
1496
|
}
|
|
|
1497
|
mLeftPageBounds = paddingLeft;
|
|
|
1498
|
mRightPageBounds = width - paddingRight;
|
|
|
1499
|
mDecorChildCount = decorCount;
|
|
|
1500
|
|
|
|
1501
|
if (mFirstLayout) {
|
|
|
1502
|
scrollToItem(mCurItem, false, 0, false);
|
|
|
1503
|
}
|
|
|
1504
|
mFirstLayout = false;
|
|
|
1505
|
}
|
|
|
1506
|
|
|
|
1507
|
@Override
|
|
|
1508
|
public void computeScroll() {
|
|
|
1509
|
if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
|
|
|
1510
|
int oldX = getScrollX();
|
|
|
1511
|
int oldY = getScrollY();
|
|
|
1512
|
int x = mScroller.getCurrX();
|
|
|
1513
|
int y = mScroller.getCurrY();
|
|
|
1514
|
|
|
|
1515
|
if (oldX != x || oldY != y) {
|
|
|
1516
|
scrollTo(x, y);
|
|
|
1517
|
if (!pageScrolled(y)) {
|
|
|
1518
|
mScroller.abortAnimation();
|
|
|
1519
|
scrollTo(x, 0);
|
|
|
1520
|
}
|
|
|
1521
|
}
|
|
|
1522
|
|
|
|
1523
|
// Keep on drawing until the animation has finished.
|
|
|
1524
|
ViewCompat.postInvalidateOnAnimation(this);
|
|
|
1525
|
return;
|
|
|
1526
|
}
|
|
|
1527
|
|
|
|
1528
|
// Done with scroll, clean up state.
|
|
|
1529
|
completeScroll(true);
|
|
|
1530
|
}
|
|
|
1531
|
|
|
|
1532
|
private boolean pageScrolled(int ypos) {
|
|
|
1533
|
if (mItems.size() == 0) {
|
|
|
1534
|
mCalledSuper = false;
|
|
|
1535
|
onPageScrolled(0, 0, 0);
|
|
|
1536
|
if (!mCalledSuper) {
|
|
|
1537
|
throw new IllegalStateException(
|
|
|
1538
|
"onPageScrolled did not call superclass implementation");
|
|
|
1539
|
}
|
|
|
1540
|
return false;
|
|
|
1541
|
}
|
|
|
1542
|
final ItemInfo ii = infoForCurrentScrollPosition();
|
|
|
1543
|
final int height = getClientHeight();
|
|
|
1544
|
final int heightWithMargin = height + mPageMargin;
|
|
|
1545
|
final float marginOffset = (float) mPageMargin / height;
|
|
|
1546
|
final int currentPage = ii.position;
|
|
|
1547
|
final float pageOffset = (((float) ypos / height) - ii.offset) /
|
|
|
1548
|
(ii.heightFactor + marginOffset);
|
|
|
1549
|
final int offsetPixels = (int) (pageOffset * heightWithMargin);
|
|
|
1550
|
|
|
|
1551
|
mCalledSuper = false;
|
|
|
1552
|
onPageScrolled(currentPage, pageOffset, offsetPixels);
|
|
|
1553
|
if (!mCalledSuper) {
|
|
|
1554
|
throw new IllegalStateException(
|
|
|
1555
|
"onPageScrolled did not call superclass implementation");
|
|
|
1556
|
}
|
|
|
1557
|
return true;
|
|
|
1558
|
}
|
|
|
1559
|
|
|
|
1560
|
/**
|
|
|
1561
|
* This method will be invoked when the current page is scrolled, either as part
|
|
|
1562
|
* of a programmatically initiated smooth scroll or a user initiated touch scroll.
|
|
|
1563
|
* If you override this method you must call through to the superclass implementation
|
|
|
1564
|
* (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled
|
|
|
1565
|
* returns.
|
|
|
1566
|
*
|
|
|
1567
|
* @param position Position index of the first page currently being displayed.
|
|
|
1568
|
* Page position+1 will be visible if positionOffset is nonzero.
|
|
|
1569
|
* @param offset Value from [0, 1) indicating the offset from the page at position.
|
|
|
1570
|
* @param offsetPixels Value in pixels indicating the offset from position.
|
|
|
1571
|
*/
|
|
|
1572
|
protected void onPageScrolled(int position, float offset, int offsetPixels) {
|
|
|
1573
|
// Offset any decor views if needed - keep them on-screen at all times.
|
|
|
1574
|
if (mDecorChildCount > 0) {
|
|
|
1575
|
final int scrollY = getScrollY();
|
|
|
1576
|
int paddingTop = getPaddingTop();
|
|
|
1577
|
int paddingBottom = getPaddingBottom();
|
|
|
1578
|
final int height = getHeight();
|
|
|
1579
|
final int childCount = getChildCount();
|
|
|
1580
|
for (int i = 0; i < childCount; i++) {
|
|
|
1581
|
final View child = getChildAt(i);
|
|
|
1582
|
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
|
|
|
1583
|
if (!lp.isDecor) continue;
|
|
|
1584
|
|
|
|
1585
|
final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
|
|
|
1586
|
int childTop = 0;
|
|
|
1587
|
switch (vgrav) {
|
|
|
1588
|
default:
|
|
|
1589
|
childTop = paddingTop;
|
|
|
1590
|
break;
|
|
|
1591
|
case Gravity.TOP:
|
|
|
1592
|
childTop = paddingTop;
|
|
|
1593
|
paddingTop += child.getHeight();
|
|
|
1594
|
break;
|
|
|
1595
|
case Gravity.CENTER_VERTICAL:
|
|
|
1596
|
childTop = Math.max((height - child.getMeasuredHeight()) / 2,
|
|
|
1597
|
paddingTop);
|
|
|
1598
|
break;
|
|
|
1599
|
case Gravity.BOTTOM:
|
|
|
1600
|
childTop = height - paddingBottom - child.getMeasuredHeight();
|
|
|
1601
|
paddingBottom += child.getMeasuredHeight();
|
|
|
1602
|
break;
|
|
|
1603
|
}
|
|
|
1604
|
childTop += scrollY;
|
|
|
1605
|
|
|
|
1606
|
final int childOffset = childTop - child.getTop();
|
|
|
1607
|
if (childOffset != 0) {
|
|
|
1608
|
child.offsetTopAndBottom(childOffset);
|
|
|
1609
|
}
|
|
|
1610
|
}
|
|
|
1611
|
}
|
|
|
1612
|
|
|
|
1613
|
if (mOnPageChangeListener != null) {
|
|
|
1614
|
mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);
|
|
|
1615
|
}
|
|
|
1616
|
if (mInternalPageChangeListener != null) {
|
|
|
1617
|
mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels);
|
|
|
1618
|
}
|
|
|
1619
|
|
|
|
1620
|
if (mPageTransformer != null) {
|
|
|
1621
|
final int scrollY = getScrollY();
|
|
|
1622
|
final int childCount = getChildCount();
|
|
|
1623
|
for (int i = 0; i < childCount; i++) {
|
|
|
1624
|
final View child = getChildAt(i);
|
|
|
1625
|
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
|
|
|
1626
|
|
|
|
1627
|
if (lp.isDecor) continue;
|
|
|
1628
|
|
|
|
1629
|
final float transformPos = (float) (child.getTop() - scrollY) / getClientHeight();
|
|
|
1630
|
mPageTransformer.transformPage(child, transformPos);
|
|
|
1631
|
}
|
|
|
1632
|
}
|
|
|
1633
|
|
|
|
1634
|
mCalledSuper = true;
|
|
|
1635
|
}
|
|
|
1636
|
|
|
|
1637
|
private void completeScroll(boolean postEvents) {
|
|
|
1638
|
boolean needPopulate = mScrollState == SCROLL_STATE_SETTLING;
|
|
|
1639
|
if (needPopulate) {
|
|
|
1640
|
// Done with scroll, no longer want to cache view drawing.
|
|
|
1641
|
setScrollingCacheEnabled(false);
|
|
|
1642
|
mScroller.abortAnimation();
|
|
|
1643
|
int oldX = getScrollX();
|
|
|
1644
|
int oldY = getScrollY();
|
|
|
1645
|
int x = mScroller.getCurrX();
|
|
|
1646
|
int y = mScroller.getCurrY();
|
|
|
1647
|
if (oldX != x || oldY != y) {
|
|
|
1648
|
scrollTo(x, y);
|
|
|
1649
|
}
|
|
|
1650
|
}
|
|
|
1651
|
mPopulatePending = false;
|
|
|
1652
|
for (int i = 0; i < mItems.size(); i++) {
|
|
|
1653
|
ItemInfo ii = mItems.get(i);
|
|
|
1654
|
if (ii.scrolling) {
|
|
|
1655
|
needPopulate = true;
|
|
|
1656
|
ii.scrolling = false;
|
|
|
1657
|
}
|
|
|
1658
|
}
|
|
|
1659
|
if (needPopulate) {
|
|
|
1660
|
if (postEvents) {
|
|
|
1661
|
ViewCompat.postOnAnimation(this, mEndScrollRunnable);
|
|
|
1662
|
} else {
|
|
|
1663
|
mEndScrollRunnable.run();
|
|
|
1664
|
}
|
|
|
1665
|
}
|
|
|
1666
|
}
|
|
|
1667
|
|
|
|
1668
|
private boolean isGutterDrag(float y, float dy) {
|
|
|
1669
|
return (y < mGutterSize && dy > 0) || (y > getHeight() - mGutterSize && dy < 0);
|
|
|
1670
|
}
|
|
|
1671
|
|
|
|
1672
|
private void enableLayers(boolean enable) {
|
|
|
1673
|
final int childCount = getChildCount();
|
|
|
1674
|
for (int i = 0; i < childCount; i++) {
|
|
|
1675
|
final int layerType = enable ?
|
|
|
1676
|
ViewCompat.LAYER_TYPE_HARDWARE : ViewCompat.LAYER_TYPE_NONE;
|
|
|
1677
|
ViewCompat.setLayerType(getChildAt(i), layerType, null);
|
|
|
1678
|
}
|
|
|
1679
|
}
|
|
|
1680
|
|
|
|
1681
|
@Override
|
|
|
1682
|
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
|
|
1683
|
/*
|
|
|
1684
|
* This method JUST determines whether we want to intercept the motion.
|
|
|
1685
|
* If we return true, onMotionEvent will be called and we do the actual
|
|
|
1686
|
* scrolling there.
|
|
|
1687
|
*/
|
|
|
1688
|
|
|
|
1689
|
final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
|
|
|
1690
|
|
|
|
1691
|
// Always take care of the touch gesture being complete.
|
|
|
1692
|
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
|
|
|
1693
|
// Release the drag.
|
|
|
1694
|
if (DEBUG) Log.v(TAG, "Intercept done!");
|
|
|
1695
|
mIsBeingDragged = false;
|
|
|
1696
|
mIsUnableToDrag = false;
|
|
|
1697
|
mActivePointerId = INVALID_POINTER;
|
|
|
1698
|
if (mVelocityTracker != null) {
|
|
|
1699
|
mVelocityTracker.recycle();
|
|
|
1700
|
mVelocityTracker = null;
|
|
|
1701
|
}
|
|
|
1702
|
return false;
|
|
|
1703
|
}
|
|
|
1704
|
|
|
|
1705
|
// Nothing more to do here if we have decided whether or not we
|
|
|
1706
|
// are dragging.
|
|
|
1707
|
if (action != MotionEvent.ACTION_DOWN) {
|
|
|
1708
|
if (mIsBeingDragged) {
|
|
|
1709
|
if (DEBUG) Log.v(TAG, "Intercept returning true!");
|
|
|
1710
|
return true;
|
|
|
1711
|
}
|
|
|
1712
|
if (mIsUnableToDrag) {
|
|
|
1713
|
if (DEBUG) Log.v(TAG, "Intercept returning false!");
|
|
|
1714
|
return false;
|
|
|
1715
|
}
|
|
|
1716
|
}
|
|
|
1717
|
|
|
|
1718
|
switch (action) {
|
|
|
1719
|
case MotionEvent.ACTION_MOVE: {
|
|
|
1720
|
/*
|
|
|
1721
|
* mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
|
|
|
1722
|
* whether the user has moved far enough from his original down touch.
|
|
|
1723
|
*/
|
|
|
1724
|
|
|
|
1725
|
/*
|
|
|
1726
|
* Locally do absolute value. mLastMotionY is set to the y value
|
|
|
1727
|
* of the down event.
|
|
|
1728
|
*/
|
|
|
1729
|
final int activePointerId = mActivePointerId;
|
|
|
1730
|
if (activePointerId == INVALID_POINTER) {
|
|
|
1731
|
// If we don't have a valid id, the touch down wasn't on content.
|
|
|
1732
|
break;
|
|
|
1733
|
}
|
|
|
1734
|
|
|
|
1735
|
final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
|
|
|
1736
|
final float y = MotionEventCompat.getY(ev, pointerIndex);
|
|
|
1737
|
final float dy = y - mLastMotionY;
|
|
|
1738
|
final float yDiff = Math.abs(dy);
|
|
|
1739
|
final float x = MotionEventCompat.getX(ev, pointerIndex);
|
|
|
1740
|
final float xDiff = Math.abs(x - mInitialMotionX);
|
|
|
1741
|
if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
|
|
|
1742
|
|
|
|
1743
|
if (dy != 0 && !isGutterDrag(mLastMotionY, dy) &&
|
|
|
1744
|
canScroll(this, false, (int) dy, (int) x, (int) y)) {
|
|
|
1745
|
// Nested view has scrollable area under this point. Let it be handled there.
|
|
|
1746
|
mLastMotionX = x;
|
|
|
1747
|
mLastMotionY = y;
|
|
|
1748
|
mIsUnableToDrag = true;
|
|
|
1749
|
return false;
|
|
|
1750
|
}
|
|
|
1751
|
if (yDiff > mTouchSlop && yDiff * 0.5f > xDiff) {
|
|
|
1752
|
if (DEBUG) Log.v(TAG, "Starting drag!");
|
|
|
1753
|
mIsBeingDragged = true;
|
|
|
1754
|
requestParentDisallowInterceptTouchEvent(true);
|
|
|
1755
|
setScrollState(SCROLL_STATE_DRAGGING);
|
|
|
1756
|
mLastMotionY = dy > 0 ? mInitialMotionY + mTouchSlop :
|
|
|
1757
|
mInitialMotionY - mTouchSlop;
|
|
|
1758
|
mLastMotionX = x;
|
|
|
1759
|
setScrollingCacheEnabled(true);
|
|
|
1760
|
} else if (xDiff > mTouchSlop) {
|
|
|
1761
|
// The finger has moved enough in the vertical
|
|
|
1762
|
// direction to be counted as a drag... abort
|
|
|
1763
|
// any attempt to drag horizontally, to work correctly
|
|
|
1764
|
// with children that have scrolling containers.
|
|
|
1765
|
if (DEBUG) Log.v(TAG, "Starting unable to drag!");
|
|
|
1766
|
mIsUnableToDrag = true;
|
|
|
1767
|
}
|
|
|
1768
|
if (mIsBeingDragged) {
|
|
|
1769
|
// Scroll to follow the motion event
|
|
|
1770
|
if (performDrag(y)) {
|
|
|
1771
|
ViewCompat.postInvalidateOnAnimation(this);
|
|
|
1772
|
}
|
|
|
1773
|
}
|
|
|
1774
|
break;
|
|
|
1775
|
}
|
|
|
1776
|
|
|
|
1777
|
case MotionEvent.ACTION_DOWN: {
|
|
|
1778
|
/*
|
|
|
1779
|
* Remember location of down touch.
|
|
|
1780
|
* ACTION_DOWN always refers to pointer index 0.
|
|
|
1781
|
*/
|
|
|
1782
|
mLastMotionX = mInitialMotionX = ev.getX();
|
|
|
1783
|
mLastMotionY = mInitialMotionY = ev.getY();
|
|
|
1784
|
mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
|
|
|
1785
|
mIsUnableToDrag = false;
|
|
|
1786
|
|
|
|
1787
|
mScroller.computeScrollOffset();
|
|
|
1788
|
if (mScrollState == SCROLL_STATE_SETTLING &&
|
|
|
1789
|
Math.abs(mScroller.getFinalY() - mScroller.getCurrY()) > mCloseEnough) {
|
|
|
1790
|
// Let the user 'catch' the pager as it animates.
|
|
|
1791
|
mScroller.abortAnimation();
|
|
|
1792
|
mPopulatePending = false;
|
|
|
1793
|
populate();
|
|
|
1794
|
mIsBeingDragged = true;
|
|
|
1795
|
requestParentDisallowInterceptTouchEvent(true);
|
|
|
1796
|
setScrollState(SCROLL_STATE_DRAGGING);
|
|
|
1797
|
} else {
|
|
|
1798
|
completeScroll(false);
|
|
|
1799
|
mIsBeingDragged = false;
|
|
|
1800
|
}
|
|
|
1801
|
|
|
|
1802
|
if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
|
|
|
1803
|
+ " mIsBeingDragged=" + mIsBeingDragged
|
|
|
1804
|
+ "mIsUnableToDrag=" + mIsUnableToDrag);
|
|
|
1805
|
break;
|
|
|
1806
|
}
|
|
|
1807
|
|
|
|
1808
|
case MotionEventCompat.ACTION_POINTER_UP:
|
|
|
1809
|
onSecondaryPointerUp(ev);
|
|
|
1810
|
break;
|
|
|
1811
|
}
|
|
|
1812
|
|
|
|
1813
|
if (mVelocityTracker == null) {
|
|
|
1814
|
mVelocityTracker = VelocityTracker.obtain();
|
|
|
1815
|
}
|
|
|
1816
|
mVelocityTracker.addMovement(ev);
|
|
|
1817
|
|
|
|
1818
|
/*
|
|
|
1819
|
* The only time we want to intercept motion events is if we are in the
|
|
|
1820
|
* drag mode.
|
|
|
1821
|
*/
|
|
|
1822
|
return mIsBeingDragged;
|
|
|
1823
|
}
|
|
|
1824
|
|
|
|
1825
|
@Override
|
|
|
1826
|
public boolean onTouchEvent(MotionEvent ev) {
|
|
|
1827
|
if (mFakeDragging) {
|
|
|
1828
|
// A fake drag is in progress already, ignore this real one
|
|
|
1829
|
// but still eat the touch events.
|
|
|
1830
|
// (It is likely that the user is multi-touching the screen.)
|
|
|
1831
|
return true;
|
|
|
1832
|
}
|
|
|
1833
|
|
|
|
1834
|
if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
|
|
|
1835
|
// Don't handle edge touches immediately -- they may actually belong to one of our
|
|
|
1836
|
// descendants.
|
|
|
1837
|
return false;
|
|
|
1838
|
}
|
|
|
1839
|
|
|
|
1840
|
if (mAdapter == null || mAdapter.getCount() == 0) {
|
|
|
1841
|
// Nothing to present or scroll; nothing to touch.
|
|
|
1842
|
return false;
|
|
|
1843
|
}
|
|
|
1844
|
|
|
|
1845
|
if (mVelocityTracker == null) {
|
|
|
1846
|
mVelocityTracker = VelocityTracker.obtain();
|
|
|
1847
|
}
|
|
|
1848
|
mVelocityTracker.addMovement(ev);
|
|
|
1849
|
|
|
|
1850
|
final int action = ev.getAction();
|
|
|
1851
|
boolean needsInvalidate = false;
|
|
|
1852
|
|
|
|
1853
|
switch (action & MotionEventCompat.ACTION_MASK) {
|
|
|
1854
|
case MotionEvent.ACTION_DOWN: {
|
|
|
1855
|
mScroller.abortAnimation();
|
|
|
1856
|
mPopulatePending = false;
|
|
|
1857
|
populate();
|
|
|
1858
|
|
|
|
1859
|
// Remember where the motion event started
|
|
|
1860
|
mLastMotionX = mInitialMotionX = ev.getX();
|
|
|
1861
|
mLastMotionY = mInitialMotionY = ev.getY();
|
|
|
1862
|
mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
|
|
|
1863
|
break;
|
|
|
1864
|
}
|
|
|
1865
|
case MotionEvent.ACTION_MOVE:
|
|
|
1866
|
if (!mIsBeingDragged) {
|
|
|
1867
|
final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
|
|
|
1868
|
final float y = MotionEventCompat.getY(ev, pointerIndex);
|
|
|
1869
|
final float yDiff = Math.abs(y - mLastMotionY);
|
|
|
1870
|
final float x = MotionEventCompat.getX(ev, pointerIndex);
|
|
|
1871
|
final float xDiff = Math.abs(x - mLastMotionX);
|
|
|
1872
|
if (DEBUG)
|
|
|
1873
|
Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
|
|
|
1874
|
if (yDiff > mTouchSlop && yDiff > xDiff) {
|
|
|
1875
|
if (DEBUG) Log.v(TAG, "Starting drag!");
|
|
|
1876
|
mIsBeingDragged = true;
|
|
|
1877
|
requestParentDisallowInterceptTouchEvent(true);
|
|
|
1878
|
mLastMotionY = y - mInitialMotionY > 0 ? mInitialMotionY + mTouchSlop :
|
|
|
1879
|
mInitialMotionY - mTouchSlop;
|
|
|
1880
|
mLastMotionX = x;
|
|
|
1881
|
setScrollState(SCROLL_STATE_DRAGGING);
|
|
|
1882
|
setScrollingCacheEnabled(true);
|
|
|
1883
|
|
|
|
1884
|
// Disallow Parent Intercept, just in case
|
|
|
1885
|
ViewParent parent = getParent();
|
|
|
1886
|
if (parent != null) {
|
|
|
1887
|
parent.requestDisallowInterceptTouchEvent(true);
|
|
|
1888
|
}
|
|
|
1889
|
}
|
|
|
1890
|
}
|
|
|
1891
|
// Not else! Note that mIsBeingDragged can be set above.
|
|
|
1892
|
if (mIsBeingDragged) {
|
|
|
1893
|
// Scroll to follow the motion event
|
|
|
1894
|
final int activePointerIndex = MotionEventCompat.findPointerIndex(
|
|
|
1895
|
ev, mActivePointerId);
|
|
|
1896
|
final float y = MotionEventCompat.getY(ev, activePointerIndex);
|
|
|
1897
|
needsInvalidate |= performDrag(y);
|
|
|
1898
|
}
|
|
|
1899
|
break;
|
|
|
1900
|
case MotionEvent.ACTION_UP:
|
|
|
1901
|
if (mIsBeingDragged) {
|
|
|
1902
|
final VelocityTracker velocityTracker = mVelocityTracker;
|
|
|
1903
|
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
|
|
|
1904
|
int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(
|
|
|
1905
|
velocityTracker, mActivePointerId);
|
|
|
1906
|
mPopulatePending = true;
|
|
|
1907
|
final int height = getClientHeight();
|
|
|
1908
|
final int scrollY = getScrollY();
|
|
|
1909
|
final ItemInfo ii = infoForCurrentScrollPosition();
|
|
|
1910
|
final int currentPage = ii.position;
|
|
|
1911
|
final float pageOffset = (((float) scrollY / height) - ii.offset) / ii.heightFactor;
|
|
|
1912
|
final int activePointerIndex =
|
|
|
1913
|
MotionEventCompat.findPointerIndex(ev, mActivePointerId);
|
|
|
1914
|
final float y = MotionEventCompat.getY(ev, activePointerIndex);
|
|
|
1915
|
final int totalDelta = (int) (y - mInitialMotionY);
|
|
|
1916
|
int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
|
|
|
1917
|
totalDelta);
|
|
|
1918
|
setCurrentItemInternal(nextPage, true, true, initialVelocity);
|
|
|
1919
|
|
|
|
1920
|
mActivePointerId = INVALID_POINTER;
|
|
|
1921
|
endDrag();
|
|
|
1922
|
needsInvalidate = mTopEdge.onRelease() | mBottomEdge.onRelease();
|
|
|
1923
|
}
|
|
|
1924
|
break;
|
|
|
1925
|
case MotionEvent.ACTION_CANCEL:
|
|
|
1926
|
if (mIsBeingDragged) {
|
|
|
1927
|
scrollToItem(mCurItem, true, 0, false);
|
|
|
1928
|
mActivePointerId = INVALID_POINTER;
|
|
|
1929
|
endDrag();
|
|
|
1930
|
needsInvalidate = mTopEdge.onRelease() | mBottomEdge.onRelease();
|
|
|
1931
|
}
|
|
|
1932
|
break;
|
|
|
1933
|
case MotionEventCompat.ACTION_POINTER_DOWN: {
|
|
|
1934
|
final int index = MotionEventCompat.getActionIndex(ev);
|
|
|
1935
|
final float y = MotionEventCompat.getY(ev, index);
|
|
|
1936
|
mLastMotionY = y;
|
|
|
1937
|
mActivePointerId = MotionEventCompat.getPointerId(ev, index);
|
|
|
1938
|
break;
|
|
|
1939
|
}
|
|
|
1940
|
case MotionEventCompat.ACTION_POINTER_UP:
|
|
|
1941
|
onSecondaryPointerUp(ev);
|
|
|
1942
|
mLastMotionY = MotionEventCompat.getY(ev,
|
|
|
1943
|
MotionEventCompat.findPointerIndex(ev, mActivePointerId));
|
|
|
1944
|
break;
|
|
|
1945
|
}
|
|
|
1946
|
if (needsInvalidate) {
|
|
|
1947
|
ViewCompat.postInvalidateOnAnimation(this);
|
|
|
1948
|
}
|
|
|
1949
|
return true;
|
|
|
1950
|
}
|
|
|
1951
|
|
|
|
1952
|
private void requestParentDisallowInterceptTouchEvent(boolean disallowIntercept) {
|
|
|
1953
|
final ViewParent parent = getParent();
|
|
|
1954
|
if (parent != null) {
|
|
|
1955
|
parent.requestDisallowInterceptTouchEvent(disallowIntercept);
|
|
|
1956
|
}
|
|
|
1957
|
}
|
|
|
1958
|
|
|
|
1959
|
private boolean performDrag(float y) {
|
|
|
1960
|
boolean needsInvalidate = false;
|
|
|
1961
|
|
|
|
1962
|
final float deltaY = mLastMotionY - y;
|
|
|
1963
|
mLastMotionY = y;
|
|
|
1964
|
|
|
|
1965
|
float oldScrollY = getScrollY();
|
|
|
1966
|
float scrollY = oldScrollY + deltaY;
|
|
|
1967
|
final int height = getClientHeight();
|
|
|
1968
|
|
|
|
1969
|
float topBound = height * mFirstOffset;
|
|
|
1970
|
float bottomBound = height * mLastOffset;
|
|
|
1971
|
boolean topAbsolute = true;
|
|
|
1972
|
boolean bottomAbsolute = true;
|
|
|
1973
|
|
|
|
1974
|
final ItemInfo firstItem = mItems.get(0);
|
|
|
1975
|
final ItemInfo lastItem = mItems.get(mItems.size() - 1);
|
|
|
1976
|
if (firstItem.position != 0) {
|
|
|
1977
|
topAbsolute = false;
|
|
|
1978
|
topBound = firstItem.offset * height;
|
|
|
1979
|
}
|
|
|
1980
|
if (lastItem.position != mAdapter.getCount() - 1) {
|
|
|
1981
|
bottomAbsolute = false;
|
|
|
1982
|
bottomBound = lastItem.offset * height;
|
|
|
1983
|
}
|
|
|
1984
|
|
|
|
1985
|
if (scrollY < topBound) {
|
|
|
1986
|
if (topAbsolute) {
|
|
|
1987
|
float over = topBound - scrollY;
|
|
|
1988
|
needsInvalidate = mTopEdge.onPull(Math.abs(over) / height);
|
|
|
1989
|
}
|
|
|
1990
|
scrollY = topBound;
|
|
|
1991
|
} else if (scrollY > bottomBound) {
|
|
|
1992
|
if (bottomAbsolute) {
|
|
|
1993
|
float over = scrollY - bottomBound;
|
|
|
1994
|
needsInvalidate = mBottomEdge.onPull(Math.abs(over) / height);
|
|
|
1995
|
}
|
|
|
1996
|
scrollY = bottomBound;
|
|
|
1997
|
}
|
|
|
1998
|
// Don't lose the rounded component
|
|
|
1999
|
mLastMotionX += scrollY - (int) scrollY;
|
|
|
2000
|
scrollTo(getScrollX(), (int) scrollY);
|
|
|
2001
|
pageScrolled((int) scrollY);
|
|
|
2002
|
|
|
|
2003
|
return needsInvalidate;
|
|
|
2004
|
}
|
|
|
2005
|
|
|
|
2006
|
/**
|
|
|
2007
|
* @return Info about the page at the current scroll position.
|
|
|
2008
|
* This can be synthetic for a missing middle page; the 'object' field can be null.
|
|
|
2009
|
*/
|
|
|
2010
|
private ItemInfo infoForCurrentScrollPosition() {
|
|
|
2011
|
final int height = getClientHeight();
|
|
|
2012
|
final float scrollOffset = height > 0 ? (float) getScrollY() / height : 0;
|
|
|
2013
|
final float marginOffset = height > 0 ? (float) mPageMargin / height : 0;
|
|
|
2014
|
int lastPos = -1;
|
|
|
2015
|
float lastOffset = 0.f;
|
|
|
2016
|
float lastHeight = 0.f;
|
|
|
2017
|
boolean first = true;
|
|
|
2018
|
|
|
|
2019
|
ItemInfo lastItem = null;
|
|
|
2020
|
for (int i = 0; i < mItems.size(); i++) {
|
|
|
2021
|
ItemInfo ii = mItems.get(i);
|
|
|
2022
|
float offset;
|
|
|
2023
|
if (!first && ii.position != lastPos + 1) {
|
|
|
2024
|
// Create a synthetic item for a missing page.
|
|
|
2025
|
ii = mTempItem;
|
|
|
2026
|
ii.offset = lastOffset + lastHeight + marginOffset;
|
|
|
2027
|
ii.position = lastPos + 1;
|
|
|
2028
|
ii.heightFactor = mAdapter.getPageWidth(ii.position);
|
|
|
2029
|
i--;
|
|
|
2030
|
}
|
|
|
2031
|
offset = ii.offset;
|
|
|
2032
|
|
|
|
2033
|
final float topBound = offset;
|
|
|
2034
|
final float bottomBound = offset + ii.heightFactor + marginOffset;
|
|
|
2035
|
if (first || scrollOffset >= topBound) {
|
|
|
2036
|
if (scrollOffset < bottomBound || i == mItems.size() - 1) {
|
|
|
2037
|
return ii;
|
|
|
2038
|
}
|
|
|
2039
|
} else {
|
|
|
2040
|
return lastItem;
|
|
|
2041
|
}
|
|
|
2042
|
first = false;
|
|
|
2043
|
lastPos = ii.position;
|
|
|
2044
|
lastOffset = offset;
|
|
|
2045
|
lastHeight = ii.heightFactor;
|
|
|
2046
|
lastItem = ii;
|
|
|
2047
|
}
|
|
|
2048
|
|
|
|
2049
|
return lastItem;
|
|
|
2050
|
}
|
|
|
2051
|
|
|
|
2052
|
private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaY) {
|
|
|
2053
|
int targetPage;
|
|
|
2054
|
if (Math.abs(deltaY) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
|
|
|
2055
|
targetPage = velocity > 0 ? currentPage : currentPage + 1;
|
|
|
2056
|
} else {
|
|
|
2057
|
final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;
|
|
|
2058
|
targetPage = (int) (currentPage + pageOffset + truncator);
|
|
|
2059
|
}
|
|
|
2060
|
|
|
|
2061
|
if (mItems.size() > 0) {
|
|
|
2062
|
final ItemInfo firstItem = mItems.get(0);
|
|
|
2063
|
final ItemInfo lastItem = mItems.get(mItems.size() - 1);
|
|
|
2064
|
|
|
|
2065
|
// Only let the user target pages we have items for
|
|
|
2066
|
targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position));
|
|
|
2067
|
}
|
|
|
2068
|
|
|
|
2069
|
return targetPage;
|
|
|
2070
|
}
|
|
|
2071
|
|
|
|
2072
|
@Override
|
|
|
2073
|
public void draw(Canvas canvas) {
|
|
|
2074
|
super.draw(canvas);
|
|
|
2075
|
boolean needsInvalidate = false;
|
|
|
2076
|
|
|
|
2077
|
final int overScrollMode = ViewCompat.getOverScrollMode(this);
|
|
|
2078
|
if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||
|
|
|
2079
|
(overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS &&
|
|
|
2080
|
mAdapter != null && mAdapter.getCount() > 1)) {
|
|
|
2081
|
if (!mTopEdge.isFinished()) {
|
|
|
2082
|
final int restoreCount = canvas.save();
|
|
|
2083
|
final int height = getHeight();
|
|
|
2084
|
final int width = getWidth() - getPaddingLeft() - getPaddingRight();
|
|
|
2085
|
|
|
|
2086
|
canvas.translate(getPaddingLeft(), mFirstOffset * height);
|
|
|
2087
|
mTopEdge.setSize(width, height);
|
|
|
2088
|
needsInvalidate |= mTopEdge.draw(canvas);
|
|
|
2089
|
canvas.restoreToCount(restoreCount);
|
|
|
2090
|
}
|
|
|
2091
|
if (!mBottomEdge.isFinished()) {
|
|
|
2092
|
final int restoreCount = canvas.save();
|
|
|
2093
|
final int height = getHeight();
|
|
|
2094
|
final int width = getWidth() - getPaddingLeft() - getPaddingRight();
|
|
|
2095
|
|
|
|
2096
|
canvas.rotate(180);
|
|
|
2097
|
canvas.translate(-width - getPaddingLeft(), -(mLastOffset + 1) * height);
|
|
|
2098
|
mBottomEdge.setSize(width, height);
|
|
|
2099
|
needsInvalidate |= mBottomEdge.draw(canvas);
|
|
|
2100
|
canvas.restoreToCount(restoreCount);
|
|
|
2101
|
}
|
|
|
2102
|
} else {
|
|
|
2103
|
mTopEdge.finish();
|
|
|
2104
|
mBottomEdge.finish();
|
|
|
2105
|
}
|
|
|
2106
|
|
|
|
2107
|
if (needsInvalidate) {
|
|
|
2108
|
// Keep animating
|
|
|
2109
|
ViewCompat.postInvalidateOnAnimation(this);
|
|
|
2110
|
}
|
|
|
2111
|
}
|
|
|
2112
|
|
|
|
2113
|
@Override
|
|
|
2114
|
protected void onDraw(Canvas canvas) {
|
|
|
2115
|
super.onDraw(canvas);
|
|
|
2116
|
|
|
|
2117
|
// Draw the margin drawable between pages if needed.
|
|
|
2118
|
if (mPageMargin > 0 && mMarginDrawable != null && mItems.size() > 0 && mAdapter != null) {
|
|
|
2119
|
final int scrollY = getScrollY();
|
|
|
2120
|
final int height = getHeight();
|
|
|
2121
|
|
|
|
2122
|
final float marginOffset = (float) mPageMargin / height;
|
|
|
2123
|
int itemIndex = 0;
|
|
|
2124
|
ItemInfo ii = mItems.get(0);
|
|
|
2125
|
float offset = ii.offset;
|
|
|
2126
|
final int itemCount = mItems.size();
|
|
|
2127
|
final int firstPos = ii.position;
|
|
|
2128
|
final int lastPos = mItems.get(itemCount - 1).position;
|
|
|
2129
|
for (int pos = firstPos; pos < lastPos; pos++) {
|
|
|
2130
|
while (pos > ii.position && itemIndex < itemCount) {
|
|
|
2131
|
ii = mItems.get(++itemIndex);
|
|
|
2132
|
}
|
|
|
2133
|
|
|
|
2134
|
float drawAt;
|
|
|
2135
|
if (pos == ii.position) {
|
|
|
2136
|
drawAt = (ii.offset + ii.heightFactor) * height;
|
|
|
2137
|
offset = ii.offset + ii.heightFactor + marginOffset;
|
|
|
2138
|
} else {
|
|
|
2139
|
float heightFactor = mAdapter.getPageWidth(pos);
|
|
|
2140
|
drawAt = (offset + heightFactor) * height;
|
|
|
2141
|
offset += heightFactor + marginOffset;
|
|
|
2142
|
}
|
|
|
2143
|
|
|
|
2144
|
if (drawAt + mPageMargin > scrollY) {
|
|
|
2145
|
mMarginDrawable.setBounds(mLeftPageBounds, (int) drawAt,
|
|
|
2146
|
mRightPageBounds, (int) (drawAt + mPageMargin + 0.5f));
|
|
|
2147
|
mMarginDrawable.draw(canvas);
|
|
|
2148
|
}
|
|
|
2149
|
|
|
|
2150
|
if (drawAt > scrollY + height) {
|
|
|
2151
|
break; // No more visible, no sense in continuing
|
|
|
2152
|
}
|
|
|
2153
|
}
|
|
|
2154
|
}
|
|
|
2155
|
}
|
|
|
2156
|
|
|
|
2157
|
/**
|
|
|
2158
|
* Start a fake drag of the pager.
|
|
|
2159
|
* <p/>
|
|
|
2160
|
* <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager
|
|
|
2161
|
* with the touch scrolling of another view, while still letting the ViewPager
|
|
|
2162
|
* control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)
|
|
|
2163
|
* Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call
|
|
|
2164
|
* {@link #endFakeDrag()} to complete the fake drag and fling as necessary.
|
|
|
2165
|
* <p/>
|
|
|
2166
|
* <p>During a fake drag the ViewPager will ignore all touch events. If a real drag
|
|
|
2167
|
* is already in progress, this method will return false.
|
|
|
2168
|
*
|
|
|
2169
|
* @return true if the fake drag began successfully, false if it could not be started.
|
|
|
2170
|
* @see #fakeDragBy(float)
|
|
|
2171
|
* @see #endFakeDrag()
|
|
|
2172
|
*/
|
|
|
2173
|
public boolean beginFakeDrag() {
|
|
|
2174
|
if (mIsBeingDragged) {
|
|
|
2175
|
return false;
|
|
|
2176
|
}
|
|
|
2177
|
mFakeDragging = true;
|
|
|
2178
|
setScrollState(SCROLL_STATE_DRAGGING);
|
|
|
2179
|
mInitialMotionY = mLastMotionY = 0;
|
|
|
2180
|
if (mVelocityTracker == null) {
|
|
|
2181
|
mVelocityTracker = VelocityTracker.obtain();
|
|
|
2182
|
} else {
|
|
|
2183
|
mVelocityTracker.clear();
|
|
|
2184
|
}
|
|
|
2185
|
final long time = SystemClock.uptimeMillis();
|
|
|
2186
|
final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);
|
|
|
2187
|
mVelocityTracker.addMovement(ev);
|
|
|
2188
|
ev.recycle();
|
|
|
2189
|
mFakeDragBeginTime = time;
|
|
|
2190
|
return true;
|
|
|
2191
|
}
|
|
|
2192
|
|
|
|
2193
|
/**
|
|
|
2194
|
* End a fake drag of the pager.
|
|
|
2195
|
*
|
|
|
2196
|
* @see #beginFakeDrag()
|
|
|
2197
|
* @see #fakeDragBy(float)
|
|
|
2198
|
*/
|
|
|
2199
|
public void endFakeDrag() {
|
|
|
2200
|
if (!mFakeDragging) {
|
|
|
2201
|
throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
|
|
|
2202
|
}
|
|
|
2203
|
|
|
|
2204
|
final VelocityTracker velocityTracker = mVelocityTracker;
|
|
|
2205
|
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
|
|
|
2206
|
int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(
|
|
|
2207
|
velocityTracker, mActivePointerId);
|
|
|
2208
|
mPopulatePending = true;
|
|
|
2209
|
final int height = getClientHeight();
|
|
|
2210
|
final int scrollY = getScrollY();
|
|
|
2211
|
final ItemInfo ii = infoForCurrentScrollPosition();
|
|
|
2212
|
final int currentPage = ii.position;
|
|
|
2213
|
final float pageOffset = (((float) scrollY / height) - ii.offset) / ii.heightFactor;
|
|
|
2214
|
final int totalDelta = (int) (mLastMotionY - mInitialMotionY);
|
|
|
2215
|
int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
|
|
|
2216
|
totalDelta);
|
|
|
2217
|
setCurrentItemInternal(nextPage, true, true, initialVelocity);
|
|
|
2218
|
endDrag();
|
|
|
2219
|
|
|
|
2220
|
mFakeDragging = false;
|
|
|
2221
|
}
|
|
|
2222
|
|
|
|
2223
|
/**
|
|
|
2224
|
* Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first.
|
|
|
2225
|
*
|
|
|
2226
|
* @param yOffset Offset in pixels to drag by.
|
|
|
2227
|
* @see #beginFakeDrag()
|
|
|
2228
|
* @see #endFakeDrag()
|
|
|
2229
|
*/
|
|
|
2230
|
public void fakeDragBy(float yOffset) {
|
|
|
2231
|
if (!mFakeDragging) {
|
|
|
2232
|
throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
|
|
|
2233
|
}
|
|
|
2234
|
|
|
|
2235
|
mLastMotionY += yOffset;
|
|
|
2236
|
|
|
|
2237
|
float oldScrollY = getScrollY();
|
|
|
2238
|
float scrollY = oldScrollY - yOffset;
|
|
|
2239
|
final int height = getClientHeight();
|
|
|
2240
|
|
|
|
2241
|
float topBound = height * mFirstOffset;
|
|
|
2242
|
float bottomBound = height * mLastOffset;
|
|
|
2243
|
|
|
|
2244
|
final ItemInfo firstItem = mItems.get(0);
|
|
|
2245
|
final ItemInfo lastItem = mItems.get(mItems.size() - 1);
|
|
|
2246
|
if (firstItem.position != 0) {
|
|
|
2247
|
topBound = firstItem.offset * height;
|
|
|
2248
|
}
|
|
|
2249
|
if (lastItem.position != mAdapter.getCount() - 1) {
|
|
|
2250
|
bottomBound = lastItem.offset * height;
|
|
|
2251
|
}
|
|
|
2252
|
|
|
|
2253
|
if (scrollY < topBound) {
|
|
|
2254
|
scrollY = topBound;
|
|
|
2255
|
} else if (scrollY > bottomBound) {
|
|
|
2256
|
scrollY = bottomBound;
|
|
|
2257
|
}
|
|
|
2258
|
// Don't lose the rounded component
|
|
|
2259
|
mLastMotionY += scrollY - (int) scrollY;
|
|
|
2260
|
scrollTo(getScrollX(), (int) scrollY);
|
|
|
2261
|
pageScrolled((int) scrollY);
|
|
|
2262
|
|
|
|
2263
|
// Synthesize an event for the VelocityTracker.
|
|
|
2264
|
final long time = SystemClock.uptimeMillis();
|
|
|
2265
|
final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
|
|
|
2266
|
0, mLastMotionY, 0);
|
|
|
2267
|
mVelocityTracker.addMovement(ev);
|
|
|
2268
|
ev.recycle();
|
|
|
2269
|
}
|
|
|
2270
|
|
|
|
2271
|
/**
|
|
|
2272
|
* Returns true if a fake drag is in progress.
|
|
|
2273
|
*
|
|
|
2274
|
* @return true if currently in a fake drag, false otherwise.
|
|
|
2275
|
* @see #beginFakeDrag()
|
|
|
2276
|
* @see #fakeDragBy(float)
|
|
|
2277
|
* @see #endFakeDrag()
|
|
|
2278
|
*/
|
|
|
2279
|
public boolean isFakeDragging() {
|
|
|
2280
|
return mFakeDragging;
|
|
|
2281
|
}
|
|
|
2282
|
|
|
|
2283
|
private void onSecondaryPointerUp(MotionEvent ev) {
|
|
|
2284
|
final int pointerIndex = MotionEventCompat.getActionIndex(ev);
|
|
|
2285
|
final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
|
|
|
2286
|
if (pointerId == mActivePointerId) {
|
|
|
2287
|
// This was our active pointer going up. Choose a new
|
|
|
2288
|
// active pointer and adjust accordingly.
|
|
|
2289
|
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
|
|
|
2290
|
mLastMotionY = MotionEventCompat.getY(ev, newPointerIndex);
|
|
|
2291
|
mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
|
|
|
2292
|
if (mVelocityTracker != null) {
|
|
|
2293
|
mVelocityTracker.clear();
|
|
|
2294
|
}
|
|
|
2295
|
}
|
|
|
2296
|
}
|
|
|
2297
|
|
|
|
2298
|
private void endDrag() {
|
|
|
2299
|
mIsBeingDragged = false;
|
|
|
2300
|
mIsUnableToDrag = false;
|
|
|
2301
|
|
|
|
2302
|
if (mVelocityTracker != null) {
|
|
|
2303
|
mVelocityTracker.recycle();
|
|
|
2304
|
mVelocityTracker = null;
|
|
|
2305
|
}
|
|
|
2306
|
}
|
|
|
2307
|
|
|
|
2308
|
private void setScrollingCacheEnabled(boolean enabled) {
|
|
|
2309
|
if (mScrollingCacheEnabled != enabled) {
|
|
|
2310
|
mScrollingCacheEnabled = enabled;
|
|
|
2311
|
if (USE_CACHE) {
|
|
|
2312
|
final int size = getChildCount();
|
|
|
2313
|
for (int i = 0; i < size; ++i) {
|
|
|
2314
|
final View child = getChildAt(i);
|
|
|
2315
|
if (child.getVisibility() != GONE) {
|
|
|
2316
|
child.setDrawingCacheEnabled(enabled);
|
|
|
2317
|
}
|
|
|
2318
|
}
|
|
|
2319
|
}
|
|
|
2320
|
}
|
|
|
2321
|
}
|
|
|
2322
|
|
|
|
2323
|
public boolean internalCanScrollVertically(int direction) {
|
|
|
2324
|
if (mAdapter == null) {
|
|
|
2325
|
return false;
|
|
|
2326
|
}
|
|
|
2327
|
|
|
|
2328
|
final int height = getClientHeight();
|
|
|
2329
|
final int scrollY = getScrollY();
|
|
|
2330
|
if (direction < 0) {
|
|
|
2331
|
return (scrollY > (int) (height * mFirstOffset));
|
|
|
2332
|
} else if (direction > 0) {
|
|
|
2333
|
return (scrollY < (int) (height * mLastOffset));
|
|
|
2334
|
} else {
|
|
|
2335
|
return false;
|
|
|
2336
|
}
|
|
|
2337
|
}
|
|
|
2338
|
|
|
|
2339
|
/**
|
|
|
2340
|
* Tests scrollability within child views of v given a delta of dx.
|
|
|
2341
|
*
|
|
|
2342
|
* @param v View to test for horizontal scrollability
|
|
|
2343
|
* @param checkV Whether the view v passed should itself be checked for scrollability (true),
|
|
|
2344
|
* or just its children (false).
|
|
|
2345
|
* @param dy Delta scrolled in pixels
|
|
|
2346
|
* @param x X coordinate of the active touch point
|
|
|
2347
|
* @param y Y coordinate of the active touch point
|
|
|
2348
|
* @return true if child views of v can be scrolled by delta of dx.
|
|
|
2349
|
*/
|
|
|
2350
|
protected boolean canScroll(View v, boolean checkV, int dy, int x, int y) {
|
|
|
2351
|
if (v instanceof ViewGroup) {
|
|
|
2352
|
final ViewGroup group = (ViewGroup) v;
|
|
|
2353
|
final int scrollX = v.getScrollX();
|
|
|
2354
|
final int scrollY = v.getScrollY();
|
|
|
2355
|
final int count = group.getChildCount();
|
|
|
2356
|
// Count backwards - let topmost views consume scroll distance first.
|
|
|
2357
|
for (int i = count - 1; i >= 0; i--) {
|
|
|
2358
|
// TODO: Add versioned support here for transformed views.
|
|
|
2359
|
// This will not work for transformed views in Honeycomb+
|
|
|
2360
|
final View child = group.getChildAt(i);
|
|
|
2361
|
if (y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
|
|
|
2362
|
x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
|
|
|
2363
|
canScroll(child, true, dy, x + scrollX - child.getLeft(),
|
|
|
2364
|
y + scrollY - child.getTop())) {
|
|
|
2365
|
return true;
|
|
|
2366
|
}
|
|
|
2367
|
}
|
|
|
2368
|
}
|
|
|
2369
|
|
|
|
2370
|
return checkV && ViewCompat.canScrollVertically(v, -dy);
|
|
|
2371
|
}
|
|
|
2372
|
|
|
|
2373
|
@Override
|
|
|
2374
|
public boolean dispatchKeyEvent(KeyEvent event) {
|
|
|
2375
|
// Let the focused view and/or our descendants get the key first
|
|
|
2376
|
return super.dispatchKeyEvent(event) || executeKeyEvent(event);
|
|
|
2377
|
}
|
|
|
2378
|
|
|
|
2379
|
/**
|
|
|
2380
|
* You can call this function yourself to have the scroll view perform
|
|
|
2381
|
* scrolling from a key event, just as if the event had been dispatched to
|
|
|
2382
|
* it by the view hierarchy.
|
|
|
2383
|
*
|
|
|
2384
|
* @param event The key event to execute.
|
|
|
2385
|
* @return Return true if the event was handled, else false.
|
|
|
2386
|
*/
|
|
|
2387
|
public boolean executeKeyEvent(KeyEvent event) {
|
|
|
2388
|
boolean handled = false;
|
|
|
2389
|
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
|
|
2390
|
switch (event.getKeyCode()) {
|
|
|
2391
|
case KeyEvent.KEYCODE_DPAD_LEFT:
|
|
|
2392
|
handled = arrowScroll(FOCUS_LEFT);
|
|
|
2393
|
break;
|
|
|
2394
|
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
|
|
2395
|
handled = arrowScroll(FOCUS_RIGHT);
|
|
|
2396
|
break;
|
|
|
2397
|
case KeyEvent.KEYCODE_TAB:
|
|
|
2398
|
if (Build.VERSION.SDK_INT >= 11) {
|
|
|
2399
|
if (event.hasNoModifiers()) {
|
|
|
2400
|
handled = arrowScroll(FOCUS_FORWARD);
|
|
|
2401
|
} else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
|
|
|
2402
|
handled = arrowScroll(FOCUS_BACKWARD);
|
|
|
2403
|
}
|
|
|
2404
|
}
|
|
|
2405
|
break;
|
|
|
2406
|
}
|
|
|
2407
|
}
|
|
|
2408
|
return handled;
|
|
|
2409
|
}
|
|
|
2410
|
|
|
|
2411
|
public boolean arrowScroll(int direction) {
|
|
|
2412
|
View currentFocused = findFocus();
|
|
|
2413
|
if (currentFocused == this) {
|
|
|
2414
|
currentFocused = null;
|
|
|
2415
|
} else if (currentFocused != null) {
|
|
|
2416
|
boolean isChild = false;
|
|
|
2417
|
for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup;
|
|
|
2418
|
parent = parent.getParent()) {
|
|
|
2419
|
if (parent == this) {
|
|
|
2420
|
isChild = true;
|
|
|
2421
|
break;
|
|
|
2422
|
}
|
|
|
2423
|
}
|
|
|
2424
|
if (!isChild) {
|
|
|
2425
|
// This would cause the focus search down below to fail in fun ways.
|
|
|
2426
|
final StringBuilder sb = new StringBuilder();
|
|
|
2427
|
sb.append(currentFocused.getClass().getSimpleName());
|
|
|
2428
|
for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup;
|
|
|
2429
|
parent = parent.getParent()) {
|
|
|
2430
|
sb.append(" => ").append(parent.getClass().getSimpleName());
|
|
|
2431
|
}
|
|
|
2432
|
Log.e(TAG, "arrowScroll tried to find focus based on non-child " +
|
|
|
2433
|
"current focused view " + sb.toString());
|
|
|
2434
|
currentFocused = null;
|
|
|
2435
|
}
|
|
|
2436
|
}
|
|
|
2437
|
|
|
|
2438
|
boolean handled = false;
|
|
|
2439
|
|
|
|
2440
|
View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused,
|
|
|
2441
|
direction);
|
|
|
2442
|
if (nextFocused != null && nextFocused != currentFocused) {
|
|
|
2443
|
if (direction == View.FOCUS_UP) {
|
|
|
2444
|
// If there is nothing to the left, or this is causing us to
|
|
|
2445
|
// jump to the right, then what we really want to do is page left.
|
|
|
2446
|
final int nextTop = getChildRectInPagerCoordinates(mTempRect, nextFocused).top;
|
|
|
2447
|
final int currTop = getChildRectInPagerCoordinates(mTempRect, currentFocused).top;
|
|
|
2448
|
if (currentFocused != null && nextTop >= currTop) {
|
|
|
2449
|
handled = pageUp();
|
|
|
2450
|
} else {
|
|
|
2451
|
handled = nextFocused.requestFocus();
|
|
|
2452
|
}
|
|
|
2453
|
} else if (direction == View.FOCUS_DOWN) {
|
|
|
2454
|
// If there is nothing to the right, or this is causing us to
|
|
|
2455
|
// jump to the left, then what we really want to do is page right.
|
|
|
2456
|
final int nextDown = getChildRectInPagerCoordinates(mTempRect, nextFocused).bottom;
|
|
|
2457
|
final int currDown = getChildRectInPagerCoordinates(mTempRect, currentFocused).bottom;
|
|
|
2458
|
if (currentFocused != null && nextDown <= currDown) {
|
|
|
2459
|
handled = pageDown();
|
|
|
2460
|
} else {
|
|
|
2461
|
handled = nextFocused.requestFocus();
|
|
|
2462
|
}
|
|
|
2463
|
}
|
|
|
2464
|
} else if (direction == FOCUS_UP || direction == FOCUS_BACKWARD) {
|
|
|
2465
|
// Trying to move left and nothing there; try to page.
|
|
|
2466
|
handled = pageUp();
|
|
|
2467
|
} else if (direction == FOCUS_DOWN || direction == FOCUS_FORWARD) {
|
|
|
2468
|
// Trying to move right and nothing there; try to page.
|
|
|
2469
|
handled = pageDown();
|
|
|
2470
|
}
|
|
|
2471
|
if (handled) {
|
|
|
2472
|
playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
|
|
|
2473
|
}
|
|
|
2474
|
return handled;
|
|
|
2475
|
}
|
|
|
2476
|
|
|
|
2477
|
private Rect getChildRectInPagerCoordinates(Rect outRect, View child) {
|
|
|
2478
|
if (outRect == null) {
|
|
|
2479
|
outRect = new Rect();
|
|
|
2480
|
}
|
|
|
2481
|
if (child == null) {
|
|
|
2482
|
outRect.set(0, 0, 0, 0);
|
|
|
2483
|
return outRect;
|
|
|
2484
|
}
|
|
|
2485
|
outRect.left = child.getLeft();
|
|
|
2486
|
outRect.right = child.getRight();
|
|
|
2487
|
outRect.top = child.getTop();
|
|
|
2488
|
outRect.bottom = child.getBottom();
|
|
|
2489
|
|
|
|
2490
|
ViewParent parent = child.getParent();
|
|
|
2491
|
while (parent instanceof ViewGroup && parent != this) {
|
|
|
2492
|
final ViewGroup group = (ViewGroup) parent;
|
|
|
2493
|
outRect.left += group.getLeft();
|
|
|
2494
|
outRect.right += group.getRight();
|
|
|
2495
|
outRect.top += group.getTop();
|
|
|
2496
|
outRect.bottom += group.getBottom();
|
|
|
2497
|
|
|
|
2498
|
parent = group.getParent();
|
|
|
2499
|
}
|
|
|
2500
|
return outRect;
|
|
|
2501
|
}
|
|
|
2502
|
|
|
|
2503
|
boolean pageUp() {
|
|
|
2504
|
if (mCurItem > 0) {
|
|
|
2505
|
setCurrentItem(mCurItem - 1, true);
|
|
|
2506
|
return true;
|
|
|
2507
|
}
|
|
|
2508
|
return false;
|
|
|
2509
|
}
|
|
|
2510
|
|
|
|
2511
|
boolean pageDown() {
|
|
|
2512
|
if (mAdapter != null && mCurItem < (mAdapter.getCount() - 1)) {
|
|
|
2513
|
setCurrentItem(mCurItem + 1, true);
|
|
|
2514
|
return true;
|
|
|
2515
|
}
|
|
|
2516
|
return false;
|
|
|
2517
|
}
|
|
|
2518
|
|
|
|
2519
|
/**
|
|
|
2520
|
* We only want the current page that is being shown to be focusable.
|
|
|
2521
|
*/
|
|
|
2522
|
@Override
|
|
|
2523
|
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
|
|
|
2524
|
final int focusableCount = views.size();
|
|
|
2525
|
|
|
|
2526
|
final int descendantFocusability = getDescendantFocusability();
|
|
|
2527
|
|
|
|
2528
|
if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
|
|
|
2529
|
for (int i = 0; i < getChildCount(); i++) {
|
|
|
2530
|
final View child = getChildAt(i);
|
|
|
2531
|
if (child.getVisibility() == VISIBLE) {
|
|
|
2532
|
ItemInfo ii = infoForChild(child);
|
|
|
2533
|
if (ii != null && ii.position == mCurItem) {
|
|
|
2534
|
child.addFocusables(views, direction, focusableMode);
|
|
|
2535
|
}
|
|
|
2536
|
}
|
|
|
2537
|
}
|
|
|
2538
|
}
|
|
|
2539
|
|
|
|
2540
|
// we add ourselves (if focusable) in all cases except for when we are
|
|
|
2541
|
// FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is
|
|
|
2542
|
// to avoid the focus search finding layouts when a more precise search
|
|
|
2543
|
// among the focusable children would be more interesting.
|
|
|
2544
|
if (
|
|
|
2545
|
descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
|
|
|
2546
|
// No focusable descendants
|
|
|
2547
|
(focusableCount == views.size())) {
|
|
|
2548
|
// Note that we can't call the superclass here, because it will
|
|
|
2549
|
// add all views in. So we need to do the same thing View does.
|
|
|
2550
|
if (!isFocusable()) {
|
|
|
2551
|
return;
|
|
|
2552
|
}
|
|
|
2553
|
if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE &&
|
|
|
2554
|
isInTouchMode() && !isFocusableInTouchMode()) {
|
|
|
2555
|
return;
|
|
|
2556
|
}
|
|
|
2557
|
if (views != null) {
|
|
|
2558
|
views.add(this);
|
|
|
2559
|
}
|
|
|
2560
|
}
|
|
|
2561
|
}
|
|
|
2562
|
|
|
|
2563
|
/**
|
|
|
2564
|
* We only want the current page that is being shown to be touchable.
|
|
|
2565
|
*/
|
|
|
2566
|
@Override
|
|
|
2567
|
public void addTouchables(ArrayList<View> views) {
|
|
|
2568
|
// Note that we don't call super.addTouchables(), which means that
|
|
|
2569
|
// we don't call View.addTouchables(). This is okay because a ViewPager
|
|
|
2570
|
// is itself not touchable.
|
|
|
2571
|
for (int i = 0; i < getChildCount(); i++) {
|
|
|
2572
|
final View child = getChildAt(i);
|
|
|
2573
|
if (child.getVisibility() == VISIBLE) {
|
|
|
2574
|
ItemInfo ii = infoForChild(child);
|
|
|
2575
|
if (ii != null && ii.position == mCurItem) {
|
|
|
2576
|
child.addTouchables(views);
|
|
|
2577
|
}
|
|
|
2578
|
}
|
|
|
2579
|
}
|
|
|
2580
|
}
|
|
|
2581
|
|
|
|
2582
|
/**
|
|
|
2583
|
* We only want the current page that is being shown to be focusable.
|
|
|
2584
|
*/
|
|
|
2585
|
@Override
|
|
|
2586
|
protected boolean onRequestFocusInDescendants(int direction,
|
|
|
2587
|
Rect previouslyFocusedRect) {
|
|
|
2588
|
int index;
|
|
|
2589
|
int increment;
|
|
|
2590
|
int end;
|
|
|
2591
|
int count = getChildCount();
|
|
|
2592
|
if ((direction & FOCUS_FORWARD) != 0) {
|
|
|
2593
|
index = 0;
|
|
|
2594
|
increment = 1;
|
|
|
2595
|
end = count;
|
|
|
2596
|
} else {
|
|
|
2597
|
index = count - 1;
|
|
|
2598
|
increment = -1;
|
|
|
2599
|
end = -1;
|
|
|
2600
|
}
|
|
|
2601
|
for (int i = index; i != end; i += increment) {
|
|
|
2602
|
View child = getChildAt(i);
|
|
|
2603
|
if (child.getVisibility() == VISIBLE) {
|
|
|
2604
|
ItemInfo ii = infoForChild(child);
|
|
|
2605
|
if (ii != null && ii.position == mCurItem) {
|
|
|
2606
|
if (child.requestFocus(direction, previouslyFocusedRect)) {
|
|
|
2607
|
return true;
|
|
|
2608
|
}
|
|
|
2609
|
}
|
|
|
2610
|
}
|
|
|
2611
|
}
|
|
|
2612
|
return false;
|
|
|
2613
|
}
|
|
|
2614
|
|
|
|
2615
|
@Override
|
|
|
2616
|
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
|
|
|
2617
|
// Dispatch scroll events from this ViewPager.
|
|
|
2618
|
if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED) {
|
|
|
2619
|
return super.dispatchPopulateAccessibilityEvent(event);
|
|
|
2620
|
}
|
|
|
2621
|
|
|
|
2622
|
// Dispatch all other accessibility events from the current page.
|
|
|
2623
|
final int childCount = getChildCount();
|
|
|
2624
|
for (int i = 0; i < childCount; i++) {
|
|
|
2625
|
final View child = getChildAt(i);
|
|
|
2626
|
if (child.getVisibility() == VISIBLE) {
|
|
|
2627
|
final ItemInfo ii = infoForChild(child);
|
|
|
2628
|
if (ii != null && ii.position == mCurItem &&
|
|
|
2629
|
child.dispatchPopulateAccessibilityEvent(event)) {
|
|
|
2630
|
return true;
|
|
|
2631
|
}
|
|
|
2632
|
}
|
|
|
2633
|
}
|
|
|
2634
|
|
|
|
2635
|
return false;
|
|
|
2636
|
}
|
|
|
2637
|
|
|
|
2638
|
@Override
|
|
|
2639
|
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
|
|
|
2640
|
return new LayoutParams();
|
|
|
2641
|
}
|
|
|
2642
|
|
|
|
2643
|
@Override
|
|
|
2644
|
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
|
|
|
2645
|
return generateDefaultLayoutParams();
|
|
|
2646
|
}
|
|
|
2647
|
|
|
|
2648
|
@Override
|
|
|
2649
|
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
|
|
|
2650
|
return p instanceof LayoutParams && super.checkLayoutParams(p);
|
|
|
2651
|
}
|
|
|
2652
|
|
|
|
2653
|
@Override
|
|
|
2654
|
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
|
|
|
2655
|
return new LayoutParams(getContext(), attrs);
|
|
|
2656
|
}
|
|
|
2657
|
|
|
|
2658
|
class MyAccessibilityDelegate extends AccessibilityDelegateCompat {
|
|
|
2659
|
|
|
|
2660
|
@Override
|
|
|
2661
|
public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
|
|
|
2662
|
super.onInitializeAccessibilityEvent(host, event);
|
|
|
2663
|
event.setClassName(ViewPager.class.getName());
|
|
|
2664
|
final AccessibilityRecordCompat recordCompat = AccessibilityRecordCompat.obtain();
|
|
|
2665
|
recordCompat.setScrollable(canScroll());
|
|
|
2666
|
if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED
|
|
|
2667
|
&& mAdapter != null) {
|
|
|
2668
|
recordCompat.setItemCount(mAdapter.getCount());
|
|
|
2669
|
recordCompat.setFromIndex(mCurItem);
|
|
|
2670
|
recordCompat.setToIndex(mCurItem);
|
|
|
2671
|
}
|
|
|
2672
|
}
|
|
|
2673
|
|
|
|
2674
|
@Override
|
|
|
2675
|
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
|
|
|
2676
|
super.onInitializeAccessibilityNodeInfo(host, info);
|
|
|
2677
|
info.setClassName(ViewPager.class.getName());
|
|
|
2678
|
info.setScrollable(canScroll());
|
|
|
2679
|
if (internalCanScrollVertically(1)) {
|
|
|
2680
|
info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
|
|
|
2681
|
}
|
|
|
2682
|
if (internalCanScrollVertically(-1)) {
|
|
|
2683
|
info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
|
|
|
2684
|
}
|
|
|
2685
|
}
|
|
|
2686
|
|
|
|
2687
|
@Override
|
|
|
2688
|
public boolean performAccessibilityAction(View host, int action, Bundle args) {
|
|
|
2689
|
if (super.performAccessibilityAction(host, action, args)) {
|
|
|
2690
|
return true;
|
|
|
2691
|
}
|
|
|
2692
|
switch (action) {
|
|
|
2693
|
case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: {
|
|
|
2694
|
if (internalCanScrollVertically(1)) {
|
|
|
2695
|
setCurrentItem(mCurItem + 1);
|
|
|
2696
|
return true;
|
|
|
2697
|
}
|
|
|
2698
|
}
|
|
|
2699
|
return false;
|
|
|
2700
|
case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: {
|
|
|
2701
|
if (internalCanScrollVertically(-1)) {
|
|
|
2702
|
setCurrentItem(mCurItem - 1);
|
|
|
2703
|
return true;
|
|
|
2704
|
}
|
|
|
2705
|
}
|
|
|
2706
|
return false;
|
|
|
2707
|
}
|
|
|
2708
|
return false;
|
|
|
2709
|
}
|
|
|
2710
|
|
|
|
2711
|
private boolean canScroll() {
|
|
|
2712
|
return (mAdapter != null) && (mAdapter.getCount() > 1);
|
|
|
2713
|
}
|
|
|
2714
|
}
|
|
|
2715
|
|
|
|
2716
|
private class PagerObserver extends DataSetObserver {
|
|
|
2717
|
@Override
|
|
|
2718
|
public void onChanged() {
|
|
|
2719
|
dataSetChanged();
|
|
|
2720
|
}
|
|
|
2721
|
|
|
|
2722
|
@Override
|
|
|
2723
|
public void onInvalidated() {
|
|
|
2724
|
dataSetChanged();
|
|
|
2725
|
}
|
|
|
2726
|
}
|
|
|
2727
|
|
|
|
2728
|
/**
|
|
|
2729
|
* Layout parameters that should be supplied for views added to a
|
|
|
2730
|
* ViewPager.
|
|
|
2731
|
*/
|
|
|
2732
|
public static class LayoutParams extends ViewGroup.LayoutParams {
|
|
|
2733
|
/**
|
|
|
2734
|
* true if this view is a decoration on the pager itself and not
|
|
|
2735
|
* a view supplied by the adapter.
|
|
|
2736
|
*/
|
|
|
2737
|
public boolean isDecor;
|
|
|
2738
|
|
|
|
2739
|
/**
|
|
|
2740
|
* Gravity setting for use on decor views only:
|
|
|
2741
|
* Where to position the view page within the overall ViewPager
|
|
|
2742
|
* container; constants are defined in {@link Gravity}.
|
|
|
2743
|
*/
|
|
|
2744
|
public int gravity;
|
|
|
2745
|
|
|
|
2746
|
/**
|
|
|
2747
|
* Width as a 0-1 multiplier of the measured pager width
|
|
|
2748
|
*/
|
|
|
2749
|
float heightFactor = 0.f;
|
|
|
2750
|
|
|
|
2751
|
/**
|
|
|
2752
|
* true if this view was added during layout and needs to be measured
|
|
|
2753
|
* before being positioned.
|
|
|
2754
|
*/
|
|
|
2755
|
boolean needsMeasure;
|
|
|
2756
|
|
|
|
2757
|
/**
|
|
|
2758
|
* Adapter position this view is for if !isDecor
|
|
|
2759
|
*/
|
|
|
2760
|
int position;
|
|
|
2761
|
|
|
|
2762
|
/**
|
|
|
2763
|
* Current child index within the ViewPager that this view occupies
|
|
|
2764
|
*/
|
|
|
2765
|
int childIndex;
|
|
|
2766
|
|
|
|
2767
|
public LayoutParams() {
|
|
|
2768
|
super(FILL_PARENT, FILL_PARENT);
|
|
|
2769
|
}
|
|
|
2770
|
|
|
|
2771
|
public LayoutParams(Context context, AttributeSet attrs) {
|
|
|
2772
|
super(context, attrs);
|
|
|
2773
|
|
|
|
2774
|
final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
|
|
|
2775
|
gravity = a.getInteger(0, Gravity.TOP);
|
|
|
2776
|
a.recycle();
|
|
|
2777
|
}
|
|
|
2778
|
}
|
|
|
2779
|
|
|
|
2780
|
static class ViewPositionComparator implements Comparator<View> {
|
|
|
2781
|
@Override
|
|
|
2782
|
public int compare(View lhs, View rhs) {
|
|
|
2783
|
final LayoutParams llp = (LayoutParams) lhs.getLayoutParams();
|
|
|
2784
|
final LayoutParams rlp = (LayoutParams) rhs.getLayoutParams();
|
|
|
2785
|
if (llp.isDecor != rlp.isDecor) {
|
|
|
2786
|
return llp.isDecor ? 1 : -1;
|
|
|
2787
|
}
|
|
|
2788
|
return llp.position - rlp.position;
|
|
|
2789
|
}
|
|
|
2790
|
}
|
|
|
2791
|
}
|
|
|
@ -0,0 +1,7 @@
|
|
|
1
|
<?xml version="1.0" encoding="utf-8"?>
|
|
|
2
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
|
|
3
|
<translate
|
|
|
4
|
android:duration="300"
|
|
|
5
|
android:fromYDelta="100%p"
|
|
|
6
|
android:toYDelta="0" />
|
|
|
7
|
</set>
|
|
|
@ -0,0 +1,7 @@
|
|
|
1
|
<?xml version="1.0" encoding="utf-8"?>
|
|
|
2
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
|
|
3
|
<translate
|
|
|
4
|
android:duration="300"
|
|
|
5
|
android:fromYDelta="0"
|
|
|
6
|
android:toYDelta="100%p" />
|
|
|
7
|
</set>
|
|
|
@ -0,0 +1,12 @@
|
|
|
1
|
<?xml version="1.0" encoding="utf-8"?>
|
|
|
2
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
|
|
3
|
android:shape="rectangle">
|
|
|
4
|
|
|
|
5
|
<corners
|
|
|
6
|
android:topLeftRadius="6dp"
|
|
|
7
|
android:topRightRadius="6dp" />
|
|
|
8
|
|
|
|
9
|
<solid android:color="#424242" />
|
|
|
10
|
|
|
|
11
|
|
|
|
12
|
</shape>
|
|
|
@ -0,0 +1,10 @@
|
|
|
1
|
<?xml version="1.0" encoding="utf-8"?>
|
|
|
2
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
|
|
3
|
android:shape="rectangle">
|
|
|
4
|
|
|
|
5
|
<corners android:radius="15dp" />
|
|
|
6
|
|
|
|
7
|
<solid android:color="#606060" />
|
|
|
8
|
|
|
|
9
|
|
|
|
10
|
</shape>
|
|
|
@ -0,0 +1,10 @@
|
|
|
1
|
<?xml version="1.0" encoding="utf-8"?>
|
|
|
2
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
|
|
3
|
android:shape="rectangle">
|
|
|
4
|
|
|
|
5
|
<corners android:radius="6dp" />
|
|
|
6
|
|
|
|
7
|
<solid android:color="#b3303030" />
|
|
|
8
|
|
|
|
9
|
|
|
|
10
|
</shape>
|
|
|
@ -0,0 +1,48 @@
|
|
|
1
|
<?xml version="1.0" encoding="utf-8"?>
|
|
|
2
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
|
3
|
android:layout_width="match_parent"
|
|
|
4
|
android:layout_height="match_parent"
|
|
|
5
|
android:background="#424242">
|
|
|
6
|
|
|
|
7
|
|
|
|
8
|
<com.electric.chargingpile.view.sview.VerticalViewPager
|
|
|
9
|
android:id="@+id/pager_video"
|
|
|
10
|
android:layout_width="match_parent"
|
|
|
11
|
android:layout_height="match_parent" />
|
|
|
12
|
|
|
|
13
|
|
|
|
14
|
<RelativeLayout
|
|
|
15
|
android:layout_width="match_parent"
|
|
|
16
|
android:layout_height="48dp">
|
|
|
17
|
|
|
|
18
|
|
|
|
19
|
<TextView
|
|
|
20
|
android:id="@+id/vd_title_tv"
|
|
|
21
|
android:layout_width="wrap_content"
|
|
|
22
|
android:layout_height="wrap_content"
|
|
|
23
|
android:layout_centerInParent="true"
|
|
|
24
|
android:text="待问答问题"
|
|
|
25
|
android:textColor="#ffffff"
|
|
|
26
|
android:textSize="18sp" />
|
|
|
27
|
|
|
|
28
|
|
|
|
29
|
<ImageView
|
|
|
30
|
android:id="@+id/vd_title_back"
|
|
|
31
|
android:layout_width="wrap_content"
|
|
|
32
|
android:layout_height="match_parent"
|
|
|
33
|
android:layout_alignParentLeft="true"
|
|
|
34
|
android:paddingLeft="15dp"
|
|
|
35
|
android:paddingRight="15dp"
|
|
|
36
|
android:src="@drawable/tubiao1" />
|
|
|
37
|
|
|
|
38
|
<ImageView
|
|
|
39
|
android:id="@+id/vd_title_dot"
|
|
|
40
|
android:layout_width="wrap_content"
|
|
|
41
|
android:layout_height="match_parent"
|
|
|
42
|
android:layout_alignParentRight="true"
|
|
|
43
|
android:paddingLeft="15dp"
|
|
|
44
|
android:paddingRight="15dp"
|
|
|
45
|
android:src="@drawable/tubiao1" />
|
|
|
46
|
|
|
|
47
|
</RelativeLayout>
|
|
|
48
|
</RelativeLayout>
|
|
|
@ -0,0 +1,11 @@
|
|
|
1
|
<?xml version="1.0" encoding="utf-8"?>
|
|
|
2
|
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
|
3
|
android:layout_width="match_parent"
|
|
|
4
|
android:layout_height="match_parent">
|
|
|
5
|
|
|
|
6
|
<com.electric.chargingpile.view.sview.VerticalViewPager
|
|
|
7
|
android:id="@+id/pager_video"
|
|
|
8
|
android:layout_width="match_parent"
|
|
|
9
|
android:layout_height="match_parent" />
|
|
|
10
|
|
|
|
11
|
</android.support.constraint.ConstraintLayout>
|
|
|
@ -0,0 +1,130 @@
|
|
|
1
|
<?xml version="1.0" encoding="utf-8"?>
|
|
|
2
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
|
3
|
android:layout_width="match_parent"
|
|
|
4
|
android:layout_height="wrap_content"
|
|
|
5
|
android:layout_marginTop="20dp">
|
|
|
6
|
|
|
|
7
|
<LinearLayout
|
|
|
8
|
android:id="@+id/item_user_comment_like"
|
|
|
9
|
android:layout_width="wrap_content"
|
|
|
10
|
android:layout_height="18dp"
|
|
|
11
|
android:layout_alignParentEnd="true"
|
|
|
12
|
android:layout_marginTop="3dp"
|
|
|
13
|
android:layout_marginEnd="19dp"
|
|
|
14
|
android:gravity="center_vertical">
|
|
|
15
|
|
|
|
16
|
<ImageView
|
|
|
17
|
android:id="@+id/item_user_like_img"
|
|
|
18
|
android:layout_width="18dp"
|
|
|
19
|
android:layout_height="18dp"
|
|
|
20
|
android:background="#fff" />
|
|
|
21
|
|
|
|
22
|
<TextView
|
|
|
23
|
android:id="@+id/item_user_like_count"
|
|
|
24
|
android:layout_width="wrap_content"
|
|
|
25
|
android:layout_height="wrap_content"
|
|
|
26
|
android:layout_marginStart="4dp"
|
|
|
27
|
android:text="1000"
|
|
|
28
|
android:textColor="#888"
|
|
|
29
|
android:textSize="12sp" />
|
|
|
30
|
</LinearLayout>
|
|
|
31
|
|
|
|
32
|
|
|
|
33
|
<ImageView
|
|
|
34
|
android:id="@+id/item_user_avatar"
|
|
|
35
|
android:layout_width="20dp"
|
|
|
36
|
android:layout_height="20dp"
|
|
|
37
|
android:layout_marginStart="15dp"
|
|
|
38
|
android:layout_marginTop="6dp"
|
|
|
39
|
android:background="#3df" />
|
|
|
40
|
|
|
|
41
|
|
|
|
42
|
<TextView
|
|
|
43
|
android:id="@+id/item_user_name"
|
|
|
44
|
android:layout_width="wrap_content"
|
|
|
45
|
android:layout_height="wrap_content"
|
|
|
46
|
android:layout_marginStart="5dp"
|
|
|
47
|
android:layout_toEndOf="@+id/item_user_avatar"
|
|
|
48
|
android:text="12sp12sp12sp"
|
|
|
49
|
android:textColor="#fff"
|
|
|
50
|
android:textSize="12sp" />
|
|
|
51
|
|
|
|
52
|
<TextView
|
|
|
53
|
android:id="@+id/item_user_time"
|
|
|
54
|
android:layout_width="wrap_content"
|
|
|
55
|
android:layout_height="wrap_content"
|
|
|
56
|
android:layout_below="@+id/item_user_name"
|
|
|
57
|
android:layout_alignStart="@+id/item_user_name"
|
|
|
58
|
android:layout_marginStart="5dp"
|
|
|
59
|
android:text="12sp12sp12sp"
|
|
|
60
|
android:textColor="#888"
|
|
|
61
|
android:textSize="12sp" />
|
|
|
62
|
|
|
|
63
|
|
|
|
64
|
<TextView
|
|
|
65
|
android:id="@+id/item_user_con"
|
|
|
66
|
android:layout_width="wrap_content"
|
|
|
67
|
android:layout_height="wrap_content"
|
|
|
68
|
android:layout_below="@+id/item_user_time"
|
|
|
69
|
android:layout_alignStart="@+id/item_user_name"
|
|
|
70
|
android:layout_marginStart="5dp"
|
|
|
71
|
android:layout_marginTop="10dp"
|
|
|
72
|
android:text="12sp12sp12sp"
|
|
|
73
|
android:textColor="#fff"
|
|
|
74
|
android:textSize="14sp" />
|
|
|
75
|
|
|
|
76
|
<LinearLayout
|
|
|
77
|
android:id="@+id/item_user_operation"
|
|
|
78
|
android:layout_width="wrap_content"
|
|
|
79
|
android:layout_height="22dp"
|
|
|
80
|
android:layout_below="@+id/item_user_con"
|
|
|
81
|
android:layout_alignStart="@+id/item_user_name"
|
|
|
82
|
android:layout_marginTop="10dp"
|
|
|
83
|
android:layout_marginBottom="22dp"
|
|
|
84
|
android:gravity="center_vertical">
|
|
|
85
|
|
|
|
86
|
<LinearLayout
|
|
|
87
|
android:id="@+id/item_user_report_ll"
|
|
|
88
|
android:layout_width="wrap_content"
|
|
|
89
|
android:layout_height="match_parent"
|
|
|
90
|
android:layout_marginEnd="10dp"
|
|
|
91
|
android:background="@drawable/bg_comment_report"
|
|
|
92
|
android:gravity="center_vertical">
|
|
|
93
|
|
|
|
94
|
<ImageView
|
|
|
95
|
android:layout_width="16dp"
|
|
|
96
|
android:layout_height="16dp"
|
|
|
97
|
android:layout_marginStart="9dp"
|
|
|
98
|
android:background="#fff" />
|
|
|
99
|
|
|
|
100
|
<TextView
|
|
|
101
|
android:id="@+id/item_user_report_count"
|
|
|
102
|
android:layout_width="wrap_content"
|
|
|
103
|
android:layout_height="wrap_content"
|
|
|
104
|
android:layout_marginStart="5dp"
|
|
|
105
|
android:layout_marginEnd="9dp"
|
|
|
106
|
android:text="1000回复"
|
|
|
107
|
android:textColor="#c2c2c2"
|
|
|
108
|
android:textSize="12sp" />
|
|
|
109
|
</LinearLayout>
|
|
|
110
|
|
|
|
111
|
|
|
|
112
|
<TextView
|
|
|
113
|
android:id="@+id/item_user_delete"
|
|
|
114
|
android:layout_width="wrap_content"
|
|
|
115
|
android:layout_height="22dp"
|
|
|
116
|
android:gravity="center"
|
|
|
117
|
android:text="删除"
|
|
|
118
|
android:textColor="#c2c2c2"
|
|
|
119
|
android:textSize="12sp" />
|
|
|
120
|
</LinearLayout>
|
|
|
121
|
|
|
|
122
|
<View
|
|
|
123
|
android:layout_width="match_parent"
|
|
|
124
|
android:layout_height="1dp"
|
|
|
125
|
android:layout_below="@+id/item_user_operation"
|
|
|
126
|
android:layout_marginStart="16dp"
|
|
|
127
|
android:background="#505050" />
|
|
|
128
|
|
|
|
129
|
|
|
|
130
|
</RelativeLayout>
|
|
|
@ -0,0 +1,85 @@
|
|
|
1
|
<?xml version="1.0" encoding="utf-8"?>
|
|
|
2
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
|
3
|
android:layout_width="match_parent"
|
|
|
4
|
android:layout_height="wrap_content"
|
|
|
5
|
android:background="@drawable/bg_bottom_dialog">
|
|
|
6
|
|
|
|
7
|
|
|
|
8
|
<View
|
|
|
9
|
android:id="@+id/video_publish_close"
|
|
|
10
|
android:layout_width="22dp"
|
|
|
11
|
android:layout_height="22dp"
|
|
|
12
|
android:layout_alignParentEnd="true"
|
|
|
13
|
android:layout_marginTop="14dp"
|
|
|
14
|
android:layout_marginEnd="14dp"
|
|
|
15
|
android:background="#fff" />
|
|
|
16
|
|
|
|
17
|
|
|
|
18
|
<LinearLayout
|
|
|
19
|
android:id="@+id/video_publish_topic"
|
|
|
20
|
android:layout_width="wrap_content"
|
|
|
21
|
android:layout_height="wrap_content"
|
|
|
22
|
android:layout_marginStart="15dp"
|
|
|
23
|
android:layout_marginTop="15dp"
|
|
|
24
|
android:layout_marginEnd="40dp"
|
|
|
25
|
android:layout_marginBottom="10dp"
|
|
|
26
|
android:background="@drawable/bg_topic"
|
|
|
27
|
android:gravity="center_vertical"
|
|
|
28
|
android:minHeight="30dp">
|
|
|
29
|
|
|
|
30
|
<View
|
|
|
31
|
android:layout_width="12dp"
|
|
|
32
|
android:layout_height="12dp"
|
|
|
33
|
android:layout_marginStart="12dp"
|
|
|
34
|
android:layout_marginEnd="4dp"
|
|
|
35
|
android:background="#fff" />
|
|
|
36
|
|
|
|
37
|
<TextView
|
|
|
38
|
android:id="@+id/video_publish_topic_con"
|
|
|
39
|
android:layout_width="wrap_content"
|
|
|
40
|
android:layout_height="wrap_content"
|
|
|
41
|
android:layout_margin="5dp"
|
|
|
42
|
android:text="显示出所有文字内容显示出所有文字内容显示出所有文字内容"
|
|
|
43
|
android:textColor="#fff"
|
|
|
44
|
android:textSize="11sp" />
|
|
|
45
|
</LinearLayout>
|
|
|
46
|
|
|
|
47
|
<LinearLayout
|
|
|
48
|
android:id="@+id/video_publish_user_ll"
|
|
|
49
|
android:layout_width="wrap_content"
|
|
|
50
|
android:layout_height="20dp"
|
|
|
51
|
android:layout_below="@+id/video_publish_topic"
|
|
|
52
|
android:layout_marginStart="15dp"
|
|
|
53
|
android:gravity="center_vertical"
|
|
|
54
|
android:orientation="horizontal">
|
|
|
55
|
|
|
|
56
|
<ImageView
|
|
|
57
|
android:id="@+id/video_publish_user_avatar"
|
|
|
58
|
android:layout_width="20dp"
|
|
|
59
|
android:layout_height="20dp"
|
|
|
60
|
android:background="#3df" />
|
|
|
61
|
|
|
|
62
|
<TextView
|
|
|
63
|
android:id="@+id/video_publish_user_name"
|
|
|
64
|
android:layout_width="wrap_content"
|
|
|
65
|
android:layout_height="wrap_content"
|
|
|
66
|
android:layout_marginStart="6dp"
|
|
|
67
|
android:text="12sp12sp12sp"
|
|
|
68
|
android:textColor="#fff"
|
|
|
69
|
android:textSize="12sp" />
|
|
|
70
|
</LinearLayout>
|
|
|
71
|
|
|
|
72
|
<TextView
|
|
|
73
|
android:layout_width="match_parent"
|
|
|
74
|
android:layout_height="wrap_content"
|
|
|
75
|
android:layout_below="@+id/video_publish_user_ll"
|
|
|
76
|
android:layout_marginStart="30dp"
|
|
|
77
|
android:layout_marginTop="15dp"
|
|
|
78
|
android:layout_marginEnd="30dp"
|
|
|
79
|
android:layout_marginBottom="35dp"
|
|
|
80
|
android:maxHeight="320dp"
|
|
|
81
|
android:text="asfsfsfsfsfsafsafsfsfsaf"
|
|
|
82
|
android:textColor="#fff"
|
|
|
83
|
android:textSize="14sp" />
|
|
|
84
|
|
|
|
85
|
</RelativeLayout>
|
|
|
@ -0,0 +1,204 @@
|
|
|
1
|
<?xml version="1.0" encoding="utf-8"?>
|
|
|
2
|
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
|
|
3
|
|
|
|
4
|
<RelativeLayout
|
|
|
5
|
android:id="@+id/sv_show_bottom"
|
|
|
6
|
android:layout_width="match_parent"
|
|
|
7
|
android:layout_height="50dp"
|
|
|
8
|
android:layout_alignParentBottom="true">
|
|
|
9
|
|
|
|
10
|
<LinearLayout
|
|
|
11
|
android:id="@+id/sv_show_forward"
|
|
|
12
|
android:layout_width="wrap_content"
|
|
|
13
|
android:layout_height="30dp"
|
|
|
14
|
android:layout_alignParentRight="true"
|
|
|
15
|
android:layout_centerVertical="true"
|
|
|
16
|
android:layout_marginRight="15dp"
|
|
|
17
|
android:gravity="center_vertical">
|
|
|
18
|
|
|
|
19
|
<ImageView
|
|
|
20
|
android:layout_width="30dp"
|
|
|
21
|
android:layout_height="30dp"
|
|
|
22
|
android:layout_centerVertical="true"
|
|
|
23
|
android:background="#fff" />
|
|
|
24
|
|
|
|
25
|
<TextView
|
|
|
26
|
android:layout_width="wrap_content"
|
|
|
27
|
android:layout_height="wrap_content"
|
|
|
28
|
android:text="转发"
|
|
|
29
|
android:textSize="12sp" />
|
|
|
30
|
|
|
|
31
|
</LinearLayout>
|
|
|
32
|
|
|
|
33
|
<LinearLayout
|
|
|
34
|
android:id="@+id/sv_show_comment_count_ll"
|
|
|
35
|
android:layout_width="wrap_content"
|
|
|
36
|
android:layout_height="30dp"
|
|
|
37
|
android:layout_centerVertical="true"
|
|
|
38
|
android:layout_marginRight="15dp"
|
|
|
39
|
android:layout_toStartOf="@+id/sv_show_forward"
|
|
|
40
|
android:gravity="center_vertical">
|
|
|
41
|
|
|
|
42
|
<ImageView
|
|
|
43
|
android:layout_width="30dp"
|
|
|
44
|
android:layout_height="30dp"
|
|
|
45
|
android:layout_centerVertical="true"
|
|
|
46
|
android:background="#fff" />
|
|
|
47
|
|
|
|
48
|
<TextView
|
|
|
49
|
android:id="@+id/sv_show_comment_count_tv"
|
|
|
50
|
android:layout_width="wrap_content"
|
|
|
51
|
android:layout_height="wrap_content"
|
|
|
52
|
android:text="1230w"
|
|
|
53
|
android:textSize="12sp" />
|
|
|
54
|
|
|
|
55
|
</LinearLayout>
|
|
|
56
|
|
|
|
57
|
<LinearLayout
|
|
|
58
|
android:id="@+id/sv_show_like_ll"
|
|
|
59
|
android:layout_width="wrap_content"
|
|
|
60
|
android:layout_height="30dp"
|
|
|
61
|
android:layout_centerVertical="true"
|
|
|
62
|
android:layout_marginRight="15dp"
|
|
|
63
|
android:layout_toStartOf="@+id/sv_show_comment_count_ll"
|
|
|
64
|
android:gravity="center_vertical">
|
|
|
65
|
|
|
|
66
|
<ImageView
|
|
|
67
|
android:id="@+id/sv_show_like_img"
|
|
|
68
|
android:layout_width="30dp"
|
|
|
69
|
android:layout_height="30dp"
|
|
|
70
|
android:layout_centerVertical="true"
|
|
|
71
|
android:background="#fff" />
|
|
|
72
|
|
|
|
73
|
<TextView
|
|
|
74
|
android:id="@+id/sv_show_like_tv"
|
|
|
75
|
android:layout_width="wrap_content"
|
|
|
76
|
android:layout_height="wrap_content"
|
|
|
77
|
android:text="12我30w"
|
|
|
78
|
android:textSize="12sp" />
|
|
|
79
|
|
|
|
80
|
</LinearLayout>
|
|
|
81
|
|
|
|
82
|
<LinearLayout
|
|
|
83
|
android:id="@+id/sv_show_comment_ll"
|
|
|
84
|
android:layout_width="match_parent"
|
|
|
85
|
android:layout_height="match_parent"
|
|
|
86
|
android:layout_centerVertical="true"
|
|
|
87
|
android:layout_marginRight="15dp"
|
|
|
88
|
android:layout_toStartOf="@+id/sv_show_like_ll"
|
|
|
89
|
android:gravity="center_vertical">
|
|
|
90
|
|
|
|
91
|
<ImageView
|
|
|
92
|
android:layout_width="30dp"
|
|
|
93
|
android:layout_height="30dp"
|
|
|
94
|
android:layout_centerVertical="true"
|
|
|
95
|
android:layout_marginLeft="15dp"
|
|
|
96
|
android:background="#fff" />
|
|
|
97
|
|
|
|
98
|
<TextView
|
|
|
99
|
android:layout_width="wrap_content"
|
|
|
100
|
android:layout_height="wrap_content"
|
|
|
101
|
android:text="写评论"
|
|
|
102
|
android:textSize="12sp" />
|
|
|
103
|
|
|
|
104
|
</LinearLayout>
|
|
|
105
|
|
|
|
106
|
</RelativeLayout>
|
|
|
107
|
|
|
|
108
|
<View
|
|
|
109
|
android:layout_width="match_parent"
|
|
|
110
|
android:layout_height="1dp"
|
|
|
111
|
android:layout_above="@+id/sv_show_bottom"
|
|
|
112
|
android:background="#505050" />
|
|
|
113
|
|
|
|
114
|
<TextView
|
|
|
115
|
android:id="@+id/sv_show_tvcon"
|
|
|
116
|
android:layout_width="match_parent"
|
|
|
117
|
android:layout_height="wrap_content"
|
|
|
118
|
android:layout_above="@+id/sv_show_bottom"
|
|
|
119
|
android:layout_marginLeft="39dp"
|
|
|
120
|
android:layout_marginRight="39dp"
|
|
|
121
|
android:layout_marginBottom="23dp"
|
|
|
122
|
android:ellipsize="end"
|
|
|
123
|
android:maxLines="3"
|
|
|
124
|
android:text="3-超出三行的,点击文字,上滑弹窗,显示出所有文字内容,根据内容自适应高度,最高为一半屏幕高度,字数超高,以滚动条滑动显示。3-超出三行的,点击文字,上滑弹窗,显示出所有文字内容,根据内容自适应高度,最高为一半屏幕高度,字数超高,以滚动条滑动显示。"
|
|
|
125
|
android:textSize="14sp" />
|
|
|
126
|
|
|
|
127
|
<View
|
|
|
128
|
android:id="@+id/sv_show_tvcon_more"
|
|
|
129
|
android:layout_width="30dp"
|
|
|
130
|
android:layout_height="30dp"
|
|
|
131
|
android:layout_above="@+id/sv_show_bottom"
|
|
|
132
|
android:layout_alignParentEnd="true"
|
|
|
133
|
android:layout_marginBottom="28dp"
|
|
|
134
|
android:background="@color/__picker_black_40" />
|
|
|
135
|
|
|
|
136
|
<RelativeLayout
|
|
|
137
|
android:id="@+id/sv_show_user_info"
|
|
|
138
|
android:layout_width="wrap_content"
|
|
|
139
|
android:layout_height="34dp"
|
|
|
140
|
android:layout_above="@+id/sv_show_tvcon"
|
|
|
141
|
android:layout_marginLeft="15dp"
|
|
|
142
|
android:layout_marginBottom="5dp"
|
|
|
143
|
android:background="#2ff">
|
|
|
144
|
|
|
|
145
|
<ImageView
|
|
|
146
|
android:id="@+id/sv_show_user_avatar"
|
|
|
147
|
android:layout_width="20dp"
|
|
|
148
|
android:layout_height="20dp"
|
|
|
149
|
android:layout_centerVertical="true"
|
|
|
150
|
android:background="#fff" />
|
|
|
151
|
|
|
|
152
|
<TextView
|
|
|
153
|
android:id="@+id/sv_show_user_name"
|
|
|
154
|
android:layout_width="wrap_content"
|
|
|
155
|
android:layout_height="wrap_content"
|
|
|
156
|
android:layout_marginLeft="5dp"
|
|
|
157
|
android:layout_toEndOf="@+id/sv_show_user_avatar"
|
|
|
158
|
android:text="adsdasasdad"
|
|
|
159
|
android:textColor="#fff"
|
|
|
160
|
android:textSize="12sp" />
|
|
|
161
|
|
|
|
162
|
<TextView
|
|
|
163
|
android:id="@+id/sv_show_user_time"
|
|
|
164
|
android:layout_width="wrap_content"
|
|
|
165
|
android:layout_height="wrap_content"
|
|
|
166
|
android:layout_below="@+id/sv_show_user_name"
|
|
|
167
|
android:layout_alignLeft="@+id/sv_show_user_name"
|
|
|
168
|
android:text="adsdasasdad"
|
|
|
169
|
android:textColor="#fff"
|
|
|
170
|
android:textSize="12sp" />
|
|
|
171
|
</RelativeLayout>
|
|
|
172
|
|
|
|
173
|
|
|
|
174
|
<LinearLayout
|
|
|
175
|
android:id="@+id/sv_show_topic"
|
|
|
176
|
android:layout_width="wrap_content"
|
|
|
177
|
android:layout_height="30dp"
|
|
|
178
|
android:layout_above="@+id/sv_show_user_info"
|
|
|
179
|
android:layout_marginStart="15dp"
|
|
|
180
|
android:layout_marginEnd="15dp"
|
|
|
181
|
android:layout_marginBottom="10dp"
|
|
|
182
|
android:background="#b3303030"
|
|
|
183
|
android:gravity="center_vertical">
|
|
|
184
|
|
|
|
185
|
<View
|
|
|
186
|
android:layout_width="12dp"
|
|
|
187
|
android:layout_height="12dp"
|
|
|
188
|
android:layout_marginStart="12dp"
|
|
|
189
|
android:layout_marginEnd="4dp"
|
|
|
190
|
android:background="#fff" />
|
|
|
191
|
|
|
|
192
|
<TextView
|
|
|
193
|
android:id="@+id/sv_show_topic_con"
|
|
|
194
|
android:layout_width="wrap_content"
|
|
|
195
|
android:layout_height="wrap_content"
|
|
|
196
|
android:layout_marginRight="12dp"
|
|
|
197
|
android:ellipsize="end"
|
|
|
198
|
android:maxLines="1"
|
|
|
199
|
android:text="显示出所有文字内容显示出所有文字内容显示出所有文字内容显示出所有文字内容显示出所有文字内容显示出所有文字内容显示出所有文字内容"
|
|
|
200
|
android:textColor="#fff"
|
|
|
201
|
android:textSize="11sp" />
|
|
|
202
|
</LinearLayout>
|
|
|
203
|
|
|
|
204
|
</merge>
|
|
|
@ -0,0 +1,74 @@
|
|
|
1
|
<?xml version="1.0" encoding="utf-8"?>
|
|
|
2
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
|
3
|
android:layout_width="match_parent"
|
|
|
4
|
android:layout_height="wrap_content"
|
|
|
5
|
android:background="@drawable/bg_bottom_dialog">
|
|
|
6
|
|
|
|
7
|
<TextView
|
|
|
8
|
android:id="@+id/show_comment_title"
|
|
|
9
|
android:layout_width="wrap_content"
|
|
|
10
|
android:layout_height="wrap_content"
|
|
|
11
|
android:layout_centerHorizontal="true"
|
|
|
12
|
android:layout_marginTop="10dp"
|
|
|
13
|
android:text="12sp12sp12sp"
|
|
|
14
|
android:textColor="#c2c2c2"
|
|
|
15
|
android:textSize="14sp" />
|
|
|
16
|
|
|
|
17
|
|
|
|
18
|
<View
|
|
|
19
|
android:id="@+id/show_comment_close"
|
|
|
20
|
android:layout_width="22dp"
|
|
|
21
|
android:layout_height="22dp"
|
|
|
22
|
android:layout_alignParentEnd="true"
|
|
|
23
|
android:layout_marginTop="9dp"
|
|
|
24
|
android:layout_marginEnd="14dp"
|
|
|
25
|
android:background="#fff" />
|
|
|
26
|
|
|
|
27
|
<View
|
|
|
28
|
android:layout_width="match_parent"
|
|
|
29
|
android:layout_height="1dp"
|
|
|
30
|
android:layout_marginTop="40dp"
|
|
|
31
|
android:background="#505050" />
|
|
|
32
|
|
|
|
33
|
<android.support.v7.widget.RecyclerView
|
|
|
34
|
android:id="@+id/show_comment_lv"
|
|
|
35
|
android:layout_width="wrap_content"
|
|
|
36
|
android:layout_height="320dp"
|
|
|
37
|
android:layout_marginBottom="54dp"
|
|
|
38
|
android:layout_marginTop="40dp" />
|
|
|
39
|
|
|
|
40
|
<LinearLayout
|
|
|
41
|
android:layout_width="match_parent"
|
|
|
42
|
android:layout_height="wrap_content"
|
|
|
43
|
android:layout_below="@+id/show_comment_lv"
|
|
|
44
|
android:background="#535353"
|
|
|
45
|
android:layout_marginTop="-54dp"
|
|
|
46
|
android:gravity="center_vertical"
|
|
|
47
|
android:minHeight="54dp">
|
|
|
48
|
|
|
|
49
|
<EditText
|
|
|
50
|
android:id="@+id/show_comment_ed"
|
|
|
51
|
android:layout_width="0dp"
|
|
|
52
|
android:layout_height="wrap_content"
|
|
|
53
|
android:layout_marginStart="14dp"
|
|
|
54
|
android:layout_weight="1"
|
|
|
55
|
android:background="@drawable/bg_comment_report"
|
|
|
56
|
android:hint="写评论"
|
|
|
57
|
android:minHeight="30dp"
|
|
|
58
|
android:paddingStart="14dp"
|
|
|
59
|
android:paddingEnd="14dp"
|
|
|
60
|
android:textColorHint="#7e7e7e" />
|
|
|
61
|
|
|
|
62
|
<TextView
|
|
|
63
|
android:id="@+id/show_comment_publish"
|
|
|
64
|
android:layout_width="wrap_content"
|
|
|
65
|
android:layout_height="match_parent"
|
|
|
66
|
android:gravity="center"
|
|
|
67
|
android:paddingStart="13dp"
|
|
|
68
|
android:paddingEnd="13dp"
|
|
|
69
|
android:text="发布"
|
|
|
70
|
android:textColor="#7e7e7e"
|
|
|
71
|
android:textSize="14sp" />
|
|
|
72
|
</LinearLayout>
|
|
|
73
|
|
|
|
74
|
</RelativeLayout>
|
|
|
@ -0,0 +1,13 @@
|
|
|
1
|
<?xml version="1.0" encoding="utf-8"?>
|
|
|
2
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
|
3
|
android:layout_width="match_parent"
|
|
|
4
|
android:layout_height="match_parent">
|
|
|
5
|
|
|
|
6
|
<ImageView
|
|
|
7
|
android:layout_width="match_parent"
|
|
|
8
|
android:layout_height="match_parent"
|
|
|
9
|
android:background="#fff0" />
|
|
|
10
|
|
|
|
11
|
<include layout="@layout/view_show_bottom" />
|
|
|
12
|
|
|
|
13
|
</RelativeLayout>
|
|
|
@ -0,0 +1,15 @@
|
|
|
1
|
<?xml version="1.0" encoding="utf-8"?>
|
|
|
2
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
|
3
|
android:layout_width="match_parent"
|
|
|
4
|
android:layout_height="match_parent">
|
|
|
5
|
|
|
|
6
|
|
|
|
7
|
<com.electric.chargingpile.widge.photoview.ZoomingViewpager
|
|
|
8
|
android:id="@+id/view_show_zoomingphoto"
|
|
|
9
|
android:layout_width="match_parent"
|
|
|
10
|
android:layout_height="match_parent" />
|
|
|
11
|
|
|
|
12
|
|
|
|
13
|
<include layout="@layout/view_show_bottom" />
|
|
|
14
|
|
|
|
15
|
</RelativeLayout>
|
|
|
@ -170,6 +170,8 @@
|
|
170
|
170
|
<color name="color_888888">#888888</color>
|
|
171
|
171
|
<color name="color_fb9349">#fb9349</color>
|
|
172
|
172
|
|
|
|
173
|
<color name="sv_black">#FF000000</color>
|
|
|
174
|
<color name="sv_white">#FFFFFF</color>
|
|
173
|
175
|
|
|
174
|
176
|
|
|
175
|
177
|
|
|
|
@ -192,4 +192,16 @@
|
|
192
|
192
|
|
|
193
|
193
|
<string name="refund_tip">退款说明:\n1、可退款金额为实际充值金额,不含充值赠送、添加站点赠送和活动赠送金额。申请退款时,赠送金额将清零,请慎重选择;\n2、退款过程中,账户将被冻结,无法充值,使用支付宝免密支付充电不受影响;\n3、微信充值订单6个月内,支付宝充值订单3个月内将原路返回,超出时间我们将联系您取得新的退款方式;\n4、退款将于3-7个工作日内完成,请耐心等待。</string>
|
|
194
|
194
|
|
|
|
195
|
|
|
|
196
|
<string name="refresh_pull_to_refresh">Slide down to refresh</string>
|
|
|
197
|
<string name="refresh_release_to_refresh">Release to refresh</string>
|
|
|
198
|
<string name="refresh_refreshing">Refreshing...</string>
|
|
|
199
|
<string name="refresh_succeed">Refreshed</string>
|
|
|
200
|
<string name="refresh_fail">Refreshing failed</string>
|
|
|
201
|
<string name="refresh_pullup_to_load">Slide up to upload more</string>
|
|
|
202
|
<string name="refresh_release_to_load">Release to upload</string>
|
|
|
203
|
<string name="refresh_loading">Loading...</string>
|
|
|
204
|
<string name="refresh_load_succeed">Loading succeeded</string>
|
|
|
205
|
<string name="refresh_load_succeed_nomore">That\'s All!</string>
|
|
|
206
|
<string name="refresh_load_fail">Loading failed</string>
|
|
195
|
207
|
</resources>
|
|
|
@ -1,4 +1,5 @@
|
|
1
|
1
|
<resources>
|
|
|
2
|
|
|
2
|
3
|
<style name="AppBaseTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
|
3
|
4
|
<item name="colorPrimary">@color/colorPrimary</item>
|
|
4
|
5
|
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
|
|
@ -55,18 +56,18 @@
|
|
55
|
56
|
<style name="Widget.ProgressBar.RegularProgressBar">
|
|
56
|
57
|
<item name="android:indeterminateOnly">false</item>
|
|
57
|
58
|
<item name="android:progressDrawable">@drawable/progressbar</item>
|
|
58
|
|
<item name="android:indeterminateDrawable">@android:drawable/progress_indeterminate_horizontal</item>
|
|
|
59
|
<item name="android:indeterminateDrawable">
|
|
|
60
|
@android:drawable/progress_indeterminate_horizontal
|
|
|
61
|
</item>
|
|
59
|
62
|
<item name="android:minHeight">1dip</item>
|
|
60
|
63
|
<item name="android:maxHeight">10dip</item>
|
|
61
|
64
|
</style>
|
|
62
|
65
|
|
|
63
|
66
|
|
|
64
|
|
<!--<style name="MyRatingBar" parent="@android:style/Widget.RatingBar">-->
|
|
65
|
|
<!--<item name="android:progressDrawable">@drawable/circle_rating_bar_full</item>-->
|
|
66
|
|
|
|
67
|
|
<!--</style>-->
|
|
68
|
|
|
|
|
67
|
<!--<style name="MyRatingBar" parent="@android:style/Widget.RatingBar">-->
|
|
|
68
|
<!--<item name="android:progressDrawable">@drawable/circle_rating_bar_full</item>-->
|
|
69
|
69
|
|
|
|
70
|
<!--</style>-->
|
|
70
|
71
|
|
|
71
|
72
|
|
|
72
|
73
|
<style name="roomRatingBar" parent="@android:style/Widget.RatingBar">
|
|
|
@ -82,8 +83,6 @@
|
|
82
|
83
|
</style>
|
|
83
|
84
|
|
|
84
|
85
|
|
|
85
|
|
|
|
86
|
|
|
|
87
|
86
|
<style name="appointent_time_item_style">
|
|
88
|
87
|
<item name="android:textColor">@drawable/appointent_item_tv_color_selector</item>
|
|
89
|
88
|
<item name="android:layout_width">fill_parent</item>
|
|
|
@ -191,16 +190,19 @@
|
|
191
|
190
|
<item name="android:windowBackground">@android:color/transparent</item>
|
|
192
|
191
|
<item name="android:windowContentOverlay">@null</item>
|
|
193
|
192
|
</style>
|
|
194
|
|
<style name="dialogAnim" parent="android:Animation" >
|
|
|
193
|
|
|
|
194
|
<style name="dialogAnim" parent="android:Animation">
|
|
195
|
195
|
<item name="android:windowEnterAnimation">@anim/dialog_show</item>
|
|
196
|
196
|
<item name="android:windowExitAnimation">@anim/dialog_dismiss</item>
|
|
197
|
197
|
</style>
|
|
|
198
|
|
|
198
|
199
|
<style name="divider_horizontal"><!-- 水平分割线 -->
|
|
199
|
200
|
<item name="android:layout_width">fill_parent</item>
|
|
200
|
201
|
<item name="android:layout_height">0.01dp</item>
|
|
201
|
202
|
<item name="android:background">@android:color/darker_gray</item>
|
|
202
|
203
|
<item name="android:orientation">vertical</item>
|
|
203
|
204
|
</style>
|
|
|
205
|
|
|
204
|
206
|
<style name="divider_vertical"><!-- 垂直分割线 -->
|
|
205
|
207
|
<item name="android:layout_width">0.01dp</item>
|
|
206
|
208
|
<item name="android:layout_height">fill_parent</item>
|
|
|
@ -220,6 +222,25 @@
|
|
220
|
222
|
<item name="android:windowCloseOnTouchOutside">false</item>
|
|
221
|
223
|
</style>
|
|
222
|
224
|
|
|
|
225
|
|
|
|
226
|
<style name="bottomDialogStyle">
|
|
|
227
|
<item name="android:windowBackground">@android:color/transparent</item>
|
|
|
228
|
<item name="android:windowContentOverlay">@null</item>
|
|
|
229
|
<item name="android:windowIsFloating">true</item>
|
|
|
230
|
<item name="android:windowFrame">@null</item>
|
|
|
231
|
<item name="android:backgroundDimEnabled">false</item>
|
|
|
232
|
<item name="android:windowNoTitle">true</item>
|
|
|
233
|
<item name="android:windowIsTranslucent">true</item>
|
|
|
234
|
<item name="android:windowCloseOnTouchOutside">true</item>
|
|
|
235
|
<item name="android:windowAnimationStyle">@style/BottomDialogAnimation</item>
|
|
|
236
|
</style>
|
|
|
237
|
|
|
|
238
|
<style name="BottomDialogAnimation">
|
|
|
239
|
<item name="android:windowEnterAnimation">@anim/bottom_in_anim</item>
|
|
|
240
|
<item name="android:windowExitAnimation">@anim/bottom_out_anim</item>
|
|
|
241
|
</style>
|
|
|
242
|
|
|
|
243
|
|
|
223
|
244
|
<style name="AnimationPreview">
|
|
224
|
245
|
<item name="android:windowEnterAnimation">@anim/fade_in</item>
|
|
225
|
246
|
<item name="android:windowExitAnimation">@anim/fade_out</item>
|