Xamarin.Android中使用ResideMenu实现侧滑菜单


Xamarin.Android中使用ResideMenu实现侧滑菜单

Xamarin.Android


这次将实现另外一个手机App中比较常用的功能:侧滑菜单。通过搜索,发现有很多侧滑菜单,有仿手机QQ的侧滑菜单,有折叠的侧滑菜单,有SlidingMenu等,不过我还是比较喜欢 ResideMenu实现的效果,所以想通过Xamarin.Android的绑定实现该效果。这次实现该菜单遇到的问题比较多,花的时间也较多,花了三四个晚上才解决所有的问题。下面是详细的实现步骤:

一、生成ResideMenu.dll

  • 从网上下载ResideMenu的源代码,我是下载的master分支的代码,如果有需要可以下载其他分支的代码。
  • 导入到MyEclispe中,编译一下(默认情况导入后会自动编译)。
  • 打开ResideMenu所在的目录,将res目录和生成的bin目录里的内容打包成residemenu.zip。
  • 在Visual Studio中新建一个Android Binding 项目,命名为ResideMenuLib。
  • 在ResideMenuLib项目的Jars目录里添加residemenu.zip和nineoldandroids-library-2.4.0.jar(在ResideMenu项目的libs目录里),将residemenu.zip的生成操作设置为LibraryProjectZip,nineoldandroids-library-2.4.0.jar的生成操作设置为ReferenceJar,注意是ReferenceJar而不是EmbeddedReferenceJar。
  • 编译ResideMenuLib项目。

二、使用ResideMenu

  普通方式使用就不贴代码了,简单描述一下使用步骤,详细的代码请看Mvvmcross中使用ResideMenu

  • 在Visual Studio中新建ResideMenuDemo项目。
  • 分别添加对ResideMenuLib和NineOldAndroids的引用,NineOldAndroids直接引用Nuget里面的就ok,否则需要重新绑定NineOldAndroids,然后添加引用。
  • 将Java的ResideMenuDemo(与ResideMenu在同一目录)转换为C#的即可。
  • 编译C#版的ResideMenuDemo,然后运行。

三、MvvmCross中使用ResideMenu

其实在MvvmCross中使用ResideMenu和普通方式使用差不多,只是MvvmCross中需要设置对应的ViewModel。需要注意的是,使用低版本SDK时需要引用Xamarin.Android.Support.v4.dll,下面是具体的步骤:

  • 新建一个可以移植的类库项目MvxResideMenu.Core,通过Nuget添加对MvvmCross的引用
  • 添加ViewModel的代码
  • 新建Android项目MvxResideMenu.Droid,删除自动生成的MainActivity,通过Nuget添加对MvvmCross和NineOldAndroids的引用
  • 编写对应的View和相关布局代码
  • 编译并运行

下面是代码:

ViewModel的代码:

  1. public class BaseViewModel : MvxViewModel
  2. {
  3. private string _hello = "Hello MvvmCross BaseViewModel";
  4. public string Hello
  5. {
  6. get { return _hello; }
  7. set
  8. {
  9. _hello = value;
  10. RaisePropertyChanged(() => Hello);
  11. }
  12. }
  13. private string _title;
  14. public string Title
  15. {
  16. get { return _title; }
  17. set
  18. {
  19. _title = value;
  20. RaisePropertyChanged(() => Title);
  21. }
  22. }
  23. }
  24. public class MainViewModel : BaseViewModel
  25. {
  26. public MainViewModel()
  27. {
  28. Hello = "Hello MvvmCross MainViewModel";
  29. Title = "MainViewModel";
  30. }
  31. }
  32. public class FirstViewModel
  33. : BaseViewModel
  34. {
  35. public FirstViewModel()
  36. {
  37. Hello = "Hello MvvmCross FirstViewModel";
  38. Title = "FirstViewModel";
  39. }
  40. }
  41. public class SecondViewModel : BaseViewModel
  42. {
  43. public SecondViewModel()
  44. {
  45. Hello = "Hello MvvmCross SecondViewModel";
  46. Title = "SecondViewModel";
  47. }
  48. }
  49. public class ThirdViewModel : BaseViewModel
  50. {
  51. public ThirdViewModel()
  52. {
  53. Hello = "Hello MvvmCross ThirdViewModel";
  54. Title = "ThirdViewModel";
  55. }
  56. }
  57. public class FourthViewModel : BaseViewModel
  58. {
  59. public FourthViewModel()
  60. {
  61. Hello = "Hello MvvmCross FourthViewModel";
  62. Title = "FourthViewModel";
  63. }
  64. }

