viewpager与viewpager2实践总结

Code概述#

工程包括2部分

graph LR
h1(ViewPager知识体系)-->A(常见类和API)
h1-->B(基本用法)
    B-->PagerAdapter
    B-->FragmentPagerAdapter
    B-->FragmetnStatePagerAdpater
    B-->RecyclerView.Adapter+FragmentStateAdapter
    B-->未完成Fragment+FragmentManager+FragmentTranscation
h1-->C(高阶用法)
    C-->P(PageTransformer)
        P-->一页多个
        P-->层叠
        P-->居中缩放
    C-->child(未完成-子项控制-5种adapter的用法区别)
        child-->remove
        child-->update
        child-->replace
        child-->insert

常见API#

名称 用途
ViewPager 视图组件
ViewPager2 视图组件
PagerAdapter viewpager1适配器
FragmentPagerAdapter viewpager1适配器
FragmentStatePagerAdapter viewpager1适配器
RecyclerView.Adapter viewpager2适配器
FragmentStateAdapter viewpager2适配器
FragmentTransaction Fragment事物控制
FragmentManager Fragment管理器
Fragment 视图组件

标准用法#

ViewPager#

总结

滑动有哪些方法:

  1. setCurrentItem、arrowScroll

能监听哪些事件:

  1. onPageScrolled、onPageSelected、onPageScrollStateChanged
  2. SCROLL_STATE_IDLE、SCROLL_STATE_DRAGGING、SCROLL_STATE_SETTLING

不同事件之间的区别:

  1. setAdapter是否进入回调
  2. 动作没完成是否进入回调

假滑了解吗:

  1. Viewpager通过fakeBy来与其他组件联动
  1. ViewPager - Android中文版 - API参考文档 (apiref.com)
  2. ViewPager | Android Developers (google.cn)

基本方法#

APi 用途
setAdapter
setPageTransformer 设置每个页面的变化效果
setPageMarginDrawable 设置一个可用于填充页面间空白的drawable
setPageMargin 设置页面的页边距,
setOnPageChangeListener
addOnPageChangeListener 监听page改变事件,支持三大类page事件:onPageScrolled、onPageSelected、onPageScrollStateChanged
其中onPageScrollStateChanged支持三类滑动事件SCROLL_STATE_IDLE滑动闲置、SCROLL_STATE_DRAGGING手势滑动、SCROLL_STATE_SETTLING代码设置的滑动
setOffscreenPageLimit 设置视图层次结构中当前页面任一侧的页数,超出此限制的页面将在需要时从适配器重新创建
setCurrentItem 设置当前显示的页面。
arrowScroll 上一页View.FOCUS_LEFT;下一页View.FOCUS_RIGHT
返回值代表能否继续滑动,好处是能判断是否到头了
onSaveInstanceState 存储视图内部状态的数据,如page的位置
onRestoreInstanceState 恢复视图内部之前存储的状态,如page的位置
removeView
isFakeDragging 返回当前是否假滑状态中
beginFakeDrag 只有不处于假滑状态中,才开始开始假滑
fakeDragBy 控制假滑移动水平方向距离
endFakeDrag 只有处于假滑动状态中,才可以结束假滑
getChildCount 获取没有被销毁的viewcontianer

实践#

参考ViewPager(六)让ViewPager用起来更顺滑——设置间距与添加转场动画_郝振兴的博客-CSDN博客_viewpage转场动画

禁止滑动#

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
package com.example.xinenhuadaka.view;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;

import androidx.viewpager.widget.ViewPager;

//禁止左右滑动的viewpager
public class NoScrollViewPager extends ViewPager {

private boolean noScroll = true;

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

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

//调用此方法 参数为false 即可禁止滑动
public void setNoScroll(boolean noScroll) {
this.noScroll = noScroll;
}

@Override
public void scrollTo(int x, int y) {
// if(noScroll){ //加上判断无法用 setCurrentItem 方法切换
super.scrollTo(x, y);
// }
}

@Override
public boolean onTouchEvent(MotionEvent arg0) {
if (!noScroll)
return false;
else
return super.onTouchEvent(arg0);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent arg0) {
if (!noScroll)
return false;
else
return super.onInterceptTouchEvent(arg0);
}

@Override
public void setCurrentItem(int item, boolean smoothScroll) {
super.setCurrentItem(item, smoothScroll);
}

@Override
public void setCurrentItem(int item) {
super.setCurrentItem(item);
}

}

区分滑动类型#

是否进入回调 onPageScrolled onPageSelected onPageScrollStateChanged
代码控制
手动滑动
setAdapter

onPageScrolled(int position, float positionOffset, int positionOffsetPixels);

  1. 代码控制的滚动会进入;
  2. 用户手滑动会进入
  3. viewpager.setAdapter初始化会进入

onPageSelected(int position);

  1. 新页面已经显示,但滑动动作没有完成的时候进入回调
  2. 用于动态设置页码、页面标题的方式,
  3. 用于动态回显tab选中的方式
  4. 初次viewpager.setAdapter不会进入回调,需要手动设置页码、页面标题、tab选中样式

onPageScrollStateChanged

这个方法可以做一些关于不同滑动方式的滑动方案的区别设计,用户滑动的时候执行一套逻辑,代码切换的时候执行一套逻辑,并且还可以判断切换动画是否已经结束,是否处于idle状态

ViewPager#SCROLL_STATE_IDLE为切换页面动画结束,或者处于idle闲置状态
ViewPager#SCROLL_STATE_DRAGGING为用户正在滑动时的状态回调,此时滑动未结束
ViewPager#SCROLL_STATE_SETTLING为非用户滑动,代码设置切换的状态,注意:这个状态会在手势滑动翻页(onPageSelected回调之前)回调一次这个状态值

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
private int mCurrentPos = 0;

mPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
//下面三个回调缺一不可,否则就会编译报错
@Override
public void onPageScrolled(int position, float positionOffset, int
positionOffsetPixels) {
Log.e("PageChange-Scroll", "position:" + position + ",positionOffset:" +
positionOffset + ",offsetPixels:" + positionOffsetPixels);
}

@Override
public void onPageSelected(int position) {
Log.e("PageChange-Select", "position:" + position);
//给标题textview设置对应标题,设置时机在这里
mTitleTv.setText(mTags[position]);
mCurrentPos = position;
}

