【问题标题】:How to save selected tab when rotating screen/changing tab on Xamarin.Android?在 Xamarin.Android 上旋转屏幕/更改选项卡时如何保存选定的选项卡?
【发布时间】:2014-10-02 22:08:49
【问题描述】:

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

我有一个 Activity,上面有三个 Tabs,每个 Tab 都有不同的 Fragment。到目前为止,我已经掌握了如何添加 Tabs 和事件处理程序的窍门。

但是当我旋转屏幕时,我设置的默认 Tab 被选中,这会导致分配给该 TabFragment 被显示。

我面临的另一个问题是,当我更改 Tabs 时,我想保留以前 Tab 的状态,所以当我再次选择它时不会再次渲染。例如,我的 Tabs 之一是 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。但是当我旋转设备时,由于某种原因,第一个 Tab(在本例中为 UploadImageTab)上的 TabSelected 事件被触发,导致保存值我必须被覆盖。

在我的 FragmentGridView 的示例中,我的代码是这样的:

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,然后我将我创建的 Photo 对象的 List 分配给我的 ImageAdapter 将下载/渲染它们。但是当我切换标签时,这些对象会丢失。

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

【问题讨论】:

    标签: android android-fragments xamarin xamarin.android


    【解决方案1】:

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

    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 处理逻辑以显示包含所需数据的视图,例如,如果您有一个带有 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;
        }
    

    有了这个,你可以避免你的片段在你改变标签或改变方向时失去它们的状态。

    【讨论】:

      【解决方案2】:

      在片段标签中:

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

      在 onCreateView 内插入: 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 到选项卡位置。 tabSelected 是一个全局变量。

      【讨论】:

        猜你喜欢
        • 2011-08-06
        • 1970-01-01
        • 2012-03-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多