ViewPager, lines drawn with canvas.drawPath disappear when switching the fragment - android

I made a ViewPager layout and 3 layouts for the fragments, every fragment got also an Activity, first_fragment.java, second, third. The first fragment contains a draw area, in which you can draw lines. The second contains buttons, for choosing color and stroke width. The third contains a statistic for used colors and widths.
The Problem is now, if I swipe from the first to the second and back the drawn lines are still there, but if I switch from, first to second to third, and then back, the drawn lines disappear. The second and third fragments are completly equal at the moment, So why do the lines disappear one time and the other not?
I used a setOffscreenPageLimit and set the limit to 2. In general the limit gives you the count of pages you can swipe to the left and right without recreating the fragments! So in my case 2 to the left and 2 to the right!
ViewPager pager = (ViewPager) findViewById(R.id.viewPager);
pager.setOffscreenPageLimit(2);

Lines dissapear because Android system unloads fragments from memory in ViewPager.
You have 2 solutions:
ask ViewPager doesn't destroy a fragment:
ViewPager pager = (ViewPager) findViewById(R.id.viewPager);
pager.setOffscreenPageLimit(2);
use onSaveInstanceState() callback for saving fragment state and recreate fragment in onCreate(Bundle state)
public class MyFragment extends Fragment {
private static final String EXTRA_PARAM = "my_custom_param1";
private static final String EXTRA_PARAM2 = "my_custom_param2";
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (savedInstanceState != null)
{
//restore state
Log.d("restore_state", savedInstanceState.getString(EXTRA_PARAM));
}
return content;
}
#Override
public void onSaveInstanceState(Bundle state) {
super.onSaveInstanceState(state);
state.putString(EXTRA_PARAM, "whatever you want");
state.putInt(EXTRA_PARAM2, 12345);
}
}
The 2nd solution is preferable

My solution is :
adding this : mPath.reset();
after canvas.drawPath(,)

Related

ViewPager/FragmentPageAdapter, after recreation, does not call onCreate() of last selected view

I am currently working on an application with ActionBar tabs inside one of its fragments (main navigation is NavigationDrawer). The fragment's onCreate():
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
actionBar = activity.getActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
Resources r = getResources();
//(…) tabNames initialization
adapter = new CustomTabAdapter(getFragmentManager());
}
and onCreateView():
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment, container, false);
viewPager = (ViewPager) rootView.findViewById(R.id.pager);
viewPager.setAdapter(adapter);
viewPager.addOnPageChangeListener(this);
for (String tabName : tabNames) {
actionBar.addTab(actionBar.newTab()
.setText(tabName)
.setTabListener(this));
}
return rootView;
}
There are 4 tabs, each of them with its own Fragment and layout. CustomTabAdapter returns new instance of proper fragment in its getItem(int). There are 4 tabs.
And now my problem is:
I start the application.
I choose this tab fragment from NavigationDrawer list. Everything is working just fine.
I choose another fragment from NavigationDrawer list which means that tab fragment is replaced with it.
I choose tab fragment again. And fragment related to tab that was selected before choosing another fragment from NavigationDrawer list, and one's adjacent to it are not recreated (blank screen under ActionBar tabs). I checked and onCreateView(…) methods of those fragment are not called. So after changing device orientation, or choosing tab not adjacent and this one again proper layout is shown.
How can I make it work as it should (showing proper layout on reentering tab fragment from NavigationDrawer list instead of blank space)? I run out of ideas.
Finally, I found a solution. My CustomTabAdapter was extension of FragmentPagerAdapter. I changed it to be extension of FragmentStatePageAdapter, and now fragments are recreated.
More details in this answer by #Louth.
It takes fragments from cache without recreation

Restarting ViewPager + FragmentStatePagerAdapter to item 0

