Xamarin开发跨平台App新人一名。
最近组里Xamarin的项目中的一个要求,假设有 A->B->C->D 这样经由控制端跳页的流程,跳到D之后按返回键时希望能够直接返回到A。
做法就是跳到D之后删除之前在控制端堆栈中删除到A为止的控制端。
Android和iOS原生的实现代码已经有不少了,参考一些之后再这里贴一下Xamarin中C#的实现代码。
Android端:
我主要参考了以下这篇文章的Java代码
http://www.jianshu.com/p/263d40582a08
思路就是把所有的Activity(控制端类)抽象出一个基础Activity,让所有的Activity继承这个CommonActivity。
利用List以及多态性做成一个管理所有Activity的类ActivityCollector,然后添加一些需要的方法比如AddActivity,RemoveActivity。
public abstract class CommonActivity : Activity
{
// 抽象控制基类
}
public class ActivityCollector
{
private static List<CommonActivity> activities = new List<CommonActivity>();
public static List<CommonActivity> Activities
{
get { return activities; }
}
public static void AddActivity(CommonActivity activity)
{
activities.Add(activity);
}
public static void RemoveActivity(CommonActivity activity)
{
activities.Remove(activity);
}
public static void FinishAll()
{
foreach(CommonActivity activity in activities)
{
if (!activity.IsFinishing)
{
activity.Finish();
}
}
}
}
那么如何利用ActivityCollector来管理控制堆栈呢?
简单地说就是要结合AndroidApp生命周期,在某个Activity被创建或是被销毁时同步更新ActivityCollector中的内容,
这就需要在CommonActivity的OnCreate和OnDestroy中添加一些代码
public abstract class CommonActivity : Activity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
ActivityCollector.AddActivity(this);
}
protected override void OnDestroy()
{
base.OnDestroy();
ActivityCollector.RemoveActivity(this);
}
}
回到项目要求。在页面D生成时,删除堆栈中直到页面A的所有Activity,就能在D页面按下返回键时直接返回到A页面。
如果有必要可能需要加一些判断语句判断是不是从C页面跳转到D页面防止误删。
public class ActivityOfD : Activity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
// 可以根据需要添加一些判断语句
for (int i = ActivityCollector.Activities.Count - 2; i >= 0; i--)
{
if ( !(ActivityCollector.Activities[i] is ActivityOfA) )
{
ActivityCollector.Activities[i].Finish();
}
}
}
}
iOS端:
iOS端的做法和Android端不太一样。iOS的SDK已经提供了控制端堆栈管理API:UINavigationController类,而Xamarin也已经帮我们包装成了C#类,可以直接调用。
我主要参考这篇文章的部分代码。
http://www.jianshu.com/p/90a104ac6633
C#中无法将NSMutableArray类强制转换成ViewController数组(应该说C#的默认数组都是继承自Array类而不是NSArray类),因此我们用List来处理,代码如下。
public class ViewControllerOfD : CommonViewController
{
public override void ViewWillAppear (bool animated)
{
base.ViewWillAppear (animated);
// 可以根据需要添加一些判断语句
List<UIViewController> controllers = new List<UIViewController> (NavigationController.ViewControllers);
for (int i = controllers.Count - 2; i >= 0; i--)
{
if( !(controllers[i] is ViewControllerOfA))
{
controllers.RemoveAt(i);
}
}
NavigationController.SetViewControllers(controllers.ToArray(), true);
}
}
NavigationControler.ViewControllers = controllers.ToArray();
这样做产生了一个问题,虽然画面可以按照预想在页面D按back键时直接跳转回页面A,但屏幕上方的导航栏却依然还是退到页面C的导航栏,需要多次按下back键才能回退到页面A的导航栏。
百思不得其解之时得到了前辈的指点,我查阅了iOS的官方开发者文档
https://developer.apple.com/library/content/documentation/WindowsViews/Conceptual/ViewControllerCatalog/Chapters/NavigationControllers.html
以及UINavigationController类的文档
https://developer.apple.com/documentation/uikit/uinavigationcontroller
如上官方示意图表示了NavigationController的构成。原来ViewControllers的堆栈与NavigationBar(导航栏)是NavigationController下不同的成员。
所以只去改掉ViewControllers的话当然不会影响到导航栏。但当我试图直接改变UINavigationBar.Item时,又会在运行中报出“Cannot set items managed by viewController”的异常。
再一查文档发现原来就如字面意思iOS SDK规定不允许直接修改ViewController管理下的导航栏控件。
那么该怎么办呢?
其实很简单,NavigationController里提供了设置ViewController堆栈的方法SetViewController,该方法会自动帮我们一起设置好相应的导航栏堆栈。最终的代码如之前所示。
iOS端的问题卡了有半天才解决,感觉自己还是对iOS原生应用开发不熟悉,还需要多积累经验和多读官方文档。