Xamarin.Android中使用ResideMenu實現側滑菜單


    上次使用Xamarin.Android實現了一個比較常用的功能PullToRefresh,詳情見:Xamarin. Android實現下拉刷新功能

這次將實現另外一個手機App中比較常用的功能:側滑菜單。通過搜索,發現有很多側滑菜單,有仿手機QQ的側滑菜單,有折疊的側滑菜單,有SlidingMenu等,不過我還是比較喜歡 ResideMenu實現的效果,所以想通過Xamarin.Android的綁定實現該效果。這次實現該菜單遇到的問題比較多,花的時間也較多,花了三四個晚上才解決所有的問題。下面是詳細的實現步驟:

作者:loyldg 出處:http://www.cnblogs.com/loyldg/ 本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接。如有問題,可以郵件:loyldg@126.com 聯系我,非常感謝。

一、生成ResideMenu.dll

  1. 從網上下載ResideMenu的源代碼,我是下載的master分支的代碼,如果有需要可以下載其他分支的代碼。
  2. 導入到MyEclispe中,編譯一下(默認情況導入后會自動編譯)。
  3. 打開ResideMenu所在的目錄,將res目錄和生成的bin目錄里的內容打包成residemenu.zip。
  4. 在Visual Studio中新建一個Android Binding 項目,命名為ResideMenuLib。
  5. 在ResideMenuLib項目的Jars目錄里添加residemenu.zip和nineoldandroids-library-2.4.0.jar(在ResideMenu項目的libs目錄里),將residemenu.zip的生成操作設置為LibraryProjectZip,nineoldandroids-library-2.4.0.jar的生成操作設置為ReferenceJar,注意是ReferenceJar而不是EmbeddedReferenceJar。
  6. 編譯ResideMenuLib項目。

二、使用ResideMenu

  普通方式使用就不貼代碼了,簡單描述一下使用步驟,詳細的代碼請看Mvvmcross中使用ResideMenu

  1. 在Visual Studio中新建ResideMenuDemo項目。
  2. 分別添加對ResideMenuLib和NineOldAndroids的引用,NineOldAndroids直接引用Nuget里面的就ok,否則需要重新綁定NineOldAndroids,然后添加引用。
  3. 將Java的ResideMenuDemo(與ResideMenu在同一目錄)轉換為C#的即可。
  4. 編譯C#版的ResideMenuDemo,然后運行。

三、MvvmCross中使用ResideMenu

其實在MvvmCross中使用ResideMenu和普通方式使用差不多,只是MvvmCross中需要設置對應的ViewModel。需要注意的是,使用低版本SDK時需要引用Xamarin.Android.Support.v4.dll,下面是具體的步驟:

  1. 新建一個可以移植的類庫項目MvxResideMenu.Core,通過Nuget添加對MvvmCross的引用
  2. 添加ViewModel的代碼
  3. 新建Android項目MvxResideMenu.Droid,刪除自動生成的MainActivity,通過Nuget添加對MvvmCross和NineOldAndroids的引用
  4. 編寫對應的View和相關布局代碼
  5. 編譯並運行

  下面是代碼:

ViewModel的代碼:

public class BaseViewModel : MvxViewModel
    {
        private string _hello = "Hello MvvmCross BaseViewModel";

        public string Hello
        {
            get { return _hello; }
            set
            {
                _hello = value;
                RaisePropertyChanged(() => Hello);
            }
        }

        private string _title;

        public string Title
        {
            get { return _title; }
            set
            {
                _title = value;
                RaisePropertyChanged(() => Title);
            }
        }
    }

    public class MainViewModel : BaseViewModel
    {
        public MainViewModel()
        {
            Hello = "Hello MvvmCross MainViewModel";
            Title = "MainViewModel";
        }
    }

    public class FirstViewModel
        : BaseViewModel
    {     
        public FirstViewModel()
        {
            Hello = "Hello MvvmCross FirstViewModel";
            Title = "FirstViewModel";
        }
    }

    public class SecondViewModel : BaseViewModel
    {
       public SecondViewModel()
        {
            Hello = "Hello MvvmCross SecondViewModel";
            Title = "SecondViewModel";
        }
    }

    public class ThirdViewModel : BaseViewModel
    {
        public ThirdViewModel()
        {
            Hello = "Hello MvvmCross ThirdViewModel";
            Title = "ThirdViewModel";
        }
    }

    public class FourthViewModel : BaseViewModel
    {
        public FourthViewModel()
        {
            Hello = "Hello MvvmCross FourthViewModel";
            Title = "FourthViewModel";
        }
}
View Code

