最近,由于正在做的一个应用中要用到侧滑菜单,所以通过查资料看视频,学习了一下自定义View,实现一个类似于QQ的侧滑菜单,顺便还将其封装为自定义组件,可以实现类似QQ的侧滑菜单和抽屉式侧滑菜单两种菜单。

下面先放上效果图:

我们这里的侧滑菜单主要是利用HorizontalScrollView来实现的,基本的思路是,一个布局中左边是菜单布局,右边是内容布局,默认情况下,菜单布局隐藏,内容布局显示,当我们向右侧滑,就会将菜单拉出来,而将内容布局的一部分隐藏,如下图所示:

下面我们就一步步开始实现一个侧滑菜单。

一、定义一个类SlidingMenu继承自HorizontalScrollView

我们后面所有的逻辑都会在这个类里面来写,我们先写上其构造方法

public class SlidingMenu extends HorizontalScrollView {/*** 在代码中使用new时会调用此方法* @param context*/public SlidingMenu(Context context) {this(context, null);}/*** 未使用自定义属性时默认调用* @param context* @param attrs*/public SlidingMenu(Context context, AttributeSet attrs) {//调用三个参数的构造方法this(context, attrs, 0);}/*** 当使用了自定义属性时会调用此方法* @param context* @param attrs* @param defStyleAttr*/public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}
}
复制代码

二、定义菜单布局文件

left_menu.xml文件代码如下

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_centerInParent="true"android:orientation="vertical"><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:drawablePadding="20dp"android:layout_marginLeft="20dp"android:gravity="left|center"android:drawableLeft="@mipmap/ic_launcher"android:text="第一个Item"/><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:drawablePadding="20dp"android:layout_marginLeft="20dp"android:gravity="left|center"android:drawableLeft="@mipmap/ic_launcher"android:text="第二个Item"/><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:drawablePadding="20dp"android:layout_marginLeft="20dp"android:gravity="left|center"android:drawableLeft="@mipmap/ic_launcher"android:text="第三个Item"/><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:drawablePadding="20dp"android:layout_marginLeft="20dp"android:gravity="left|center"android:drawableLeft="@mipmap/ic_launcher"android:text="第四个Item"/><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:drawablePadding="20dp"android:layout_marginLeft="20dp"android:gravity="left|center"android:drawableLeft="@mipmap/ic_launcher"android:text="第五个Item"/></LinearLayout>
</RelativeLayout>
复制代码

上面其实就是定义了一列TextView来模仿菜单的Item项

三、定义主布局文件,使用自定义的View

activity_main.xml文件代码如下

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"><!--自定义View--><com.codekong.qq_50_slidingmenu.view.SlidingMenuandroid:layout_width="match_parent"android:layout_height="match_parent"app:rightPadding="100dp"app:drawerType="false"android:scrollbars="none"><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="match_parent"android:orientation="horizontal"><!--引入菜单布局--><include layout="@layout/left_menu"/><!--内容布局--><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:background="@drawable/qq_bg"></LinearLayout></LinearLayout></com.codekong.qq_50_slidingmenu.view.SlidingMenu>
</RelativeLayout>
复制代码

四、自定义成员变量

我们定义一些成员变量以便于后面使用

//自定义View布局中内嵌的第一层的LinearLayout
private LinearLayout mWapper;
//菜单布局
private ViewGroup mMenu;
//内容布局
private ViewGroup mContent;
//屏幕宽度
private int mScreenWidth;
//菜单距屏幕右侧的距离,单位dp
private int mMenuRightPadding = 50;
//菜单的宽度
private int mMenuWidth;
//定义标志,保证onMeasure只执行一次
private boolean once = false;
//菜单是否是打开状态
private boolean isOpen = false;
复制代码

五、拿到屏幕宽度的像素值

因为目前为止,我们没有使用自定义属性,所以自定义View默认会调用两个参数的构造方法,但因为我们第一步中写构造方法时是在两个参数的构造方法中调用了三个参数的构造方法,所以,我们将获取屏幕宽度的代码写在三个参数的构造方法中,后面我们自定义属性后获取属性值也是在三个参数的构造方法中书写相应的逻辑。

/*** 当使用了自定义属性时会调用此方法* @param context* @param attrs* @param defStyleAttr*/
public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);//通过以下步骤拿到屏幕宽度的像素值WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);DisplayMetrics displayMetrics = new DisplayMetrics();windowManager.getDefaultDisplay().getMetrics(displayMetrics);mScreenWidth = displayMetrics.widthPixels;
}
复制代码

六、实现onMeasure()方法