@Override
public void onPageScrollStateChanged(int state) {
switch (state) {
case ViewPager.SCROLL_STATE_IDLE:
Log.e("PageChange-State", "state:SCROLL_STATE_IDLE(滑动闲置或滑动结束)");
break;
case ViewPager.SCROLL_STATE_DRAGGING:
Log.e("PageChange-State", "state:SCROLL_STATE_DRAGGING(手势滑动中)");
break;
case ViewPager.SCROLL_STATE_SETTLING:
Log.e("PageChange-State", "state:SCROLL_STATE_SETTLING(代码执行滑动中)");
break;
default:
break;
}
}
});

设置假滑#

假滑的意义:

某些场景下,用户不想触摸ViewPager,而是想在操作其他视图组件的时候,让ViewPager自动执行滑动操作,看上去好像有人滑动它一样;

简单来说就是Viewpager与其他组件联动机制的工具

一段假滑的步骤:

boolean isFakeDragging // 是否可以假滑操作

boolean beginFakeDrag() // 开始假滑
Void fakeDragBy(float xOffset) // 控制假滑移动水平方向距离
void endFakeDrag() // 结束假滑
boolean isFakeDragging() // 返回当前是否假滑状态中

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
public void fakePage(View view) {
vpContianer.removeView(mViewPager);
SeekBar seekBar = new SeekBar(BasicUsageScrollActivity.this);
mViewPager = new ViewPager(BasicUsageScrollActivity.this);
ViewGroup.LayoutParams layoutParams = vpContianer.getLayoutParams();
layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
vpContianer.addView(mViewPager, layoutParams);
vpContianer.addView(seekBar,layoutParams);
int mSource = 0;
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if(mViewPager.isFakeDragging()){
mViewPager.fakeDragBy(mSource - progress);
}
}

@Override
public void onStartTrackingTouch(SeekBar seekBar) {
if(!mViewPager.isFakeDragging()){
mViewPager.beginFakeDrag();
}
}

@Override
public void onStopTrackingTouch(SeekBar seekBar) {
if(mViewPager.isFakeDragging()){
mViewPager.endFakeDrag();
}
seekBar.setProgress(mSource);
}
});
// requires a view id
mViewPager.setId(view.getId() + 10);

mViewPager.setAdapter(screenSlidePageAdapter);
mViewPager.setPageTransformer(true, new DepthPageTransformer());
}

懒加载#

预加载是结合FragmentPagerAdapter、FragmentStatePagerAdapter说的,在Fragment生命周期过程中,网络请求数据未返回之前,可以通过setUserVisibleHint隐藏正在展示的Fragment;网络数据返回后,再通过setUserVisibleHint展示Fragment

预加载#

setOffscreenPageLimit–设置屏幕之外页面数量

PageLimit<1,缓存1页

不预加载#

将ViewPager的常量DEFAULT_OFFSCREEN_PAGES 改为0

修改方式1:照抄ViewPager源码,只修改这个变量的值为0

修改方式2:反射修改ViewPager的DEFAULT_OFFSCREEN_PAGES 常量值为0

ViewPager2#

!>ViewPager2 基于Recyclerview构建

与Viewpager不同之处:

  • ViewPager2为final类型,禁止继承复写
  • 支持可修改的 Fragment 集合,能正确显示修改后的Fragment
  • setUserInputEnabled控制禁止滑动,无需自定义
  • fakeDragBy支持假滑
  • 使用FragmentStateAdapter、RecyclerView.Adapter替换原先的ViewPager使用的三个Adapter
  • 需要重载的方法发生变化,新增getItemCount()、createFragment(),
  • 监听事件使用OnPageChangeCallback
  • 支持DiffUtil ,可以添加指定数据集改变的item动画

参考例子GitHub - android/views-widgets-samples: Multiple samples showing the best practices in views-widgets on Android.

基本方法#

API 用途
registerOnPageChangeCallback onPageScrolled、onPageSelectedonPageSelected、onPageScrollStateChanged
setOrientation() 设置垂直ORIENTATION_VERTICAL或水平ORIENTATION_HORIZONTAL
setLayoutDirection() 设置轮播、右至左
setPageTransformer
setUserInputEnabled 设置禁止用户滑动

实践#

PagerAdapter#

总结:

必须复写的方法:instantiateItem(), destroyItem(), getCount() 以及 isViewFromObject()

  1. instantiateItem的作用:被ViewPager.addNewItem()调用,创建新的Page
  2. destroyItem:释放指定的view
  3. getCount:设置Page数量
  4. isViewFromObject:在instantiateItem内调用用于判断是否view跟object进行了绑定

三者区别

名称 内存优化 不可见是否立即销毁Page 设置limit是否影响destory POSITION_NONE对destroyItem的影响 优点 场景
PagerAdapter 极佳 是,不销毁数量内的view 不可见即销毁
FragmentPagerAdapter 较差 是,先绑定新的fragment;后detach旧的fragment:两个fragment生命周期如下
onAttach: 3
onCreate: 3
onCreateView3
onViewCreated: 3
onActivityCreated: 3
onDestroyView:—0
是,不销毁数量内的Fragment 从FragmentManager里移除Fragment的视图,Fragment仅进入onDestroyView回调 适合固定的少量页面之间导航 - getItem()新的与数据无关的Fragment
- instantiateItem()中通过super.instantiateItem获取父类Fragment对象,设置数据
FragmentStatePagerAdapter 中等 是,先绑定新fragment,后onDetach旧的fragment;两个fragment生命周期如下
onAttach: 3
onCreate: 3
onCreateView:3
onViewCreated: 3
onActivityCreated: 3
onDestroyView:—0
onDestroy:–0
onDetach: - 0
是,不销毁数量内的Fragment 从FragmentManager里移除Fragment实例,Fragment,会进入onDestroyView回调、onDestroy回调、onDetach回调 适合对未知数量的页面进行分页导航

基本方法#

ViewPager将每个页面与一个关键对象相关联

