上一篇文章中,我们已经简要分析了一个MonoDroid工程的基本结构,这次我们来讨论更多。每一种应用程序基本都会有用户界面(UI),在.Net里,winform程序的UI是一个form窗体,wpf或silverlight程序的UI是一个xaml,asp.net页面是一个aspx,那么在MonoDroid里,就是一个Activity。上面说的.net里的程序都可以既使用可视化界面进行拖拽,也可以在源代码中进行编辑,但MonoDroid中,只能对xml文件或axml文件进行编辑或者在Activity类中使用c#代码进行动态加载。那么如何对程序的界面进行布局呢?我们这篇文章就来进行探讨。
打开Main.xml,可以看到Button是被嵌套在LiearLayout里的,这个LiearLayout直接翻译过来就是线性布局,它是MonoDroid中布局的一种,MonoDroid的支持多种布局方式:有LinearLayout(线性布局),RelativeLayout(相对位置布局),TableLayout(表格布局),GridView(网格视图),TabLayout(标签页布局),ListView(列表视图)等等,如下图所示,详细的布局可以参考Android.Widget命名空间下以Layout结尾的类都有哪些。
线性布局 相对布局 表格布局
网格视图 标签页布局 列表视图
下面我们来试试写一个例子,把几个布局都用上。设计这样一个界面:有四个标签页,第一个标签页内有线性布局和相对布局;第二个标签页内有表格布局,第三个标签页内有网格视图,第四个标签页内有列表视图。
首先我们要先设计标签页界面。需要使用TabHost和TabWidget。TabHost必须是布局的根节点,它包括用来显示标签页的TabWdiget和显示标签内容的FrameLayout。在Main.axml中添加如下代码:
<?xml version="1.0" encoding="utf-8"?> <TabHost xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/tabhost" android:layout_width="fill_parent" android:layout_height="fill_parent"> <LinearLayout android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:padding="5dp"> <TabWidget android:id="@android:id/tabs" android:layout_width="fill_parent" android:layout_height="wrap_content"/> <FrameLayout android:id="@android:id/tabcontent" android:layout_width="fill_parent" android:layout_height="fill_parent" android:padding="5dp"/> </LinearLayout> </TabHost>
在Resources/Layout文件夹下增加Tab1~Tab4共四个axml布局文件,分别表示四个标签页的内容:
Tab1.xaml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation= "vertical" android:layout_width= "fill_parent" android:layout_height= "fill_parent" > <LinearLayout android:orientation= "horizontal" android:layout_width= "fill_parent" android:layout_height= "wrap_content" android:layout_weight= "1" > <TextView android:text= "红" android:gravity= "center_horizontal" android:background= "#aa0000" android:layout_width= "wrap_content" android:layout_height= "wrap_content" android:layout_weight= "1" /> <TextView android:text= "绿" android:gravity= "center_horizontal" android:background= "#00aa00" android:layout_width= "wrap_content" android:layout_height= "wrap_content" android:layout_weight= "1" /> <TextView android:text= "蓝" android:gravity= "center_horizontal" android:background= "#0000aa" android:layout_width= "wrap_content" android:layout_height= "wrap_content" android:layout_weight= "1" /> <TextView android:text= "黄" android:gravity= "center_horizontal" android:background= "#aaaa00" android:layout_width= "wrap_content" android:layout_height= "wrap_content" android:layout_weight= "1" /> </LinearLayout> <LinearLayout android:orientation= "vertical" android:layout_width= "fill_parent" android:layout_height= "wrap_content" android:layout_weight= "1" > <TextView android:text= "第一行" android:layout_width= "fill_parent" android:layout_height= "wrap_content" android:layout_weight= "1" /> <TextView android:text= "第二行" android:layout_width= "fill_parent" android:layout_height= "wrap_content" android:layout_weight= "1" /> <TextView android:text= "第三行" android:layout_width= "fill_parent" android:layout_height= "wrap_content" android:layout_weight= "1" /> <TextView android:text= "第四行" android:layout_width= "fill_parent" android:layout_height= "wrap_content" android:layout_weight= "1" /> </LinearLayout> <RelativeLayout android:layout_width="fill_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="账号:"/> <EditText android:id="@+id/entry" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_below="@id/label"/> <TextView android:id="@+id/label2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/entry" android:text="密码:"/> <EditText android:id="@+id/entry2" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_below="@id/label2" android:password="true" /> <Button android:id="@+id/ok" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/entry2" android:layout_alignParentRight="true" android:layout_marginLeft="10dip" android:text="OK" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toLeftOf="@id/ok" android:layout_alignTop="@id/ok" android:text="Cancel" /> </RelativeLayout> </LinearLayout>
Tab2.xaml
[xhtml] view plaincopyprint? <?xml version="1.0" encoding="utf-8"?> <TableLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:stretchColumns="1"> <TableRow> <TextView android:layout_column="1" android:text="打开..." android:padding="3dip"/> <TextView android:text="Ctrl-O" android:gravity="right" android:padding="3dip"/> </TableRow> <TableRow> <TextView android:layout_column="1" android:text="保存..." android:padding="3dip"/> <TextView android:text="Ctrl-S" android:gravity="right" android:padding="3dip"/> </TableRow> <TableRow> <TextView android:layout_column="1" android:text="另存为..." android:padding="3dip"/> <TextView android:text="Ctrl-Shift-S" android:gravity="right" android:padding="3dip"/> </TableRow> <View android:layout_height="2dip" android:background="#FF909090"/> <TableRow> <TextView android:text="X" android:padding="3dip"/> <TextView android:text="导入..." android:padding="3dip"/> </TableRow> <TableRow> <TextView android:text="X" android:padding="3dip"/> <TextView android:text="导出..." android:padding="3dip"/> <TextView android:text="Ctrl-E" android:gravity="right" android:padding="3dip"/> </TableRow> <View android:layout_height="2dip" android:background="#FF909090"/> <TableRow> <TextView android:layout_column="1" android:text="退出" android:padding="3dip"/> </TableRow> </TableLayout>
Tab3.xaml
[xhtml] view plaincopyprint? <?xml version="1.0" encoding="utf-8"?> <GridView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/gridview" android:layout_width="fill_parent" android:layout_height="fill_parent" android:columnWidth="90dp" android:numColumns="auto_fit" android:verticalSpacing="10dp" android:horizontalSpacing="10dp" android:stretchMode="columnWidth" android:gravity="center" />
Tab4.xaml
[xhtml] view plaincopyprint? <?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:padding="10dp" android:textSize="16sp" > </TextView>
新建一个Tabs文件夹,在文件夹中增加四个Activity,分别表示四个标签页的后台类:
Tab1.cs
[c-sharp] view plaincopyprint? 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.Tabs { [Activity(Label = "My Activity")] public class Tab1 : Activity { protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); // Create your application here SetContentView(Resource.Layout.Tab1); Button btnOK = FindViewById<Button>(Resource.Id.ok); btnOK.Click += (sender, e) => { EditText entry = FindViewById<EditText>(Resource.Id.entry); EditText entry2 = FindViewById<EditText>(Resource.Id.entry2); AlertDialog.Builder dlg = new AlertDialog.Builder(this); dlg.SetTitle("提示"); dlg.SetMessage(string.Format("你输入的账号是:{0},密码是:{1}", entry.Text, entry2.Text)); dlg.SetPositiveButton("确定", delegate { }); dlg.Show(); }; } } }
Tab2.cs
[c-sharp] view plaincopyprint? 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.Tabs { [Activity(Label = "My Activity")] public class Tab2 : Activity { protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); // Create your application here SetContentView(Resource.Layout.Tab2); } } }
Tab3.cs
[c-sharp] view plaincopyprint? 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.Tabs { [Activity(Label = "My Activity")] public class Tab3 : Activity { protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); try { // Create your application here SetContentView(Resource.Layout.Tab3); var gridview = FindViewById<GridView>(Resource.Id.gridview); gridview.Adapter = new ImageAdapter(this); gridview.ItemClick += (sender, e) => { Toast.MakeText(this, e.Position.ToString(), ToastLength.Short).Show(); }; } catch (Exception ex) { AlertDialog.Builder dlg = new AlertDialog.Builder(this); dlg.SetTitle("错误"); dlg.SetMessage(ex.Message); dlg.SetPositiveButton("确定", delegate { }); dlg.Show(); } } } public class ImageAdapter : BaseAdapter { Context context; public ImageAdapter(Context c) { context = c; } public override int Count { get { return thumbIds.Length; } } public override Java.Lang.Object GetItem(int position) { return null; } public override long GetItemId(int position) { return 0; } // create a new ImageView for each item referenced by the Adapter public override View GetView(int position, View convertView, ViewGroup parent) { ImageView imageView; if (convertView == null) { // if it's not recycled, initialize some attributes imageView = new ImageView(context); imageView.LayoutParameters = new GridView.LayoutParams(85, 85); imageView.SetScaleType(ImageView.ScaleType.CenterCrop); imageView.SetPadding(8, 8, 8, 8); } else { imageView = (ImageView)convertView; } imageView.SetImageResource(thumbIds[position]); return imageView; } // references to our images int[] thumbIds = { Resource.Drawable.btn1_b, Resource.Drawable.btn2, Resource.Drawable.btn2_b, Resource.Drawable.btn3, Resource.Drawable.btn3_b, Resource.Drawable.btn4, Resource.Drawable.btn4_b, Resource.Drawable.btn1, Resource.Drawable.btn1_b, Resource.Drawable.btn2, Resource.Drawable.btn2_b, Resource.Drawable.btn3, Resource.Drawable.btn3_b, Resource.Drawable.btn4, Resource.Drawable.btn4_b, Resource.Drawable.btn1, Resource.Drawable.btn1_b, Resource.Drawable.btn2, Resource.Drawable.btn2_b, Resource.Drawable.btn3, Resource.Drawable.btn3_b, Resource.Drawable.btn4, }; } }
Tab4.cs
[c-sharp] view plaincopyprint? 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.Tabs { [Activity(Label = "My Activity")] public class Tab4 : ListActivity { protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); // Create your application here try { ListAdapter = new ArrayAdapter<string>(this, Resource.Layout.Tab4, countries); ListView.TextFilterEnabled = true; ListView.ItemClick += delegate(object sender, ItemEventArgs args) { // When clicked, show a toast with the TextView text Toast.MakeText(Application, ((TextView)args.View).Text, ToastLength.Short).Show(); }; } catch (Exception ex) { AlertDialog.Builder dlg = new AlertDialog.Builder(this); dlg.SetTitle("错误"); dlg.SetMessage(ex.Message); dlg.SetPositiveButton("确定", delegate { }); dlg.Show(); } } static readonly string[] countries = new String[] { "Afghanistan","Albania","Algeria","American Samoa","Andorra", "Angola","Anguilla","Antarctica","Antigua and Barbuda","Argentina", "Armenia","Aruba","Australia","Austria","Azerbaijan", "Bahrain","Bangladesh","Barbados","Belarus","Belgium", "Belize","Benin","Bermuda","Bhutan","Bolivia", "Bosnia and Herzegovina","Botswana","Bouvet Island","Brazil","British Indian Ocean Territory", "British Virgin Islands","Brunei","Bulgaria","Burkina Faso","Burundi", "Cote d'Ivoire","Cambodia","Cameroon","Canada","Cape Verde", "Cayman Islands","Central African Republic","Chad","Chile","China", "Christmas Island","Cocos (Keeling) Islands","Colombia","Comoros","Congo", "Cook Islands","Costa Rica","Croatia","Cuba","Cyprus","Czech Republic", "Democratic Republic of the Congo","Denmark","Djibouti","Dominica","Dominican Republic", "East Timor","Ecuador","Egypt","El Salvador","Equatorial Guinea","Eritrea", "Estonia","Ethiopia","Faeroe Islands","Falkland Islands","Fiji","Finland", "Former Yugoslav Republic of Macedonia","France","French Guiana","French Polynesia", "French Southern Territories","Gabon","Georgia","Germany","Ghana","Gibraltar", "Greece","Greenland","Grenada","Guadeloupe","Guam","Guatemala","Guinea","Guinea-Bissau", "Guyana","Haiti","Heard Island and McDonald Islands","Honduras","Hong Kong","Hungary", "Iceland","India","Indonesia","Iran","Iraq","Ireland","Israel","Italy","Jamaica", "Japan","Jordan","Kazakhstan","Kenya","Kiribati","Kuwait","Kyrgyzstan","Laos", "Latvia","Lebanon","Lesotho","Liberia","Libya","Liechtenstein","Lithuania","Luxembourg", "Macau","Madagascar","Malawi","Malaysia","Maldives","Mali","Malta","Marshall Islands", "Martinique","Mauritania","Mauritius","Mayotte","Mexico","Micronesia","Moldova", "Monaco","Mongolia","Montserrat","Morocco","Mozambique","Myanmar","Namibia", "Nauru","Nepal","Netherlands","Netherlands Antilles","New Caledonia","New Zealand", "Nicaragua","Niger","Nigeria","Niue","Norfolk Island","North Korea","Northern Marianas", "Norway","Oman","Pakistan","Palau","Panama","Papua New Guinea","Paraguay","Peru", "Philippines","Pitcairn Islands","Poland","Portugal","Puerto Rico","Qatar", "Reunion","Romania","Russia","Rwanda","Sqo Tome and Principe","Saint Helena", "Saint Kitts and Nevis","Saint Lucia","Saint Pierre and Miquelon", "Saint Vincent and the Grenadines","Samoa","San Marino","Saudi Arabia","Senegal", "Seychelles","Sierra Leone","Singapore","Slovakia","Slovenia","Solomon Islands", "Somalia","South Africa","South Georgia and the South Sandwich Islands","South Korea", "Spain","Sri Lanka","Sudan","Suriname","Svalbard and Jan Mayen","Swaziland","Sweden", "Switzerland","Syria","Taiwan","Tajikistan","Tanzania","Thailand","The Bahamas", "The Gambia","Togo","Tokelau","Tonga","Trinidad and Tobago","Tunisia","Turkey", "Turkmenistan","Turks and Caicos Islands","Tuvalu","Virgin Islands","Uganda", "Ukraine","United Arab Emirates","United Kingdom", "United States","United States Minor Outlying Islands","Uruguay","Uzbekistan", "Vanuatu","Vatican City","Venezuela","Vietnam","Wallis and Futuna","Western Sahara", "Yemen","Yugoslavia","Zambia","Zimbabwe" }; } }
你要为每个标签页分别指定标签页被选中与没被选中两种状态时的图标,所以准备8张图片,放到Resources/Drawable文件夹下。再在这个文件夹下新建4个xml文件,用以标识每个标签页使用哪两张图片作为图标。这里只列举其中一个xml文件作为例子,其余三个类似:
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <!-- When selected, use grey --> <item android:drawable="@drawable/btn1" android:state_selected="true"/> <!-- When not selected, use white--> <item android:drawable="@drawable/btn1_b"/> </selector>
最终的工程文件结构如下:
Activity1的代码如下,注意这次Activity1不再继承Activity,而是继承TabActivity:
[c-sharp] view plaincopyprint? 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 : TabActivity { protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); SetContentView(Resource.Layout.Main); TabHost.TabSpec spec; // Resusable TabSpec for each tab Intent intent; // Reusable Intent for each tab // Create an Intent to launch an Activity for the tab (to be reused) intent = new Intent(this, typeof(Tab1)); intent.AddFlags(ActivityFlags.NewTask); // Initialize a TabSpec for each tab and add it to the TabHost spec = TabHost.NewTabSpec("tab1"); spec.SetIndicator("页签1", Resources.GetDrawable(Resource.Drawable.Tab1)); spec.SetContent(intent); TabHost.AddTab(spec); // Do the same for the other tabs intent = new Intent(this, typeof(Tab2)); intent.AddFlags(ActivityFlags.NewTask); spec = TabHost.NewTabSpec("tab2"); spec.SetIndicator("页签2", Resources.GetDrawable(Resource.Drawable.Tab2)); spec.SetContent(intent); TabHost.AddTab(spec); intent = new Intent(this, typeof(Tab3)); intent.AddFlags(ActivityFlags.NewTask); spec = TabHost.NewTabSpec("tab3"); spec.SetIndicator("页签3", Resources.GetDrawable(Resource.Drawable.Tab3)); spec.SetContent(intent); TabHost.AddTab(spec); intent = new Intent(this, typeof(Tab4)); intent.AddFlags(ActivityFlags.NewTask); spec = TabHost.NewTabSpec("tab4"); spec.SetIndicator("页签4", Resources.GetDrawable(Resource.Drawable.Tab4)); spec.SetContent(intent); TabHost.AddTab(spec); TabHost.CurrentTab = 0; } } }
运行程序,好了,我们设计的界面出来了~~
我首先在Debug版本下生成了项目,运行起来一切正常,但是后来我改成了Release版本以后,在切换到第三和第四个页签时都会报An exception was thrown by the type initializer for Android.Widget.ItemClickImplementor的异常,但是如果把项目属性中的Linking 选为None,那么在Release版本下也不会出错了,但是生成的apk安装文件却有13M+之巨……具体原因现在不明,初步估计应该是Mono的一个Bug,在官网与技术人员沟通过后,他们也不知原因何在,让我关注他们的下一版本,如果下一版本还存在这个问题,到时给他们提一个Bug。