How to Maintain Scroll position of recyclerView on Screen Rotation - android

I'm populating the recyclerView with gridlayoutManager. Now I want to save the scroll position on Screen Rotation.
I've tried to do so using onSaveInstanceState and onRestoreInstanceState() as shown in this post :
How to save RecyclerView's scroll position using RecyclerView.State?
Below is my code:
#Override
protected void onSaveInstanceState(Bundle outState) {
Log.e(TAG, "onSaveInstanceState");
super.onSaveInstanceState(outState);
outState.putParcelable(KEY_INSTANCE_STATE_RV_POSITION,
gridLayoutManager.onSaveInstanceState());
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
Log.e(TAG, "onRestoreInstanceState");
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState!= null){
Parcelable savedState =
savedInstanceState.getParcelable(KEY_INSTANCE_STATE_RV_POSITION);
movieAdapter.addAll(movieItemList);
if (savedState!= null){
gridLayoutManager.onRestoreInstanceState(savedState);
}
}
}
//this is my movieAdapter.addAll() method
public void addAll(List<MovieItem>items){
movieItems = items;
}
//This is the method to get lists of movies from ViewModel Class
private void loadFavMovies() {
FavViewModel favViewModel =
ViewModelProviders.of(MainActivity.this).get(FavViewModel.class);
favViewModel.getFavListLiveData().observe(MainActivity.this, new
Observer<List<FavlistItem>>() {
#Override
public void onChanged(List<FavlistItem> favlistItems) {
if (!favlistItems.isEmpty()) {
loadingIndicator.setVisibility(View.GONE);
movieRecycler.setVisibility(View.GONE);
favRecycler.setVisibility(View.VISIBLE);
favAdapter.setFavlistItems(favlistItems);
Toast.makeText(getApplicationContext(), "Swipe Left Or
Right To remove Item",Toast.LENGTH_SHORT).show();
}else {
loadingIndicator.setVisibility(View.GONE);
Toast.makeText(getApplicationContext(), "No Favorite
Movies",Toast.LENGTH_SHORT).show();
}
}
});
}
This is the link to GitHub for this project https://github.com/harshabhadra/Movies-Mela

Now,It has simple solution.use this dependency
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha05"
And just set the stateRestorationPolicy like below
myAdapter.stateRestorationPolicy=RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY

I know i am late but still if it helps!!
Storing the recycler view position is lot simpler than what other answers have made it look like
Here's how you can do it
First create a member variable
Parcelable state;
Now,
#Override
protected void onPause() {
super.onPause();
state = recyclerView.getLayoutManager().onSaveInstanceState();
}
#Override
protected void onResume() {
super.onResume();
recyclerView.getLayoutManager().onRestoreInstanceState(state);
}
overide on pause and on resume methods with the above code and you are good to go!!

One way to do that would be to stop re-creation of activity on orientation change. You can add that to the activity in the manifest to do so.
android:configChanges="orientation|screenSize"
But that isn't always a good practice, so another alternative would be to save the adapter position before exiting in onSaveInstanceState and then scroll to that position in onCreate using scrollToPosition. So, for example, you'd do something along the following lines.
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putInt("position", mRecyclerView.getAdapterPosition()); // get current recycle view position here.
//your other code
super.onSaveInstanceState(savedInstanceState);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//your other code
if(savedInstanceState != null){
// scroll to existing position which exist before rotation.
mRecyclerView.scrollToPosition(savedInstanceState.getInt("position"));
}
}

Scroll position should already be automatically saved if you are using GridLayoutManager, which subclasses LinearLayoutManager. You can take a look at SavedState inside LinearLayoutManager:
public static class SavedState implements Parcelable {
int mAnchorPosition;
int mAnchorOffset;
boolean mAnchorLayoutFromEnd;
That mAnchorPosition is effectively your scrolling position and it's saved during rotations. If that's not working for you, something else is likely wrong : you might be reloading / reapplying data incorrectly for example. That is probably the real question you need to be asking. You might need to cache that data somewhere so that you can automatically reapply it immediately after the configuration change.
I can also confirm for what its worth that on every project I've used LinearLayoutManager and GridLayoutManager on, scroll position is correctly maintained during rotations with no additional work on my part.

You should check out this SO post.
Basically, you want to create a new class extending RecyclerView (remember you will have to use your new class instead of RecyclerView) and override onSaveInstanceState() and onRestoreInstanceState(). In onSaveInstanceState() you will save the index of the first visible element and scroll to that element in onRestoreInstanceState().
The way they did it in the accepted answer of the linked post is different, but used LinearLayoutManager instead so I will tailor the code to use GridLayoutManager:
public class CustomRecyclerView extends RecyclerView {
private int mScrollPosition;
public VacationsRecyclerView(#NonNull Context context) {
super(context);
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
super.onRestoreInstanceState(state);
if(state != null && state instanceof SavedState){
mScrollPosition = ((SavedState) state).mScrollPosition;
LayoutManager layoutManager = getLayoutManager();
if(layoutManager != null){
int count = layoutManager.getItemCount();
if(mScrollPosition != RecyclerView.NO_POSITION && mScrollPosition < count){
layoutManager.scrollToPosition(mScrollPosition);
}
}
}
}
#Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
LayoutManager layoutManager = getLayoutManager();
if(layoutManager != null && layoutManager instanceof LinearLayoutManager){
mScrollPosition = ((GridLayoutManager) layoutManager).findFirstVisibleItemPosition();
}
SavedState newState = new SavedState(superState);
newState.mScrollPosition = mScrollPosition;
return newState;
}
static class SavedState extends android.view.View.BaseSavedState {
public int mScrollPosition;
SavedState(Parcel in) {
super(in);
mScrollPosition = in.readInt();
}
SavedState(Parcelable superState) {
super(superState);
}
#Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(mScrollPosition);
}
public static final Parcelable.Creator<SavedState> CREATOR
= new Parcelable.Creator<SavedState>() {
#Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
#Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
}

The easiest way to achieve what you're trying to get would be to use the RecyclerView within a Fragment instead of directly within an Activity.
Unlike activities, fragments by default don't get re-created on rotation, but preserve their state. This way, the recyclerview will always remain at the same scroll position.
I am using a RecyclerView this way in one of my apps, which displays lists with > 200 elements.

I've tried everything suggested by everyone but finally, this gist work for me
https://gist.github.com/FrantisekGazo/a9cc4e18cee42199a287
I just import this in my project and replace the recyclerView provided by default with this
"StateFulRecyclerView" and it solved my problem and handle the scroll position automatically during screen rotation or any configuration change. No need to use onSaveInstanceState to maintain scroll positon.

very simple. save the position on pause and restore is onresume.
ON pause
lastVisitPosition = ((LinearLayoutManager)recycleView.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
onResume
((LinearLayoutManager) recycleView.getLayoutManager()).scrollToPosition(lastVisitPosition);
or
((LinearLayoutManager) recycleView.getLayoutManager()).scrollToPositionWithOffset(lastVisitPosition,0)

One solution is by saving scroll positions of recycleview
in onSaveInstanceState() method of fragment you can save the scroll position of RecycleView
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
LinearLayoutManager layoutManager = (LinearLayoutManager)
recyclerView.getLayoutManager();
outState.putInt("scrolled_position",
layoutManager.findFirstCompletelyVisibleItemPosition());
}
then you can retrieve saved scroll position in onViewStateRestored() method
#Override
public void onViewStateRestored(#Nullable Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
if (savedInstanceState != null) {
int scrollPosition = savedInstanceState.getInt("scrolled_position");
recyclerView.scrollToPosition(scrollPosition);
}
}

Related

Does Android save and restore instance state of some field automatically?

I have a RecyclerView with MyItem as holding the data for each rows in the RecyclerView, and everything is properly implemented and working fine.
public class MyAdapter extends RecyclerView.Adapter<MyViewHolder>{
protected final List<MyItem> items= new ArrayList<>();
public MyAdapter (List<RVItem> list) {
if (list != null) {
items.addAll(list);
}
}
}
And the List items is passed from another object.
public class MyObject{
private String otherThing;
private List<MyItem> items;
public someMethodToInitItems() {
items = new ArrayList<>();
items.add(...);
items.add(...);
}
public createFragment() {
Fragment f = new MyCustomFragment();
f.setAdapter(new MyAdapter(items));
// replace commit fragment...
}
}
The problem here is when my Activity is restored, the field private List<MyItem> items in MyObject somehow magically get restored too, but field like otherThing is still null and not getting restored.
FYI, I did not explicitly save and restore items in any way. Also, onSaveInstanceState in MyCustomFragment is left untouched and not overridden from super.
Below is how I'm trying to save and restore the Fragment state from the Activity. That's it.
public class MyActivity{
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
super.onSaveInstanceState(outState);
getSupportFragmentManager().putFragment(outState, tag, getLastFragment());
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState != null) {
Fragment fg = getSupportFragmentManager().getFragment(savedInstanceState, tag);
}
}
}
So, I don't get it. Is Android system did this job? Save and restore the field items in MyObject automatically? Or there must be something else causing this behavior.
You need to override onRetainNonConfigurationInstance method. This method could helps you to store your data
More details here