名称 用途
POSITION_NONE 对象发生变化,用于getItemPosition中指定返回值,供notifyDataSetChanged判断
POSITION_UNCHANGED 对象没有改变,用于getItemPosition中指定返回值,供notifyDataSetChanged判断
instantiateItem(ViewGroup, int) 创建给定位置的页面。被ViewPager.addNewItem()调用
destroyItem(ViewGroup, int, Object) 删除给定位置的页面。
getCount() 返回可用的视图数量。
isViewFromObject(View, Object) 确定页面视图是否与 instantiateItem(ViewGroup, int)返回的特定键对象相关联。
getItemPosition(Object object) 当主视图试图确定某个项目的位置是否已更改时调用,返回POSITION_NONE或POSITION_UNCHANGED
getPageWidth(int position) 以(0.f-1.f)的ViewPager测量宽度的百分比形式返回给定页面的比例宽度。
notifyDataSetChanged() 如果支持此适配器的数据发生更改并且关联的视图应该更新,应该由应用程序调用此方法。
registerDataSetObserver(DataSetObserver observer) 注册观察者以接收与适配器数据更改有关的回调。
unregisterDataSetObserver(DataSetObserver observer) 从与适配器数据更改相关的回调中注销观察者。
restoreState(Parcelable state, ClassLoader loader) 还原与此适配器及其先前由 saveState()保存的页面相关的任何实例状态。
saveState 如果需要重建当前UI状态,请保存与此适配器及其应恢复的页面关联的任何实例状态。
setPrimaryItem(ViewGroup container, int position, Object object) 被调用以通知适配器当前哪个项目被认为是“主要”,这是向用户显示的当前页面。
startUpdate(ViewGroup container) 当所显示页面的变化即将开始时调用。

实践#

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
public class ChildSlidePageAdapter extends PagerAdapter {

private final Context mContext;
private int[] images = {R.drawable.item1, R.drawable.item2, R.drawable.item3,
R.drawable.item4, R.drawable.item5, R.drawable.item6, R.drawable.item7,
R.drawable.item8, R.drawable.item9, R.drawable.item10};

public ChildSlidePageAdapter(Context context) {
mContext = context;
}

@Override
public int getCount() {
return BasicUsageScrollActivity.MAX_PAGE_SIZE;
}

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

@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
ImageView imageView = new ImageView(mContext);
imageView.setImageResource(images[position]);
container.addView(imageView);
return imageView;
}

@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView((View) object);
}
}

FragmentPagerAdapter#

总结:

  1. 只有getItem、getCount是必须复写的
  2. getItem用于给instantiateItem提供Fragment
  3. getCount用于计算子页面数量
  4. 构造函数的FragmentManager是必须提供的,用于恢复、刷新、保存子页面持有的Fragment
  5. makeFragmentName是FragmentPagerAdpater的基类方法,子类复写,可用于修改FragmentTag的生成方式
  6. 刷新指定Fragment有2种方式

方式1

  1. 复写makeFragmentName,修改Adapter管理Fragment的方式
  2. 复写instantiateItem,定义map存储<position,FragmentTag>
  3. adapter对外暴露update方法,方法内通过FragmentManager#findFragmentByTag拿到指定Fragment,并调用Fragment对外暴露的public方法如((DetailsFragment)fragment).update

方式2

  1. 复写getItemPosition,根据外部条件,决策是否要全量刷新所有的Fragment,全量刷新返回POSITION_NONE;不需要刷新,返回POSITION_UNCHANGED
  2. 移除、替换Fragment也有2种方式

方式1类似6.2

  1. 复写getItemPosition,通知POSITION_NONE,刷新全部子项
  2. 传入新的list数据集

基本方法#

API 用途
getItem()
getCount()
instantiateItem 一般不需要复写,用于将Fragment存入FragmentTransaction,并且为Fragment设置TAG
makeFragmentName 优点1:可重写默认生成FragmentTag的方法
默认生成Fragment专属Tag,用于FM查询Fragment
"android:switcher:" + viewId + ":" + id;"

实践#

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
public class DetailsPagerAdapter extends FragmentPagerAdapter {

private List<Fragment> pageDetails;
private FragmentManager mFragmentManager;
private FragmentTransaction mCurTransaction;

public DetailsPagerAdapter(FragmentManager fragmentManager,List<Fragment> datas) {
super(fragmentManager);
mFragmentManager = fragmentManager;
pageDetails = datas;
}

public DetailsPagerAdapter(@NonNull FragmentManager fm, int behavior) {
super(fm, behavior);
}



@NonNull
@Override
public Fragment getItem(int position) {
return pageDetails.get(position);
}

/**
* Size != Capacity
* List<Integer> arr = Arrays.asList(new Integer[10]);
* !=
* new ArrayList<Integer>(10)
*/
private List<String> mTagList = Arrays.asList(new String[50]); // 用来存放所有的 Tag


private String makeFragmentName(int viewId, int index) {
return "android:switcher:" + viewId + ":" + index;
}

@Override
public int getCount() {
return pageDetails.size();
}
/**
* way2-step two
* @param container
* @param position
* @return
*/
@Override
public Object instantiateItem(ViewGroup container, int position) {
mTagList.set(position, makeFragmentName(container.getId(),
(int) getItemId(position)));
return super.instantiateItem(container, position);
}

/**
* way2-step one
* @param position update page
* @param isClearVisitors isshow visitors view
*/
public void update(int position, boolean isClearVisitors) {
Fragment fragment = mFragmentManager.findFragmentByTag(mTagList.get(position));
if (fragment == null) return;
if (fragment instanceof Fragment) {
((DetailsFragment)fragment).update(isClearVisitors);
}
notifyDataSetChanged();
}

/**
* way1-step one
* solution frag dose note refresh
* bug: always remove invisible view
* @param container
* @param position
* @param object
*/
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
super.destroyItem(container, position, object);
// if (fragmentTransaction == null) {
// fragmentTransaction = manager.beginTransaction();
// }
// fragmentTransaction.remove((Fragment) object);
// fragmentTransaction.commitNowAllowingStateLoss();
}

/**
* way1-step two
* solution frag dose note refresh
*
* @param object
* @return
*/
// @Override
// public int getItemPosition(@NonNull Object object) {
// String tagName = ((Fragment) object).getTag();
// if (tagName.equals(ErrorUsageNoRefreshActivity.currentPagePosition + "")) {
// return POSITION_NONE;
// }
// return POSITION_UNCHANGED;
// }
}

FragmentStatePagerAdapter#

总结:

  1. 只有getItem、getCount是必须复写的
  2. getItem用于给instantiateItem提供Fragment
  3. getCount用于计算子页面数量
  4. 构造函数的FragmentManager是必须提供的,用于恢复、刷新、保存子页面持有的Fragment

基本方法#

