做过asp.net或其他web开发的朋友都知道,想要在两个网页间做转换,只要利用超链接就可以实现。但在手机里,要如何实现手机页面之间的转换呢?最简单的方法就是改变Activity的Layout。首先准备两个布局文件Main.axml和Layout2.axml。在Layout1中放置一个按钮,当单击时,显示Layout2,同样地,在Layout2里也放一个按钮,当单击时回到Main.
<?xml version="1.0" encoding="utf-8"?> <AbsoluteLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TextView android:text="这是Layout1" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:text="跳到Layout2" android:layout_y="30px" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btn1"/> </AbsoluteLayout>
<?xml version="1.0" encoding="utf-8"?> <AbsoluteLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@color/white"> <TextView android:text="这是Layout2" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <Button android:text="跳到Layout1" android:layout_y="30px" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btn2"/> </AbsoluteLayout>
然后在Activity1.cs中的OnCreate方法里加入如下代码:
using System; using Android.App; using Android.Content; using Android.Runtime; using Android.Views; using Android.Widget; using Android.OS; using MonoDroidTest.Tabs; using Android.Util; using Java.IO; using Android.Database; namespace MonoDroidTest { [Activity(Label = "MonoDroidTest", MainLauncher = true)] public class Activity1 : Activity { protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); SetContentView(Resource.Layout.Main); Button btn1 = FindViewById<Button>(Resource.Id.btn1); btn1.Click += (sender, e) => { this.SetContentView(Resource.Layout.Activity2); Button btn2 = FindViewById<Button>(Resource.Id.btn2); btn2.Click += (sender2, e2) => { OnCreate(bundle); }; }; } } }
运用改变Activity的Layout这个技巧,就可以做出手机页面转换的效果,当然也可以搭配之前介绍的Style设置,进行更加灵活的布局配置运用。再者,利用SetContentView来置换页面还有一个特别的优点,即所有程序里的变量皆存在相同的状态,无论是类成员、类方法等,都在一个Activity的状态中直接取得,并没有参数传递的问题。比如要进行分步导航的页面,从第一个页面得到的信息无需传递到第二个页面,而是一直存放在当前Activity的成员中,可以随时拿来使用。
如果要转换的页面不单只是背景,颜色或文字的内容的不同,而是Activity的置换,那就不是单单改变Layout就能完成的了。尤其是需要传递的数据不像网页可以通过Session或Cache来实现,在程序里移交主控权到另一个Activity,光靠先前的Layout技巧是办不到的。
那要如何解决Activity控制权的移交呢?在MonoDroid里,可以在主程序中使用StartActivity方法来调用另一个Activity(主程序本身就是一个Activity),但当中的关键并不在StartActivity这个方法,而是Intent这个特有的对象。Intent就如同其英文字义,是“想要”或“意图”之意,在主Activity中,告诉程序自己是什么,并想要前往哪里,这就是Intent对象所处理的事情了。
布局文件与刚才一样,不同的是程序代码,修改Activity.cs如下,在这里教大家另外一个绑定控件事件的方法,在.net程序里,我们要绑定事件,只要在事件名后+=事件处理对象或lambda表达式即可,如btn.Click += (sender, e) => { };在MonoDroid里,为了与Java中的写法保持一致,除了上面这种方式之外,还提供了另外一种方式,即SetOnXXXListener方法。如按钮的Click事件,就调用SetOnClickListener,传入的参数是实现View.IOnClickListener的类。另外,要实现Java中的借口,你的类还要继承Java.Lang.Object类,这样就无需实现IObject的Handle属性了。
using System; using Android.App; using Android.Content; using Android.Runtime; using Android.Views; using Android.Widget; using Android.OS; using MonoDroidTest.Tabs; using Android.Util; using Java.IO; using Android.Database; namespace MonoDroidTest { [Activity(Label = "MonoDroidTest", MainLauncher = true)] public class Activity1 : Activity { protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); SetContentView(Resource.Layout.Main); Button btn = FindViewById<Button>(Resource.Id.btn1); btn.SetOnClickListener(new ClickListener()); } } public class ClickListener : Java.Lang.Object, View.IOnClickListener { public void OnClick(View v) { Activity act = v.Context as Activity; Intent intent = new Intent(); intent.SetClass(act, typeof(Activity2)); act.StartActivity(intent); act.Finish(); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Android.App; using Android.Content; using Android.OS; using Android.Runtime; using Android.Views; using Android.Widget; namespace MonoDroidTest { [Activity(Label = "My Activity")] public class Activity2 : Activity { protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); SetContentView(Resource.Layout.Activity2); Button btn = FindViewById<Button>(Resource.Id.btn2); btn.Click += delegate { Intent intent = new Intent(); intent.SetClass(this, typeof(Activity1)); this.StartActivity(intent); this.Finish(); }; } } }
在Activity1和Activity2两个类里都调用了Finish()这个方法,它代表这个Activity已经运作完毕,当系统接收到这个命令时,即会关闭此Activity,所以此时单击模拟器的返回(Back)键,并不会回到上一个Activity的画面,如果要让模拟器的返回键有回上一页的效果,可以将此行代码注释掉。同理,在两个Acitivity在切换时,并非真的只有两个Activity在切换,而是在单击按钮时再重新调用一个新的Activity。
若需要在调用另一个Activity的同时传递数据,那么就需要利用Bundle对象来封装数据了。将想要传递的数据或参数通过Bundle来传递不同Intent之间的数据。
我们从Activity1调用Activity2的同时,把一个字符串和一个整型传到Activity2试试,对ClickListener类的OnClick方法增加几行代码:
public void OnClick(View v) { Activity act = v.Context as Activity; Intent intent = new Intent(); intent.SetClass(act, typeof(Activity2)); Bundle b = new Bundle(); b.PutString("name", "ojlovecd"); b.PutInt("score", 76195); intent.PutExtras(b); act.StartActivity(intent); act.Finish(); }
然后在Activity2中进行接收这两个值,为了在Activity2加载后弹出提示,我们先来封装一个信息框提示类。这里要用到AlertDialog窗口,它常用于“程序提示”,“警告”或“确认”等。
using System; using Android.App; using Android.Content; namespace MonoDroidTest { public class MessageBox { public static void Show(Context ctx,string title, string message) { AlertDialog.Builder dlg = new AlertDialog.Builder(ctx); dlg.SetTitle(title); dlg.SetMessage(message); dlg.SetPositiveButton("确定", delegate { }); dlg.Show(); } public static void ShowErrorMessage(Context ctx, Exception ex) { Show(ctx, "错误", ex.Message); } } }
然后我们再在Activity2中使用这个类来显示从Activity1传递过来的参数:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Android.App; using Android.Content; using Android.OS; using Android.Runtime; using Android.Views; using Android.Widget; namespace MonoDroidTest { [Activity(Label = "My Activity")] public class Activity2 : Activity { protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); SetContentView(Resource.Layout.Activity2); Bundle b = this.Intent.Extras; Button btn = FindViewById<Button>(Resource.Id.btn2); btn.Click += delegate { Intent intent = new Intent(); intent.SetClass(this, typeof(Activity1)); this.StartActivity(intent); this.Finish(); }; MessageBox.Show(this, "信息", string.Format("用户名:{0}/n积分:{1}", b.GetString("name"), b.GetInt("score"))); } } }
运行结果:
Bundle对象针对不同的数据类型提供了多种方法,例如上面传递string类型的数据,使用了PutString,而要传递double类型的数据,使用PutDouble,反之,要从Bundle对象取出数据,就把Put改为Get即可。我曾经尝试传递非基础类型的数据,即自定义类型的数据,但无论我的对象是实现了Java.IO.ISerializable,然后调用PutSerializable方法,还是实现IParcelable,调用PutParcelable方法,在Get的时候都会报错,实在无奈,如果有哪位朋友能够成功传递自定义类型的数据的话,还请共享一下。
在上一个范例中,好不容易将数据从Activity1传递到Activity2,如果要再回到Activity1,数据该不会要再封装一次吧?而且前一个Activity1早就被destroy了,倘若在Activity1最后以Finish()结束程序,再通过Activity2将数据采用Bundle的方式通过新打开Activity1传递参数,这样的做法虽然也可以恢复用户输入的数据,但并不符合我们的期待,尤其是用户曾经输入过的数据,如果不小心单击回到上一页,数据就消失不见了。
如果要在第二个页面加上一个“回上页”的按钮,而非通过模拟器的返回键,且回上页后又能保留之前的相关信息,那么就必须用StartActivityForResult方法来唤起另一个Activity。要处理Activity2返回的结果,就要在Activity1中重写OnActivityResult方法,程序如下:
using System; using Android.App; using Android.Content; using Android.Runtime; using Android.Views; using Android.Widget; using Android.OS; using MonoDroidTest.Tabs; using Android.Util; using Java.IO; using Android.Database; namespace MonoDroidTest { [Activity(Label = "MonoDroidTest", MainLauncher = true)] public class Activity1 : Activity { protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); SetContentView(Resource.Layout.Main); Button btn = FindViewById<Button>(Resource.Id.btn1); btn.SetOnClickListener(new ClickListener()); } protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) { if (resultCode == Result.Ok) { Bundle b = data.Extras; string name = b.GetString("name"); int score = b.GetInt("score"); string blogUrl = b.GetString("blogUrl"); int availableScore = b.GetInt("availableScore"); MessageBox.Show(this, "提示", string.Format("名称:{0}/n积分:{1}/n博客地址:{2}/n可用分:{3}", name, score, blogUrl, availableScore)); } } } public class ClickListener : Java.Lang.Object, View.IOnClickListener { public void OnClick(View v) { //try //{ Activity act = v.Context as Activity; Intent intent = new Intent(); intent.SetClass(act, typeof(Activity2)); Bundle b = new Bundle(); b.PutString("name", "ojlovecd"); b.PutInt("score", 76195); intent.PutExtras(b); act.StartActivityForResult(intent, 0); //act.StartActivity(intent); //act.Finish(); //} //catch (Exception ex) //{ // MessageBox.Show(v.Context, "错误", ex.Message); //} } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Android.App; using Android.Content; using Android.OS; using Android.Runtime; using Android.Views; using Android.Widget; namespace MonoDroidTest { [Activity(Label = "My Activity")] public class Activity2 : Activity { protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); SetContentView(Resource.Layout.Activity2); Bundle b = this.Intent.Extras; // Create your application here Button btn = FindViewById<Button>(Resource.Id.btn2); btn.Click += delegate { Intent intent = new Intent(); b.PutString("blogUrl", "http://blog.csdn.net/ojlovecd"); b.PutInt("availableScore", 18734); intent.PutExtras(b); this.SetResult(Result.Ok, intent); this.Finish(); }; MessageBox.Show(this, "信息", string.Format("用户名:{0}/n积分:{1}", b.GetString("name"), b.GetInt("score"))); } } }
运行结果:
就本范例而言,其实使用StartActivity也可以达成相同的效果,仅需在Activity1被Create时判断Intent内有没有数据,有的话,就将数据带入,没有就带入null。但程序还需要做有无值的比较,较为繁琐,既然MonoDroid API提供了更好用的方法,何乐而不为呢?更何况如果系统不是只有几行,而是几百几千行,那不就头大了?