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

上次使用Xamarin.Android实现了一个比较常用的功能PullToRefresh,详情见:Xamarin. Android实现下拉刷新功能

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

作者:loyldg 出处:http://www.cnblogs.com/loyldg/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。如有问题,可以邮件:[email protected] 联系我,非常感谢。

一、生成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的代码:

[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
    }

布局文件的代码:

<?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>

几个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>

运行效果如下:

四、遇到的问题以及总结

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/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。如有问题,可以邮件:[email protected] 联系我,非常感谢。

时间: 2024-08-02 10:56:00

Xamarin.Android中使用ResideMenu实现侧滑菜单的相关文章

Android 实现形态各异的双向侧滑菜单 自定义控件来袭

1.概述 关于自定义控件侧滑已经写了两篇了~~今天决定把之前的单向改成双向,当然了,单纯的改动之前的代码也没意思,今天不仅 会把之前的单向改为双向,还会多添加一种侧滑效果,给大家带来若干种形态各异的双向侧滑菜单,不过请放心,代码会很简单~~然后根据这若干种,只要你喜 欢,相信你可以打造任何绚(bian)丽(tai)效果的双向侧滑菜单~~ 首先回顾一下,之前写过的各种侧滑菜单,为了不占据篇幅,就不贴图片了: 1.最普通的侧滑效果,请参考:Android 自定义控件打造史上最简单的侧滑菜单 2.仿Q

Android 高仿 QQ5.0 侧滑菜单效果 自定义控件来袭【学习鸿洋_视频博客笔记总结】

学习鸿洋博客:http://blog.csdn.net/lmj623565791/article/details/39257409 学习鸿洋视频:慕课网视频 看看Android 高仿 QQ5.0 侧滑菜单效果 自定义控件实现效果: 技术上,继承HorizontalScrollView 加上自定义ViewGroup来实现: 1.onMeasure:决定内部View(子View)的宽和高,以及自己的宽和高 2.onLayout:决定子View的放置位置 3.onTouchEvent[监听动作] 自定

Android 实现形态各异的双向侧滑菜单 自定义控件来袭(转载)

1.概述 关于自定义控件侧滑已经写了两篇了~~今天决定把之前的单向改成双向,当然了,单纯的改动之前的代码也没意思,今天不仅会把之前的单向改为双向,还会多添加一种侧滑效果,给大家带来若干种形态各异的双向侧滑菜单,不过请放心,代码会很简单~~然后根据这若干种,只要你喜欢,相信你可以打造任何绚(bian)丽(tai)效果的双向侧滑菜单~~ 2.目标效果 1.最普通的双向侧滑 是不是很模糊,嗯,没办法,电脑显卡弱.... 2.抽屉式双向侧滑 3.菜单在内容之下的双向侧滑 凑合看下,文章最后会提供源码下载

Android 使用DrawerLayout快速实现侧滑菜单

一.概述 DrawerLayout是一个可以方便的实现Android侧滑菜单的组件,我最近开发的项目中也有一个侧滑菜单的功能,于是DrawerLayout就派上用场了.如果你从未使用过DrawerLayout,那么本篇博客将使用一个简单的案例带你迅速掌握DrawerLayout的用法. 二.效果图 三.代码实现 主布局activity_main.xml <?xml version="1.0" encoding="utf-8"?> <LinearLa

Android实现网易新闻客户端侧滑菜单(二)

前面已经讲过通过三方开源库SlideMenu来实现这种效果,请参考Android实现网易新闻客户端侧滑菜单(一) 今天通过自定义View来实现这种功能 代码如下: SlideMenu.java <span style="font-family:SimSun;font-size:14px;">package com.jackie.slidemenu.view; import android.content.Context; import android.graphics.Ca

一个Xamarin.Android中与intent有关的&quot;动人&quot;爱情故事

一个Xamarin.Android中与intent有关的"动人"爱情故事 第一步,写项目中的第一个界面. <?xml version="1.0" encoding =" utf-8" ?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation= "vertical &q

Xamarin.Android中使用android:onClick=&quot;xxx&quot;属性

在原生Android开发中,为一个View增加点击事件,有三种方式: 1.使用匿名对象 ((ImageButton) findViewById(R.id.music_cover)) .setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { } }); 我们知道,View.OnClickListener是一个接口,接口是不能直接实例化的,因此上述是使用匿名对象的原理来实现的: 2

Android 高仿 QQ5.0 侧滑菜单效果 自定义控件来袭

1.原理分析 首先对比一下我们上篇的实现距离QQ的效果还有多远: 差距还是蛮大的 区别1.QQ的内容区域会伴随菜单的出现而缩小 区别2.QQ的侧滑菜单给人的感觉是隐藏在内容的后面,而不是拖出来的感觉 区别3.QQ的侧滑菜单有一个缩放以及透明度的效果~ 那么我们如何能做到呢: 对于区别1:这个好办,我们可以在滑动的时候,不断的改变内容区域的大小:如何改变呢?我们在菜单出现的整个过程中,不断记录菜单显示的宽度与其总宽度的比值,是个从0到1的过程,然后把0~1转化为1~0.7(假设内容区域缩小至0.7

android:自定义HorizontalScrollView实现qq侧滑菜单

今天看了鸿洋_大神在慕课网讲的qq5.0侧滑菜单.学了不少的知识,同时也佩服鸿洋_大神思路的清晰. 看了教程课下也自己实现了一下.代码几乎完全相同  别喷我啊..没办法 o(︶︿︶)o 唉 像素不好 没办法 找不到好的制作gif的软件. 我们暂且称侧滑左边界面的为menu,右边为content 首先是menu的布局 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:androi