FragmentStatePagerAdapter
public ScreenSlidePageAdapter(@NonNull FragmentManager fm) {} 传入FragmentManager
public abstract Fragment getItem(int position); 返回Fragment实例
public Object instantiateItem(@NonNull ViewGroup container, int position) {}
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {}
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {}
public void restoreState(@Nullable Parcelable state, @Nullable ClassLoader loader) {}
public Parcelable saveState() {}
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {}
public void startUpdate(@NonNull ViewGroup container) {}

实践#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MydFragmentStatePagerAdapter extends FragmentStatePagerAdapter {


public MydFragmentStatePagerAdapter(FragmentManager fm) {
super(fm);
}

@Override
public Fragment getItem(int i) {
return null;
}

@Override
public int getCount() {
return 0;
}
}

实现FragmentStatePagerAdapter子类#

复写getItem方法、getCount方法

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
/**
* @author jackyoung
* @description ScreenSlidePageAdapter
* https://developer.android.google.cn/training/animation/screen-slide lessons
*/
public class ScreenSlidePageAdapter extends FragmentStatePagerAdapter {

public ScreenSlidePageAdapter(FragmentManager fm, int id) {
super(fm, id);
}


public ScreenSlidePageAdapter(@NonNull FragmentManager fm) {
super(fm);
}

@NonNull
@Override
public Fragment getItem(int position) {
ScreenSlidePageFragment screenSlidePageFragment = new ScreenSlidePageFragment();
screenSlidePageFragment.setData(position);
return screenSlidePageFragment;
}

@Override
public int getCount() {
return BasicUsageScrollActivity.MAX_PAGE_SIZE;
}
}

实现Fragment子类#

fragment复写onCreateView方法,设置TextView的text属性和backGroundColor属性

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
/**
* @author jackyoung
* @description ScreenSlidePageFragment
* from https://developer.android.google.cn/training/animation/screen-slide lessons
*/
public class ScreenSlidePageFragment extends Fragment {
private int[] contentList = {R.string.anmiation_samples, R.string.storage_samples,
R.string.dev_compose_samples, R.string.graphics_samples, R.string.permissions_samples};
private int[] colorList = {R.color.design_default_color_error, R.color.design_default_color_primary_dark,
R.color.purple_200, R.color.teal_700, R.color.teal_200};

public ScreenSlidePageFragment() {
}

private int index = 0;

public void setData(int position) {
this.index = position;
}

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_sreen_slide_page, container, false);
TextView textView = rootView.findViewById(R.id.tv_sample_content);
int contentId = contentList[index];
textView.setText(contentId);
textView.setBackgroundColor(getResources().getColor(colorList[index]));
return rootView;
}
}

编写Fragment布局文件#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/sl_content"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:id="@+id/tv_sample_content"
style="@android:style/TextAppearance.Medium"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:lineSpacingMultiplier="1.2"
android:padding="16dp"
android:text="@string/anmiation_samples" />
</ScrollView>

RecyclerView.Adapter#

总结

必须复写的方法:onCreateViewHolder()、onBindViewHolder()、getItemCount()

如果有异步任务,推荐复写:onViewAttachedToWindow、onViewDetachedFromWindow

如果数据集频繁可变,推荐复写:getItemId、containsItem

推荐的优化做法:在页面ondestory中setAdapter(null),显示进入onViewDetachedFromWindow回调

基本方法#

onCreateViewHolder() 创建并初始化 ViewHolder 及其关联的 View,但不会填充view的内容
onBindViewHolder() ViewHolder 与数据相关联
setHasStableIds adapter数据集中每个子项是否允许使用唯一标识符表示
getItemCount() 获取数据集的大小
getItemId
getItemViewType
onViewAttachedToWindow adapter创建的子view已经绑定的窗口,进入回调
onViewDetachedFromWindow adapter创建的子view已经从窗口分离,进入回调,只有2种情况会进入
1.显式的调用Adapter的remove方法;2.重新设置RecyclerView的Adapter;
onAttachedToRecyclerView 当RecyclerView绑定了当前adapter,进入回调
onDetachedFromRecyclerView 指定RecyclerView解绑了当前adapter,进入回调
onViewRecycled adapter创建的子view被回收,进入回调
notifyItemChanged
notifyItemInserted
notifyItemRemoved
notifyItemRangeChanged
notifyItemRangeInserted
notifyItemRangeRemoved

实践#

以viewpager2的层叠案例中用的到适配器

HorizontalVpAdapter为例

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
class HorizontalVpAdapter extends RecyclerView.Adapter<HorizontalVpAdapter.HorizontalVpViewHolder> {

private List<Integer> backgrounds;
private Context mContext;
private boolean isOverride = false;

HorizontalVpAdapter(Context context) {
mContext = context;
if (backgrounds == null) {
backgrounds = new ArrayList<>();
backgrounds.add(android.R.color.holo_blue_bright);
backgrounds.add(android.R.color.holo_red_dark);
backgrounds.add(android.R.color.holo_green_dark);
backgrounds.add(android.R.color.holo_orange_light);
backgrounds.add(android.R.color.holo_purple);
}
}

public void setIsOverride(boolean should) {
this.isOverride = should;
}

@NonNull
@Override
public HorizontalVpViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (isOverride) {
return new HorizontalVpViewHolder(LayoutInflater.from(mContext).inflate((R.layout.item_view_pager_2_h_v_2), parent, false));
} else {
return new HorizontalVpViewHolder(LayoutInflater.from(mContext).inflate((R.layout.item_view_pager_2_h_v), parent, false));
}
}

@SuppressLint("SetTextI18n")
@Override
public void onBindViewHolder(@NonNull HorizontalVpViewHolder holder, int position) {
holder.mTextView.setText("第 " + (position + 1) + " 界面");
holder.mLinearLayout.setBackgroundResource(backgrounds.get(position));
}

@Override
public int getItemCount() {
if (backgrounds == null) {
return 0;
}
return backgrounds.size();
}


class HorizontalVpViewHolder extends RecyclerView.ViewHolder {
LinearLayout mLinearLayout;
TextView mTextView;
ConstraintLayout mSonstraintLayout;

HorizontalVpViewHolder(@NonNull View itemView) {
super(itemView);
mSonstraintLayout = itemView.findViewById(R.id.cl_container);
mLinearLayout = itemView.findViewById(R.id.ll_h_v);
mTextView = itemView.findViewById(R.id.tv_hv);
}
}
}

FragmentStateAdapter#

总结

必须复写的方法:createFragment()、getItemCount

