我正在使用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)被触发,导致我必须覆盖保存的值。
在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请求最新的图像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是一个全局变量。