周末两天时间在公司里研究新版QQ的侧滑菜单的实现,在网上也看到一些实例,也看了慕课网上的课程,终于完全搞明白了整个实现过程,现将我的理解和实现写出来。
自定义控件--显示侧滑菜单和内容的horizontalScrollView
需要用到 HorizontalScrollView控件,同时要实现
(1)初始状态是显示内容,隐藏菜单
(2)当滑动超过屏幕一般的响应。(隐藏菜单or显示菜单)
(3)滑动过程中内容区域的缩放,菜单的偏移和缩放以及透明度变化。
(4)点击按钮,切换菜单的显示和隐藏
(1)初始状态是显示内容,隐藏菜单
第一步:重写了自定义控件的两个参数的构造方法,主要目的是得到屏幕宽度和菜单的右边距
//未定义属性时使用
public SlidingMenu(Context context, AttributeSet attrs) {
super(context, attrs);
// //获取屏幕宽度
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
mScreenWidth = outMetrics.widthPixels;
//menu右边距,转化为像素值
mMenuRight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, context.getResources().getDisplayMetrics());
// this(context, attrs, 0);
}
第二步:重写onMeasure(),决定菜单区域和内容区域的宽高,这样自定义视图的宽高就决定了
<pre name="code" class="html">//重写onMeasure(),决定内部子view的宽高,并决定该控件的宽高 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (!once) { //获取LinearLayout mWapper = (LinearLayout) getChildAt(0); //获取菜单和内容 mMenu = (ViewGroup) mWapper.getChildAt(0); mContent = (ViewGroup) mWapper.getChildAt(1); //设置菜单和内容的宽高 mMenu.getLayoutParams().width = mScreenWidth - mMenuRight; mMenuWidth = mScreenWidth - mMenuRight; mContent.getLayoutParams().width = mScreenWidth; //决定子view视图的宽高后该视图的宽高就决定了,无需再设定 once = true; } }
第三步:重写onLayout()方法,决定控件的布局,即初始状态只显示内容区域隐藏菜单区域
//重写onLayout(),决定视图的位置
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (changed) {
this.scrollTo(mMenuWidth, 0);
}
}
这里使用到scrollTo()方法,设置scrollView的偏移,实现隐藏菜单。
第四步:重写OnTouchEvent()方法,实现滑动之后如果菜单显示超过屏幕一般则全部显示,否则隐藏
//重写TouchEvent(),监听手抬起时的动作 @Override public boolean onTouchEvent(MotionEvent ev) { int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_UP: { //判断此时scrollview滚动的距离与menu宽度比较,如果大于一般则隐藏,如果小于一半则显示 int scrollX = this.getScrollX(); int helfMenuWidth = mMenuWidth / 2; if (scrollX >= helfMenuWidth) { //隐藏menu this.smoothScrollTo(mMenuWidth, 0); isOpen = false; } else { //显示menu this.smoothScrollTo(0, 0); isOpen = true; } return true; } } return super.onTouchEvent(ev); }
这样基本的功能已经实现,再添加上按钮实现(2)点击按钮,菜单显示隐藏的切换
第一步:按钮设置监听事件第二步:实现切换:btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //切换菜单 slideMenu.changeMenu(); } });
菜单的显示隐藏就是使用的scrollView的smoothSrollTo()这样的函数,让滑动更加平滑。切换是定义了一个标志,状态改变时修改标志,同时注意要在onTouchEvent()中显示隐藏也要修改标志。public void changeMenu() { if(isOpen){//如果是开启则关闭 closeMenu(); }else{ openMenu(); } } //打开菜单,设置标志 private void openMenu() { this.smoothScrollTo(0,0); isOpen = true; } //关闭菜单,设置标志 private void closeMenu() { this.smoothScrollTo(mMenuWidth,0); isOpen = false; }
(3)实现自定义属性
<com.example.chengyajun.slidingmenu.view.SlidingMenu android:id="@+id/slidemenu" android:layout_width="match_parent" test:rightPadding="80dp" android:layout_height="match_parent">
如何实现在xml布局文件中动态修改菜单的右边距呢? test:rightPadding="80dp"第一步:values文件夹下创建attr.xml文件,设置属性的基本信息<?xml version="1.0" encoding="utf-8"?> <resources> <attr name="rightPadding" format="dimension"></attr> <declare-styleable name="SlidingMenu"> <attr name="rightPadding"></attr> </declare-styleable> </resources>
第二步:布局文件中,声明xmlns,在eclipse和android studio中优点不一样,实现的时候要注意一下。eclipse : xmlns: test = "http://schemas.android.com/apk/res/ com.example.chengyajun. slidingmenu "android studio : xmlns: test = "http://schemas.android.com/apk/res-auto"
第三步:重写自定义视图的构造方法,获取自定义属性的值分别重写一个参数,两个参数,三个参数的构造方法,//一个参数的构造方法,代码中只用new来创建对象 public SlidingMenu(Context context) { // super(context); this(context, null); } //未定义属性时使用 public SlidingMenu(Context context, AttributeSet attrs) { // super(context, attrs); // //获取屏幕宽度 // WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); // DisplayMetrics outMetrics = new DisplayMetrics(); // wm.getDefaultDisplay().getMetrics(outMetrics); // mScreenWidth = outMetrics.widthPixels; // // //menu右边距,转化为像素值 // mMenuRight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, context.getResources().getDisplayMetrics()); this(context, attrs, 0); } //三个参数的构造方法,使用了自动以属性 public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //对右边距进行设置 // 获取我们定义的属性 TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SlidingMenu, defStyleAttr, 0); int n = a.getIndexCount(); for (int i = 0; i < n; i++) { int attr = a.getIndex(i); switch (attr) { case R.styleable.SlidingMenu_rightPadding: mMenuRight = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, 50, context .getResources().getDisplayMetrics())); break; } } a.recycle(); //获取屏幕宽度 WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(outMetrics); mScreenWidth = outMetrics.widthPixels; }
一个参数:new一个对象时候两个参数:没有自定义控件三个参数:使用了自定义控件注意:自定义属性值得获取,需要及时的释放资源。
(4)实现属性动画:内容区域的缩放,菜单的偏移、缩放和透明度变化此时需要引入一个jar包 :nineoldandroids-2.4.0.jar,android3.0以下的需要这个jar包需要重写scrollView的 onScrollChanged(),在滑动过程中实现动画效果通过ViewHelper来实现动画,缩放的比例控制需要根据scrollView的滑动比例来决定。@Override protected void onScrollChanged(int l, int t, int oldl, int oldt) {//l=getScrollX() float scale = l*1.0f/mMenuWidth; //隐藏的比例1.0~0 //抽屉菜单的实现,菜单在拉动过程中偏移 //当滑动过程中,移动menu,menu的偏移 // ViewHelper.setTranslationX(mMenu,l);//隐藏多少,就让menu偏移多少,这样保证menu的x起点始终在屏幕x起点 //QQ菜单实现 /** * 内容区域的缩放:1.0~0.7 0.7+0.3*scale * 菜单区域的缩放:0.7~1.0 1.0-0.3*scale * 菜单区域的透明度:0.7~1.0 1.0-0.3*scale * */ //内容区域 缩放 ViewHelper.setScaleX(mContent,0.7f+0.3f*scale); ViewHelper.setScaleY(mContent,0.7f+0.3f*scale); //移动到最后发现全部隐藏了,因为当到达最后的80dp后,区域缩放且默认缩放点在中心,导致看不见 //修改区域的缩放点 ViewHelper.setPivotX(mContent,0); ViewHelper.setPivotY(mContent,mContent.getHeight()/2); //菜单区域 //偏移 ViewHelper.setTranslationX(mMenu,l*0.8f); // //缩放 ViewHelper.setScaleX(mMenu, 1.0f-0.3f*scale); ViewHelper.setScaleY(mMenu,1.0f-0.3f*scale); //透明度 ViewHelper.setAlpha(mMenu,1.0f-0.3f*scale); }
在实现过程中开始没有修改内容区域的缩放中心点,发现直接隐藏了,后面才知道原来到最后的80dp后,区域还要缩放,而且默认缩放中心点在区域中心,这样就看不见了,后来修改区域缩放的中心点未左边中间点,这样就是到最后也是停留在80dp的位置。这样整个自定义的视图就写完了,技术难点也解释了。