如果有异步任务,推荐复写:onViewAttachedToWindow、onViewDetachedFromWindow

如果数据集频繁可变,推荐复写:getItemId、containsItem

推荐的优化做法:在页面ondestory中setAdapter(null),显示进入onViewDetachedFromWindow回调

基本方法#

createFragment
getItemCount
getItemId
containsItem

实践#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private class ScreenSlideAdapter extends FragmentStateAdapter {

public ScreenSlideAdapter(@NonNull FragmentManager fragmentManager, Lifecycle lifecycle) {
super(fragmentManager, lifecycle);
}

@NonNull
@Override
public Fragment createFragment(int position) {
ScreenSlidePageFragment screenSlidePageFragment = new ScreenSlidePageFragment();
screenSlidePageFragment.setData(position);
return screenSlidePageFragment;
}

@Override
public int getItemCount() {
return NUM_PAGES;
}
}

PageTransformer#

参考案例GitHub - OCNYang/PageTransformerHelp: :+1: A PageTransformer library for Android ViewPager,have some Banner styles. ViewPager 实现轮播图、实现卡片切换。

参考文章(2条消息) 一个卡片式的ViewPager,带你玩转ViewPager的PageTransformer属性!__江南一点雨的博客-CSDN博客_setpagetransformer

参考文章(2条消息) 关于ViewPager.PageTransformer的一些理解_microhex的博客-CSDN博客_pagetransformer

基本方法#

1
void transformPage(@NonNull View page, float position);

当页面填满整个屏幕时,其位置值为 0

当页面刚刚离开屏幕右侧时,其位置值为 1

如果用户在第一页和第二页之间滚动到一半,则第一页的位置为 -0.5,第二页的位置为 0.5。

        graph LR
        left-->mid-->right

mid为屏幕中可见的页面,当mid滑动至right,

left view

mid view收到的position变化为:-0.10f–>-0.990f

rigth view收到的position变化为: 0.00f–>0.00f

动图表示滑动规则#

在这里插入图片描述

那么在滑动的过程中:

img

我们动图模拟右滑时,三个view收到的position变化

右滑#

在这里插入图片描述

我们动图模拟左滑时,三个view收到的position变化

左滑在这里插入图片描述#

setOffscreenPageLimit与View个数的关系#

limitN :setOffscreenPageLimit

count: 子项数量

count = setOffscreenPageLimit +1

实践#

准备工作#

以下示例均共用同一个适配器\布局文件\子项布局文件

1
2
3
4
5
6
7
8
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager"
android:layout_marginStart="350px"
android:layout_marginEnd="350px"
android:layout_width="match_parent"
android:layout_height="700px"
android:clipChildren="false"
tools:ignore="MissingConstraints" />

子项布局文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/cl_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/ll_h_v"
android:layout_width="match_parent"
android:layout_height="500px"
tools:ignore="MissingConstraints">
<TextView
android:id="@+id/tv_hv"
android:layout_width="match_parent"
android:layout_height="500px"/>
</LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

HorizontalVpAdapter

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
class HorizontalVpAdapter extends RecyclerView.Adapter<HorizontalVpAdapter.HorizontalVpViewHolder> {

private List<Integer> backgrounds;
private Context mContext;
private boolean isOverride = false;

HorizontalVpAdapter(Context context) {
mContext = context;
if (backgrounds == null) {
backgrounds = new ArrayList<>();
backgrounds.add(android.R.color.holo_blue_bright);
backgrounds.add(android.R.color.holo_red_dark);
backgrounds.add(android.R.color.holo_green_dark);
backgrounds.add(android.R.color.holo_orange_light);
backgrounds.add(android.R.color.holo_purple);
}
}

public void setIsOverride(boolean should) {
this.isOverride = should;
}

@NonNull
@Override
public HorizontalVpViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (isOverride) {
return new HorizontalVpViewHolder(LayoutInflater.from(mContext).inflate((R.layout.item_view_pager_2_h_v_2), parent, false));
} else {
return new HorizontalVpViewHolder(LayoutInflater.from(mContext).inflate((R.layout.item_view_pager_2_h_v), parent, false));
}
}

@SuppressLint("SetTextI18n")
@Override
public void onBindViewHolder(@NonNull HorizontalVpViewHolder holder, int position) {
holder.mTextView.setText("第 " + (position + 1) + " 界面");
holder.mLinearLayout.setBackgroundResource(backgrounds.get(position));
}

@Override
public int getItemCount() {
if (backgrounds == null) {
return 0;
}
return backgrounds.size();
}


class HorizontalVpViewHolder extends RecyclerView.ViewHolder {
LinearLayout mLinearLayout;
TextView mTextView;
ConstraintLayout mSonstraintLayout;

HorizontalVpViewHolder(@NonNull View itemView) {
super(itemView);
mSonstraintLayout = itemView.findViewById(R.id.cl_container);
mLinearLayout = itemView.findViewById(R.id.ll_h_v);
mTextView = itemView.findViewById(R.id.tv_hv);
}
}
}

一页多个#

1
2
3
4
5
6
7
8
9
10
11
   
public void moreItem(View view) {
ViewPager2 viewPager2 = findViewById(R.id.pager);
int offscreenPageLimit = 3;
viewPager2.setOffscreenPageLimit(offscreenPageLimit);
viewPager2.setAdapter(new HorizontalVpAdapter(this));
viewPager2.setPageTransformer(null);
}



层叠显示#

  1. 倒序层叠:默认后面的item遮盖前面的item
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
 public void moreItemOveride(View view) {
ViewPager2 viewPager2 = findViewById(R.id.pager);
int offscreenPageLimit = 5;
viewPager2.setOffscreenPageLimit(offscreenPageLimit);
viewPager2.setAdapter(new HorizontalVpAdapter(this));
viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
});
viewPager2.setPageTransformer(new HorizontalStackTransformerWithRotation(viewPager2));
}
/**
* 层叠动画-后进后层叠在上面(原生view的特性,后进先显示)
* 1. 设置偏移
*/
public class HorizontalStackTransformerWithRotation implements ViewPager2.PageTransformer {
private static final float CENTER_PAGE_SCALE = 0.8f;
private int offscreenPageLimit;
private ViewPager2 boundViewPager;

public HorizontalStackTransformerWithRotation(@NonNull ViewPager2 boundViewPager) {
this.boundViewPager = boundViewPager;
this.offscreenPageLimit = boundViewPager.getOffscreenPageLimit();
}

@Override
public void transformPage(@NonNull View view, float position) {
int pagerContainerWidth = boundViewPager.getWidth();
int pageItemWidth = view.getWidth();
float horizontalOffsetBase = (pagerContainerWidth - pagerContainerWidth * CENTER_PAGE_SCALE) / 2 / offscreenPageLimit + 60;

if (position >= 0) {
float translationX = (horizontalOffsetBase - pageItemWidth) * position;
view.setTranslationX(translationX);
}
Log.e("ycf", "getX: " + view.getX());
}
}
  1. 正序层叠-顺序靠前的item在z轴上面