Restore Recycler View state on device's rotation

I have RecyclerView which views are populated from the internet (page number is a parameter) and that is why I used the EndlessRecyclerViewScrollListener. The only problem is that when I rotate the device, it doesn't restore the same state.
This is my ScrollListener initiation in onCreate():
mScrollListener = new EndlessRecyclerViewScrollListener(mGridLayoutManager) {
#Override
public void onLoadMore(int page, int totalItemsCount, RecyclerView view) {
mPage++;
new TheMovieRequestTask().execute();
}
};
mRecyclerView.addOnScrollListener(mScrollListener);
onSaveInstanceState():
protected void onSaveInstanceState(Bundle outState) {
outState.putInt("mPageKey", mPage);
mListState = mRecyclerView.getLayoutManager().onSaveInstanceState();
outState.putParcelable("mListStateKey", mListState);
super.onSaveInstanceState(outState);
}
At the beginning of onCreate() I have:
if (savedInstanceState != null) {
mPage = savedInstanceState.getInt("mPageKey");
mBundleRecyclerViewState = new Bundle();
mListState = savedInstanceState.getParcelable("mListStateKey");
mBundleRecyclerViewState.putParcelable("mListStateBundleKey", mListState);
} else {
mPage = 1;
}
And when I load data from TheMovieRequestTask() (it's an AsyncTask), in onPostExecute() i have:
if (mBundleRecyclerViewState != null) {
Parcelable state = mBundleRecyclerViewState.getParcelable("mListStateBundleKey");
mRecyclerView.getLayoutManager().onRestoreInstanceState(state);
}
What Can I do? When I go back from another activity, it's perfect but on rotation, it just doesn't work.
This code loads e.g. 5th page and then properly restore it but then I can scroll only down (6,7,8,9... pages) but not up(not 4,3,2,1, pages).

Orientation: restore ListView position that is inside a Fragment

I went trough a famous post in SO, Maintain/Save restore scroll position
but does not help me at all.
I have a ListView inside a Fragment, if I change the orientation, I would like that saveInstance Bundle will save my position.
I have
private static final String LIST_STATE = "listState";
private Parcelable mListState = null;
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mListState = listView.onSaveInstanceState();
outState.putParcelable(LIST_STATE, mListState);
}
and does not matter if I put in OnCreateView or in onActivityCreated the following code
if(savedInstanceState!=null) {
mListState = savedInstanceState.getParcelable(LIST_STATE);
listView.onRestoreInstanceState(mListState);
the up position of the list is not restored at all.
I can easily see from the debug in Bundle that the debug recognize in the Bundle the position,
AbsListView.SavedState{3d7562e0 selectedId=-9223372036854775808
firstId=25 viewTop=-38 position=5 height=717 filter=null
checkState=null}
I even tried to extract from the Bundle this position in an isolated way instead of all the values, but without success.
add following code inside fragment
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
Found it!
The solution is
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mPosition2=listView.getFirstVisiblePosition();
if(mPosition2!=0) {
mPosition = mPosition2;
}
if (mPosition != ListView.INVALID_POSITION) {
outState.putInt(SELECTED_KEY, mPosition);
}
}
and in onCreateView
if (savedInstanceState != null && savedInstanceState.containsKey(SELECTED_KEY)) {
mPosition = savedInstanceState.getInt(SELECTED_KEY);
}
finally I have a loader that query a content provider at end of onLoadFinished( but you can put you just where you need)
if (mPosition != ListView.INVALID_POSITION) {
listView.setSelection(mPosition);
I have also tried listView.smoothScrollToPosition(mPosition)
but is not working at the moment, but never mind it works really well to me.

How to detect when a fragment appears on the screen?

How could some part of my code be aware of Fragment instance become visible on a screen?
The following snippet will explain my question.
public class MyApp extends Application {
public static final String TAG = MyApp.class.getSimpleName();
#Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
...
#Override
public void onActivityResumed(Activity activity) {
Log.d(TAG, activity.getClass().getSimpleName() + " is on screen");
}
#Override
public void onActivityStopped(Activity activity) {
Log.d(TAG, activity.getClass().getSimpleName() + " is NOT on screen");
}
...
});
}
Here i can track when any activity within my app appears on the screen. Is there any way to extend this approach on Fragments?
Something like
Activity.getFragmentManager().registerFragmentLifecycleCallbacks();
UPD. I know nothing about activities implementations, do they use fragments at all and how do they use them (injection via xml, ViewPager etc.) The only thing I have within my class is an application context. Let's assume Activity and Fragment implementations are black boxes and i am not able to make any changes.
In your fragment, override onHiddenChanged(...) method:
#Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (hidden) {
Log.d(TAG, ((Object) this).getClass().getSimpleName() + " is NOT on screen");
}
else
{
Log.d(TAG, ((Object) this).getClass().getSimpleName() + " is on screen");
}
}
Hope this work for you!
Without touching the Activity or Fragment code and assuming you don't know the tag or layout it is placed in, there is very little that you can do. The best that I can see is that you could get the FragmentManager in ActivityResumed and ActivityStopped callbacks (because here you have an Activity reference) and apply a BackstackChangedListener. This assumes that you use the backstack when changing between fragments.
The issue with what you are asking is that you want lifecycle callbacks for Fragments on the Application level when you have no control over the middle men, the Activities which are already starved for Fragment callbacks. They do most everything through their FragmentManager, and propagate their own lifecycle callbacks down to the Fragments so that the fragments will behave appropriately. The onResume and onPause callbacks in fragments only occur when they are first created or when the Activity experiences those callbacks. There is only one lifecycle callback for Fragments in Activities, onAttachFragment, which if you could override, would give you references to the Fragments that are attached to the Activity. But you said you can't change the Activity or the Fragment, and you want to know when the Fragments are shown.
So if you don't use the backstack, I don't think there's a way to do what you want.
For putting Fragments inside Activity i use SlidingTabLayout which Google uses. Inside it you have ViewPager and some Adapter to populate many Fragments. First of all you have to put this and this files in your project. Then here there is good tutorial for how you can implement SlidingTabLayout.
1) After you have implemented SlidingTabLayout in your Activity, you can detect when and which Fragment becomes visible from Activity:
mSlidingTabLayout.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
//Do nothing
}
#Override
public void onPageSelected(int position) {
if (position == 0) {
//Whenever first fragment is visible, do something
} else if (position == 1) {
//Whenever second fragment is visible, do something
} else if (position == 2) {
//Whenever third fragment is visible, do something
} else if (position == 3) {
//Whenever fourth fragment is visible, do something
}
}
#Override
public void onPageScrollStateChanged(int state) {
//Do nothing
}
});
2) You can detect if Fragment is visible from Fragment itself as i answered here, however this may get called before onCreateView() of Fragment, so check answer in the link:
#Override
public void setUserVisibleHint(boolean visible){
super.setUserVisibleHint(visible);
if (visible){
//when this Fragment is active, do something
}
}
3) You can change also change colors of indicators of each Tab like this from Activity:
mSlidingTabLayout.setCustomTabColorizer(new SlidingTabLayout.TabColorizer() {
#Override
public int getIndicatorColor(int position) {
if (position == 0) {
return getResources().getColor(R.color.orange);
} else if (position == 1) {
return getResources().getColor(R.color.redDimmed);
} else if (position == 2) {
return getResources().getColor(R.color.yellow);
} else if (position == 3) {
return getResources().getColor(R.color.green);
} else {
return getResources().getColor(R.color.redLight);
}
}
#Override
public int getDividerColor(int position) {
return getResources().getColor(R.color.defaultActionBarBg);
}
});
Use same way as activity
set flag in application class to check visiblity of fragment, use below code in fragment
#Override
public void onStart() {
super.onStart();
Log.e( "Fragment is visible", "Fragment is visible");
Application Class.isFragmentShow = true;
}
#Override
public void onPause() {
super.onPause();
Log.e("Fragment is not visible", "Fragment is not visible");
Application Class.isFragmentShow = false;
}
to communicate with fragment you have to call that activity in which fragment added then use below code
MainFragment fragment = (MainFragment) fragmentManager.findFragmentByTag("MainFragment");
fragment.setFilter();
Don't exist a default way to do, but you can make your own Callbacks, I made this and works fine, first need have a BaseFragment class where we'll handle all fragment events.
public class BaseFragment extends Fragment {
private String fragmentName;
private FragmentLifecycleCallbacks listener;
public void registerCallBacks(String fragmentName){
// handle the listener that implement 'MyApp' class
try{
listener = (FragmentLifecycleCallbacks) getActivity().getApplication();
} catch (ClassCastException e) {
throw new ClassCastException("Application class must implement FragmentLifecycleCallbacks");
}
// set the current fragment Name for the log
this.fragmentName = fragmentName;
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if(listener!=null) {
listener.onAttachFragment(fragmentName);
}
}
#Override
public void onResume() {
super.onResume();
if(listener!=null) {
listener.onResumeFragment(fragmentName);
}
}
#Override
public void onStop() {
super.onStop();
if(listener!=null) {
listener.onStopFragment(fragmentName);
}
}
// 'MyApp' class needs implement this interface to handle all the fragments events
public interface FragmentLifecycleCallbacks{
void onStopFragment(String fragmentName);
void onResumeFragment(String fragmentName);
void onAttachFragment(String fragmentName);
}}
On 'MyApp' class implement the interface of BaseFragment
public class MyApp extends Application implements BaseFragment.FragmentLifecycleCallbacks{
public static final String TAG = MyApp.class.getSimpleName();
#Override
public void onCreate() {
super.onCreate();
}
#Override
public void onStopFragment(String fragmentName) {
Log.d(TAG, fragmentName + " is NOT on screen");
}
#Override
public void onResumeFragment(String fragmentName) {
Log.d(TAG, fragmentName + " is on screen");
}
#Override
public void onAttachFragment(String fragmentName) {
Log.d(TAG, fragmentName + " is attached to screen");
}}
And now each Fragment that you have need extends 'BaseFragment' and register to the global listener
public class FragmentA extends BaseFragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_simple, container, false);
// here register to the global listener
registerCallBacks(FragmentA.class.getName());
return rootView;
}}
Hope this helps!
Intercept onWindowFocusChanged() in the activity and propagate that to the interested fragment.
Try this
private Boolean isFragmentVisible()
{
if(getFragmentManager().findFragmentByTag("TAG") != null && getFragmentManager().findFragmentByTag("TAG").isVisible())
{
//The fragment is visible
return true;
}
return false;
}
Alternative way
private Boolean isFragmentVisible()
{
return getFragmentManager().findFragmentByTag("TAG") != null && getFragmentManager().findFragmentByTag("TAG").isVisible();
}
You can know the following with the built in method called "onActivityCreated(Bundle)" this method tells that the fragment has been created thus you get to know that the fragment appears on the screen Click here for reference
Hope it helps
I've looked through what's available without using a base Fragment or Activity class but couldn't find any. I've made an implementation that provides basic (onAdded / onRemoved) functionality for all fragments in your application. It is certainly possible to extend it to report the current state of the fragment (onAttach, onResume, onPause, onDetach, ...).
You can find the code along with a sample here: https://github.com/Nillerr/FragmentLifecycleCallbacks
It works both for non-support library Fragments and support library Fragments through different implementations. The support library class is safer to use and should perform better, because the non-support one uses Reflection to access the fragments, while the support library FragmentManager includes a getFragments() method.
If you are setting a Fragment to your View, you probably have a container where it will be shown. Given that this container is, say, a FrameLayout with id R.id.container, you can do that:
Fragment f = fragmentManager.findFragmentById(R.id.container);
if (f instanceof YourFragment) {
// TODO something when YourFragment is ready
}
Does this interface provide anything helpful to you?
https://github.com/soarcn/AndroidLifecyle/blob/master/lifecycle/src/main/java/com/cocosw/lifecycle/FragmentLifecycleCallbacks.java
It sounds like your best bet if you can't override the Fragment's own onResume() method is to create your own interface that extends ActivityLifecycleCallbacks, then put your logging code in the onFragmentResumed(Fragment yourFragment) method.
You can get a pointer to the Fragment by doing something like this:
int yourFragmentId = 0; //assign your fragment's ID to this variable; Fragment yourFragment.getId();
FragmentManager fm = activity.getFragmentManager();
Fragment f = fm.findFragmentById(yourFragmentId);
whereever u want to check if fragment is visible or not.. just check isMenuVisible() value.
this is fragment's method which i used to check visible fragment when i have to fire some http request from viewpager selected Item.
hope this helps.
in my case i was using this method in onActivityCreated().
In you fragment override method setMenuVisibility If you are using ViewPager and are swiping from left and right, this method is called when the visivility of the fragment gets changed.
Here is a sample from my project
public abstract class DemosCommonFragment extends Fragment {
protected boolean isVisible;
public DemosCommonFragment() {
}
#Override
public void setMenuVisibility(boolean menuVisible) {
super.setMenuVisibility(menuVisible);
isVisible = menuVisible;
// !!! Do Something Here !!!
}
}
Animation listener
I have NOT checked all use cases and there is an unhandled exception. You can play around with it to fit your use case. Please feel free to comment your opinions or use cases it did not solve.
NOTE: You can add fragmentWillDisappear and fragmentDidDisappear by handling for enter in onCreateAnimation.
Parent Fragment:
public class BaseFragment extends Fragment {
private Animation.AnimationListener animationListener;
private void setAnimationListener(Animation.AnimationListener animationListener) {
this.animationListener = animationListener;
}
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
fragmentWillAppear(animation);
}
#Override
public void onAnimationEnd(Animation animation) {
fragmentDidAppear(animation);
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
}
#Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
AnimationSet animSet = new AnimationSet(true);
Animation anim = null;
try {
anim = AnimationUtils.loadAnimation(getActivity(), nextAnim);
} catch (Exception error) {
}
if (anim != null) {
anim.setAnimationListener(animationListener);
animSet.addAnimation(anim);
}
return animSet;
}
public void fragmentDidAppear(Animation animation) {
}
public void fragmentWillAppear(Animation animation) {
}
}
Child Fragment:
class ChildFragment extends BaseFragment {
#Override
public void fragmentDidAppear(Animation animation) {
super.fragmentDidAppear(animation);
}
#Override
public void fragmentWillAppear(Animation animation) {
super.fragmentWillAppear(animation);
}
}

