0%

ViewPager API地址 http://developer.android.com/reference/android/support/v4/view/ViewPager.html

这里介绍三种ViewPager的动画切换效果

setPageTransformer实现动画的切换

ViewPager的切换,从A页切换到B页

  • A页的position: 0.0 ~ -1.0
  • B页的position: 1.0 ~ 0.0

可以利用ViewPager.setPageTransformer实现动画的切换,只支持3.0API+
为了兼容3.0以下的系统,可以修改ViewPager内部代码加上nineoldandroids代替属性动画实现向下兼容。

setPageTransformer的源码如下,可以看到源码中一句 if(Build.VERSION.SDK_INT >= 11) 的判断限制了系统的版本,其实就是由于属性动画是在3.0以后引入的原因。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* Set a {@link PageTransformer} that will be called for each attached page whenever
* the scroll position is changed. This allows the application to apply custom property
* transformations to each page, overriding the default sliding look and feel.
*
* <p><em>Note:</em> Prior to Android 3.0 the property animation APIs did not exist.
* As a result, setting a PageTransformer prior to Android 3.0 (API 11) will have no effect.</p>
*
* @param reverseDrawingOrder true if the supplied PageTransformer requires page views
* to be drawn from last to first instead of first to last.
* @param transformer PageTransformer that will modify each page's animation properties
*/
public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer) {
if (Build.VERSION.SDK_INT >= 11) {
final boolean hasTransformer = transformer != null;
final boolean needsPopulate = hasTransformer != (mPageTransformer != null);
mPageTransformer = transformer;
setChildrenDrawingOrderEnabledCompat(hasTransformer);
if (hasTransformer) {
mDrawingOrder = reverseDrawingOrder ? DRAW_ORDER_REVERSE : DRAW_ORDER_FORWARD;
} else {
mDrawingOrder = DRAW_ORDER_DEFAULT;
}
if (needsPopulate) populate();
}
}

自定义ViewPager实现动画切换

观察API的规律,自定义ViewPager的切换动画。同时我们也可以自定义ViewPager实现动画切换效果。如下代码实现效果是Translation和Scale,完成的前提是:

  • a 需要拿到当前切换的两个View –> 通过Map存储于获取
  • b 需要得到一个动画的梯度 –> 通过offset,offsetPixels

在初始化的时候,将View保存到一个HashMap中,利用position找到View

  • 01 – pisition = 0 ;offset:01
  • 10 – pisition = 0 ;offset:10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class ViewPagerWithTranformAnim extends ViewPager {
private View mLeft;
private View mRight;

private float mTrans;
private float mScale;

private static final float MIN_SCALE = 0.7F;

private Map<Integer, View> mChildren = new HashMap<Integer, View>();

public void setViewForPosition(View view, int position) {
mChildren.put(position, view);
}

public void removeViewFromPosition(Integer position) {
mChildren.remove(position);
}

public ViewPagerWithTranformAnim(Context context) {
super(context);
}

public ViewPagerWithTranformAnim(Context context, AttributeSet attrs) {
super(context, attrs);
}

@Override
protected void onPageScrolled(int position, float offset, int offsetPixels) {
mLeft = mChildren.get(position);
mRight = mChildren.get(position + 1);

animStack(mLeft, mRight, offset, offsetPixels);
super.onPageScrolled(position, offset, offsetPixels);
}

private void animStack(View left, View right, float offset, int offsetPixels) {
if (right != null) {
//从0到1页,offset:0~1
mScale = (1 - MIN_SCALE) * offset + MIN_SCALE;

//-width~0
mTrans = -getWidth()-getPageMargin()+offsetPixels;

ViewHelper.setScaleX(right,mScale);
ViewHelper.setScaleY(right, mScale);

ViewHelper.setTranslationX(right,mTrans);
}

if(left!=null){
left.bringToFront();
}
}
}

在activity中维护一个HashMap保存view,代码如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
vp_transform_anim.setAdapter(new PagerAdapter() {
@Override
public int getCount() {
return mImgIds.length;
}

@Override
public boolean isViewFromObject(View view, Object object) {
return view==object;
}

@Override
public Object instantiateItem(ViewGroup container, int position) {
ImageView iv = new ImageView(ViewPagerWithTranformAnimActivity.this);
iv.setImageResource(mImgIds[position]);
iv.setScaleType(ImageView.ScaleType.CENTER_CROP);
container.addView(iv);
mImages.add(iv);
vp_transform_anim.setViewForPosition(iv,position);
return iv;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView(mImages.get(position));
vp_transform_anim.removeViewFromPosition(position);
}
});

利用开源项目

ViewPager的切换动画开源项目 JazzyViewPager
下面贴出了jazzyviewpager的源码,里面提供了非常多的动画效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
package com.jfeinstein.jazzyviewpager;

import java.util.HashMap;
import java.util.LinkedHashMap;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Camera;
import android.graphics.Color;
import android.graphics.Matrix;
import android.os.Build;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

import com.nineoldandroids.view.ViewHelper;

public class JazzyViewPager extends ViewPager {

public static final String TAG = "JazzyViewPager";

private boolean mEnabled = true;
private boolean mFadeEnabled = false;
private boolean mOutlineEnabled = false;
public static int sOutlineColor = Color.WHITE;
private TransitionEffect mEffect = TransitionEffect.Standard;

private HashMap<Integer, Object> mObjs = new LinkedHashMap<Integer, Object>();

private static final float SCALE_MAX = 0.5f;
private static final float ZOOM_MAX = 0.5f;
private static final float ROT_MAX = 15.0f;

public enum TransitionEffect {
Standard,
Tablet,
CubeIn,
CubeOut,
FlipVertical,
FlipHorizontal,
Stack,
ZoomIn,
ZoomOut,
RotateUp,
RotateDown,
Accordion
}

private static final boolean API_11;
static {
API_11 = Build.VERSION.SDK_INT >= 11;
}

public JazzyViewPager(Context context) {
this(context, null);
}

@SuppressWarnings("incomplete-switch")
public JazzyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
setClipChildren(false);
// now style everything!
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.JazzyViewPager);
int effect = ta.getInt(R.styleable.JazzyViewPager_style, 0);
String[] transitions = getResources().getStringArray(R.array.jazzy_effects);
setTransitionEffect(TransitionEffect.valueOf(transitions[effect]));
setFadeEnabled(ta.getBoolean(R.styleable.JazzyViewPager_fadeEnabled, false));
setOutlineEnabled(ta.getBoolean(R.styleable.JazzyViewPager_outlineEnabled, false));
setOutlineColor(ta.getColor(R.styleable.JazzyViewPager_outlineColor, Color.WHITE));
switch (mEffect) {
case Stack:
case ZoomOut:
setFadeEnabled(true);
}
ta.recycle();
}

public void setTransitionEffect(TransitionEffect effect) {
mEffect = effect;
// reset();
}

public void setPagingEnabled(boolean enabled) {
mEnabled = enabled;
}

public void setFadeEnabled(boolean enabled) {
mFadeEnabled = enabled;
}

public boolean getFadeEnabled() {
return mFadeEnabled;
}

public void setOutlineEnabled(boolean enabled) {
mOutlineEnabled = enabled;
wrapWithOutlines();
}

public void setOutlineColor(int color) {
sOutlineColor = color;
}

private void wrapWithOutlines() {
for (int i = 0; i < getChildCount(); i++) {
View v = getChildAt(i);
if (!(v instanceof OutlineContainer)) {
removeView(v);
super.addView(wrapChild(v), i);
}
}
}

private View wrapChild(View child) {
if (!mOutlineEnabled || child instanceof OutlineContainer) return child;
OutlineContainer out = new OutlineContainer(getContext());
out.setLayoutParams(generateDefaultLayoutParams());
child.setLayoutParams(new OutlineContainer.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
out.addView(child);
return out;
}

public void addView(View child) {
super.addView(wrapChild(child));
}

public void addView(View child, int index) {
super.addView(wrapChild(child), index);
}

public void addView(View child, LayoutParams params) {
super.addView(wrapChild(child), params);
}

public void addView(View child, int width, int height) {
super.addView(wrapChild(child), width, height);
}

public void addView(View child, int index, LayoutParams params) {
super.addView(wrapChild(child), index, params);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent arg0) {
return mEnabled ? super.onInterceptTouchEvent(arg0) : false;
}

private State mState;
private int oldPage;

private View mLeft;
private View mRight;
private float mRot;
private float mTrans;
private float mScale;

private enum State {
IDLE,
GOING_LEFT,
GOING_RIGHT
}

// public void reset() {
// resetPrivate();
// int curr = getCurrentItem();
// onPageScrolled(curr, 0.0f, 0);
//}
//
//private void resetPrivate() {
// for (int i = 0; i < getChildCount(); i++) {
// View v = getChildAt(i);
// // ViewHelper.setRotation(v, -ViewHelper.getRotation(v));
// // ViewHelper.setRotationX(v, -ViewHelper.getRotationX(v));
// // ViewHelper.setRotationY(v, -ViewHelper.getRotationY(v));
// //
// // ViewHelper.setTranslationX(v, -ViewHelper.getTranslationX(v));
// // ViewHelper.setTranslationY(v, -ViewHelper.getTranslationY(v));
//
// ViewHelper.setRotation(v, 0);
// ViewHelper.setRotationX(v, 0);
// ViewHelper.setRotationY(v, 0);
//
// ViewHelper.setTranslationX(v, 0);
// ViewHelper.setTranslationY(v, 0);
//
// ViewHelper.setAlpha(v, 1.0f);
//
// ViewHelper.setScaleX(v, 1.0f);
// ViewHelper.setScaleY(v, 1.0f);
//
// ViewHelper.setPivotX(v, 0);
// ViewHelper.setPivotY(v, 0);
//
// logState(v, "Child " + i);
// }
//}

private void logState(View v, String title) {
Log.v(TAG, title + ": ROT (" + ViewHelper.getRotation(v) + ", " +
ViewHelper.getRotationX(v) + ", " +
ViewHelper.getRotationY(v) + "), TRANS (" +
ViewHelper.getTranslationX(v) + ", " +
ViewHelper.getTranslationY(v) + "), SCALE (" +
ViewHelper.getScaleX(v) + ", " +
ViewHelper.getScaleY(v) + "), ALPHA " +
ViewHelper.getAlpha(v));
}

protected void animateScroll(int position, float positionOffset) {
if (mState != State.IDLE) {
mRot = (float)(1-Math.cos(2*Math.PI*positionOffset))/2*30.0f;
ViewHelper.setRotationY(this, mState == State.GOING_RIGHT ? mRot : -mRot);
ViewHelper.setPivotX(this, getMeasuredWidth()*0.5f);
ViewHelper.setPivotY(this, getMeasuredHeight()*0.5f);
}
}

protected void animateTablet(View left, View right, float positionOffset) {
if (mState != State.IDLE) {
if (left != null) {
manageLayer(left, true);
mRot = 30.0f * positionOffset;
mTrans = getOffsetXForRotation(mRot, left.getMeasuredWidth(),
left.getMeasuredHeight());
ViewHelper.setPivotX(left, left.getMeasuredWidth()/2);
ViewHelper.setPivotY(left, left.getMeasuredHeight()/2);
ViewHelper.setTranslationX(left, mTrans);
ViewHelper.setRotationY(left, mRot);
logState(left, "Left");
}
if (right != null) {
manageLayer(right, true);
mRot = -30.0f * (1-positionOffset);
mTrans = getOffsetXForRotation(mRot, right.getMeasuredWidth(),
right.getMeasuredHeight());
ViewHelper.setPivotX(right, right.getMeasuredWidth()*0.5f);
ViewHelper.setPivotY(right, right.getMeasuredHeight()*0.5f);
ViewHelper.setTranslationX(right, mTrans);
ViewHelper.setRotationY(right, mRot);
logState(right, "Right");
}
}
}

private void animateCube(View left, View right, float positionOffset, boolean in) {
if (mState != State.IDLE) {
if (left != null) {
manageLayer(left, true);
mRot = (in ? 90.0f : -90.0f) * positionOffset;
ViewHelper.setPivotX(left, left.getMeasuredWidth());
ViewHelper.setPivotY(left, left.getMeasuredHeight()*0.5f);
ViewHelper.setRotationY(left, mRot);
}
if (right != null) {
manageLayer(right, true);
mRot = -(in ? 90.0f : -90.0f) * (1-positionOffset);
ViewHelper.setPivotX(right, 0);
ViewHelper.setPivotY(right, right.getMeasuredHeight()*0.5f);
ViewHelper.setRotationY(right, mRot);
}
}
}

private void animateAccordion(View left, View right, float positionOffset) {
if (mState != State.IDLE) {
if (left != null) {
manageLayer(left, true);
ViewHelper.setPivotX(left, left.getMeasuredWidth());
ViewHelper.setPivotY(left, 0);
ViewHelper.setScaleX(left, 1-positionOffset);
}
if (right != null) {
manageLayer(right, true);
ViewHelper.setPivotX(right, 0);
ViewHelper.setPivotY(right, 0);
ViewHelper.setScaleX(right, positionOffset);
}
}
}

private void animateZoom(View left, View right, float positionOffset, boolean in) {
if (mState != State.IDLE) {
if (left != null) {
manageLayer(left, true);
mScale = in ? ZOOM_MAX + (1-ZOOM_MAX)*(1-positionOffset) :
1+ZOOM_MAX - ZOOM_MAX*(1-positionOffset);
ViewHelper.setPivotX(left, left.getMeasuredWidth()*0.5f);
ViewHelper.setPivotY(left, left.getMeasuredHeight()*0.5f);
ViewHelper.setScaleX(left, mScale);
ViewHelper.setScaleY(left, mScale);
}
if (right != null) {
manageLayer(right, true);
mScale = in ? ZOOM_MAX + (1-ZOOM_MAX)*positionOffset :
1+ZOOM_MAX - ZOOM_MAX*positionOffset;
ViewHelper.setPivotX(right, right.getMeasuredWidth()*0.5f);
ViewHelper.setPivotY(right, right.getMeasuredHeight()*0.5f);
ViewHelper.setScaleX(right, mScale);
ViewHelper.setScaleY(right, mScale);
}
}
}

private void animateRotate(View left, View right, float positionOffset, boolean up) {
if (mState != State.IDLE) {
if (left != null) {
manageLayer(left, true);
mRot = (up ? 1 : -1) * (ROT_MAX * positionOffset);
mTrans = (up ? -1 : 1) * (float) (getMeasuredHeight() - getMeasuredHeight()*Math.cos(mRot*Math.PI/180.0f));
ViewHelper.setPivotX(left, left.getMeasuredWidth()*0.5f);
ViewHelper.setPivotY(left, up ? 0 : left.getMeasuredHeight());
ViewHelper.setTranslationY(left, mTrans);
ViewHelper.setRotation(left, mRot);
}
if (right != null) {
manageLayer(right, true);
mRot = (up ? 1 : -1) * (-ROT_MAX + ROT_MAX*positionOffset);
mTrans = (up ? -1 : 1) * (float) (getMeasuredHeight() - getMeasuredHeight()*Math.cos(mRot*Math.PI/180.0f));
ViewHelper.setPivotX(right, right.getMeasuredWidth()*0.5f);
ViewHelper.setPivotY(right, up ? 0 : right.getMeasuredHeight());
ViewHelper.setTranslationY(right, mTrans);
ViewHelper.setRotation(right, mRot);
}
}
}

private void animateFlipHorizontal(View left, View right, float positionOffset, int positionOffsetPixels) {
if (mState != State.IDLE) {
if (left != null) {
manageLayer(left, true);
mRot = 180.0f * positionOffset;
if (mRot > 90.0f) {
left.setVisibility(View.INVISIBLE);
} else {
if (left.getVisibility() == View.INVISIBLE)
left.setVisibility(View.VISIBLE);
mTrans = positionOffsetPixels;
ViewHelper.setPivotX(left, left.getMeasuredWidth()*0.5f);
ViewHelper.setPivotY(left, left.getMeasuredHeight()*0.5f);
ViewHelper.setTranslationX(left, mTrans);
ViewHelper.setRotationY(left, mRot);
}
}
if (right != null) {
manageLayer(right, true);
mRot = -180.0f * (1-positionOffset);
if (mRot < -90.0f) {
right.setVisibility(View.INVISIBLE);
} else {
if (right.getVisibility() == View.INVISIBLE)
right.setVisibility(View.VISIBLE);
mTrans = -getWidth()-getPageMargin()+positionOffsetPixels;
ViewHelper.setPivotX(right, right.getMeasuredWidth()*0.5f);
ViewHelper.setPivotY(right, right.getMeasuredHeight()*0.5f);
ViewHelper.setTranslationX(right, mTrans);
ViewHelper.setRotationY(right, mRot);
}
}
}
}

private void animateFlipVertical(View left, View right, float positionOffset, int positionOffsetPixels) {
if(mState != State.IDLE) {
if (left != null) {
manageLayer(left, true);
mRot = 180.0f * positionOffset;
if (mRot > 90.0f) {
left.setVisibility(View.INVISIBLE);
} else {
if (left.getVisibility() == View.INVISIBLE)
left.setVisibility(View.VISIBLE);
mTrans = positionOffsetPixels;
ViewHelper.setPivotX(left, left.getMeasuredWidth()*0.5f);
ViewHelper.setPivotY(left, left.getMeasuredHeight()*0.5f);
ViewHelper.setTranslationX(left, mTrans);
ViewHelper.setRotationX(left, mRot);
}
}
if (right != null) {
manageLayer(right, true);
mRot = -180.0f * (1-positionOffset);
if (mRot < -90.0f) {
right.setVisibility(View.INVISIBLE);
} else {
if (right.getVisibility() == View.INVISIBLE)
right.setVisibility(View.VISIBLE);
mTrans = -getWidth()-getPageMargin()+positionOffsetPixels;
ViewHelper.setPivotX(right, right.getMeasuredWidth()*0.5f);
ViewHelper.setPivotY(right, right.getMeasuredHeight()*0.5f);
ViewHelper.setTranslationX(right, mTrans);
ViewHelper.setRotationX(right, mRot);
}
}
}
}

protected void animateStack(View left, View right, float positionOffset, int positionOffsetPixels) {
if (mState != State.IDLE) {
if (right != null) {
manageLayer(right, true);
mScale = (1-SCALE_MAX) * positionOffset + SCALE_MAX;
mTrans = -getWidth()-getPageMargin()+positionOffsetPixels;
ViewHelper.setScaleX(right, mScale);
ViewHelper.setScaleY(right, mScale);
ViewHelper.setTranslationX(right, mTrans);
}
if (left != null) {
left.bringToFront();
}
}
}

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void manageLayer(View v, boolean enableHardware) {
if (!API_11) return;
int layerType = enableHardware ? View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE;
if (layerType != v.getLayerType())
v.setLayerType(layerType, null);
}

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void disableHardwareLayer() {
if (!API_11) return;
View v;
for (int i = 0; i < getChildCount(); i++) {
v = getChildAt(i);
if (v.getLayerType() != View.LAYER_TYPE_NONE)
v.setLayerType(View.LAYER_TYPE_NONE, null);
}
}

private Matrix mMatrix = new Matrix();
private Camera mCamera = new Camera();
private float[] mTempFloat2 = new float[2];

protected float getOffsetXForRotation(float degrees, int width, int height) {
mMatrix.reset();
mCamera.save();
mCamera.rotateY(Math.abs(degrees));
mCamera.getMatrix(mMatrix);
mCamera.restore();

mMatrix.preTranslate(-width * 0.5f, -height * 0.5f);
mMatrix.postTranslate(width * 0.5f, height * 0.5f);
mTempFloat2[0] = width;
mTempFloat2[1] = height;
mMatrix.mapPoints(mTempFloat2);
return (width - mTempFloat2[0]) * (degrees > 0.0f ? 1.0f : -1.0f);
}

protected void animateFade(View left, View right, float positionOffset) {
if (left != null) {
ViewHelper.setAlpha(left, 1-positionOffset);
}
if (right != null) {
ViewHelper.setAlpha(right, positionOffset);
}
}

protected void animateOutline(View left, View right) {
if (!(left instanceof OutlineContainer))
return;
if (mState != State.IDLE) {
if (left != null) {
manageLayer(left, true);
((OutlineContainer)left).setOutlineAlpha(1.0f);
}
if (right != null) {
manageLayer(right, true);
((OutlineContainer)right).setOutlineAlpha(1.0f);
}
} else {
if (left != null)
((OutlineContainer)left).start();
if (right != null)
((OutlineContainer)right).start();
}
}

@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (mState == State.IDLE && positionOffset > 0) {
oldPage = getCurrentItem();
mState = position == oldPage ? State.GOING_RIGHT : State.GOING_LEFT;
}
boolean goingRight = position == oldPage;
if (mState == State.GOING_RIGHT && !goingRight)
mState = State.GOING_LEFT;
else if (mState == State.GOING_LEFT && goingRight)
mState = State.GOING_RIGHT;

float effectOffset = isSmall(positionOffset) ? 0 : positionOffset;

// mLeft = getChildAt(position);
// mRight = getChildAt(position+1);
mLeft = findViewFromObject(position);
mRight = findViewFromObject(position+1);

if (mFadeEnabled)
animateFade(mLeft, mRight, effectOffset);
if (mOutlineEnabled)
animateOutline(mLeft, mRight);

switch (mEffect) {
case Standard:
break;
case Tablet:
animateTablet(mLeft, mRight, effectOffset);
break;
case CubeIn:
animateCube(mLeft, mRight, effectOffset, true);
break;
case CubeOut:
animateCube(mLeft, mRight, effectOffset, false);
break;
case FlipVertical:
animateFlipVertical(mLeft, mRight, positionOffset, positionOffsetPixels);
break;
case FlipHorizontal:
animateFlipHorizontal(mLeft, mRight, effectOffset, positionOffsetPixels);
case Stack:
animateStack(mLeft, mRight, effectOffset, positionOffsetPixels);
break;
case ZoomIn:
animateZoom(mLeft, mRight, effectOffset, true);
break;
case ZoomOut:
animateZoom(mLeft, mRight, effectOffset, false);
break;
case RotateUp:
animateRotate(mLeft, mRight, effectOffset, true);
break;
case RotateDown:
animateRotate(mLeft, mRight, effectOffset, false);
break;
case Accordion:
animateAccordion(mLeft, mRight, effectOffset);
break;
}

super.onPageScrolled(position, positionOffset, positionOffsetPixels);

if (effectOffset == 0) {
disableHardwareLayer();
mState = State.IDLE;
}

}

