在Xamarin.Android上旋转屏幕/更改标签时如何保存选定的标签?

时间:2014-10-02 22:08:50

标签: android android-fragments xamarin xamarin.android

我正在使用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);
    }
}

对于初学者,我尝试保存所选的标签。但是当我旋转设备时,出于某种原因,第一个选项卡上的 TabSelected 事件(在这种情况下是 UploadImageTab )被触发,导致保存价值我不得不被覆盖。

在我的片段的示例中,带有 GridView ,我的代码是这样的:

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请求以获取最新的图片网址,然后将我创建的 Photo 对象的列表分配给我的 ImageAdapter 将下载/渲染它们。但是当我切换标签时,这些对象就会丢失。

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

2 个答案:

答案 0 :(得分:3)

我找到了一个基本的例子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 子类保留它们的实例和你正在使用的任何参数(由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 处理逻辑以显示包含所需数据的视图,例如,如果您的片段带有列表视图,那么你想要引用它的适配器及其内容,然后在创建它的视图时检查其中是否有任何为空,如果是,那么你需要按照你的逻辑初始化它们,否则重新分配它们到将显示的视图:

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

使用此功能,您可以在更改标签或更改方向时避免碎片丢失状态。

答案 1 :(得分:0)

在“片段”标签中:

 `@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是一个全局变量。