onMeasure()方法是自定义View的正式第一步,它用来决定内部View(子View)的宽和高,以及自身的宽和高,下面是具体的代码逻辑。

/**
* 设置子View的宽和高
* 设置自身的宽和高
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {if (!once){once = true;mWapper = (LinearLayout) getChildAt(0);mMenu = (ViewGroup) mWapper.getChildAt(0);mContent = (ViewGroup) mWapper.getChildAt(1);//菜单和内容区域的高度都可以保持默认match_parent//菜单宽度 = 屏幕宽度 - 菜单距屏幕右侧的间距mMenuWidth = mMenu.getLayoutParams().width = mScreenWidth - mMenuRightPadding;mContent.getLayoutParams().width = mScreenWidth;//当设置了其中的菜单的宽高和内容区域的宽高之后,最外层的LinearLayout的mWapper就自动设置好了}super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
复制代码

七、实现onLayout()方法

onLayout()方法中主要是确定自定义View中子View放置的位置。下面是具体的代码。

/*** 通过设置偏移量将Menu隐藏* @param changed* @param l* @param t* @param r* @param b*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {super.onLayout(changed, l, t, r, b);if (changed){//布局发生变化时调用(水平滚动条向右移动menu的宽度,则正好将menu隐藏)this.scrollTo(mMenuWidth, 0);}
}
复制代码

这个比较好理解,由于我们使用的是水平滚动布局,我们默认情况下相当于将水平滚动条向右拖动菜单宽度的的距离,这样左边布局的菜单就正好被隐藏了。

八、onTouchEvent()方法

该方法主要处理内部内部View的移动,我们可以在其中写一些逻辑控制自定义View内部的滑动事件。 由于我们的自定义View是继承自HorizontalScrollView,我们不再处理按下和移动事件,保持HorizontalScrollView默认的即可,但对于手指抬起事件,我们需要根据手指在水平X轴方向的位移来做出打开菜单或关闭菜单的操作,所以我们的逻辑代码如下:

@Override
public boolean onTouchEvent(MotionEvent ev) {int action = ev.getAction();//按下和移动使用HorizontalScrollView的默认处理switch (action){case MotionEvent.ACTION_UP://隐藏在左边的位置int scrollX = getScrollX();if (scrollX > mMenuWidth / 2){//隐藏的部分较大, 平滑滚动不显示菜单this.smoothScrollTo(mMenuWidth, 0);isOpen = false;}else{//完全显示菜单this.smoothScrollTo(0, 0);isOpen = true;}return true;}return super.onTouchEvent(ev);
}
复制代码

上面最难理解的的就是getScrollX(),它指的是菜单隐藏未显示的那部分的宽度。关于详细的解释,大家可以去看源码,也可以去看看这篇博客** 图解Android View的scrollTo(),scrollBy(),getScrollX(), getScrollY()**,讲的很清楚。

其实到这一步为止,一个基本的侧滑菜单已经做出来了,下面我们将使用属性动画对我们的自定义View进行扩展,使其实现最开始展示的抽屉式侧滑菜单。

九、属性动画实现抽屉式侧滑

接下来我们实现抽屉式侧滑,抽屉式侧滑说白了就是,我们的菜单不是一点点被拉出来,而是看起来菜单就藏在页面的背后,随着我们向右滑动,一点点显露出来。 实现的思路很简单,当我们拖动时,我们让菜单布局的偏移量等于getScrollX()的值,也就是时刻把菜单隐藏在左边的部分向右偏移出来,这样我们看起来就像菜单藏在页面后面。如下图:

当我们左右滑动时会触发onScrollChanged()方法,我们在此处算出菜单需要的实时的偏移量,然后调用属性动画即可。 下面说说具体实现代码:

/*** 滚动发生时调用* @param l  getScrollX()* @param t* @param oldl* @param oldt*/
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {super.onScrollChanged(l, t, oldl, oldt);float scale = l * 1.0f / mMenuWidth;  //1 ~ 0//调用属性动画,设TranslationXmMenu.setTranslationX(mMenuWidth * scale);
}
复制代码

上面方法中的 l 就是上一步中提到的getScrollX()获得的值。当我们没有拉菜单时,菜单需要的偏移量就是整个菜单的宽度,当我们将菜单完全拉出时,菜单就不需要偏移量了,此时偏移量为0。此时我们的抽屉式侧滑就做好了。

注:此处的属性动画是在Android3.0之后引入的,如果需要兼容更早的版本,可以用相关的兼容库。

十、自定义属性实现灵活配置

