如何在Xamarin.Android上旋转屏幕/更改选项卡时保存所选选项卡



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

我有一个活动,上面有三个选项卡,每个选项卡都有一个不同的片段。到目前为止,我已经掌握了如何添加Tabs和事件处理程序的窍门。

但当我旋转屏幕时,会选择默认的选项卡我设置,这会导致显示分配给该标签的Fragment

我面临的另一个问题是,当我更改选项卡时,我希望保留上一个标签的状态,因此当我再次选择它时,它将不会再次渲染。例如,我的选项卡之一是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);
    }
}

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

GridViewFragment示例中,我的代码如下:

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请求最新的图像url,然后将我创建的Photo对象的List分配给我的ImageAdapter,该适配器将下载/渲染它们。但是当我切换选项卡时,这些对象会丢失。

如何确保保存碎片的状态?如何保存Fragment的GridView适配器的状态?

我在这里找到了一个基本的例子,它帮助我处理了我所面临的情况。我对我的代码做了以下更改(注释将解释功能):

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

在进行配置更改(更改选项卡、更改方向等)时,这些是确保Activity上每个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处理逻辑以显示具有所需数据的视图,例如,如果您有一个带有ListView的片段,则您希望对其适配器及其内容进行引用,然后在创建视图时检查其中是否有空,如果是空,则您需要遵循您的逻辑对其进行初始化,否则将它们重新分配到将显示的视图:

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

这样可以避免碎片在更改选项卡或方向时丢失状态。

在片段选项卡中:

 `@Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt('tabSelected', viewPager.getCurrentItem());
    }`

CreateView插入时的内部: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);
            }**
            ....
            ....
`

现在旋转后,您将被选项卡选定到选项卡位置。tabSelected是一个全局变量。

最新更新