Using SDK 19, min 13, and support.v4.app Fragments.
I have searched SO and found similiar threads which should help, but everything I have tried has not addressed my issue yet. Furthermore, many people seem to have the issue of the ViewPager restarting when they don't want it to, whereas my problem seems to be the opposite.
These posts do not seem to have helped me yet:
PagerAdapter start position
ViewPager PagerAdapter not updating the View
How to force ViewPager to re-instantiate its items
Here is my setup:
A (support.v4) Fragment in memory, which contains a ViewPager, inflated from a layout file, thus not added programmatically. The Fragment itself is not recreated, but in memory for the life of the app; it is being attached/detached to the root FragmentActivity's FragmentManager.
When the user visits this Fragment sometime during the app's lifetime, it is attached and the onCreateView is called, where I findViewById the ViewPager and assign a new FragmentStatePagerAdapter to it.
Here is my problem:
The first time the user visits this pager, everything works fine. The next time they visit it, I expect it to "start over" from page 0, meaning that I expect to not be able to scroll left immediately, but must start scrolling right. I also expect the getItem() of the FragmentStatePagerAdapter to start back at position 0. I basically want it to function the same as the first time the user visited it. However, this is not the case.
I have tried several things, but it always seems to start at the previous index of page I left it at. So if the first time I scrolled 5 pages over, and then left the Fragment and returned later, the ViewPager starts at that same page index, meaning I can scroll 5 pages left. I don't want this.
It may have something to do with the ViewPager or FragmentStatePagerAdapter storing pages internally, which are not being released. But I thought the commented out code I tried would have done that. There might be another way that I have not tried. Perhaps it is related to not recreating my Fragment, but doing so at this point in my architecture will not be great. I was hoping I could restart the ViewPager without doing this.
Here is my code:
My Fragment's onCreateView code. Everything commented out I have tried without success.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_playscreen, container, false);
//while (getChildFragmentManager().popBackStackImmediate()) {}
mAdapter = new AdapterPlayPages(this, getChildFragmentManager());
mPager = (ViewPlayPager) view.findViewById(R.id.pagerPlayScreen);
//mPager.setAdapter(null);
mPager.storeAdapter(mAdapter);
//mPager.setCurrentItem(0);
//mPager.removeAllViews();
return view;
}
My ViewPager, which has been overridden like so to get around a completely unrelated bug, as suggested here: https://stackoverflow.com/a/19900206/1002098. This does not effect the problem; I removed these changes so that I could call setAdapter(null) as suggested, but it did not address my question.
public class ViewPlayPager extends ViewPager
{
PagerAdapter mPagerAdapter;
public ViewPlayPager(Context context)
{
super(context);
}
public ViewPlayPager(Context context, AttributeSet attrs)
{
super(context, attrs);
}
#Override
protected void onAttachedToWindow()
{
super.onAttachedToWindow();
if (mPagerAdapter != null)
{
//super.setAdapter(null); // did not help
super.setAdapter(mPagerAdapter);
}
}
#Override
public void setAdapter(PagerAdapter adapter)
{
// do nothing
}
public void storeAdapter(PagerAdapter adapter)
{
mPagerAdapter = adapter;
}
}
My FragmentStatePagerAdapter. I've removed some unrelated logic and members.
public class AdapterPlayPages extends FragmentStatePagerAdapter
{
private FragmentPlayScreen mParent;
public AdapterPlayPages(FragmentPlayScreen parent, FragmentManager fragmentManager)
{
super(fragmentManager);
mParent = parent;
}
#Override
public int getCount()
{
// some logic from parent to determine true count
// the count shouldn't effect the position to start at
return Integer.MAX_VALUE;
}
#Override
public Fragment getItem(int position)
{
// logic to determine type of page to display,
// which is not changing based on position at the moment
return new FragmentPlayPage();
}
// this did not seem to help me
//#Override
//public int getItemPosition(Object object)
//{
// //return super.getItemPosition(object);
// return POSITION_NONE;
//}
}
I had to change my architecture to recreate the Fragments - add/remove them from FragmentManager, as opposed to attach/detach them while kept in memory.
It is not clear to me why the Fragment would have to be recreated for the ViewPager to reset. Android FragmentManager and FragmentTransaction supports keeping Fragments in memory and simply attaching/detaching them, or showing/hiding them, so it should be possible to simply reset the ViewPager from the in-memory Fragment's onCreateView event, but again, none of the commented out calls above seemed to work.
If anyone else has a solution, I'd still consider it. It will help me understand this issue.

How do I only load one tab into memory at a time in Android?

