zoukankan      html  css  js  c++  java
  • Android 实现形态各异的双向侧滑菜单 自定义控件来袭(转载)

    1、概述

    关于自定义控件侧滑已经写了两篇了~~今天决定把之前的单向改成双向,当然了,单纯的改动之前的代码也没意思,今天不仅会把之前的单向改为双向,还会多添加一种侧滑效果,给大家带来若干种形态各异的双向侧滑菜单,不过请放心,代码会很简单~~然后根据这若干种,只要你喜欢,相信你可以打造任何绚(bian)丽(tai)效果的双向侧滑菜单~~

    2、目标效果

    1、最普通的双向侧滑

    是不是很模糊,嗯,没办法,电脑显卡弱。。。。

    2、抽屉式双向侧滑

    3、菜单在内容之下的双向侧滑

    凑合看下,文章最后会提供源码下载,大家可以安装体验一下~

    所有的代码的内容区域都是一个ListView,两侧菜单都包含按钮,基本的冲突都检测过~~~当然如果有bug在所难免,请直接留言;如果你解决了某些未知bug,希望你也可以留言,或许可以帮助到其他人~~

    下面就开始我们的代码了。

    3、代码是最好的老师

    1、布局文件

    既然是双向菜单,那么我们的布局文件是这样的:

    1. <com.zhy.view.BinarySlidingMenu xmlns:android="http://schemas.android.com/apk/res/android"  
    2.     xmlns:tools="http://schemas.android.com/tools"  
    3.     xmlns:zhy="http://schemas.android.com/apk/res/com.zhy.zhy_bin_slidingmenu02"  
    4.     android:id="@+id/id_menu"  
    5.     android:layout_width="wrap_content"  
    6.     android:layout_height="fill_parent"  
    7.     android:scrollbars="none"  
    8.     zhy:rightPadding="100dp" >  
    9.   
    10.     <LinearLayout  
    11.         android:layout_width="wrap_content"  
    12.         android:layout_height="fill_parent"  
    13.         android:orientation="horizontal" >  
    14.   
    15.         <include layout="@layout/layout_menu" />  
    16.   
    17.         <LinearLayout  
    18.             android:layout_width="fill_parent"  
    19.             android:layout_height="fill_parent"  
    20.             android:background="@drawable/eee"  
    21.             android:gravity="center"  
    22.             android:orientation="horizontal" >  
    23.   
    24.             <ListView  
    25.                 android:id="@android:id/list"  
    26.                 android:layout_width="fill_parent"  
    27.                 android:layout_height="fill_parent" >  
    28.             </ListView>  
    29.         </LinearLayout>  
    30.   
    31.         <include layout="@layout/layout_menu2" />  
    32.     </LinearLayout>  
    33.   
    34. </com.zhy.view.BinarySlidingMenu>  

    最外层是我们的自定义的BinarySlidingMenu,内部一个水平方向的LinearLayout,然后是左边的菜单,内容区域,右边的菜单布局~~

    关键就是我们的BinarySlidingMenu

    2、BinarySlidingMenu的构造方法

    1. /** 
    2.      * 屏幕宽度 
    3.      */  
    4.     private int mScreenWidth;  
    5.   
    6.     /** 
    7.      * dp 菜单距离屏幕的右边距 
    8.      */  
    9.     private int mMenuRightPadding;  
    10.   
    11.     public BinarySlidingMenu(Context context, AttributeSet attrs, int defStyle)  
    12.     {  
    13.         super(context, attrs, defStyle);  
    14.         mScreenWidth = ScreenUtils.getScreenWidth(context);  
    15.   
    16.         TypedArray a = context.getTheme().obtainStyledAttributes(attrs,  
    17.                 R.styleable.BinarySlidingMenu, defStyle, 0);  
    18.         int n = a.getIndexCount();  
    19.         for (int i = 0; i < n; i++)  
    20.         {  
    21.             int attr = a.getIndex(i);  
    22.             switch (attr)  
    23.             {  
    24.             case R.styleable.BinarySlidingMenu_rightPadding:  
    25.                 // 默认50  
    26.                 mMenuRightPadding = a.getDimensionPixelSize(attr,  
    27.                         (int) TypedValue.applyDimension(  
    28.                                 TypedValue.COMPLEX_UNIT_DIP, 50f,  
    29.                                 getResources().getDisplayMetrics()));// 默认为10DP  
    30.                 break;  
    31.             }  
    32.         }  
    33.         a.recycle();  
    34.     }  

    我们在构造方法中,获取我们自定义的一个属性rightPadding,然后赋值给我们的成员变量mMenuRightPadding;关于如何自定义属性参考侧滑菜单的第一篇博文,这里就不赘述了。

    3、onMeasure

    onMeasure中肯定是对侧滑菜单的宽度、高度等进行设置:

    1. @Override  
    2.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)  
    3.     {  
    4.         /** 
    5.          * 显示的设置一个宽度 
    6.          */  
    7.         if (!once)  
    8.         {  
    9.   
    10.             mWrapper = (LinearLayout) getChildAt(0);  
    11.             mLeftMenu = (ViewGroup) mWrapper.getChildAt(0);  
    12.             mContent = (ViewGroup) mWrapper.getChildAt(1);  
    13.             mRightMenu = (ViewGroup) mWrapper.getChildAt(2);  
    14.   
    15.             mMenuWidth = mScreenWidth - mMenuRightPadding;  
    16.             mHalfMenuWidth = mMenuWidth / 2;  
    17.             mLeftMenu.getLayoutParams().width = mMenuWidth;  
    18.             mContent.getLayoutParams().width = mScreenWidth;  
    19.             mRightMenu.getLayoutParams().width = mMenuWidth;  
    20.   
    21.         }  
    22.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
    23.   
    24.     }  

    可以看到我们分别给左侧、右侧的菜单设置了宽度(mScreenWidth - mMenuRightPadding);

    宽度设置完成以后,肯定就是定位了,把左边的菜单弄到左边去,右边的菜单放置到右边,中间依然是我们的内容区域,那么请看onLayout方法~

    4、onLayout

    1. @Override  
    2.     protected void onLayout(boolean changed, int l, int t, int r, int b)  
    3.     {  
    4.         super.onLayout(changed, l, t, r, b);  
    5.         if (changed)  
    6.         {  
    7.             // 将菜单隐藏  
    8.             this.scrollTo(mMenuWidth, 0);  
    9.             once = true;  
    10.         }  
    11.   
    12.     }  


    哈,出奇的简单,因为我们的内部是个横向的线性布局,所以直接把左侧滑出去即可~~定位也完成了,那么此时应该到了我们的处理触摸了。

    5、onTouchEvent

    1. @Override  
    2. public boolean onTouchEvent(MotionEvent ev)  
    3. {  
    4.     int action = ev.getAction();  
    5.     switch (action)  
    6.     {  
    7.     // Up时,进行判断,如果显示区域大于菜单宽度一半则完全显示,否则隐藏  
    8.     case MotionEvent.ACTION_UP:  
    9.         int scrollX = getScrollX();  
    10.         // 如果是操作左侧菜单  
    11.         if (isOperateLeft)  
    12.         {  
    13.             // 如果影藏的区域大于菜单一半,则影藏菜单  
    14.             if (scrollX > mHalfMenuWidth)  
    15.             {  
    16.                 this.smoothScrollTo(mMenuWidth, 0);  
    17.                 // 如果当前左侧菜单是开启状态,且mOnMenuOpenListener不为空,则回调关闭菜单  
    18.                 if (isLeftMenuOpen && mOnMenuOpenListener != null)  
    19.                 {  
    20.                     // 第一个参数true:打开菜单,false:关闭菜单;第二个参数 0 代表左侧;1代表右侧  
    21.                     mOnMenuOpenListener.onMenuOpen(false, 0);  
    22.                 }  
    23.                 isLeftMenuOpen = false;  
    24.   
    25.             } else  
    26.             // 关闭左侧菜单  
    27.             {  
    28.                 this.smoothScrollTo(0, 0);  
    29.                 // 如果当前左侧菜单是关闭状态,且mOnMenuOpenListener不为空,则回调打开菜单  
    30.                 if (!isLeftMenuOpen && mOnMenuOpenListener != null)  
    31.                 {  
    32.                     mOnMenuOpenListener.onMenuOpen(true, 0);  
    33.                 }  
    34.                 isLeftMenuOpen = true;  
    35.             }  
    36.         }  
    37.   
    38.         // 操作右侧  
    39.         if (isOperateRight)  
    40.         {  
    41.             // 打开右侧侧滑菜单  
    42.             if (scrollX > mHalfMenuWidth + mMenuWidth)  
    43.             {  
    44.                 this.smoothScrollTo(mMenuWidth + mMenuWidth, 0);  
    45.             } else  
    46.             // 关闭右侧侧滑菜单  
    47.             {  
    48.                 this.smoothScrollTo(mMenuWidth, 0);  
    49.             }  
    50.         }  
    51.   
    52.         return true;  
    53.     }  
    54.     return super.onTouchEvent(ev);  
    55. }  



    依然是简单~~~我们只需要关注ACTION_UP,然后得到手指抬起后的scrollX,然后我们通过一个布尔值,判断用户现在操作是针对左侧菜单,还是右侧菜单?

    如果是操作左侧,那么判断scorllX是否超过了菜单宽度的一半,然后做相应的操作。

    如果是操作右侧,那么判断scrollX与 mHalfMenuWidth + mMenuWidth ( 注意下,右侧菜单完全影藏的时候,scrollX 就等于 mMenuWidth ),然后做对应的操作。

    我们还给左侧的菜单加上了一个回调:

    if (isLeftMenuOpen && mOnMenuOpenListener != null)
    {
    //第一个参数true:打开菜单,false:关闭菜单;第二个参数 0 代表左侧;1代表右侧
    mOnMenuOpenListener.onMenuOpen(false, 0);
    }

    扫一眼我们的回调接口:

    1. /** 
    2.      * 回调的接口 
    3.      * @author zhy 
    4.      * 
    5.      */  
    6.     public interface OnMenuOpenListener  
    7.     {  
    8.         /** 
    9.          *  
    10.          * @param isOpen true打开菜单,false关闭菜单 
    11.          * @param flag 0 左侧, 1右侧 
    12.          */  
    13.         void onMenuOpen(boolean isOpen, int flag);  
    14.     }  


    右侧菜单我没有添加回调,大家按照左侧的形式自己添加下就ok ; 

    好了,接下来,看下我们判断用户操作是左侧还是右侧的代码写在哪。

    6、onScrollChanged

    1. @Override  
    2. protected void onScrollChanged(int l, int t, int oldl, int oldt)  
    3. {  
    4.     super.onScrollChanged(l, t, oldl, oldt);  
    5.   
    6.     if (l > mMenuWidth)  
    7.     {  
    8.         isOperateRight = true;  
    9.         isOperateLeft = false;  
    10.     } else  
    11.     {  
    12.         isOperateRight = false;  
    13.         isOperateLeft = true;  
    14.     }  
    15. }  


    如果看过前两篇,对这个方法应该很眼熟了吧。我们直接通过 l 和 菜单宽度进行比较, 如果大于菜单宽度,那么肯定是想操作右侧菜单,否则那么就是想操作左侧菜单;

    到此,我们的双向侧滑菜单已经大功告成了,至于你信不信,反正我有效果图。看效果图前,贴一下MainActivity的代码:

    7、MainActivity

    1. package com.zhy.zhy_bin_slidingmenu02;  
    2.   
    3. import java.util.ArrayList;  
    4. import java.util.List;  
    5.   
    6. import android.app.ListActivity;  
    7. import android.os.Bundle;  
    8. import android.view.Window;  
    9. import android.widget.ArrayAdapter;  
    10. import android.widget.Toast;  
    11.   
    12. import com.zhy.view.BinarySlidingMenu;  
    13. import com.zhy.view.BinarySlidingMenu.OnMenuOpenListener;  
    14.   
    15. public class MainActivity extends ListActivity  
    16. {  
    17.     private BinarySlidingMenu mMenu;  
    18.     private List<String> mDatas = new ArrayList<String>();  
    19.   
    20.     @Override  
    21.     protected void onCreate(Bundle savedInstanceState)  
    22.     {  
    23.         super.onCreate(savedInstanceState);  
    24.         requestWindowFeature(Window.FEATURE_NO_TITLE);  
    25.         setContentView(R.layout.activity_main);  
    26.   
    27.         mMenu = (BinarySlidingMenu) findViewById(R.id.id_menu);  
    28.         mMenu.setOnMenuOpenListener(new OnMenuOpenListener()  
    29.         {  
    30.             @Override  
    31.             public void onMenuOpen(boolean isOpen, int flag)  
    32.             {  
    33.                 if (isOpen)  
    34.                 {  
    35.                       
    36.                     Toast.makeText(getApplicationContext(),  
    37.                             flag == 0 ? "LeftMenu Open" : "RightMenu Open",  
    38.                             Toast.LENGTH_SHORT).show();  
    39.                 } else  
    40.                 {  
    41.                       
    42.                     Toast.makeText(getApplicationContext(),  
    43.                             flag == 0 ? "LeftMenu Close" : "RightMenu Close",  
    44.                             Toast.LENGTH_SHORT).show();  
    45.                 }  
    46.   
    47.             }  
    48.         });  
    49.         // 初始化数据  
    50.         for (int i = 'A'; i <= 'Z'; i++)  
    51.         {  
    52.             mDatas.add((char) i + "");  
    53.         }  
    54.         // 设置适配器  
    55.         setListAdapter(new ArrayAdapter<String>(this, R.layout.item, mDatas));  
    56.     }  
    57. }  

    没撒好说的,为了方便直接继承了ListActivity,然后设置了一下回调,布局文件一定要有ListView,为了测试我们是否有冲突~~不过有咱们也不怕~

    效果图:

    当然了,最简单的双向侧滑怎么能满足大家的好(Zhao)奇(Nue)心呢,所以我们准备玩点神奇的花样~~

    4、打造抽屉式双向侧滑

    我们在onScrollChanged添加两行代码~~为mContent设置一个属性动画

    1. @Override  
    2.     protected void onScrollChanged(int l, int t, int oldl, int oldt)  
    3.     {  
    4.         super.onScrollChanged(l, t, oldl, oldt);  
    5.           
    6.         if (l > mMenuWidth)  
    7.         {  
    8.             isOperateRight = true;  
    9.             isOperateLeft = false;  
    10.         } else  
    11.         {  
    12.             isOperateRight = false;  
    13.             isOperateLeft = true;  
    14.         }  
    15.           
    16.         float scale = l * 1.0f / mMenuWidth;  
    17.         ViewHelper.setTranslationX(mContent, mMenuWidth * (scale - 1));  
    18.           
    19.     }  

    简单分析一下哈:

    1、scale,在滑动左侧菜单时:值为1.0~0.0;mMenuWidth * (scale - 1) 的值就是从 0.0~ -mMenuWidth(注意:负的) ; 那么mContent的向偏移量,就是0到mMenuWidth ;也就是说,整个滑动的过程,我们强制让内容区域固定了。

    2、scale,在滑动右侧菜单时:值为:1.0~2.0;mMenuWidth * (scale - 1) 的值就是从 0.0~ mMenuWidth(注意:正数) ;那么mContent的向偏移量,就是0到mMenuWidth ;也就是说,整个滑动的过程,我们强制让内容区域固定了。

    好了,内容固定了,那么我们此刻的两边菜单应该是在内容之上显示出来~~这不就是我们的抽屉效果么~

    嗯,这次木有效果图了,因为测试结果发现,左侧的菜单会被内容区域遮盖住,看不到;右侧菜单符合预期效果;因为,左侧菜单滑动出来以后,被内容区域遮盖住了,这个也很容易理解,毕竟我们的布局,内容在左侧菜单后面,肯定会挡住它的。那么,怎么办呢?

    起初,我准备使用bringToFont方法,在拖动的时候,让菜单在上面~~~不过呢,问题大大的,有兴趣可以试试~~

    于是乎,我换了个方法,我将BinarySlidingMenu内部的Linearlayout进行了自定义,现在布局文件是这样的:

    1. <com.zhy.view.BinarySlidingMenu xmlns:android="http://schemas.android.com/apk/res/android"  
    2.     xmlns:tools="http://schemas.android.com/tools"  
    3.     xmlns:zhy="http://schemas.android.com/apk/res/com.zhy.zhy_bin_slidingmenu03"  
    4.     android:id="@+id/id_menu"  
    5.     android:layout_width="wrap_content"  
    6.     android:layout_height="fill_parent"  
    7.     android:scrollbars="none"  
    8.     zhy:rightPadding="100dp" >  
    9.   
    10.     <com.zhy.view.MyLinearLayout  
    11.         android:layout_width="wrap_content"  
    12.         android:layout_height="fill_parent"  
    13.         android:orientation="horizontal" >  
    14.   
    15.         <include layout="@layout/layout_menu" />  
    16.   
    17.         <LinearLayout  
    18.             android:layout_width="fill_parent"  
    19.             android:layout_height="fill_parent"  
    20.             android:background="@drawable/eee"  
    21.             android:gravity="center"  
    22.             android:orientation="horizontal" >  
    23.   
    24.             <ListView  
    25.                 android:id="@android:id/list"  
    26.                 android:layout_width="fill_parent"  
    27.                 android:layout_height="fill_parent" >  
    28.             </ListView>  
    29.         </LinearLayout>  
    30.   
    31.         <include layout="@layout/layout_menu2" />  
    32.     </com.zhy.view.MyLinearLayout>  
    33.   
    34. </com.zhy.view.BinarySlidingMenu>  


    MyLinearlayout的代码:

    1. package com.zhy.view;  
    2.   
    3. import android.content.Context;  
    4. import android.util.AttributeSet;  
    5. import android.util.Log;  
    6. import android.widget.LinearLayout;  
    7.   
    8. public class MyLinearLayout extends LinearLayout  
    9. {  
    10.   
    11.     public MyLinearLayout(Context context, AttributeSet attrs)  
    12.     {  
    13.         super(context, attrs);  
    14. //      Log.e("TAG", "MyLinearLayout");  
    15.         setChildrenDrawingOrderEnabled(true);  
    16.     }  
    17.   
    18.     @Override  
    19.     protected int getChildDrawingOrder(int childCount, int i)  
    20.     {  
    21. //      Log.e("tag", "getChildDrawingOrder" + i + " , " + childCount);  
    22.   
    23.         if (i == 0)  
    24.             return 1;  
    25.         if (i == 2)  
    26.             return 2;  
    27.         if (i == 1)  
    28.             return 0;  
    29.         return super.getChildDrawingOrder(childCount, i);  
    30.   
    31.     }  
    32.   
    33. }  


    在构造方法设置setChildrenDrawingOrderEnabled(true);然后getChildDrawingOrder复写一下绘制子View的顺序,让内容(i==0)始终是最先绘制。

    现在再运行,效果图:

    效果是不是很赞,请允许我把图挪过来了~~~

    现在,还有最后一个效果,如果让,菜单在内容之下呢?

    5、打造菜单在内容之下的双向侧滑

    不用说,大家都能想到,无非就是在onScrollChanged改改属性动画呗,说得对!

    1、改写onScrollChanged方法

    1. @Override  
    2.     protected void onScrollChanged(int l, int t, int oldl, int oldt)  
    3.     {  
    4.         super.onScrollChanged(l, t, oldl, oldt);  
    5.   
    6.         if (l > mMenuWidth)  
    7.         {  
    8.             // 1.0 ~2.0 1.0~0.0  
    9.             // (2-scale)  
    10.             float scale = l * 1.0f / mMenuWidth;  
    11.             isOperateRight = true;  
    12.             isOperateLeft = false;  
    13.             ViewHelper.setTranslationX(mRightMenu, -mMenuWidth * (2 - scale));  
    14.   
    15.         } else  
    16.         {  
    17.             float scale = l * 1.0f / mMenuWidth;  
    18.             isOperateRight = false;  
    19.             isOperateLeft = true;  
    20.             ViewHelper.setTranslationX(mLeftMenu, mMenuWidth * scale);  
    21.   
    22.         }  
    23.     }  


    也就是拉的时候,尽量让菜单保证在内容之下~~~代码自己琢磨下

    2、改写MyLinearLayout

    当然了,仅仅这些是不够的,既然我们的样式变化了,那么改写View的绘制顺序肯定也是必须的。

    看下我们的MyLinearLayout

    1. package com.zhy.view;  
    2.   
    3. import android.content.Context;  
    4. import android.util.AttributeSet;  
    5. import android.util.Log;  
    6. import android.widget.LinearLayout;  
    7.   
    8. public class MyLinearLayout extends LinearLayout  
    9. {  
    10.   
    11.     public MyLinearLayout(Context context, AttributeSet attrs)  
    12.     {  
    13.         super(context, attrs);  
    14.         Log.e("TAG", "MyLinearLayout");  
    15.         setChildrenDrawingOrderEnabled(true);  
    16.     }  
    17.   
    18.     @Override  
    19.     protected int getChildDrawingOrder(int childCount, int i)  
    20.     {  
    21.   
    22.         if (i == 0)  
    23.             return 0;  
    24.         if (i == 2)  
    25.             return 1;  
    26.         if (i == 1)  
    27.             return 2;  
    28.         return super.getChildDrawingOrder(childCount, i);  
    29.   
    30.     }  
    31.   
    32. }  

    效果图:

  • 相关阅读:
    windows开启PostgreSQL数据库远程访问
    Git使用介绍
    linux 常用工具记录及简介
    ubuntu18 安装坑点记录(华硕飞行堡垒)
    快手自动视频随机点赞脚本
    接触手机脚本编程------基于触动精灵的lua编程
    使电脑蜂鸣器发声小脚本
    tensorflow--非线性回归
    python笔记--------numpy
    python笔记--------二
  • 原文地址:https://www.cnblogs.com/lianghe01/p/4250779.html
Copyright © 2011-2022 走看看