Reputation: 226
I want to create a video list using ViewPager. I must know when pager item visible, and invisible.
I use VideoListAdapter extending FragmentStatePagerAdapter for ViewPager.
I use Fragment method setUserVisibleHint to trigger video start or pause.
But there is a problem, the Fragment in the position 0 of ViewPager throw a NullPointerException. And then i print log for releated method of Fragment.
The log i come into 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
The log i scroll to position 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
I analyse these logs, I find that fragment in positino 0 invokes setUserVisibleHint(true) first and then invokes onAttach(), but fragment in position 1 invokes onAttach() first and setUserVisibleHint(true) after.
So I write a method onTrigger() in Fragment called in both onAttach() and setUserVisibleHint(true), but fail. I then debug my code, it's show that isAdded() in onTrigger() returns false when invoke onTrigger in onAttach().
So any suggestions here to let me know when to trigger my video start. Thanks a lot.
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;
}
}
The fragment 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>
Upvotes: 0
Views: 2234
Reputation: 107
I found that Fragments often use these three ways to switch:
show/hide
attach/detach(replace)
ViewPager
These three ways lead to Fragment visible state there are some differences, so I defined the following classes to distinguish between these three ways. If a group of Fragments are using the same way to switch, I think it should be reliable, if the switch in a different way, then I do not know, no careful test.
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();
}
So you can do onTrigger() in onFragmentVisible().
Upvotes: 1