I'm creating an application with a CustomPagerAdapter that can be controlled by ActionBar tabs or horizontal swipe. When you select a tab, a fragment corresponding to that tab is displayed on the screen. When the app is created and when any tab is selected, the adjacent tabs, fragments are loaded into memory. I do not want this to happen. I would like it so that when a tab is selected only that selected tab's fragment is loaded into memory. Is there a way to do this?
Edit: The code I'm currently having trouble with is as follows:
public class fragA extende Fragment
{
private VideoView videoViewA;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View rootView = inflater.inflate(R.layout.fragment_A, container, false);
videoViewA = (VideoView) rootView.findViewById(R.id.videoViewA);
return rootView;
}
#Override
public void setUserVisibleHint(final boolean isVisibleToUser)
{
super.setUserVisibleHint(isVisibleToUser)
if (isVisibleToUser)
{
videoViewA.setVideoURI(LINK);
videoViewA.start();
}
else
{
videoViewA.stopPlayback();
}
}
}
The error I'm receiving is at the videoViewA.setVideoURI(LINK); line. Mind you, the link is actually there, but for privacy reasons I cannot post it.
Edit 2: It's ajava.lang.NullPointerException.
Edit 3: Sorry, but I'm doing this all the hard way. The code now reflects what I have actually written.
Try loading your videos within setUserVisbleHint(), which gets fired by the FragmentPageAdapter upon showing the fragment.
http://developer.android.com/reference/android/support/v4/app/Fragment.html#setUserVisibleHint(boolean)
If that doesn't work for you, you can also try to do check onHiddenChanged(boolean hidden).
http://developer.android.com/reference/android/app/Fragment.html#onHiddenChanged(boolean)

Fragment view in ViewPager is not restored when resuming

