android - 在 Xamarin.Android 上旋转屏幕/更改选项卡时如何保存选定的选项卡?

标签 android android-fragments xamarin xamarin.android

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

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

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

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

我的主要 Activity 如下所示:

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)被触发,导致保存的我的值必须被覆盖。

在带有 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);
                    });
                }
            }
        }
    }

}

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

如何确保保存 fragment 的状态?如何保存 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);
    }
}

这些是确保在进行配置更改(更改选项卡、更改方向等)时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处理逻辑以显示包含所需数据的 View ,例如,如果您有一个带有 ListView 的 fragment ,那么您希望拥有对其适配器及其内容的引用,然后在创建 View 时检查其中任何一个是否为空,如果是则您需要按照您的逻辑来初始化它们,否则将它们重新分配给将显示的 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;
    }

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

关于android - 在 Xamarin.Android 上旋转屏幕/更改选项卡时如何保存选定的选项卡?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26170422/

相关文章:

android-fragments - 在片段 xamarin 之间进行通信

c# - 如何在 Xamarin Forms 中将 BackgroundImage 添加到 ListView?

java - 网址非法字符

java - 使用 @PrimaryKey 0(零)初始化 Realm 对象在第二个对象上失败

android - 检测特定应用程序(不是我的)何时对用户可见/不可见

xamarin - 如何在Xamarin Carousel中将所有项目模板数据设置到单个 View 中

ios - Xamarin iOS 导航栏图像

android - 滑动时 ViewPager 更新 fragment

android - 为什么 Observable.interval() 不能取消订阅?

android - 我应该导入哪个 fragment 库?