ExpandableListView collapses all group items every time I come back from another Activity

I need someone to guide me to find a fault. When I receive a call or go to another activity, when i come back all the groups are collapsed. The system has not Destroyed the activity so I can not save and restore the state in the onRestoreInstanceState because the method is never called (the Activity is only paused and stopped, but is not destroyed).
The Activity extends ExpandableListActivity and the adapter I use is a SimpleCursorTreeAdapter.
In which cases the expandable list is collapsed? I'm going crazzy and I can't find out a solution.
Thanks in advance.
Edit: This is the lifecycle of my Activitywhen the Activivy Starts the first time: onCreate() -> onStart() -> onResume() when I go to other Activity:onPause() -> onStop() and when I come back: onRestart -> onStart() -> onResume()
Unfortunately the expanded state always resets whenever the focus is lost and onCreate() is not called when it gets the focus again but onStart(). So my workaround for now is to manually store the IDs of all expanded items and expand them in onStart() again. I implemented a subclass of ExpandableListActivity to reuse the behavior.
public class PersistentExpandableListActivity extends ExpandableListActivity {
private long[] expandedIds;
#Override
protected void onStart() {
super.onStart();
if (this.expandedIds != null) {
restoreExpandedState(expandedIds);
}
}
#Override
protected void onStop() {
super.onStop();
expandedIds = getExpandedIds();
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
this.expandedIds = getExpandedIds();
outState.putLongArray("ExpandedIds", this.expandedIds);
}
#Override
protected void onRestoreInstanceState(Bundle state) {
super.onRestoreInstanceState(state);
long[] expandedIds = state.getLongArray("ExpandedIds");
if (expandedIds != null) {
restoreExpandedState(expandedIds);
}
}
private long[] getExpandedIds() {
ExpandableListView list = getExpandableListView();
ExpandableListAdapter adapter = getExpandableListAdapter();
if (adapter != null) {
int length = adapter.getGroupCount();
ArrayList<Long> expandedIds = new ArrayList<Long>();
for(int i=0; i < length; i++) {
if(list.isGroupExpanded(i)) {
expandedIds.add(adapter.getGroupId(i));
}
}
return toLongArray(expandedIds);
} else {
return null;
}
}
private void restoreExpandedState(long[] expandedIds) {
this.expandedIds = expandedIds;
if (expandedIds != null) {
ExpandableListView list = getExpandableListView();
ExpandableListAdapter adapter = getExpandableListAdapter();
if (adapter != null) {
for (int i=0; i<adapter.getGroupCount(); i++) {
long id = adapter.getGroupId(i);
if (inArray(expandedIds, id)) list.expandGroup(i);
}
}
}
}
private static boolean inArray(long[] array, long element) {
for (long l : array) {
if (l == element) {
return true;
}
}
return false;
}
private static long[] toLongArray(List<Long> list) {
long[] ret = new long[list.size()];
int i = 0;
for (Long e : list)
ret[i++] = e.longValue();
return ret;
}
}
Maybe someone has a better solution though.
You can save any state information by overriding onPause() and onResume() in your Activity.
You should be able to persist the selected item and then reselect the item when the Activity is resumed. You could also add this in onStop() and onStart().
If the Activity is only being paused/stopped however all of the Views should retain their state as they aren't being destroyed and recreated. Try adding more logging to each of the "big" lifecycle methods:
protected void onCreate(Bundle savedInstanceState);
protected void onStart();
protected void onRestart();
protected void onResume();
protected void onPause();
protected void onStop();
protected void onDestroy();
Creating a logging activity which you extend from should be your first step when tracking down lifecycle inconsistencies.

Categories

Resources