I have ActionBar Tabs setup. It consists of 4 tabs. Everything is fine until I navigate away from TabbedFragment and returning back.
I create tabs like this:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final ActionBar actionBar = getActionBar();
tabs = Lists.newArrayList();
tabs.add(new TabDefinition<>("Tab 1"));
tabs.add(new TabDefinition<>("Tab 2"));
tabs.add(new TabDefinition<>("Tab 3"));
tabs.add(new TabDefinition<>("Tab 4"));
for (TabDefinition tab : tabs) {
actionBar.addTab(actionBar.newTab()
.setText(tab.text)
.setTag(tab.tag)
.setTabListener(this));
}
}
And initialize adapter like this:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.paging_tab_container, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
viewPager = (ViewPager) view.findViewById(R.id.pager);
viewPager.setAdapter(new FragmentStatePagerAdapter(getFragmentManager()) {
#Override
public Fragment getItem(int position) {
return tabs.get(position).fragment;
}
#Override
public int getCount() {
return tabs.size();
}
});
viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
getActionBar().setSelectedNavigationItem(position);
}
});
viewPager.setCurrentItem(getActionBar().getSelectedNavigationIndex(), true);
}
When returning back to TabbedFragment selected tab and 1 next to it would not have any content. Just empty view. But if I select current + 2 fragment content is loaded. And then returning to that first fragment content is reloaded.
For example I have A, B, C, D tabs. Before leaving TabbedFragment I had selected tab A.
When returning to TabbedFragment I still am at tab A, but it's empty. So is tab B.
But when selecting tab C it is created and loaded. Returning to tab A it is recreated.
What could be the problem here?
After a while ran into the same problem again, so updating this question.
If you're using FragmentStatePagerAdapter you should provide FragmentManager via getChildFragmentManager() instead of getFragmentManager(). See Issue 55068: ViewPager doesn't refresh child fragments when back navigation via backstack
Okay so When using a FragmentStatePagerAdapter your fragments will be destroyed when you navigate anymore than one fragment Away since by default offScreenPageLimit is set to 1 by default just as mentioned above.
Typically this Class is used for an activity that has a very large set of Fragments, i.e have to scroll through a large amount of views. If your application does not need more than say 3-4 tabs I would suggest using FragmentPagerAdapter instead, and then specifying your offScreenPageLimit to something like 3, so if you get to the 4th Tab, all 3 tabs before will still be in memory.
Here is some Sample Code for a project on github that i created illustrating how to dynamically load the fragments if you don't want to add this offScreenPageLimit.
https://github.com/lt-tibs1984/InterfaceDemo/blob/master/src/com/divshark/interfacedemo/InterfaceDemoMain.java
Walk through all this code in this Class, and you will see how I'm dynamically loading the fragments, each time my ViewPager is slid over. Most notably at the bottom.
You can download this code, and use it as a test base for what you want to do.
Try adding the setOffScreenPageLimit(2) in the onCreate() method for the viewPager and notice the different behavior. To check the behavior, edit the text in fragment 1. Navigate Away and navigate back, with this set or not. You will see when it is set, the fragment's text remains what you change it to, since the fragment is never recreated.
Please provide additional questions if you have them.
GoodLuck
UPDATE
private static final String [] fragmentClasses = {"com.example.project.YourFragment1","com.example.project.YourFragment2","com.example.project.YourFragment3"};
viewPager.setAdapter(new FragmentStatePagerAdapter(getFragmentManager()) {
#Override
public Fragment getItem(int position) {
Fragment fragmentAtPosition = null;
// $$$$ This is the Important Part $$$$$
// Check to make sure that your array is not null, size is greater than 0 , current position is greater than equal to 0, and position is less than length
if((fragmentClasses != null) && (fragmentClasses.length > 0)&&(position >= 0)&& (position < fragmentClasses.length))
{
// Instantiate the Fragment at the current position of the Adapter
fragmentAtPosition = Fragment.instantiate(getBaseContext(), fragmentClasses[position]);
fragmentAtPosition.setRetainInstance(true);
}
return fragmentAtPosition;
}
#Override
public int getCount() {
return fragmentClasses.length;
}
});
The problem exists in the Fragments you use as tabs, I think. They seem to not show anything when they are resumed (see Fragment lifecycle). The "weird" issue that only the currently selected +/-1 tab is empty, is because the offScreenPageLimit of your ViewPager is 1 by default. All tabs above this threshold are re-created.
Therefore, increasing the value will -- in your case -- cause all your tabs to appear empty after resuming. Check in your Fragment code which lifecycle methods you use to inflate your layout, set adapters and so forth, because that's what's causing your trouble.
I guess this happens because while loading fragment android loads current and current+1, if you debug you would not see onPause getting called for the immediate next fragment.
You can reload content programmatically in onTabChanged() method of TabHost.OnTabChangeListener.
After doing much research, this worked for me.
I have a complex layout with 3 tabs in a fragment, that gets switched out for other fragments. I realized that the ViewpagerAdapter will retain state, even if you press the home button. My problem was switching back and forth would null out the child fragment UI view elements and crash. The key is to not new out your ViewPagerAdapter. Adding the null check for the Adapter worked for me. Also, be sure to allocate setOffscreenPageLimit() for your needs. Also, from what I understand setRetainInstance(true); should not be used for fragments that have UI, it is designed for headless fragments.
In the fragment that holds your Tabs:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_tab, container, false);
tabLayout = (TabLayout) view.findViewById(R.id.tablayout);
viewPager = (ViewPager) view.findViewById(R.id.viewPager);
//Important!!! Do not fire the existing adapter!!
if (viewPagerAdapter == null) {
viewPagerAdapter = new ViewPagerAdapter(getChildFragmentManager());
viewPagerAdapter.addFragments(new AFragment(), "A");
viewPagerAdapter.addFragments(new BFragment(), "B");
viewPagerAdapter.addFragments(new CFragment(), "C");
}
//Allocate retention buffers for three tabs, mandatory
viewPager.setOffscreenPageLimit(3);
tabLayout.setupWithViewPager(viewPager);
viewPager.setAdapter(viewPagerAdapter);
return view;
}
Or more simply when navigating back to tabbedfragment (assuming you use an intent and the fragment is within an activity) use:
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
This keeps the original activity and moves it to the top of the stack rather than recreating it, thus you never need to recreate the viewPager.

Android FragmentTab host and Fragments inside Fragments