自定义属性主要是方便使用者可以根据具体的场景实现不同的效果。比如,我们可以通过在xml文件中配置,实现菜单是普通的侧滑式还是抽屉式。在刚开始,我们在自定义View中将菜单打开时,菜单右边缘距离屏幕右边缘的值设置为50dp,我们通过自定义属性可以实现在xml文件中自己配置合适的值。

自定义属性按下面的步骤进行:

1 . 在 res/values 目录下新建attr.xml文件,文件中写入的内容如下:

<?xml version="1.0" encoding="utf-8"?>
<resources><attr name="rightPadding" format="dimension"/><attr name="drawerType" format="boolean"/><declare-styleable name="SlidingMenu"><attr name="rightPadding"/><attr name="drawerType"/></declare-styleable>
</resources>
复制代码

上面的比较好理解,我们先在上面声明两个自定义的属性的名称及其对应的类型,然后再在下面的具体的自定义样式中引用它们。上面两个自定义的属性分别是菜单拉开时右边缘距离屏幕右边缘的距离,以及菜单是否是抽屉式布局。

2 . 在自定义View类中获取到自定义的属性值。如果用户在xml文件中自定义了属性值,我们则获取,如果没有显式设置,则使用默认值即可。

顺便说一下,前面提到当我们使用自定义属性时,会默认调用三个参数的构造方法,所以我们获取自定义属性值的代码也是写在三个参数的构造方法中。

下面是获取属性值的代码:

/*** 当使用了自定义属性时会调用此方法* @param context* @param attrs* @param defStyleAttr*/
public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);//获取我们自定义的属性TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs,R.styleable.SlidingMenu, defStyleAttr, 0);int n = typedArray.getIndexCount();//遍历每一个属性for (int i = 0; i < n; i++) {int attr = typedArray.getIndex(i);switch (attr){//对我们自定义属性的值进行读取case R.styleable.SlidingMenu_rightPadding://如果在应用样式时没有赋值则使用默认值50,如果有值则直接读取mMenuRightPadding = typedArray.getDimensionPixelSize(attr,(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mMenuRightPadding, context.getResources().getDisplayMetrics()));break;case R.styleable.SlidingMenu_drawerType:isDrawerType = typedArray.getBoolean(attr, false);break;default:break;}}//释放,一定要释放typedArray.recycle();
}
复制代码

3 . 上面的代码中我们已经可以读取到设置的属性值,我们可以如下面一样设置自定义属性值:

<com.codekong.qq_50_slidingmenu.view.SlidingMenuandroid:layout_width="match_parent"android:layout_height="match_parent"app:rightPadding="100dp"app:drawerType="true"android:scrollbars="none"></com.codekong.qq_50_slidingmenu.view.SlidingMenu>
复制代码