private boolean isSmall(float positionOffset) {
return Math.abs(positionOffset) < 0.0001;
}

public void setObjectForPosition(Object obj, int position) {
mObjs.put(Integer.valueOf(position), obj);
}

public View findViewFromObject(int position) {
Object o = mObjs.get(Integer.valueOf(position));
if (o == null) {
return null;
}
PagerAdapter a = getAdapter();
View v;
for (int i = 0; i < getChildCount(); i++) {
v = getChildAt(i);
if (a.isViewFromObject(v, o))
return v;
}
return null;
}

}

RecyclerView提供了一种插拔式的体验,高度的解耦,异常的灵活,通过设置它提供的不同LayoutManager、ItemDecoration、ItemAnimator实现令人瞠目的效果。RecyclerView的基本使用如下:

1
2
3
4
5
6
7
8
9
10
11
mRecyclerView = findView(R.id.id_recyclerview);
//设置布局管理器
mRecyclerView.setLayoutManager(layout);
//设置adapter
mRecyclerView.setAdapter(adapter)
//设置Item增加、移除动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
//添加分割线
mRecyclerView.addItemDecoration(
new DividerItemDecoration(getActivity(), DividerItemDecoration.HORIZONTAL_LIST)
);

相较于ListView的代码可能只需要去设置一个adapter就能正常使用,RecyclerView基本需要上面一系列的步骤,原因总得来说是高度解耦,给予各组件充分定制自由,轻松实现如GridView、瀑布流效果。RecyclerView的官方Traning页里面详细说明了RecyclerView推出的原因、优点以及使用方式,强烈推荐阅读。