View的代码:

  1. [Activity(Label = "View for MainViewModel")]
  2. public class MainView : MvxActivity<MainViewModel>, View.IOnClickListener
  3. {
  4. private ResideMenu _resideMenu;
  5. private ResideMenuItem _firstMenuItem;
  6. private ResideMenuItem _secondMenuItem;
  7. private ResideMenuItem _thirdMenuItem;
  8. private ResideMenuItem _fourthMenuItem;
  9. protected override void OnCreate(Bundle bundle)
  10. {
  11. base.OnCreate(bundle);
  12. SetContentView(Resource.Layout.Main);
  13. InitMenus();
  14. ChangeFragment(new FirstView() { ViewModel = new FirstViewModel() });
  15. }
  16. #region Overrides of Activity
  17. public override bool DispatchTouchEvent(MotionEvent ev)
  18. {
  19. return _resideMenu.DispatchTouchEvent(ev);
  20. }
  21. #endregion
  22. private void InitMenus()
  23. {
  24. _resideMenu = new ResideMenu(this);
  25. _resideMenu.SetBackground(Resource.Drawable.background2);
  26. _resideMenu.AttachToActivity(this);
  27. _resideMenu.SetScaleValue(0.6f);
  28. _firstMenuItem=new ResideMenuItem(this,Resource.Drawable.mail,"First View");
  29. _secondMenuItem=new ResideMenuItem(this,Resource.Drawable.home,"Second View");
  30. _thirdMenuItem=new ResideMenuItem(this,Resource.Drawable.download,"Third View");
  31. _fourthMenuItem=new ResideMenuItem(this,Resource.Drawable.weather,"Fourth View");
  32. _firstMenuItem.SetOnClickListener(this);
  33. _secondMenuItem.SetOnClickListener(this);
  34. _thirdMenuItem.SetOnClickListener(this);
  35. _fourthMenuItem.SetOnClickListener(this);
  36. _resideMenu.AddMenuItem(_firstMenuItem, ResideMenu.DirectionLeft);
  37. _resideMenu.AddMenuItem(_secondMenuItem, ResideMenu.DirectionLeft);
  38. _resideMenu.AddMenuItem(_thirdMenuItem, ResideMenu.DirectionLeft);
  39. _resideMenu.AddMenuItem(_fourthMenuItem, ResideMenu.DirectionRight);
  40. }
  41. private void ChangeFragment(MvxFragment fragment)
  42. {
  43. _resideMenu.ClearIgnoredViewList();
  44. FragmentManager
  45. .BeginTransaction()
  46. .Replace(Resource.Id.main_fragment, fragment, "fragment")
  47. .SetTransition(FragmentTransit.FragmentFade)
  48. .Commit();
  49. ViewModel.Title = (fragment.ViewModel as BaseViewModel).Title;
  50. }
  51. #region Implementation of IOnClickListener
  52. public void OnClick(View v)
  53. {
  54. if (v == _firstMenuItem) {
  55. ChangeFragment(new FirstView(){ViewModel = new FirstViewModel()});
  56. }
  57. else if (v == _secondMenuItem)
  58. {
  59. ChangeFragment(new SecondView() { ViewModel = new SecondViewModel() });
  60. }
  61. else if (v == _thirdMenuItem)
  62. {
  63. ChangeFragment(new ThirdView() { ViewModel = new ThirdViewModel() });
  64. }
  65. else if (v == _fourthMenuItem)
  66. {
  67. ChangeFragment(new FourthView() { ViewModel = new FourthViewModel() });
  68. }
  69. _resideMenu.CloseMenu();
  70. }
  71. #endregion
  72. }
  73. public class FirstView : MvxFragment<FirstViewModel>
  74. {
  75. public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
  76. {
  77. base.OnCreateView(inflater, container, savedInstanceState);
  78. return this.BindingInflate(Resource.Layout.FirstView, null);
  79. }
  80. }
  81. public class SecondView : MvxFragment<SecondViewModel>
  82. {
  83. public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
  84. {
  85. base.OnCreateView(inflater, container, savedInstanceState);
  86. return this.BindingInflate(Resource.Layout.SecondView, null);
  87. }
  88. }
  89. public class ThirdView : MvxFragment<ThirdViewModel>
  90. {
  91. public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
  92. {
  93. base.OnCreateView(inflater, container, savedInstanceState);
  94. return this.BindingInflate(Resource.Layout.ThirdView, null);
  95. }
  96. }
  97. public class FourthView : MvxFragment<FourthViewModel>
  98. {
  99. public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
  100. {
  101. base.OnCreateView(inflater, container, savedInstanceState);
  102. return this.BindingInflate(Resource.Layout.FourthView, null);
  103. }
  104. }
  105. public class MenuOnClickListener : Java.Lang.Object, View.IOnClickListener
  106. {
  107. public ResideMenu Menu { get; set; }
  108. public bool IsLeft { get; set; }
  109. public MenuOnClickListener(ResideMenu menu, bool isLeft)
  110. {
  111. Menu = menu;
  112. IsLeft = isLeft;
  113. }
  114. #region Implementation of IOnClickListener
  115. public void OnClick(View v)
  116. {
  117. Menu.OpenMenu(IsLeft ? 0 : 1);
  118. }
  119. #endregion
  120. }