4 . 使用属性值控制具体的逻辑,我们的rightPadding一旦获取到就会在onMeasure()方法中被设置,而drawerType被获取到就可以控制是否会调用onScrollChanged()中的代码。代码如下:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {if (!once){once = true;mWapper = (LinearLayout) getChildAt(0);mMenu = (ViewGroup) mWapper.getChildAt(0);mContent = (ViewGroup) mWapper.getChildAt(1);//菜单和内容区域的高度都可以保持默认match_parent//菜单宽度 = 屏幕宽度 - 菜单距屏幕右侧的间距mMenuWidth = mMenu.getLayoutParams().width = mScreenWidth - mMenuRightPadding;mContent.getLayoutParams().width = mScreenWidth;//当设置了其中的菜单的宽高和内容区域的宽高之后,最外层的LinearLayout的mWapper就自动设置好了}super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
复制代码
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {super.onScrollChanged(l, t, oldl, oldt);if (isDrawerType){float scale = l * 1.0f / mMenuWidth;  //1 ~ 0//调用属性动画,设TranslationXmMenu.setTranslationX(mMenuWidth * scale);}
}
复制代码

十一、给自定义View设置方法

对于我们的滑动式菜单,我们最常用的功能便是菜单的打开和关闭,所以我们可以在自定义View中定义这两个方法,方便我们的使用,下面是具体的代码:

/*** 打开菜单*/
public void openMenu(){if (!isOpen){this.smoothScrollTo(0, 0);isOpen = true;}
}/*** 关闭菜单*/
public void closeMenu(){if (isOpen){this.smoothScrollTo(mMenuWidth, 0);isOpen = false;}
}/*** 切换菜单*/
public void toggleMenu(){if (isOpen){closeMenu();}else{openMenu();}
}
复制代码

当我们在Activity中使用时可以按下面的代码使用:

SlidingMenu slidingMenu = (SlidingMenu) findViewById(R.id.sliding_menu);
slidingMenu.toggleMenu();
复制代码

最后面放上完整的自定义View的代码:

public class SlidingMenu extends HorizontalScrollView {//自定义View布局中内嵌的最外层的LinearLayoutprivate LinearLayout mWapper;//菜单布局private ViewGroup mMenu;//内容布局private ViewGroup mContent;//屏幕宽度private int mScreenWidth;//菜单距屏幕右侧的距离,单位dpprivate int mMenuRightPadding = 50;//菜单的宽度private int mMenuWidth;//定义标志,保证onMeasure只执行一次private boolean once = false;//菜单是否是打开状态private boolean isOpen = false;//是否是抽屉式private boolean isDrawerType = false;/*** 在代码中使用new时会调用此方法* @param context*/public SlidingMenu(Context context) {this(context, null);}/*** 未使用自定义属性时默认调用* @param context* @param attrs*/public SlidingMenu(Context context, AttributeSet attrs) {//调用三个参数的构造方法this(context, attrs, 0);}/*** 当使用了自定义属性时会调用此方法* @param context* @param attrs* @param defStyleAttr*/public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);//获取我们自定义的属性TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs,R.styleable.SlidingMenu, defStyleAttr, 0);int n = typedArray.getIndexCount();//遍历每一个属性for (int i = 0; i < n; i++) {int attr = typedArray.getIndex(i);switch (attr){//对我们自定义属性的值进行读取case R.styleable.SlidingMenu_rightPadding://如果在应用样式时没有赋值则使用默认值50,如果有值则直接读取mMenuRightPadding = typedArray.getDimensionPixelSize(attr,(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mMenuRightPadding, context.getResources().getDisplayMetrics()));break;case R.styleable.SlidingMenu_drawerType:isDrawerType = typedArray.getBoolean(attr, false);break;default:break;}}//释放typedArray.recycle();//通过以下步骤拿到屏幕宽度的像素值WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);DisplayMetrics displayMetrics = new DisplayMetrics();windowManager.getDefaultDisplay().getMetrics(displayMetrics);mScreenWidth = displayMetrics.widthPixels;}/*** 设置子View的宽和高* 设置自身的宽和高* @param widthMeasureSpec* @param heightMeasureSpec*/@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {if (!once){once = true;mWapper = (LinearLayout) getChildAt(0);mMenu = (ViewGroup) mWapper.getChildAt(0);mContent = (ViewGroup) mWapper.getChildAt(1);//菜单和内容区域的高度都可以保持默认match_parent//菜单宽度 = 屏幕宽度 - 菜单距屏幕右侧的间距mMenuWidth = mMenu.getLayoutParams().width = mScreenWidth - mMenuRightPadding;mContent.getLayoutParams().width = mScreenWidth;//当设置了其中的菜单的宽高和内容区域的宽高之后,最外层的LinearLayout的mWapper就自动设置好了}super.onMeasure(widthMeasureSpec, heightMeasureSpec);}/*** 通过设置偏移量将Menu隐藏* @param changed* @param l* @param t* @param r* @param b*/@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {super.onLayout(changed, l, t, r, b);if (changed){//布局发生变化时调用(水平滚动条向右移动menu的宽度,则正好将menu隐藏)this.scrollTo(mMenuWidth, 0);}}@Overridepublic boolean onTouchEvent(MotionEvent ev) {int action = ev.getAction();//按下和移动使用HorizontalScrollView的默认处理switch (action){case MotionEvent.ACTION_UP://隐藏在左边的位置int scrollX = getScrollX();if (scrollX > mMenuWidth / 2){//隐藏的部分较大, 平滑滚动不显示菜单this.smoothScrollTo(mMenuWidth, 0);isOpen = false;}else{//完全显示菜单this.smoothScrollTo(0, 0);isOpen = true;}return true;}return super.onTouchEvent(ev);}/*** 打开菜单*/public void openMenu(){if (!isOpen){this.smoothScrollTo(0, 0);isOpen = true;}}/*** 关闭菜单*/public void closeMenu(){if (isOpen){this.smoothScrollTo(mMenuWidth, 0);isOpen = false;}}/*** 切换菜单*/public void toggleMenu(){if (isOpen){closeMenu();}else{openMenu();}}/*** 滚动发生时调用* @param l  getScrollX()* @param t* @param oldl* @param oldt*/@Overrideprotected void onScrollChanged(int l, int t, int oldl, int oldt) {super.onScrollChanged(l, t, oldl, oldt);if (isDrawerType){float scale = l * 1.0f / mMenuWidth;  //1 ~ 0//调用属性动画,设TranslationXmMenu.setTranslationX(mMenuWidth * scale);}}
}
复制代码

Android自定义View之仿QQ侧滑菜单实现相关推荐

  1. 名片夹android布局代码,Android自定义布局实现仿qq侧滑部分代码

    自定义布局实现仿qq侧滑部分Android代码,供大家参考,具体内容如下 实现说明: 通过自定义布局实现: SlidingLayout继承于 HorizontalScrollView /** * Cr ...

  2. android仿qq布局,Android自定义布局实现仿qq侧滑部分代码

    自定义布局实现仿qq侧滑部分android代码,供大家参考,具体内容如下 实现说明: 通过自定义布局实现: slidinglayout继承于 horizontalscrollview /** * cr ...

  3. Android自定义View之仿QQ运动步数进度效果

    文章目录 前言 先看效果图 ![在这里插入图片描述](https://img-blog.csdnimg.cn/6e4ddec17933496ea4830fa08d8ffbe5.png?x-oss-pr ...

  4. android 自定义view实现仿QQ运动步数进度效果

    最近公司在策划一个新的项目,原型还没出来,再说这公司人都要走没了,估计又要找工作了,所以必须要学习,争取每个写个关于自定义view方面的,这样几个月积累下来,也能学习到东西,今天就带来简单的效果,就是 ...

  5. 04.自定义View(SlidingView仿QQ侧滑)

    感谢红橙Darren博主 布局文件中 <?xml version="1.0" encoding="utf-8"?> <LinearLayout ...

  6. Android自定义View实现仿QQ实现运动步数效果

    效果图: 1.attrs.xml中 <declare-styleable name="QQStepView"><attr name="outerColo ...

  7. Android仿QQ侧滑菜单

    先上效果图: GIF图有点模糊,源码已上传Github:Android仿QQ侧滑菜单 ####整体思路: 自定义ItemView的根布局(SwipeMenuLayout extends LinearL ...

  8. Android高仿QQ侧滑菜单

    文章目录 效果图 整体思路 实现过程 先分析SwipeMenuLayout 再分析下SwipeRecycleView 踩过的坑 后记 效果图 GIF图有点模糊,源码已上传Github:Android仿 ...

  9. Android 仿QQ侧滑菜单

    前言 集成方式 兼容超强的BaseRecyclerViewAdapterHelper 方法及属性介绍 THANKS 侧滑的雏形 测绘布局 onLayout onMeasure MotionEvent事 ...

最新文章

  1. java怎么让表格的字段相乘,excel表格怎么让数据相乘-如何在excel表格中设置乘法公式...
  2. Application Architecture Guide 2.0 (Chapter 7: Quality Attributes) Part 3
  3. php并发访问mysql_php并发对MYSQL造成压力的解决方法_PHP
  4. Mybatis-plus之RowBounds实现分页查询
  5. java g1 收集调优_Java性能调优:充分利用垃圾收集器
  6. 工业互联网智能智造-工业企业大数据汇聚通道-产品设计
  7. 大数据技术之 Kafka (第 3 章 Kafka 架构深入 ) Kafka 生产者
  8. ubuntu 下LAMP服务器环境搭建
  9. 董淳光SQLITE3使用总结-转
  10. 基于android个人记账本的软件设计,基于Android的个人记账软件的设计与实现-开题报告...
  11. 万字长文解析Redis数据倾斜与JD开源hotkey源码分析
  12. 纯音乐 -《抒情中国系列-烟雨江南》
  13. 完美修改证件照背景详细步骤,可以消除边界处的白边或者红边
  14. 关爱残障儿童,天使之翼爱心活动进行
  15. 精华|风控相关欺诈防范要点(规则制定)
  16. Erebus以Linux勒索软件的方式重出江湖,勒索韩国公司100万美元
  17. 福昕PDF阅读器护眼设置
  18. 如何使用Sulu设置在线多语言杂志
  19. 企业管理推脱责任要不得.
  20. “入坑”自媒体写作,我有干货与你分享

热门文章

  1. UA MATH636 信息论1 熵
  2. Windows编程设备描述表的概念和在客户区绘制、在窗口标题栏绘制、在桌面绘制图解
  3. 80x86汇编按二进制输出数字的程序图解和代码解释
  4. Java Struts 特性和新特性总结
  5. 认证模式之Basic模式
  6. java数据结构之递归算法
  7. ubuntu多节点安装kubernetes
  8. [cocos2d-x·总结]关于cocos2d-x几种画图方法的用法与思考
  9. ArcEngine 获取HDF文件中的子文件
  10. [翻译]Silverlight2 RC版本发布