我正在使用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;
}
这样,您可以避免片段在更改标签或方向时丢失其状态。