布局文件的代码:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:local="http://schemas.android.com/apk/res-auto"
  4. android:orientation="vertical"
  5. android:background="@android:color/white"
  6. android:layout_width="fill_parent"
  7. android:layout_height="fill_parent">
  8. <LinearLayout
  9. android:orientation="vertical"
  10. android:layout_width="fill_parent"
  11. android:layout_height="wrap_content"
  12. android:id="@+id/layout_top">
  13. <TextView
  14. android:layout_width="wrap_content"
  15. android:layout_height="wrap_content"
  16. android:padding="7dp"
  17. android:text="ReSideMenu MvvmCross DEMO"
  18. android:textSize="24sp"
  19. android:textColor="#999999"
  20. local:MvxBind="Text Title"
  21. android:layout_gravity="center"/>
  22. <ImageView
  23. android:layout_width="match_parent"
  24. android:layout_height="3dp"
  25. android:background="#FF21A549"/>
  26. </LinearLayout>
  27. <FrameLayout
  28. android:layout_width="match_parent"
  29. android:layout_height="match_parent"
  30. android:orientation="vertical"
  31. android:id="@+id/main_fragment">
  32. </FrameLayout>
  33. </LinearLayout>

几个Fragment对应View的布局代码都是一样的,这里就只给出一个Fragment的代码

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:local="http://schemas.android.com/apk/res-auto"
  4. android:orientation="vertical"
  5. android:layout_width="fill_parent"
  6. android:layout_height="fill_parent">
  7. <EditText
  8. android:layout_width="fill_parent"
  9. android:layout_height="wrap_content"
  10. android:textSize="40dp"
  11. local:MvxBind="Text Hello"
  12. />
  13. <TextView
  14. android:layout_width="fill_parent"
  15. android:layout_height="wrap_content"
  16. android:textSize="40dp"
  17. local:MvxBind="Text Hello"
  18. />
  19. </LinearLayout>

