FragmentStatePagerAdapter 中片段的生命周期是意外的
LifeCycle of Fragment in FragmentStatePagerAdapter is unexpected
我想使用 ViewPager 创建视频列表。我必须知道寻呼机项何时可见和不可见。
我使用 VideoListAdapter 为 ViewPager 扩展 FragmentStatePagerAdapter。
我使用 Fragment 方法 setUserVisibleHint 来触发视频开始或暂停。
但是有个问题,ViewPager的0号位置的Fragment抛出NullPointerException。然后我为片段的相关方法打印日志。
我进入 VideoListActivity 的日志:
07-08 17:06:50.264 E/lemon: startUpdate
07-08 17:06:50.264 E/lemon: instantiateItem 0
07-08 17:06:50.264 E/lemon: getItem 0
07-08 17:06:50.264 E/lemon: setUserVisibleHint 0 isVisibleToUser false
07-08 17:06:50.264 E/lemon: instantiateItem 1
07-08 17:06:50.264 E/lemon: getItem 1
07-08 17:06:50.264 E/lemon: setUserVisibleHint 0 isVisibleToUser false
07-08 17:06:50.264 E/lemon: setPrimaryItem 0
07-08 17:06:50.264 E/lemon: setUserVisibleHint 0 isVisibleToUser true
07-08 17:06:50.264 E/lemon: finishUpdate
07-08 17:06:50.265 E/lemon: onAttach 0
07-08 17:06:50.265 E/lemon: onAttach 1
07-08 17:06:50.265 E/lemon: onCreateView 0
07-08 17:06:50.267 E/lemon: onstart 0
07-08 17:06:50.267 E/lemon: onCreateView 1
07-08 17:06:50.269 E/lemon: onstart 1
07-08 17:06:50.270 E/lemon: startUpdate
07-08 17:06:50.270 E/lemon: setPrimaryItem 0
07-08 17:06:50.270 E/lemon: finishUpdate
07-08 17:06:50.297 E/lemon: startUpdate
07-08 17:06:50.297 E/lemon: setPrimaryItem 0
07-08 17:06:50.297 E/lemon: finishUpdate
07-08 17:06:50.297 E/lemon: startUpdate
07-08 17:06:50.297 E/lemon: setPrimaryItem 0
07-08 17:06:50.297 E/lemon: finishUpdate
07-08 17:06:50.703 E/lemon: startUpdate
07-08 17:06:50.703 E/lemon: setPrimaryItem 0
07-08 17:06:50.703 E/lemon: finishUpdate
07-08 17:06:50.704 E/lemon: startUpdate
07-08 17:06:50.704 E/lemon: setPrimaryItem 0
07-08 17:06:50.704 E/lemon: finishUpdate
我滚动到位置 1 的日志:
07-08 17:09:41.154 E/lemon: startUpdate
07-08 17:09:41.154 E/lemon: setPrimaryItem 0
07-08 17:09:41.154 E/lemon: finishUpdate
07-08 17:09:41.966 E/lemon: startUpdate
07-08 17:09:41.966 E/lemon: instantiateItem 2
07-08 17:09:41.967 E/lemon: getItem 2
07-08 17:09:41.967 E/lemon: setUserVisibleHint 0 isVisibleToUser false
07-08 17:09:41.967 E/lemon: setPrimaryItem 1
07-08 17:09:41.967 E/lemon: setUserVisibleHint 0 isVisibleToUser false
07-08 17:09:41.967 E/lemon: setUserVisibleHint 1 isVisibleToUser true
07-08 17:09:41.967 E/lemon: finishUpdate
07-08 17:09:41.968 E/lemon: onAttach 2
07-08 17:09:41.968 E/lemon: onCreateView 2
07-08 17:09:41.971 E/lemon: onstart 2
07-08 17:09:41.971 E/lemon: startUpdate
07-08 17:09:41.971 E/lemon: setPrimaryItem 1
07-08 17:09:41.971 E/lemon: finishUpdate
07-08 17:09:41.972 E/lemon: startUpdate
07-08 17:09:41.972 E/lemon: setPrimaryItem 1
07-08 17:09:41.972 E/lemon: finishUpdate
我分析这些日志,我发现positino 0中的片段先调用setUserVisibleHint(true),然后调用onAttach(),但是位置1中的片段先调用onAttach(),然后调用setUserVisibleHint(true)。
所以我在 Fragment 中写了一个 onTrigger() 方法,在 onAttach() 和 setUserVisibleHint(true) 中调用,但失败了。然后我调试我的代码,它显示 onTrigger() 中的 isAdded() returns 在 onAttach().
中调用 onTrigger 时为 false
这里有任何建议可以让我知道何时触发我的视频开始。非常感谢。
public class FullScreenVideoFragment extends Fragment {
private FragmentFullScreenVideoBinding binding;
int colorRes;
int position;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
Log.e("lemon", "onCreateView " + position);
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_full_screen_video, container, false);
setView();
return binding.getRoot();
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
public void setBgAndPosition(int position, int colorRes) {
this.position = position;
this.colorRes = colorRes;
}
@Override
public void onAttach(Context context) {
Log.e("lemon", "onAttach " + position);
super.onAttach(context);
onTriger();
}
@Override
public void onDetach() {
Log.e("lemon", "onDetach " + position);
super.onDetach();
}
@Override
public void onAttachFragment(Fragment childFragment) {
Log.e("lemon", "onAttachFragment " + position);
super.onAttachFragment(childFragment);
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
Log.e("lemon", "setUserVisibleHint " + position + " isVisibleToUser " + isVisibleToUser);
super.setUserVisibleHint(isVisibleToUser);
onTriger();
}
private void setView() {
binding.getRoot().setBackgroundResource(colorRes);
binding.position.setText(String.valueOf(position));
}
private void onTriger() {
if (!isVisible()) return;
binding.position.setText(position + " start");
}
}
public class VideoListAdapter extends FragmentStatePagerAdapter {
private LinkedList<FullScreenVideoFragment> fragmentCaches;
private int[] colors = new int[]{android.graphics.Color.RED, android.graphics.Color.BLUE, android.graphics.Color.GREEN};
public VideoListAdapter(FragmentManager fm) {
super(fm);
fragmentCaches = new LinkedList<>();
}
@Override
public Fragment getItem(int position) {
Log.e("lemon", "getItem " + position);
FullScreenVideoFragment fragment = generateItem();
return fragment;
}
@Override
public int getCount() {
return 10;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
Log.e("lemon", "destroyItem " + position);
super.destroyItem(container, position, object);
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
Log.e("lemon", "setPrimaryItem " + position);
super.setPrimaryItem(container, position, object);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Log.e("lemon", "instantiateItem " + position);
FullScreenVideoFragment fragment = (FullScreenVideoFragment) super.instantiateItem(container, position);
fragment.setBgAndPosition(position, colors[position % 3]);
return fragment;
}
@Override
public void startUpdate(ViewGroup container) {
Log.e("lemon", "startUpdate");
super.startUpdate(container);
}
@Override
public void finishUpdate(ViewGroup container) {
Log.e("lemon", "finishUpdate");
super.finishUpdate(container);
}
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
Log.e("lemon", "restoreState");
super.restoreState(state, loader);
}
@Override
public Parcelable saveState() {
Log.e("lemon", "saveState");
return super.saveState();
}
private FullScreenVideoFragment generateItem() {
FullScreenVideoFragment neededFragment = null;
if (!fragmentCaches.isEmpty()) {
neededFragment = fragmentCaches.get(0);
fragmentCaches.remove(0);
return neededFragment;
}
neededFragment = new FullScreenVideoFragment();
return neededFragment;
}
}
片段xml:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data></data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/position"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="@color/account_name_color"/>
</RelativeLayout>
</layout>
我发现Fragments经常使用这三种方式切换:
show/hide
attach/detach(替换)
ViewPager
这三种方式导致Fragment的可见状态存在一些差异,所以我定义如下类来区分这三种方式。如果一组Fragments都是用同样的方式切换,我觉得应该靠谱,如果用不同的方式切换,那我就不知道了,没有仔细测试。
import android.support.annotation.IntDef;
import android.support.v4.app.Fragment;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Created by Kilnn on 2017/7/12.
* A smart fragment know itself's visible state.
*/
public abstract class SmartFragment extends Fragment {
private boolean isFragmentVisible;
@Override
public void onResume() {
super.onResume();
int switchType = getSwitchType();
if (switchType == ATTACH_DETACH) {
notifyOnFragmentVisible();
} else if (switchType == SHOW_HIDE) {
if (!isHidden()) {
notifyOnFragmentVisible();
}
} else if (switchType == VIEW_PAGER) {
//If the parent fragment exist and hidden when activity destroy,
//when the activity restore, The parent Fragment will be restore to hidden state.
//And the sub Fragment which in ViewPager is also be restored, and the onResumed() method will callback.
//And The sub Fragment's getUserVisibleHint() method will return true if it is in active position.
//So we need to judge the parent Fragment visible state.
if (getUserVisibleHint() && isParentFragmentVisible()) {
notifyOnFragmentVisible();
}
}
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
int switchType = getSwitchType();
if (switchType == VIEW_PAGER) {
if (isVisibleToUser) {
notifyOnFragmentVisible();
} else {
notifyOnFragmentInvisible();
}
}
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
int switchType = getSwitchType();
if (switchType == SHOW_HIDE) {
if (hidden) {
notifyOnFragmentInvisible();
} else {
notifyOnFragmentVisible();
}
}
}
@Override
public void onPause() {
super.onPause();
notifyOnFragmentInvisible();
}
private boolean isParentFragmentVisible() {
Fragment parent = getParentFragment();
if (parent == null) return true;
if (parent instanceof SmartFragment) {
return ((SmartFragment) parent).isFragmentVisible();
} else {
//TODO May be can't get the correct visible state if parent Fragment is not SmartFragment
return parent.isVisible();
}
}
public boolean isFragmentVisible() {
// Don't judge the state of the parent fragment,
// because if the parent fragment visible state changes,
// you must take the initiative to change the state of the sub fragment
// return isFragmentVisible && isParentFragmentVisible();
return isFragmentVisible;
}
public void notifyOnFragmentVisible() {
if (!isFragmentVisible) {
onFragmentVisible();
isFragmentVisible = true;
}
}
public void notifyOnFragmentInvisible() {
if (isFragmentVisible) {
onFragmentInvisible();
isFragmentVisible = false;
}
}
/**
* If this method callback, the Fragment must be resumed.
*/
public void onFragmentVisible() {
}
/**
* If this method callback, the Fragment maybe is resumed or in onPause().
*/
public void onFragmentInvisible() {
}
/**
* Fragments switch with attach/detach(replace)
*/
public static final int ATTACH_DETACH = 0;
/**
* Fragments switch with show/hide
*/
public static final int SHOW_HIDE = 1;
/**
* Fragments manage by view pager
*/
public static final int VIEW_PAGER = 2;
@Retention(RetentionPolicy.SOURCE)
@IntDef({ATTACH_DETACH, SHOW_HIDE, VIEW_PAGER})
@interface SwitchType {
}
@SwitchType
public abstract int getSwitchType();
}
所以你可以在 onFragmentVisible() 中执行 onTrigger()。
我想使用 ViewPager 创建视频列表。我必须知道寻呼机项何时可见和不可见。
我使用 VideoListAdapter 为 ViewPager 扩展 FragmentStatePagerAdapter。
我使用 Fragment 方法 setUserVisibleHint 来触发视频开始或暂停。
但是有个问题,ViewPager的0号位置的Fragment抛出NullPointerException。然后我为片段的相关方法打印日志。
我进入 VideoListActivity 的日志:
07-08 17:06:50.264 E/lemon: startUpdate
07-08 17:06:50.264 E/lemon: instantiateItem 0
07-08 17:06:50.264 E/lemon: getItem 0
07-08 17:06:50.264 E/lemon: setUserVisibleHint 0 isVisibleToUser false
07-08 17:06:50.264 E/lemon: instantiateItem 1
07-08 17:06:50.264 E/lemon: getItem 1
07-08 17:06:50.264 E/lemon: setUserVisibleHint 0 isVisibleToUser false
07-08 17:06:50.264 E/lemon: setPrimaryItem 0
07-08 17:06:50.264 E/lemon: setUserVisibleHint 0 isVisibleToUser true
07-08 17:06:50.264 E/lemon: finishUpdate
07-08 17:06:50.265 E/lemon: onAttach 0
07-08 17:06:50.265 E/lemon: onAttach 1
07-08 17:06:50.265 E/lemon: onCreateView 0
07-08 17:06:50.267 E/lemon: onstart 0
07-08 17:06:50.267 E/lemon: onCreateView 1
07-08 17:06:50.269 E/lemon: onstart 1
07-08 17:06:50.270 E/lemon: startUpdate
07-08 17:06:50.270 E/lemon: setPrimaryItem 0
07-08 17:06:50.270 E/lemon: finishUpdate
07-08 17:06:50.297 E/lemon: startUpdate
07-08 17:06:50.297 E/lemon: setPrimaryItem 0
07-08 17:06:50.297 E/lemon: finishUpdate
07-08 17:06:50.297 E/lemon: startUpdate
07-08 17:06:50.297 E/lemon: setPrimaryItem 0
07-08 17:06:50.297 E/lemon: finishUpdate
07-08 17:06:50.703 E/lemon: startUpdate
07-08 17:06:50.703 E/lemon: setPrimaryItem 0
07-08 17:06:50.703 E/lemon: finishUpdate
07-08 17:06:50.704 E/lemon: startUpdate
07-08 17:06:50.704 E/lemon: setPrimaryItem 0
07-08 17:06:50.704 E/lemon: finishUpdate
我滚动到位置 1 的日志:
07-08 17:09:41.154 E/lemon: startUpdate
07-08 17:09:41.154 E/lemon: setPrimaryItem 0
07-08 17:09:41.154 E/lemon: finishUpdate
07-08 17:09:41.966 E/lemon: startUpdate
07-08 17:09:41.966 E/lemon: instantiateItem 2
07-08 17:09:41.967 E/lemon: getItem 2
07-08 17:09:41.967 E/lemon: setUserVisibleHint 0 isVisibleToUser false
07-08 17:09:41.967 E/lemon: setPrimaryItem 1
07-08 17:09:41.967 E/lemon: setUserVisibleHint 0 isVisibleToUser false
07-08 17:09:41.967 E/lemon: setUserVisibleHint 1 isVisibleToUser true
07-08 17:09:41.967 E/lemon: finishUpdate
07-08 17:09:41.968 E/lemon: onAttach 2
07-08 17:09:41.968 E/lemon: onCreateView 2
07-08 17:09:41.971 E/lemon: onstart 2
07-08 17:09:41.971 E/lemon: startUpdate
07-08 17:09:41.971 E/lemon: setPrimaryItem 1
07-08 17:09:41.971 E/lemon: finishUpdate
07-08 17:09:41.972 E/lemon: startUpdate
07-08 17:09:41.972 E/lemon: setPrimaryItem 1
07-08 17:09:41.972 E/lemon: finishUpdate
我分析这些日志,我发现positino 0中的片段先调用setUserVisibleHint(true),然后调用onAttach(),但是位置1中的片段先调用onAttach(),然后调用setUserVisibleHint(true)。
所以我在 Fragment 中写了一个 onTrigger() 方法,在 onAttach() 和 setUserVisibleHint(true) 中调用,但失败了。然后我调试我的代码,它显示 onTrigger() 中的 isAdded() returns 在 onAttach().
中调用 onTrigger 时为 false这里有任何建议可以让我知道何时触发我的视频开始。非常感谢。
public class FullScreenVideoFragment extends Fragment {
private FragmentFullScreenVideoBinding binding;
int colorRes;
int position;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
Log.e("lemon", "onCreateView " + position);
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_full_screen_video, container, false);
setView();
return binding.getRoot();
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
public void setBgAndPosition(int position, int colorRes) {
this.position = position;
this.colorRes = colorRes;
}
@Override
public void onAttach(Context context) {
Log.e("lemon", "onAttach " + position);
super.onAttach(context);
onTriger();
}
@Override
public void onDetach() {
Log.e("lemon", "onDetach " + position);
super.onDetach();
}
@Override
public void onAttachFragment(Fragment childFragment) {
Log.e("lemon", "onAttachFragment " + position);
super.onAttachFragment(childFragment);
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
Log.e("lemon", "setUserVisibleHint " + position + " isVisibleToUser " + isVisibleToUser);
super.setUserVisibleHint(isVisibleToUser);
onTriger();
}
private void setView() {
binding.getRoot().setBackgroundResource(colorRes);
binding.position.setText(String.valueOf(position));
}
private void onTriger() {
if (!isVisible()) return;
binding.position.setText(position + " start");
}
}
public class VideoListAdapter extends FragmentStatePagerAdapter {
private LinkedList<FullScreenVideoFragment> fragmentCaches;
private int[] colors = new int[]{android.graphics.Color.RED, android.graphics.Color.BLUE, android.graphics.Color.GREEN};
public VideoListAdapter(FragmentManager fm) {
super(fm);
fragmentCaches = new LinkedList<>();
}
@Override
public Fragment getItem(int position) {
Log.e("lemon", "getItem " + position);
FullScreenVideoFragment fragment = generateItem();
return fragment;
}
@Override
public int getCount() {
return 10;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
Log.e("lemon", "destroyItem " + position);
super.destroyItem(container, position, object);
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
Log.e("lemon", "setPrimaryItem " + position);
super.setPrimaryItem(container, position, object);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Log.e("lemon", "instantiateItem " + position);
FullScreenVideoFragment fragment = (FullScreenVideoFragment) super.instantiateItem(container, position);
fragment.setBgAndPosition(position, colors[position % 3]);
return fragment;
}
@Override
public void startUpdate(ViewGroup container) {
Log.e("lemon", "startUpdate");
super.startUpdate(container);
}
@Override
public void finishUpdate(ViewGroup container) {
Log.e("lemon", "finishUpdate");
super.finishUpdate(container);
}
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
Log.e("lemon", "restoreState");
super.restoreState(state, loader);
}
@Override
public Parcelable saveState() {
Log.e("lemon", "saveState");
return super.saveState();
}
private FullScreenVideoFragment generateItem() {
FullScreenVideoFragment neededFragment = null;
if (!fragmentCaches.isEmpty()) {
neededFragment = fragmentCaches.get(0);
fragmentCaches.remove(0);
return neededFragment;
}
neededFragment = new FullScreenVideoFragment();
return neededFragment;
}
}
片段xml:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data></data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/position"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="@color/account_name_color"/>
</RelativeLayout>
</layout>
我发现Fragments经常使用这三种方式切换:
show/hide
attach/detach(替换)
ViewPager
这三种方式导致Fragment的可见状态存在一些差异,所以我定义如下类来区分这三种方式。如果一组Fragments都是用同样的方式切换,我觉得应该靠谱,如果用不同的方式切换,那我就不知道了,没有仔细测试。
import android.support.annotation.IntDef;
import android.support.v4.app.Fragment;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Created by Kilnn on 2017/7/12.
* A smart fragment know itself's visible state.
*/
public abstract class SmartFragment extends Fragment {
private boolean isFragmentVisible;
@Override
public void onResume() {
super.onResume();
int switchType = getSwitchType();
if (switchType == ATTACH_DETACH) {
notifyOnFragmentVisible();
} else if (switchType == SHOW_HIDE) {
if (!isHidden()) {
notifyOnFragmentVisible();
}
} else if (switchType == VIEW_PAGER) {
//If the parent fragment exist and hidden when activity destroy,
//when the activity restore, The parent Fragment will be restore to hidden state.
//And the sub Fragment which in ViewPager is also be restored, and the onResumed() method will callback.
//And The sub Fragment's getUserVisibleHint() method will return true if it is in active position.
//So we need to judge the parent Fragment visible state.
if (getUserVisibleHint() && isParentFragmentVisible()) {
notifyOnFragmentVisible();
}
}
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
int switchType = getSwitchType();
if (switchType == VIEW_PAGER) {
if (isVisibleToUser) {
notifyOnFragmentVisible();
} else {
notifyOnFragmentInvisible();
}
}
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
int switchType = getSwitchType();
if (switchType == SHOW_HIDE) {
if (hidden) {
notifyOnFragmentInvisible();
} else {
notifyOnFragmentVisible();
}
}
}
@Override
public void onPause() {
super.onPause();
notifyOnFragmentInvisible();
}
private boolean isParentFragmentVisible() {
Fragment parent = getParentFragment();
if (parent == null) return true;
if (parent instanceof SmartFragment) {
return ((SmartFragment) parent).isFragmentVisible();
} else {
//TODO May be can't get the correct visible state if parent Fragment is not SmartFragment
return parent.isVisible();
}
}
public boolean isFragmentVisible() {
// Don't judge the state of the parent fragment,
// because if the parent fragment visible state changes,
// you must take the initiative to change the state of the sub fragment
// return isFragmentVisible && isParentFragmentVisible();
return isFragmentVisible;
}
public void notifyOnFragmentVisible() {
if (!isFragmentVisible) {
onFragmentVisible();
isFragmentVisible = true;
}
}
public void notifyOnFragmentInvisible() {
if (isFragmentVisible) {
onFragmentInvisible();
isFragmentVisible = false;
}
}
/**
* If this method callback, the Fragment must be resumed.
*/
public void onFragmentVisible() {
}
/**
* If this method callback, the Fragment maybe is resumed or in onPause().
*/
public void onFragmentInvisible() {
}
/**
* Fragments switch with attach/detach(replace)
*/
public static final int ATTACH_DETACH = 0;
/**
* Fragments switch with show/hide
*/
public static final int SHOW_HIDE = 1;
/**
* Fragments manage by view pager
*/
public static final int VIEW_PAGER = 2;
@Retention(RetentionPolicy.SOURCE)
@IntDef({ATTACH_DETACH, SHOW_HIDE, VIEW_PAGER})
@interface SwitchType {
}
@SwitchType
public abstract int getSwitchType();
}
所以你可以在 onFragmentVisible() 中执行 onTrigger()。