RecyclerView中三个重要类

LayoutManager

LayoutManager 说明
LinearLayoutManager shows items in a vertical or horizontal scrolling list.
GridLayoutManager shows items in a grid.
StaggeredGridLayoutManager shows items in a staggered grid.

ItemDecoration

我们可以通过该方法添加分割线:mRecyclerView.addItemDecoration(RecyclerView.ItemDecoration),该方法的参数是为抽象类,官方目前提供了一个默认的实现类DividerItemDecoration。绘制时机是在RecyclerView的measure中先测量出每个child的decoration,添加到mItemDecorations数组中;然后在onDraw中会遍历mItemDecorations数组注意绘制decoration。onDraw和onDrawOver在调用时机上有所区别:

onDraw方法先于drawChildren
onDrawOver在drawChildren之后,一般我们选择复写其中一个即可
getItemOffsets 可以通过outRect.set()为每个Item设置一定的偏移量,主要用于绘制Decorator

关于如何自定义ItemDecoration,参考Android RecyclerView 使用完全解析 体验艺术般的控件

ItemAnimator

ItemAnimator也是一个抽象类,好在系统为我们提供了一种默认的实现类DefaultItemAnimator,github上已经有很多类似的项目了,这里我们直接引用下:RecyclerViewItemAnimators,大家自己下载查看。项目提供如下效果:

SlideInOutLeftItemAnimator,
SlideInOutRightItemAnimator,
SlideInOutTopItemAnimator,
SlideInOutBottomItemAnimator

Adapter

需要指出的是RecyclerView使用的adapter必须是RecyclerView.Adapter的子类,Google已经开始强制我们使用ViewHolder这种模式了。

关于Click and LongClick

一个挺郁闷的地方就是,系统没有提供ClickListener和LongClickListener。 不过我们也可以自己去添加,只是会多了些代码而已。实现的方式比较多,可以通过mRecyclerView.addOnItemTouchListener去监听然后去判断手势,当然你也可以通过adapter中自己去提供回调,这里我们选择后者,前者的方式,大家有兴趣自己去实现。


参考:

  1. Android RecyclerView 使用完全解析 体验艺术般的控件
  2. Create a List with RecyclerView

这个Demo建立在 张鸿洋的博客Android 超高仿微信图片选择器图片该这么加载 的基础上,
先上个动态效果图:

高仿微信选择图片

穿越去下载:https://github.com/LeeeYou/demoimgpick

主要的修改有以下几点:

1、根据用户的选择,动态显示已选张数情况

2、新增图片预览功能

2.1:预览图片入口有两个:①点击gridview图片预览区域;②点击右下角的【预览】按钮
2.2:预览图片界面可以放大、缩小图片
2.3: 预览图片界面可以选中和取消选中图片,在退出界面后“根据情况”动态刷新ImageGridShowActivity界面中图片的选中状态
2.4:上述的“根据情况”有如下两种:
  2.4.1:如果用户点击预览图片,只是单单预览而未做任何选中、取消选中图片的操作,则在退出界面时不会刷新ImageGridShowActivity界面
  2.4.2:如果用户在退出界面时选中的图片和进入界面时不同,则在退出界面时会动态刷新ImageGridShowActivity界面
2.5:预览图片界面处理OOM
2.6:监听物理返回键,动态刷新ImageGridShowActivity界面

3、修改选择文件夹界面的样式

3.1:文件夹名称不显示问题
3.2:文件名错误显示问题
3.3:文件夹默认都选中问题
3.4:文件夹item选择时的样式

4、选中图片区域和预览图片区域的划分

5、优化 createAdapter() 方法,adapter只创建一次

6、文件夹和图片按照时间倒序