我正在使用Xamarin Studio,并为Android开发一个小型测试项目。

我有一个带有三个选项卡的活动,每个选项卡都有一个不同的片段。到目前为止,我对如何添加选项卡和事件处理程序一无所知。

但是,当我旋转屏幕时,将选择我设置的默认选项卡,这将导致显示分配给该选项卡的片段。

我面临的另一个问题是,当我更改选项卡时,我想保留前一个选项卡的状态,因此当我再次选择它时,它将不会再次呈现。例如,我的一个选项卡是GridView,它在其单元格中加载了远程图像。切换标签时,我不想再次加载图像。

我的主要活动如下:

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);
    }
}


首先,我尝试保存选定的选项卡。但是,当我旋转设备时,由于某种原因,第一个Tab上的TabSelected事件(在本例中为UploadImageTab)被触发,导致保存的值不得不被覆盖。

在带有GridView的Fragment的示例中,我的代码如下:

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);
                    });
                }
            }
        }
    }

}


创建视图后,我向Imgur发出HTTP请求以获取最新的图像URL,然后将创建的照片对象列表分配给ImageAdapter,以下载/渲染它们。但是,当我切换选项卡时,这些对象会丢失。

如何确保保存片段的状态?以及如何保存Fragment的GridView适配器的状态?

最佳答案

我能够找到一个基本的示例here,它可以帮助我处理所面临的情况。我对代码进行了以下更改(注释将说明功能):

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);
    }
}


这些是在更改配置(更改选项卡,更改方向等)时确保活动中每个Fragment的状态持久的重要部分。

现在,您只需要将创建的每个Fragment子类保留其实例,并将您正在使用的任何参数(由HTTP请求,适配器填充的列表)重新分配给它们所属的位置(不要重新初始化变量或您不会保留相同的值)。

每个Fragment子类的OnCreate方法必须具有以下内容:

    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
    }


然后,您需要确保OnCreateView处理逻辑以显示具有所需数据的视图,例如,如果您有一个带有List View的片段,那么您希望引用其适配器及其内容,那么创建视图时,请检查其中是否为空,如果是,则需要遵循逻辑对其进行初始化,否则将它们重新分配给将显示的视图:

    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;
    }


这样,您可以避免片段在更改标签或方向时丢失其状态。

10-07 19:44
查看更多