现在的位置: 首页 > 综合 > 正文

EmployeeDirectory范例解析

2019年10月03日 ⁄ 综合 ⁄ 共 8546字 ⁄ 字号 评论关闭

  从官网上下载EmployeeDirectory范例,编译并运行,得到一个员工管理的应用程序,可以搜索员工信息,设置为常用联系人,或取消常用联系人。这个应用程序包含登陆窗口、继承自ListActivity的主界面(显示员工列表)、员工信息明细界面等。并实现从员工明细界面中获取到电话号码、EMail地址等信息,直接拨打电话、发送邮件等功能。

  这个程序采用MVVM模式,即View(Activity)包含一个ViewModel实例,ViewModel又包含一个Model实例。ViewModel中主要实现业务逻辑,并把与界面相关的事件、命令放在这里实现。Model是存放和操作数据的地方,是封装核心数据、核心逻辑、核心功能的地方。在Xamarin中打开项目,从解决方案列表中可以看到EmployeeDirectory库封装了所有的非界面部分,包括ViewModel层、Model层、其他功能性代码。而在EmployeeDirectory.Android工程下重用了EmployeeDirectory库中的所有源码并添加了View层(界面)的内容。如果需要支持IOS,则创建IOS工程后,直接重用EmployeeDirectory库中所有ViewModel、Model的代码,只需实现IOS的View层即可。

下面开始分析实现代码。大概浏览一下解决方案目录中的内容,含有一个EmployeeDirectory的库工程,和一个EmployeeDirectory.Android工程,上面说那个库工程主要是为了实现不同平台下共享业务逻辑的,其中的代码会分别引入到对应平台的工程项目中,因此不再分析其中的内容,我们直接查看EmployeeDirectory.Android工程的内容。

一、Model的定义

先看看源码文件,发现一些Activity源码和Adapter源码,这些在上面的范例中都已经接触过了。但里面还有个Application.cs单元,有过Delphi、Winform等开发经验的人很快就猜到,这个Application应该就是代表应用程序的实例,其中实现了对应用程序进行管理的功能。我们看一下代码实现,其中定义了一个Application类型,从global::Android.App.Application类继承而来。到网上查询一下基类的信息,发现重新实现这个基类,就是为了在此实例化应用程序范围内的全局数据成员,而且还需要在AndroidManifest.xml中进行注册;当然在Xamarin中会使用注解来实现注册,在class Application上方有一个注解:[Application (Label = "Employees", Theme = "@style/CustomHoloTheme", Icon="@drawable/Icon")]就实现了注册功能。继续查看代码,发现声明了静态数据成员和属性:

private static IFavoritesRepository repo;//实现数据读写操作

public static IDirectoryService Service   //实现常用操作如登陆、查找等
         {
            get;
            set;
         }

并在重写OnCreate的时候,对这两个静态数据成员赋值:

        public override void OnCreate ()
        {
            base.OnCreate ();

            using (var reader = new System.IO.StreamReader (Assets.Open ("XamarinDirectory.csv"))) {
                Service = new MemoryDirectoryService (new CsvReader<Person> (reader).ReadAll ());
            }

            var filePath = Path.Combine (System.Environment.GetFolderPath (System.Environment.SpecialFolder.Personal), "XamarinFavorites.xml");
            using (var stream = Assets.Open ("XamarinFavorites.xml")) {
                using (var filestream = File.Open (filePath, FileMode.Create)) {
                    stream.CopyTo (filestream);
                }
            }
            repo = XmlFavoritesRepository.OpenFile (filePath);
        }

好了,至此Model层对象已经实例化完毕。在ViewModel中引用Model层对象只需使用Application中的这两个静态数据成员即可。从上面代码可以看出,范例程序的数据分别存储在XamarinFavorites.xml(常用联系人)和XamarinDirectory.csv(员工信息)中,数据的解析可以查看XmlFavoritesRepository类和MemoryDirectoryService的FromCsv方法。

二、ViewModel实现

应用程序中的所有ViewModel都从ViewModelBase类继承而来,ViewModelBase实现了INotifyPropertyChanged 接口。INotifyPropertyChanged 接口是 WPF/Silverlight 开发中非常重要的接口,它构成了 ViewModel 的基础,数据绑定基本上都需要这个接口。接口包含一个事件和一个函数定义,主要用来在属性发生变化的时候触发事件。

public event PropertyChangedEventHandler PropertyChanged;
         void OnPropertyChanged(string propertyName);

ViewModelBase中包含了这两个成员,并在IsBusy属性值改变的时候调用OnPropertyChanged触发PropertyChangedEventHandler事件,在Validate函数中调用OnPropertyChanged触发PropertyChangedEventHandler事件,从而通知View层刷新UI

简单看看LoginViewModel类的实现,其中包含UserName和Password开放(public)属性,View(Activity)中在输入框文本改变事件中对这两个属性赋值;构造函数接收IDirectoryService service参数,View(Activity)中实例化这个ViewModel时传递Application.Service实例。其中提供了一个核心LoginAsync方法,调用Service.LoginAsync方法,开启一个Task异步执行登陆操作(范例只是Sleep了2秒,直接登陆成功)。这个ViewModel为View层提供了调用接口,并转调Model层对象(Service属性)提供的操作。

三、View层实现

还是查看LoginActivity的设计和实现,从layout中找到Login.axml,双击查看界面,在输入用户名和密码的EditText控件中有个Hint属性,设置为提示信息,当控件中Text为空时显示Hint的内容。而进度条显示在Button上方,是设置了进度条控件属性android:layout_alignTop="@+id/logIn"。LoginActivity类的实现和前面的范例中Activity相似,只是实例化了LoginViewModel实例,在登陆时调用ViewModel提供的登陆方法。

其中执行登陆的代码:

loginViewModel
                    .LoginAsync (System.Threading.CancellationToken.None)
                    .ContinueWith (_ => {
                        Android.Application.LastUseTime = System.DateTime.UtcNow;
                        RunOnUiThread (() => {
                            StartActivity (typeof (MainActivity));
                        });
                    });

loginViewModel.LoginAsync返回一个Task实例,Task.ContinueWith 表示任务执行完毕后继续在任务中异步执行内部指定代码,更新用户登陆时间,并启动主界面。

这个Activity实现了TextView.IOnEditorActionListener接口,并在OnCreate方法中指定用户名和密码输入框实例的OnEditorActionListener为本对象:

  userName.SetOnEditorActionListener (this);

  password.SetOnEditorActionListener (this);

当用户名和密码文本框收到键盘输入时将触发TextView.IOnEditorActionListener接口的OnEditorAction函数,在这里处理特殊按键(如go、next等)。

public bool OnEditorAction (TextView v, ImeAction actionId, KeyEvent e)
        {
            //go edit action will login
            if (actionId == ImeAction.Go) {
                if (!string.IsNullOrEmpty (userName.Text) && !string.IsNullOrEmpty (password.Text)) {
                    Login ();
                } else if (string.IsNullOrEmpty (userName.Text)) {
                    userName.RequestFocus ();
                } else {
                    password.RequestFocus ();
                }
                return true;
                //next action will set focus to password edit text.
            } else if (actionId == ImeAction.Next) {
                if (!string.IsNullOrEmpty (userName.Text)) {
                    password.RequestFocus ();
                }
                return true;
            }
            return false;
        }

四、显示一个对话框:

           var builder = new AlertDialog.Builder (this)
                    .SetTitle ("Need Help?")
                    .SetMessage ("Enter any username or password.")
                    .SetPositiveButton ("Ok", (innerSender, innere) => { });
                var dialog = builder.Create ();
                dialog.Show ();

五、发邮件

       var intent = new Intent (Intent.ActionSend);
                intent.SetType ("message/rfc822");
                intent.PutExtra (Intent.ExtraEmail, new [] { Property.Value });
                v.Context.StartActivity (Intent.CreateChooser (intent, "Send email"));

六、打电话

      var intent = new Intent (Intent.ActionCall, global::Android.Net.Uri.Parse ( "tel:" + Uri.EscapeDataString (Property.Value)));
               v.Context.StartActivity (intent);

七、主界面ListView按首字母分组的实现方式

从Model层获取到Person列表后,按人员姓名排序,提取首字母后生成PersonGroup列表,在主界面ListView的Adapter类中,将PersonGroup列表中的组头和组内人员都放入到List<Java.Lang.Object> items私有成员中;重写GetView方法时,根据items当前行数据是组头还是人员信息,生成相应的行View,并绑定数据,实现按首字母进行分组的效果。在主界面点击ListView的重写函数OnListItemClick中,判断当前行对应Adapter中的数据类型,确定当前行是人员还是分组。如点击的是人员则显示人员信息界面。

八、Person信息界面的实现方式

主界面ListView选择一个人员后,创建一个Intent对象并传递Person实例的序列号字节数组,在PersonActivity中反序列化得到Person实例;并使用这个实例创建对应的ViewModel--PersonViewModel实例;使用PersonViewModel实例创建PersonAdapter。在PersonViewModel构造函数中,已将Person实例按属性拆分为一个列表集合(包含属性名和属性值),在Adapter中用这个集合作为数据源创建ListView界面。

九、创建菜单

重写OnCreateOptionsMenu 方法

public override bool OnCreateOptionsMenu (IMenu menu)
        {
            MenuInflater.Inflate (Resource.Menu.PersonActivityOptionsMenu, menu);//加载菜单
            favoriteItem = menu.FindItem (Resource.Id.MenuFavorite);
            UpdateFavoriteIcon ();
            return true;
        }

处理菜单选中事件重写OnOptionsItemSelected 方法

public override bool OnOptionsItemSelected (IMenuItem item)
        {
            if (item.ItemId == Resource.Id.MenuFavorite) {//判断点击的是哪个菜单项
                viewModel.ToggleFavorite ();                           //调用ViewModel层的方法
                return true;
            } else {
                return base.OnOptionsItemSelected (item);
            }
        }

十、主界面的搜索菜单

public override bool OnCreateOptionsMenu (IMenu menu)
        {
            MenuInflater.Inflate (Resource.Menu.AppActivityOptionsMenu, menu);                            //加载菜单
            var searchManager = (SearchManager)GetSystemService (Context.SearchService);  //获取系统搜索服务
            var searchView = (SearchView)menu.FindItem (Resource.Id.MenuSearch).ActionView;
            var searchInfo = searchManager.GetSearchableInfo (ComponentName);
            searchView.SetSearchableInfo (searchInfo);
            return base.OnCreateOptionsMenu (menu);
        }

那么谁来处理这个系统搜索呢,我们看一下MainActivity的注解:[MetaData ("android.app.default_searchable", Value = "employeedirectory.android.SearchActivity")],直接将搜索处理动作指向了employeedirectory.android.SearchActivity类。

下面转发一个网友关于SearchView的总结:

前言

  searchview是安卓常用的搜索控件,网上有很多关于searchview都是java的,所以我参看xamaroin官网的一些demo总结一些方法。

导读

  1.如何创建一个searchview

  2.searchview的常用事件

  3.如何使用searchview

正文

  1.如何创建一个searchview

   创建一个searchview很简单,我们只需要新建一个布局文件,然后重写OnCreateOptionsMenu即可。

 技术分享

 如图所示,在Resources文件夹下新建一个文件夹名为Menu,在文件夹内新建一个xml布局文件。

 search.xml

<?xml version="1.0" encoding="utf-8" ?> 
<menu xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:id="@+id/search"
         android:title="Search"
         android:showAsAction="ifRoom"
         android:actionViewClass="android.widget.SearchView" />
  </menu>

 这样我们的布局文件就建好了,然后重写OnCreateOptionsMenu即可。

public override bool OnCreateOptionsMenu(IMenu menu)
        {
            MenuInflater.Inflate(Resource.Menu.search, menu);
            var searchView = (SearchView)menu.FindItem(Resource.Id.search).ActionView;return true;
        }

  最终效果图如下:

技术分享

 这样我们的searchview就算创建完成了。

   2.searchview的主要方法

   searchview的作用无非就是进行搜索,那我们常用到的事件就包括这几种:

 OnQueryTextListener   查询内容发生改变时调用

 OnCloseListener       关闭searchview时调用

 onQueryTextChange     查询的文本字段的新内容,相当于边写边搜

 onQueryTextSubmit     查询要提交的查询内容

 onClose            关闭searchview时触发的事件

 下面就用代码演示这几种事件的使用用法

 3.如何使用searchview

 调用这个方法首先要继承searchview类

public class Activity1 : SearchView.IOnQueryTextListener,SearchView.IOnCloseListener

  这里同时继承了IOnQueryTextListener与IOnCloseListener,如果不继承这个类就会提示类型无法转换的错误。

 继承之后我们就可以设置监听事件,同样是重写OnCreateOptionsMenu。

searchView.SetOnQueryTextListener(this);
searchView.SetOnCloseListener(this);

 设置完监听事件即可重写这几种方法

        public bool OnQueryTextChange(string newText)
        {
      
            return true;
        }

        public bool OnQueryTextSubmit(string query)
        {
            
            return true;
        }

        public bool OnClose()
        {
           
            return true;
        }

  图片演示

技术分享

技术分享

 

xamarin android searchview的一些用法

抱歉!评论已关闭.