View的代碼:

[Activity(Label = "View for MainViewModel")]
    public class MainView : MvxActivity<MainViewModel>, View.IOnClickListener
    {
        private ResideMenu _resideMenu;
        private ResideMenuItem _firstMenuItem;
        private ResideMenuItem _secondMenuItem;
        private ResideMenuItem _thirdMenuItem;
        private ResideMenuItem _fourthMenuItem;

        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);
            SetContentView(Resource.Layout.Main);
            InitMenus();
            ChangeFragment(new FirstView() { ViewModel = new FirstViewModel() });
        }

        #region Overrides of Activity

        public override bool DispatchTouchEvent(MotionEvent ev)
        {
            return _resideMenu.DispatchTouchEvent(ev);
        }

        #endregion

        private void InitMenus()
        {
            _resideMenu = new ResideMenu(this);
            _resideMenu.SetBackground(Resource.Drawable.background2);
            _resideMenu.AttachToActivity(this);
            _resideMenu.SetScaleValue(0.6f);
            
            _firstMenuItem=new ResideMenuItem(this,Resource.Drawable.mail,"First View");
            _secondMenuItem=new ResideMenuItem(this,Resource.Drawable.home,"Second View");
            _thirdMenuItem=new ResideMenuItem(this,Resource.Drawable.download,"Third View");
            _fourthMenuItem=new ResideMenuItem(this,Resource.Drawable.weather,"Fourth View");

            _firstMenuItem.SetOnClickListener(this);
            _secondMenuItem.SetOnClickListener(this);
            _thirdMenuItem.SetOnClickListener(this);
            _fourthMenuItem.SetOnClickListener(this);

            _resideMenu.AddMenuItem(_firstMenuItem, ResideMenu.DirectionLeft);
            _resideMenu.AddMenuItem(_secondMenuItem, ResideMenu.DirectionLeft);
            _resideMenu.AddMenuItem(_thirdMenuItem, ResideMenu.DirectionLeft);

            _resideMenu.AddMenuItem(_fourthMenuItem, ResideMenu.DirectionRight);
        }

        private void ChangeFragment(MvxFragment fragment)
        {
            _resideMenu.ClearIgnoredViewList();
            FragmentManager
                .BeginTransaction()
                .Replace(Resource.Id.main_fragment, fragment, "fragment")
                .SetTransition(FragmentTransit.FragmentFade)
                .Commit();
            ViewModel.Title = (fragment.ViewModel as BaseViewModel).Title;
        }

        #region Implementation of IOnClickListener

        public void OnClick(View v)
        {
            if (v == _firstMenuItem) {
                ChangeFragment(new FirstView(){ViewModel = new FirstViewModel()});
            }
            else if (v == _secondMenuItem)
            {
                ChangeFragment(new SecondView() { ViewModel = new SecondViewModel() });
            }
            else if (v == _thirdMenuItem)
            {
                ChangeFragment(new ThirdView() { ViewModel = new ThirdViewModel() });
            }
            else if (v == _fourthMenuItem)
            {
                ChangeFragment(new FourthView() { ViewModel = new FourthViewModel() });
            }
            _resideMenu.CloseMenu();
        }

        #endregion
    }
    public class FirstView : MvxFragment<FirstViewModel>
    {
        public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
        {
            base.OnCreateView(inflater, container, savedInstanceState);
            return this.BindingInflate(Resource.Layout.FirstView, null);
        }
    }
    public class SecondView : MvxFragment<SecondViewModel>
    {
        public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
        {
            base.OnCreateView(inflater, container, savedInstanceState);
            return this.BindingInflate(Resource.Layout.SecondView, null);
        }
    }

    public class ThirdView : MvxFragment<ThirdViewModel>
    {
        public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
        {
            base.OnCreateView(inflater, container, savedInstanceState);
            return this.BindingInflate(Resource.Layout.ThirdView, null);
        }
    }

    public class FourthView : MvxFragment<FourthViewModel>
    {
        public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
        {
            base.OnCreateView(inflater, container, savedInstanceState);
            return this.BindingInflate(Resource.Layout.FourthView, null);
        }
    }

    public class MenuOnClickListener : Java.Lang.Object, View.IOnClickListener
    {
        public ResideMenu Menu { get; set; }
        public bool IsLeft { get; set; }

        public MenuOnClickListener(ResideMenu menu, bool isLeft)
        {
            Menu = menu;
            IsLeft = isLeft;
        }

        #region Implementation of IOnClickListener

        public void OnClick(View v)
        {
            Menu.OpenMenu(IsLeft ? 0 : 1);
        }

        #endregion
    }