运行效果如下:
280054067181304.gif-1244.8kB

四、遇到的问题以及总结

  • 1.现象:绑定的ResideMenu对象的MenuListener属性只有get方法,没有set方法,不能设置值。

原因:不太清楚,知道的朋友可以说一下。我的理解是set方法引用了R.java里的内容,而R.java生成的时间晚于绑定代码的生成,所以导致了找不到引用的问题。

解决方法:在Metadata.xml文件里增加下面的代码,手动增加一个方法。

  1. <add-node path="/api/package[@name='com.special.ResideMenu']/class[@name='ResideMenu']">
  2. <method name="setMenuListener" return="void" abstract="false" native="false" synchronized="false" static="false" final="false" deprecated="not deprecated" visibility="public" >
  3. <parameter name="listener" type="com.special.ResideMenu.ResideMenu.OnMenuListener"/>
  4. </method>
  5. </add-node>
  • 2.现象:编译能够通过,运行时报Java.Lang.NullPointerException异常

at com.special.ResideMenu.ResideMenu.setBackground(ResideMenu.java:143),通过跟踪发现要设置背景的对象为空,所以导致了空引用异常。

原因:residemenu.jar文件内包含了R.java的代码,最开始我是手动导出的residemenu.jar,将R.java的代码一起导出了。这样会导致ResideMenu类里的所有findViewById方法返回null,解决这个问题花的时间最长,差不多过了两天才发现。

解决方法:residemenu.jar文件里不要包含R.java的代码。

  • 3.现象:编译能够通过,运行时报Java.Lang.NoClassDefFoundError: com.special.ResideMenu.ResideMenu$2异常

原因:查看Visual Studio的Output窗口,可以发现如下信息:

resolving Lcom/special/ResideMenu/ResideMenu$2; interface 264 'Lcom/nineoldandroids/animation/Animator$AnimatorListener;',通过提示,我们发现错误原因是不能解析nineoldandroids.jar里的Animator.AnimatorListener接口

解决方法:在Nuget里添加NineOldAndroids的引用。这里有一点还没弄明白,ResideMenuLib项目已经包含了引用的NineOldAndroids.jar,正常情况下应该不需要再次添加引用了。

  • 4.现象:运行时滑动界面无法显示侧滑菜单

原因:未重写DispatchTouchEvent方法

解决方法:添加如下代码即可

  1. public override bool DispatchTouchEvent(MotionEvent ev)
  2. {
  3. return _resideMenu.DispatchTouchEvent(ev);
  4. }
  • 5.MvvmCross中使用ResideMenu稍微有一点问题,每次切换Fragment时需要手动指定Fragment的ViewModel。如果需要实现ViewModel的单例,还需要额外处理,并且ViewModel的构造函数带有注入参数时,处理起来更麻烦。

  • 6.网上也有ResideMenu的绑定,见https://github.com/nishanil/XResideMenu ,本来我是想直接用这个绑定好的ResideMenu的,但是我用最新的java版residemenu生成的代码替换此绑定里ResideMenu.aar对应的文件后,重新生成后的dll还是有问题,所以就重新绑定了一个。网上这个库也说了绑定的时候有点问题,他给出了两种解决方案:

      1)将java库的package从大写修改为小写,并将AndroidManifest.xml文件里的名称也修改为小写,然后重新编译

      2)手动修改VS生成的R.java文件里的package名称,然后重新运行就可以了,修改之后不能重新生成和清理解决方案

上面说的问题只存在于monodroid-4.18以前的版本,4.18之后已修复了大小写问题的BUG

  • 7.最近绑定了一些java的库,有时间我整理一下发出来。

280101139195174.png-22.2kB

作者:loyldg
原文地址:http://www.cnblogs.com/loyldg/p/Xamarin-Android-ResideMenu.html

分享到