只加了一行 ViewCompat.setElevation(view, (offscreenPageLimit - position) * 5);

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
public void moreItemOveride2(View view) {
ViewPager2 viewPager2 = findViewById(R.id.pager);
int offscreenPageLimit = 5;
viewPager2.setOffscreenPageLimit(offscreenPageLimit);
viewPager2.setAdapter(new HorizontalVpAdapter(this));
viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
});
viewPager2.setPageTransformer(new HorizontalStackTransformerWithRotation2(viewPager2));
}


/**
* 层叠动画2-先进先层叠在上面,先进的层叠值设大一点
* 1. 设置偏移
* 2. 设置轴排序,先进先展示
*/
public class HorizontalStackTransformerWithRotation2 implements ViewPager2.PageTransformer {
private static final float CENTER_PAGE_SCALE = 0.8f;
private int offscreenPageLimit;
private ViewPager2 boundViewPager;

public HorizontalStackTransformerWithRotation2(@NonNull ViewPager2 boundViewPager) {
this.boundViewPager = boundViewPager;
this.offscreenPageLimit = boundViewPager.getOffscreenPageLimit();
}

@Override
public void transformPage(@NonNull View view, float position) {
int pagerContainerWidth = boundViewPager.getWidth();
int pageItemWidth = view.getWidth();
float horizontalOffsetBase = (pagerContainerWidth - pagerContainerWidth * CENTER_PAGE_SCALE) / 2 / offscreenPageLimit + 60;
if (position >= 0) {
float translationX = (horizontalOffsetBase - pageItemWidth) * position;
view.setTranslationX(translationX);
}
Log.e("ycf", "getX: " + view.getX());
// Z=elevation+translationZ
ViewCompat.setElevation(view, (offscreenPageLimit - position) * 5);
}
}
  1. 实现前后缩放

主要在判断position的位置,根据position重新计算了偏移量horizontalOffsetBase、缩放量scaleFactor;区分了正负(左滑右滑)的情况

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
 public void moreItemOveride5(View view) {
ViewPager2 viewPager2 = findViewById(R.id.pager);
int offscreenPageLimit = 5;
viewPager2.setOffscreenPageLimit(offscreenPageLimit);
viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
});
viewPager2.setAdapter(new HorizontalVpAdapter(this));
viewPager2.setPageTransformer(new HorizontalStackTransformerWithRotation5(viewPager2));

}

/**
* 层叠动画-
* 1. 设置偏移
* 2. 设置轴排序,先进先展示
* 3. 左滑超过左侧,移动到下面
* 4. 超过中间,都小于中间的
* 5. 增加透明度
*/
public class HorizontalStackTransformerWithRotation5 implements ViewPager2.PageTransformer {
private static final float CENTER_PAGE_SCALE = 0.8f;
private int offscreenPageLimit;
private ViewPager2 boundViewPager;
private float padding = 60f;
private float scaleFa = 0.1f;

public HorizontalStackTransformerWithRotation5(@NonNull ViewPager2 boundViewPager) {
this.boundViewPager = boundViewPager;
this.offscreenPageLimit = boundViewPager.getOffscreenPageLimit();
}

public HorizontalStackTransformerWithRotation5(@NonNull ViewPager2 boundViewPager,float padding) {
this.boundViewPager = boundViewPager;
this.offscreenPageLimit = boundViewPager.getOffscreenPageLimit();
this.padding = padding;
}

public HorizontalStackTransformerWithRotation5(@NonNull ViewPager2 boundViewPager,float padding,float scaleFa) {
this.boundViewPager = boundViewPager;
this.offscreenPageLimit = boundViewPager.getOffscreenPageLimit();
this.padding = padding;
this.scaleFa = scaleFa;
}
@Override
public void transformPage(@NonNull View view, float position) {
int pagerContainerWidth = boundViewPager.getWidth();
int pageItemWidth = view.getWidth();
float horizontalOffsetBase = (pagerContainerWidth - pagerContainerWidth * CENTER_PAGE_SCALE) / 2 / offscreenPageLimit + padding;

// 设置偏移
float translationX = (horizontalOffsetBase - pageItemWidth) * position;
view.setTranslationX(translationX);


Log.e("ycf", "getX: " + view.getX());
// 设置透明度
if (position > -1 && position < 0) {
view.setAlpha((position * position * position + 1));
} else if (position > offscreenPageLimit - 1) {
view.setAlpha((float) (1 - position + Math.floor(position)));
} else {
view.setAlpha(1);
}

// 设置缩放
if (position == 0) {
view.setScaleX(CENTER_PAGE_SCALE);
view.setScaleY(CENTER_PAGE_SCALE);
} else if (position > 0) {
// position 自增,自己变小
float scaleFactor = Math.min(CENTER_PAGE_SCALE - position * scaleFa, CENTER_PAGE_SCALE);
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
} else if (position < 0) {
// position 负数自增,自己变小
float scaleFactor = Math.min(CENTER_PAGE_SCALE + position * scaleFa, CENTER_PAGE_SCALE);
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
}

// 设置层叠
if (position >= 0) {
// position 自增,z轴越小
ViewCompat.setElevation(view, (offscreenPageLimit - position) * 5);
} else {
// position 自减,z轴越小
ViewCompat.setElevation(view, (position - offscreenPageLimit) * 5);
}
}
}

Fragment#

API 用途
startPostponedEnterTransition()
getFragmentManager() 获得单签Fragment的宿主Activity持有的管理器
getChildFragmentManager() 获得当前Fragment持有的管理器
getParentFragmentManager() 获得当前Fragment宿主持有的管理器
postponeEnterTransition()
setExitSharedElementCallback()
getArguments() 获取Bundle参数
setArguments() 设置Bundle惨呼
isAdded() 是否添加至Activity
isDetached() 是否从UI布局上解绑
onCreateView()

FragmentManager#

!> 获取管理器有多种方式

1
2
3
fragmentActivity.getSupportFragmentManager()     
fragment. getChildFragmentManager()
fragment.getParentFragmentManager()
API 用途
beginTransaction() 获取事物对象
popBackStack() 将片段从返回栈中弹出
findFragmentByTag() 获取 Activity 中存在的片段
findFragmentById() 获取 Activity 中存在的片段

FragmentTransaction #

API 用途
setReorderingAllowed()
addSharedElement()
add() 设置viewId、Tag,Fragment实例,
默认不传递TAG,使用adapter的makeFragmentName的返回值
replace()
remove()
show()
addToBackStack() 添加Fragment至回退栈,用于响应物理返回键
commit() 异步执行事物
executePendingTransactions() 在主线程执行事物,可能会抛出异常
commitNow() 同步执行事物,可能会抛出异常
commitAllowingStateLoss() 提交无关紧要的页面,用于容灾
setTransition() 设置过渡动画
setTransitionStyle() 设置过渡样式

#

常见问题#

ViewPager子项的数量如何获取?#

不要用ViewPager.getChildCount,它返回的是没有被销毁的

想要获取所有的子项或Fragment数量,应该调用ViewPager.getAdapter().getCount().

ViewPager一屏展示多个子项?#

方式1:设置子项宽度#

适用于版本1

1
2
3
4
5
6
7
8
9
/**
* 返回给定的页面所占ViewPager 测量宽度的比例,范围(0,1]
* @param position
* @return
*/
@Override
public float getPageWidth(int position) {
return 0.3f;
}

方式2:clipchildren#

适用于1和2版本

第一步:viewpager2父布局clipChildren=false;viewpager2也clipChildren=false

ViewPager2一屏展示多个子项?#

参考PageTransfromer#实践#一页多个

ViewPager如何绑定指示器或Tab?#

待定

ViewPager中刷新单个Fragment?#

(ViewPager中刷新单个Fragment出现异常,数据源改变,Fragment不刷新)

解决方法

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
   
private List<String> mTagList = new ArrayList<>(); // 用来存放所有的 Tag
/**
* way2-step two
* @param container
* @param position
* @return
*/
@Override
public Object instantiateItem(ViewGroup container, int position) {
mTagList.add(position, makeFragmentName(container.getId(),
(int) getItemId(position)));
return super.instantiateItem(container, position);
}

/**
* way2-step one
* @param position update page
* @param isClearVisitors isshow visitors view
*/
public void update(int position, boolean isClearVisitors) {
Fragment fragment = mFragmentManager.findFragmentByTag(mTagList.get(position));
if (fragment == null) return;
if (fragment instanceof Fragment) {
((DetailsFragment)fragment).update(isClearVisitors);
}
notifyDataSetChanged();
}

如何做懒加载?#

参考前文标准用法#viewpager#实践#预加载

如何做预加载?#

参考前文标准用法#viewpager#实践#预加载

如何刷新指定位置元素?#

Fragment为例,view的话查看《#fragment不刷新?》

初始化taglist的长度为最大长度

1
private List<String> mTagList = Arrays.asList(new String[50]); // 用来存放所有的 Tag

taglist存储fragment,并复写adapter的makeFragmentName

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
   /**
* for FragmentPagerdapter call,set Fragment Tag
*
* @param viewId
* @param index
* @return
*/
private String makeFragmentName(int viewId, int index) {
return "android:switcher:" + viewId + ":" + index;
}

/**
* way2-step two
* @param container
* @param position
* @return
*/
@Override
public Object instantiateItem(ViewGroup container, int position) {
mTagList.set(position, makeFragmentName(container.getId(),
(int) getItemId(position)));
return super.instantiateItem(container, position);
}

update更新指定fragment

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* way2-step one
* @param position update page
* @param isClearVisitors isshow visitors view
*/
public void update(int position, boolean isClearVisitors) {
Fragment fragment = mFragmentManager.findFragmentByTag(mTagList.get(position));
if (fragment == null) return;
if (fragment instanceof Fragment) {
((DetailsFragment)fragment).update(isClearVisitors);
}
notifyDataSetChanged();
}

fragment不刷新?#

思路1:强制清空FragmentManger#

解决方法1<span class=”hint–top hint–error hint–medium hint–rounded hint–bounce” aria-label=” FragmentPagerAdapter数据刷新notifyDataSetChanged没效果研究


“>[4]

adapter更新数据前,先使用FragmentManager强制清空缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void setFragments(ArrayList fragments) {
if(this.fragments != null){
FragmentTransaction ft = fm.beginTransaction();
for(Fragment f:this.fragments){
ft.remove(f);
}
ft.commit();
ft=null;
fm.executePendingTransactions();
}
this.fragments = fragments;
notifyDataSetChanged();
}
// 设置tag为POSITION_NONE意思是没有找到child要求重新加载。
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}

思路2:更新指定Fragment#

解决方法2<span class=”hint–top hint–error hint–medium hint–rounded hint–bounce” aria-label=”ViewPager刷新问题详解


“>[5]

原因:查看FragmentPagerAdpater#instantiateItem源码,有如下一句话:
Do we already have this fragment?
原来他会先去FragmentManager里面去查找有没有相关的fragment:
如果有就直接使用 ;如果没有才会触发fragmentpageadapter的getItem方法获取一个fragment。 所以你更新的fragmentList集合是没有作用的,还要清除FragmentManager里面缓存的fragment。

依据如何更新FragmentManager的Fragment,我们可以得到两种思路

方法1:移除指定的Fragment,刷新一遍的同时,重新创建指定位置的Fragment#

1
2
3
4
5
6
7
8
9
10
11
@Override
public int getItemPosition(Object object) {
// 最简单解决 notifyDataSetChanged() 页面不刷新问题的方法
return POSITION_NONE;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
// 把 Object 强转为 View,然后将 view 从 ViewGroup 中清除
container.removeView((View) object);
}

如果是FragmentStateAdapter,思路1写法可能是这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
   @Override
public int getItemPosition(Object object) {
// 最简单解决 notifyDataSetChanged() 页面不刷新问题的方法
return POSITION_NONE;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
if (mCurTransaction == null) {
mCurTransaction = fm.beginTransaction();
}
mCurTransaction.remove((Fragment)object);
mCurTransaction.commitNowAllowingStateLoss();
}

方法2:从FragmentManager获取Fragment,刷新它#

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
/**
* way2-step two
* 临时集合 taglist存储每个Fragment的TagName
* @param container
* @param position
* @return
*/
@Override
public Object instantiateItem(ViewGroup container, int position) {
mTagList.set(position, makeFragmentName(container.getId(),
(int) getItemId(position)));
return super.instantiateItem(container, position);
}

/**
* way2-step one
* 从FragmentManager中拿到该Fragment实例,手动更新
* @param position update page
* @param isClearVisitors isshow visitors view
*/
public void update(int position, boolean isClearVisitors) {
Fragment fragment = mFragmentManager.findFragmentByTag(mTagList.get(position));
if (fragment == null) return;
if (fragment instanceof Fragment) {
((DetailsFragment) fragment).update(isClearVisitors);
}
notifyDataSetChanged();
}

PageAdapter每个方法的坑<span class=”hint–top hint–error hint–medium hint–rounded hint–bounce” aria-label=”FragmentPagerAdapter+FragementStatePagerAdapter API的坑#


“>[6]

viewpager2#adapter异步任务释放问题#

参考(3条消息) RecyclerView的Adapter中attach和detach探索_weixin_33882443的博客-CSDN博客

该文章遇到的问题:

Fragment销毁了,Recyclerview的异步任务没执行,尝试在adapter的onViewDetachedFromWindow释放任务,没进入该onViewDetachedFromWindow回调

该文找到做法:

第一步:在activity或fragment生命周期设置recyclerView.setAdapter(null);

第二步:adapter回进入onViewDetachedFromWindow回调

第三步:在onViewDetachedFromWindow中释放异步任务

该文章的参考

参考(Deprecated) Why you should call setAdapter(null) :: eneim’s blog

viewpager1、2替换指定Fragment问题#

参考ViewPage2 +FragmentStateAdapter + Fragment 刷新问题 - 简书 (jianshu.com)

以下代码都是FragmentPagerAdapter、FragmentStatePagerAdapter、FragmentStateAdapter可以参考的

第一步:存储taglist,重写tag的生成规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* way2-step two
* 临时集合 taglist存储每个Fragment的TagName
* @param container
* @param position
* @return
*/
@Override
public Object instantiateItem(ViewGroup container, int position) {
mTagList.set(position, makeFragmentName(container.getId(),
(int) getItemId(position)));
return super.instantiateItem(container, position);
}

private String makeFragmentName(int viewId, int index) {
return "android:switcher:" + viewId + ":" + index;
}

第二步:replace函数中remove掉FragmentManager中的Fragment,并重新初始化fragment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* replace the fragment,if not please delete
* way1-step one
*/
public void replace(int position, Fragment newFragment) {
Fragment fragment = mFragmentManager.findFragmentByTag(mTagList.get(position));
if (fragment instanceof Fragment) {
pageDetails.set(6, newFragment);
mFragmentManager.beginTransaction().remove(fragment).commitNow();
}
notifyDataSetChanged();
}

/**
* replace the fragment,if not please delete
* way1-step two
*
* @return
*/
@Override
public int getItemPosition(@NonNull Object object) {
return POSITION_NONE;
}

其他问题#

java.lang.IllegalStateException: Fragment already active#

在 Fragment 没有被添加到 FragmentManager 之前,我们可以通过 Fragment.setArguments() 来设置参数,并在 Fragment 中,使用 getArguments() 来取得参数。这是常用的参数传递方式。

但是这种方式在 Fragment 被添加到 FragmentManager 后,再第二次调用 setArguments() 将会导致 java.lang.IllegalStateException: Fragment already active 异常。

解决此问题的办法是在继承的 Fragment 子类中,新增几个 setter,然后通过这些 setter 将数据传递过去。反向也是类似。

?>这些 setter 中要注意不要操作那些 View,这些 View 只有在 onCreateView() 事件后才可以操作。

java.lang.IllegalStateException: The application’s PagerAdapter changed the adapter’s contents without calling PagerAdapter#notifyDataSetChanged!#

1
java.lang.IllegalStateException: The application's PagerAdapter changed the adapter's contents without calling PagerAdapter#notifyDataSetChanged! Expected adapter item count: 0, found: 4 Pager id: com.component.viewpager:id/vp_details Pager class: class androidx.viewpager.widget.ViewPager Problematic adapter: class com.component.viewpager.adapter.DetailsPagerAdapter

Android java.lang.IllegalArgumentException: Invalid Transition types#

1
2
atal Exception: java.lang.IllegalArgumentException: Invalid Transition types   at android.support.v4.app.FragmentTransition.chooseImpl(FragmentTransition.java:461)   at android.support.v4.app.FragmentTransition.configureTransitionsOrdered(FragmentTransition.java:3317)   at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2380)   at android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2338)   at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2245)   at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:703)   at android.os.Handler.handleCallback(Handler.java:733)   at android.os.Handler.dispatchMessage(Handler.java:95)   at android.os.Looper.loop(Looper.java:136)   at android.app.ActivityThread.main(ActivityThread.java:5590)   at java.lang.reflect.Method.invokeNative(Method.java)   at java.lang.reflect.Method.invoke(Method.java:515)   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1268)   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1084)   at dalvik.system.NativeStart.main(NativeStart.java)

原因:

如下代码,使用了错误包下的TransitionAPI

1
2
((TransitionSet) Objects.requireNonNull(fragment.getExitTransition()))

解决:

将Transition包导入改为如下:

1
2
3
4
5
6
7
8
# error import
import androidx.transition.TransitionSet;


# change this
import android.transition.TransitionSet;


Mixing framework transitions#

1
2
java.lang.IllegalArgumentException: Mixing framework transitions and AndroidX transitions is not allowed. Fragment ImagePagerFragment{a3b0fd8} (0be5f4cc-4fae-4a54-87be-5a0b57d19a90 id=0x7f08007c tag=ImagePagerFragment) returned Transition null which uses a different Transition  type than other Fragments.

原因:

多个Fragment切换,传递的Transition引用属于不同的包,一个Fragment引用的androidx包下的Transition,另一个Fragment引用android包下的Transition

解决:

将Transition的包导入统一改为android

1
2
3
4
5
6
7
8
# error import
import androidx.transition.Transition;
import androidx.transition.TransitionInflater;

# change this
import android.transition.Transition;
import android.transition.TransitionInflater;


点击查看
-------------------本文结束 感谢您的阅读-------------------