View Code

布局文件的代碼:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:local="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:background="@android:color/white"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
  <LinearLayout
          android:orientation="vertical"
          android:layout_width="fill_parent"
          android:layout_height="wrap_content"
          android:id="@+id/layout_top">
    <TextView
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:padding="7dp"
                 android:text="ReSideMenu MvvmCross DEMO"
                 android:textSize="24sp"
                 android:textColor="#999999"
                 local:MvxBind="Text Title"
                 android:layout_gravity="center"/>
    <ImageView
                android:layout_width="match_parent"
                android:layout_height="3dp"
                android:background="#FF21A549"/>
  </LinearLayout>
  <FrameLayout
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:orientation="vertical"
          android:id="@+id/main_fragment">
  </FrameLayout>
</LinearLayout>
View Code

幾個Fragment對應View的布局代碼都是一樣的,這里就只給出一個Fragment的代碼

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:local="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
  <EditText
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:textSize="40dp"
    local:MvxBind="Text Hello"
    />
  <TextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:textSize="40dp"
    local:MvxBind="Text Hello"
    />
</LinearLayout>
View Code

運行效果如下:

四、遇到的問題以及總結

1.現象:綁定的ResideMenu對象的MenuListener屬性只有get方法,沒有set方法,不能設置值。

原因:不太清楚,知道的朋友可以說一下。我的理解是set方法引用了R.java里的內容,而R.java生成的時間晚於綁定代碼的生成,所以導致了找不到引用的問題。

解決方法:在Metadata.xml文件里增加下面的代碼,手動增加一個方法。

<add-node path="/api/package[@name='com.special.ResideMenu']/class[@name='ResideMenu']">
    <method name="setMenuListener" return="void" abstract="false" native="false" synchronized="false" static="false" final="false" deprecated="not deprecated" visibility="public" >
      <parameter name="listener" type="com.special.ResideMenu.ResideMenu.OnMenuListener"/>
</method> 
 </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窗口,可以發現如下信息:

Failed resolving Lcom/special/ResideMenu/ResideMenu$2; interface 264 'Lcom/nineoldandroids/animation/Animator$AnimatorListener;',通過提示,我們發現錯誤原因是不能解析nineoldandroids.jar里的Animator.AnimatorListener接口

解決方法:在Nuget里添加NineOldAndroids的引用。這里有一點還沒弄明白,ResideMenuLib項目已經包含了引用的NineOldAndroids.jar,正常情況下應該不需要再次添加引用了。

4.現象:運行時滑動界面無法顯示側滑菜單

原因:未重寫DispatchTouchEvent方法

解決方法:添加如下代碼即可

public override bool DispatchTouchEvent(MotionEvent ev)
{
      return _resideMenu.DispatchTouchEvent(ev);
}

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的庫,有時間我整理一下發出來。

作者:loyldg 出處:http://www.cnblogs.com/loyldg/ 本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接。如有問題,可以郵件:loyldg@126.com 聯系我,非常感謝。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM