ViewPager基础
ViewPager 基础用法
ViewPager smoothScroll 速度控制
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
/**
* 通过反射来修改 ViewPager的mScroller属性
*/
try {
Class clazz=Class.forName("android.support.v4.view.ViewPager");
Field f=clazz.getDeclaredField("mScroller");
FixedSpeedScroller fixedSpeedScroller=new FixedSpeedScroller(this,new LinearOutSlowInInterpolator());
fixedSpeedScroller.setmDuration(2000);
f.setAccessible(true);
f.set(mViewPager,fixedSpeedScroller);
} catch (Exception e) {
e.printStackTrace();
}
/**
* 利用这个类来修正ViewPager的滑动速度
* 我们重写 startScroll方法,忽略传过来的 duration 属性
* 而是采用我们自己设置的时间
*/
public class FixedSpeedScroller extends Scroller {
public int mDuration=1500;
public FixedSpeedScroller(Context context) {
super(context);
}
public FixedSpeedScroller(Context context, Interpolator interpolator) {
super(context, interpolator);
}
public FixedSpeedScroller(Context context, Interpolator interpolator, boolean flywheel) {
super(context, interpolator, flywheel);
}
@Override public void startScroll(int startX, int startY, int dx, int dy) {
startScroll(startX,startY,dx,dy,mDuration);
}
@Override public void startScroll(int startX, int startY, int dx, int dy, int duration) {
//管你 ViewPager 传来什么时间,我完全不鸟你
super.startScroll(startX, startY, dx, dy, mDuration);
}
public int getmDuration() {
return mDuration;
}
public void setmDuration(int duration) {
mDuration = duration;
}
}
- ViewPager smoothScroll 速度控制
https://www.jianshu.com/p/44f6e3502412
ViewPager 中的 setCurrentItem 失效
在 onCreate 中给 ViewPager 设置进入哪个 Fragment 页面时 setCurrentItem() 未起作用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@SuppressWarnings("deprecation")
@SuppressLint("NewApi")
@Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
getViewTreeObserver().removeGlobalOnLayoutListener(this);
} else {
getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
currentPosition = pager.getCurrentItem();
currentPositionOffset = 0f;
scrollToChild(currentPosition, 0);
}
});
ViewPager 的 wrap_content 无效
原因
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
// ViewPager
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// For simple implementation, our internal size is always 0.
// We depend on the container to specify the layout size of
// our view. We can't really know what it is since we will be
// adding and removing different arbitrary views and do not
// want the layout to change as this happens.
// 设置测量大小为默认的大小
setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
getDefaultSize(0, heightMeasureSpec));
final int measuredWidth = getMeasuredWidth();
final int maxGutterSize = measuredWidth / 10;
mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);
// 设置child的宽高,大小为ViewPager的测量大小减去其内边距
int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight();
int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
// ---忽略Decor views 的测量---
// 设置child view的 MeasureSpec
mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);
// ---忽略其他代码
// Page views next.
size = getChildCount();
for (int i = 0; i < size; ++i) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
// ---忽略其他代码
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp == null || !lp.isDecor) {
final int widthSpec = MeasureSpec.makeMeasureSpec(
(int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY);
// 测量child 的大小
child.measure(widthSpec, mChildHeightMeasureSpec);
}
}
}
}
ViewPager 在 onMeasure(int widthMeasureSpec, int heightMeasureSpec) 方法一开始就对自身宽高进行了默认设置,在此之前并没有进行 child view 的测量,故而当高度设置为 “wrap_content” 时不会去匹配 child view 的高度。
在测量完自己之后,取得测量的宽高减去内边距后,设置为 child view 的宽高,而后再生成 MeasureSpec,并以此来对 child view 进行测量。这也就解释了为什么明明设置了 child view 的宽高,但是并不生效,反而去匹配父布局的大小。
错误解决
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class WrapContentHeightViewPager extends ViewPager {
public WrapContentHeightViewPager(@NonNull Context context) {
super(context);
}
public WrapContentHeightViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
int h = child.getMeasuredHeight();
if (h > height) height = h;
}
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
指定 height 为具体的数值时,显示不对。
1
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
这一行,高度的 MeasureSpec 指定大小是 0,mode 为 MeasureSpec.UNSPECIFIED。我们知道一个 view 的大小受自身的 LayoutParams 和父 view 的 MeasureSpec 的双重限制,还需要加上 LayoutParams 的设置。
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
public class WrapContentHeightViewPager extends ViewPager {
public WrapContentHeightViewPager(@NonNull Context context) {
super(context);
}
public WrapContentHeightViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
// child 的大小受自身的LayoutParams 和父view 的MeasureSpec 的双重限制!测量高度需要同时考虑这两个因素。
ViewGroup.LayoutParams lp = child.getLayoutParams();
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(lp.height, heightMeasureSpec));
int h = child.getMeasuredHeight();
if (h > height) height = h;
}
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
ListView 头部添加 ViewPager 事件不响应,ListView 下面的 ViewPager 事件不响应
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
// 自定义ViewPager,重写dispatchTouchEvent()方法
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//请求父容器不拦截事件,响应viewpager的左右滑动
getParent().requestDisallowInterceptTouchEvent(true);
downX = ev.getX();
downY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
float moveX = ev.getX();
float moveY = ev.getY();
if (Math.abs(downX - moveX) > Math.abs(downY - moveY)) {
//请求父容器不可以拦截事件,不响应viewpager的左右滑动,响应listview的上下滑动
getParent().requestDisallowInterceptTouchEvent(true);
} else {
//请求拦截事件
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
}
return super.dispatchTouchEvent(ev);
}
定制 ViewPager
不会滑动的 ViewPager
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class NoSlidingViewPager extends LazyViewPager {
public NoSlidingViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NoSlidingViewPager(Context context) {
super(context);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
return false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// return super.onInterceptTouchEvent(ev);
//FIXME:返回false,看看ViewPager事件是怎么拦截的
return false;
}
}
不会预加载页面的 ViewPager,懒加载的 ViewPager
ViewPager 中本来充满善意的预加载就有点令人不爽了。我们能做的就是屏蔽掉 ViewPager 的预加载机制。虽然 ViewPager 中提供的有
setOffscreenPageLimit() 来控制其预加载的数目,但是当设置为 0 后我们发现其根本没效果,这个的最小值就是 1,也就是你只能最少前后各预加载一页。
方案 1:修改源码
直接将 ViewPager 中的将 private static final int DEFAULT_OFFSCREEN_PAGES = 0;//默认的加载页面,ViewPager是1个,所以会加载两个Fragment
缺点:
应用太受限制,比如使用 ViewPagerIndicator 时需要传入 ViewPager 对象
方案 2:Fragment 的懒加载
缺点:
不是真正意义上的懒加载,还是会创建 3 个 Fragment 实例
方案 3:LazyViewPager
开源方案https://github.com/lianghanzhen/LazyViewPager
PageTransformer
ViewPager.PageTransformer 介绍
实现 ViewPager 切换动画
1
2
3
4
5
6
7
8
9
10
11
12
public interface PageTransformer {
/**
* Apply a property transformation to the given page.
*
* @param page Apply the transformation to this page
* @param position Position of page relative to the current front-and-center
* position of the pager. 0 is front and center. 1 is one full
* page position to the right, and -1 is one page position to the left.
*/
void transformPage(View page, float position);
}
比如当前页是 0 页,从第 0 页滑动到第 1 页,默认情况下 (setOffscreenPageLimit 为默认值 1):
- 第 0 页 position 的变化:
[0,-1]
- 第 1 页的 position 变化为:
[1,0]
- 页面正中间的 item 是正常的,两遍
- 如果设置了 pageMargin 为 10,左滑
- 选中的 item position:
[0,1-offset]
// offset = PageMargin/PageWidth - https://www.jianshu.com/p/722ece163629
- https://github.com/ToxicBakery/ViewPagerTransforms
ViewPager 中一个页面显示多个 ViewPager 的 Item
方式 1-clipChildren
- 在父容器和 ViewPager 中都添加上 clipChildren 属性
- 然后给 ViewPager 设置左右两个 margin,使其不致于把整个屏幕占满
- 给 ViewPager 设置 setPageMargin() 和 setOffscreenPageLimit()
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
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:clipChildren="false"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.view.ViewPager
android:layout_marginLeft="50dp"
android:layout_marginRight="50dp"
android:visibility="visible"
android:clipChildren="false"
android:layout_width="match_parent"
android:id="@+id/vp"
android:layout_height="wrap_content">
</android.support.v4.view.ViewPager>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:clipChildren="false"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.view.ViewPager
android:layout_marginLeft="50dp"
android:layout_marginRight="50dp"
android:visibility="visible"
android:clipChildren="false"
android:layout_width="match_parent"
android:id="@+id/vp"
android:layout_height="wrap_content">
</android.support.v4.view.ViewPager>
方式 2:clipToPadding
ViewPager Ref
- ViewPager 超详解:玩出十八般花样
https://juejin.im/post/5a4c2f496fb9a044fd122631