I have an app with hierarchy like this:
FragmentTabHost (Main Activity)
- Fragment (tab 1 content - splitter view)
- Fragment (lhs, list)
- Framment (rhs, content view)
- Fragment (tab 2 content)
- Fragment (tab 2 content)
All fragment views are being inflated from resources.
When the app starts everything appears and looks fine. When I switch from the first tab to another tab and back again I get inflate exceptions trying to recreate tab 1's views.
Digging a little deeper, this is what's happening:
On the first load, inflating the splitter view causes its two child fragments to be added to the fragment manager.
On switching away from the first tab, it's view is destroyed but it's child fragments are left in the fragment manager
On switching back to the first tab, the view is re-inflated and since the old child fragments are still in the fragment manager an exception is thrown when the new child fragments are instantiated (by inflation)
I've worked around this by removing the child fragments from the fragment manager (I'm using Mono) and now I can switch tabs without the exception.
public override void OnDestroyView()
{
var ft = FragmentManager.BeginTransaction();
ft.Remove(FragmentManager.FindFragmentById(Resource.Id.ListFragment));
ft.Remove(FragmentManager.FindFragmentById(Resource.Id.ContentFragment));
ft.Commit();
base.OnDestroyView();
}
So I have a few questions:
Is the above the correct way to do this?
If not, how should I be doing it?
Either way, how does saving instance state tie into all of this so that I don't lose view state when switching tabs?
I'm not sure how to do this in Mono, but to add child fragments to another fragment, you can't use the FragmentManager of the Activity. Instead, you have to use the ChildFragmentManager of the hosting Fragment:
http://developer.android.com/reference/android/app/Fragment.html#getChildFragmentManager()
http://developer.android.com/reference/android/support/v4/app/Fragment.html#getChildFragmentManager()
The main FragmentManager of the Activity handles your tabs.
The ChildFragmentManager of tab1 handles the split views.
OK, I finally figured this out:
As suggested above, first I changed the fragment creation to be done programatically and had them added to the child fragment manager, like so:
public override View OnCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle savedInstance)
{
var view = inflater.Inflate(Resource.Layout.MyView, viewGroup, false);
// Add fragments to the child fragment manager
// DONT DO THIS, SEE BELOW
var tx = ChildFragmentManager.BeginTransaction();
tx.Add(Resource.Id.lhs_fragment_frame, new LhsFragment());
tx.Add(Resource.Id.rhs_fragment_frame, new RhsFragment());
tx.Commit();
return view;
}
As expected, each time I switch tabs, an extra instance of Lhs/RhsFragment would be created, but I noticed that the old Lhs/RhsFragment's OnCreateView would also get called. So after each tab switch, there would be one more call to OnCreateView. Switch tabs 10 times = 11 calls to OnCreateView. This is obviously wrong.
Looking at the source code for FragmentTabHost, I can see that it simply detaches and re-attaches the tab's content fragment when switching tabs. It seems the parent Fragment's ChildFragmentManager is keeping the child fragments around and automatically recreating their views when the parent fragment is re-attached.
So, I moved the creation of fragments to OnCreate, and only if we're not loading from saved state:
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
if (savedInstanceState == null)
{
var tx = ChildFragmentManager.BeginTransaction();
tx.Add(Resource.Id.lhs_fragment_frame, new LhsFragment());
tx.Add(Resource.Id.rhs_fragment_frame, new RhsFragment());
tx.Commit();
}
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle savedInstance)
{
// Don't instatiate child fragments here
return inflater.Inflate(Resource.Layout.MyView, viewGroup, false);
}
This fixed the creation of the additional views and switching tab's basically worked now.
The next question was saving and restoring view state. In the child fragments I need to save and restore the currently selected item. Originally I had something like this (this is the child fragment's OnCreateView)
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance)
{
var view = inflater.Inflate(Resource.Layout.CentresList, container, false);
// ... other code ommitted ...
// DONT DO THIS, SEE BELOW
if (savedInstance != null)
{
// Restore selection
_selection = savedInstance.GetString(KEY_SELECTION);
}
else
{
// Select first item
_selection =_items[0];
}
return view;
}
The problem with this is that the tab host doesn't call OnSaveInstanceState when switching tabs. Rather the child fragment is kept alive and it's _selection variable can be just left alone.
So I moved the code to manage selection to OnCreate:
public override void OnCreate(Bundle savedInstance)
{
base.OnCreate(savedInstance);
if (savedInstance != null)
{
// Restore Selection
_selection = savedInstance.GetString(BK_SELECTION);
}
else
{
// Select first item
_selection = _items[0];
}
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance)
{
// Don't restore/init _selection here
return inflater.Inflate(Resource.Layout.CentresList, container, false);
}
Now it all seems to be working perfectly, both when switching tabs and changing orientation.

Categories

Resources