问题
I'm using Xamarin Studio and developing a small test project for Android.
I have an Activity with three Tabs on it, each Tab have a different Fragment. So far I got the hang of how to add Tabs and event handlers.
But when I rotate the screen, the default Tab I set is selected which causes the Fragment assigned to that Tab to be displayed.
Another problem I face is that when I change Tabs, I want to preserve the state of the previous Tab, so when I select it again it won't be rendered again. For example, one of my Tabs is a GridView which loads remote images in its cells. When I switch Tabs I don't want for the images to be loaded again.
My main Activity looks like this:
public class MainActivity : Activity
{
private ActionBar.Tab UploadImageTab;
private ActionBar.Tab ImgurTwitterTab;
private ActionBar.Tab RecentImagesTab;
private int selected_tab;
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
if (bundle != null) {
selected_tab = bundle.GetInt ("selected_tab", 0);
Log.Debug (GetType ().FullName, "selected tab was " + selected_tab);
}
if (ActionBar != null) {
InitializeActionBar ();
}
SetContentView (Resource.Layout.Main);
}
protected override void OnSaveInstanceState (Bundle outState)
{
Log.Debug (GetType ().FullName, "Saving state tab selected " + selected_tab);
outState.PutInt ("selected_tab", selected_tab);
base.OnSaveInstanceState (outState);
}
protected void InitializeActionBar(){
ActionBar.NavigationMode = ActionBarNavigationMode.Tabs;
AddTab (UploadImageTab, Resources.GetString (Resource.String.upload_image), Resource.Drawable.ic_upload, new UploadImageFragment(), 1);
AddTab (ImgurTwitterTab, Resources.GetString (Resource.String.imgur_twitter), Resource.Drawable.ic_com, new ImgurOnTwitterFragment(), 2);
AddTab (RecentImagesTab, Resources.GetString (Resource.String.recent_images), Resource.Drawable.ic_gallery, new RecentImagesFragment(), 3);
if (selected_tab == 0) {
Log.Debug (GetType ().FullName, "No value found");
ActionBar.SelectTab (UploadImageTab);
} else {
if (selected_tab == 1) {
Log.Debug (GetType ().FullName, "Selecting tab 1");
ActionBar.SelectTab (UploadImageTab);
} else if (selected_tab == 2) {
Log.Debug (GetType ().FullName, "Selecting tab 2");
ActionBar.SelectTab (ImgurTwitterTab);
}else if(selected_tab == 3){
Log.Debug (GetType ().FullName, "Selecting tab 3");
ActionBar.SelectTab (RecentImagesTab);
}
}
}
protected void AddTab(ActionBar.Tab tab, string tabText, int iconResourceId, Fragment fragment, int index){
tab = ActionBar.NewTab ();
tab.SetText (tabText);
tab.SetIcon (iconResourceId);
tab.TabSelected += delegate(object sender, ActionBar.TabEventArgs e) {
e.FragmentTransaction.Replace(Resource.Id.fragmentContainer, fragment);
if(ActionBar.SelectedTab.Position == 0){
selected_tab = 1;
}else if(ActionBar.SelectedTab.Position == 1){
selected_tab = 2;
}else if(ActionBar.SelectedTab.Position == 2){
selected_tab = 3;
}
Log.Debug(GetType().FullName, "selection is " + selected_tab);
};
ActionBar.AddTab (tab);
}
}
For starters I tried to save the selected Tab. But when I rotate the device, for some reason the TabSelected event on the first Tab (UploadImageTab in this case) is fired, causing the saved value I had to be overwritten.
On the example for my Fragment with a GridView, my code is like this:
public class RecentImagesFragment : Fragment
{
private GridView collectionView;
public List<Photo> photos;
public static float DENSITY;
public override void OnCreate (Bundle savedInstanceState)
{
base.OnCreate (savedInstanceState);
}
public override View OnCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
Console.WriteLine ("is this called every time I switch tabs");
base.OnCreateView (inflater, container, savedInstanceState);
var view = inflater.Inflate (Resource.Layout.RecentImagesTab, container, false);
DENSITY = Activity.Resources.DisplayMetrics.Density;
collectionView = view.FindViewById<GridView> (Resource.Id.collectionView);
collectionView.ItemClick += ItemClick;
photos = new List<Photo> ();
MakeRequest ();
return view;
}
public void ItemClick(object sender, AdapterView.ItemClickEventArgs args){
Console.WriteLine ("photo selected " + photos [args.Position].OriginalUrl);
Intent intent = new Intent (this.Activity, typeof(PhotoDetail));
intent.PutExtra ("url", photos [args.Position].OriginalUrl);
StartActivity (intent);
}
public void MakeRequest(){
var request = (HttpWebRequest)WebRequest.Create("https://api.imgur.com/3/gallery/hot/viral/0.json");
request.Headers.Add ("Authorization", "Client-ID " + "XXXXXXXXXXX");
request.Method = "GET";
Task<WebResponse> task = Task.Factory.FromAsync (
request.BeginGetResponse,
asyncResult => request.EndGetResponse (asyncResult),
(object)null);
task.ContinueWith (t => ReadStreamFromResponse (t.Result));
}
private void ReadStreamFromResponse(WebResponse response){
using (Stream responseStream = response.GetResponseStream ()) {
using (StreamReader sr = new StreamReader (responseStream)) {
string content = sr.ReadToEnd ();
Console.WriteLine (content);
try{
var json = JsonObject.Parse (content);
var array = json ["data"];
foreach (JsonObject o in array) {
string url = o ["link"];
bool isAlbum = o ["is_album"];
if (!isAlbum) {
var short_url = url.Insert (url.Length - 4, "s");
photos.Add (new Photo{ OriginalUrl = url, SmallThumbUrl = short_url });
}
}
} catch(Exception ex){
Console.WriteLine ("Error: " + ex.Message);
}
if (photos.Count > 0) {
Activity.RunOnUiThread (() => {
collectionView.Adapter = new ImageAdapter (this.Activity, photos);
});
}
}
}
}
}
When the view is created I make a HTTP request to Imgur for the latest images url, then I assign the List of Photo objects I create to my ImageAdapter that will download/render them. But these objects are lost when I switch Tabs.
How can I make sure I save the state of my Fragments? And how do I save the state of my Fragment's GridView adapter?
回答1:
I was able to find an basic example here which helped me dealing with the situation I'm facing. I made the following changes to my code (comments will explain the functionality):
MainActivity.cs
public class MainActivity : Activity
{
private ActionBar.Tab UploadImageTab;
private ActionBar.Tab ImgurTwitterTab;
private ActionBar.Tab RecentImagesTab;
private int selected_tab;
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
// Set our view from the "main" layout resource
SetContentView (Resource.Layout.Main);
// Initialize Action Bar
InitializeActionBar ();
// Check if bundle is different from null, then load saved state and set selected tab
if (bundle != null) {
selected_tab = bundle.GetInt ("selected_tab", 0);
ActionBar.SetSelectedNavigationItem (selected_tab);
Log.Debug (GetType ().FullName, "selected tab was " + selected_tab);
}
}
// Save the selected tab
protected override void OnSaveInstanceState (Bundle outState)
{
Log.Debug (GetType ().FullName, "Saving state tab selected " + this.ActionBar.SelectedNavigationIndex);
outState.PutInt ("selected_tab", this.ActionBar.SelectedNavigationIndex);
base.OnSaveInstanceState (outState);
}
// Initialize Action Bar
protected void InitializeActionBar(){
ActionBar.NavigationMode = ActionBarNavigationMode.Tabs;
// First big change
// Pass to AddTab method a tab instace, tab text, icon and a tag
AddTab<UploadImageFragment> (UploadImageTab, Resources.GetString (Resource.String.upload_image), Resource.Drawable.ic_upload, "upload");
AddTab<ImgurOnTwitterFragment> (ImgurTwitterTab, Resources.GetString (Resource.String.imgur_twitter), Resource.Drawable.ic_com, "tweets");
AddTab<RecentImagesFragment> (RecentImagesTab, Resources.GetString (Resource.String.recent_images), Resource.Drawable.ic_gallery, "recent");
}
// AddTab now handles generic types that inherit from Fragment
protected void AddTab<T> (ActionBar.Tab tab, string tabText, int iconResourceId, string tag) where T : Fragment{
tab = ActionBar.NewTab ();
tab.SetText (tabText);
tab.SetIcon (iconResourceId);
// tag will help us id this tab
tab.SetTag (tag);
// Get instance of Fragment if it exists
T existing = (T)FragmentManager.FindFragmentByTag (tag);
// Set listener for tab
tab.SetTabListener(new ActivityTabListener<T>(this, tag, existing));
ActionBar.AddTab (tab);
}
}
ActivityTabListener.cs
// Tab listener for generic type that inherits from Fragment
public class ActivityTabListener<T> : Java.Lang.Object, ActionBar.ITabListener where T : Fragment{
// Instance of current context
private Activity context;
// Reference to fragment to be displayed
private Fragment fragment;
// Name of Fragment class
private string fragmentName;
// Tag for tab
private string tag;
// Base constructor requires an Activity instance
public ActivityTabListener(Activity context){
this.context = context;
this.fragmentName = typeof(T).Namespace.ToLower() + "." + typeof(T).Name;
}
// Second constructor receives context, tag and existing fragment instance if available
public ActivityTabListener(Activity context, string tag, T existingFragment = null) : this(context){
this.fragment = existingFragment;
this.tag = tag;
}
// if fragment instance is null then create instance from generic type
// else just attach the fragment
public void OnTabSelected(ActionBar.Tab tab, FragmentTransaction ft){
if (fragment == null) {
fragment = (T)global::Android.App.Fragment.Instantiate (this.context, this.fragmentName);
// if there's a tag then add the fragment to its container and tag it
// else just fragment
if (this.tag != null) {
ft.Add (Resource.Id.fragmentContainer, fragment, tag);
} else {
ft.Add (Resource.Id.fragmentContainer, fragment);
}
} else {
ft.Attach (fragment);
}
}
// if fragment is not null then detach it
public void OnTabUnselected(ActionBar.Tab tab, FragmentTransaction ft){
if (fragment != null) {
ft.Detach (fragment);
}
}
public void OnTabReselected(ActionBar.Tab tab, FragmentTransaction ft){
}
// if disposing the dispose of fragment
protected override void Dispose (bool disposing)
{
if (disposing)
this.fragment.Dispose ();
base.Dispose (disposing);
}
}
These are the important parts for making sure the state of each Fragment on the Activity is persistent when making a configuration change (changing tab, changing orientation, etc).
Now you only need for each Fragment subclass you create to retain their instance and whatever parameters you were using (a list filled by a HTTP request, an adapter, etc) to be reassigned to where they belong (DON'T REINITIALIZE YOUR VARIABLES OR YOU WON'T RETAIN THE SAME VALUES).
Each Fragment subclass must have the following on its OnCreate method:
public override void OnCreate (Bundle savedInstanceState)
{
base.OnCreate (savedInstanceState);
// Don't call this method again
RetainInstance = true;
// whatever code you need on its first creation
}
Then you need to make sure your OnCreateView handles the logic to display the view with the data you want, for example if you have a fragment with a List View then you'd be wanting to have a reference to its adapter and its content, then when the view its being created check if any of those is null, if it is then you need to follow your logic to initialize them, else reassign them to the view that will be displayed:
public override View OnCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
base.OnCreateView (inflater, container, savedInstanceState);
var view = inflater.Inflate (Resource.Layout.some_layout, container, false);
some_list_view = view.FindViewById<ListView> (Resource.Id.some_list_view);
// since the state of this object is retained then check if the list that holds the objects for the list view is not null
// else then just reassing the adapter to the list view
if (some_list == null) {
some_list = new List<SomeObject> ();
// make a HTTP request, load images, create adapter, etc
} else {
some_list_view.Adapter = someAdapter;
}
return view;
}
With this you can avoid your fragments from losing their state when you change tabs or change orientation.
回答2:
In Fragment Tab:
`@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt('tabSelected', viewPager.getCurrentItem());
}`
Inside onCreateView insert: tabSelected=savedInstanceState.getInt("tabSelected", 0);
` @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_config_assoc_tab, container, false);
viewPager = rootView.findViewById(R.id.viewpager_config);
viewPager.setPagingEnabled(false);
viewPager.setOffscreenPageLimit(0);
**if (savedInstanceState != null) {
tabSelected=savedInstanceState.getInt("tabSelected", 0);
}**
....
....
`
Now after rotating you will be tabSelected to the tab position. tabSelected is a global var.
来源:https://stackoverflow.com/questions/26170422/how-to-save-selected-tab-when-rotating-screen-changing-tab-on-xamarin-android