您的位置:首页 > 移动开发 > Android开发

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

2016-05-06 13:51 531 查看

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/39670935,本文出自:【张鸿洋的博客】

1、概述

首先回顾一下,之前写过的各种侧滑菜单,为了不占据篇幅,就不贴图片了:

1、最普通的侧滑效果,请参考:Android
自定义控件打造史上最简单的侧滑菜单

2、仿QQ5.0侧滑效果,请参考:Android
高仿 QQ5.0 侧滑菜单效果 自定义控件来袭

3、菜单在内容之后的侧滑效果,请参考:Android
高仿 QQ5.0 侧滑菜单效果 自定义控件来袭

2、目标效果

1、最普通的双向侧滑



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

2、抽屉式双向侧滑



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



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

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

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

3、代码是最好的老师

1、布局文件

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

[html]
view plain
copy

<com.zhy.view.BinarySlidingMenu xmlns:android="http://schemas.android.com/apk/res/android"  
    xmlns:tools="http://schemas.android.com/tools"  
    xmlns:zhy="http://schemas.android.com/apk/res/com.zhy.zhy_bin_slidingmenu02"  
    android:id="@+id/id_menu"  
    android:layout_width="wrap_content"  
    android:layout_height="fill_parent"  
    android:scrollbars="none"  
    zhy:rightPadding="100dp" >  
  
    <LinearLayout  
        android:layout_width="wrap_content"  
        android:layout_height="fill_parent"  
        android:orientation="horizontal" >  
  
        <include layout="@layout/layout_menu" />  
  
        <LinearLayout  
            android:layout_width="fill_parent"  
            android:layout_height="fill_parent"  
            android:background="@drawable/eee"  
            android:gravity="center"  
            android:orientation="horizontal" >  
  
            <ListView  
                android:id="@android:id/list"  
                android:layout_width="fill_parent"  
                android:layout_height="fill_parent" >  
            </ListView>  
        </LinearLayout>  
  
        <include layout="@layout/layout_menu2" />  
    </LinearLayout>  
  
</com.zhy.view.BinarySlidingMenu>  

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

2、BinarySlidingMenu的构造方法

[java]
view plain
copy

/** 
     * 屏幕宽度 
     */  
    private int mScreenWidth;  
  
    /** 
     * dp 菜单距离屏幕的右边距 
     */  
    private int mMenuRightPadding;  
  
    public BinarySlidingMenu(Context context, AttributeSet attrs, int defStyle)  
    {  
        super(context, attrs, defStyle);  
        mScreenWidth = ScreenUtils.getScreenWidth(context);  
  
        TypedArray a = context.getTheme().obtainStyledAttributes(attrs,  
                R.styleable.BinarySlidingMenu, defStyle, 0);  
        int n = a.getIndexCount();  
        for (int i = 0; i < n; i++)  
        {  
            int attr = a.getIndex(i);  
            switch (attr)  
            {  
            case R.styleable.BinarySlidingMenu_rightPadding:  
                // 默认50  
                mMenuRightPadding = a.getDimensionPixelSize(attr,  
                        (int) TypedValue.applyDimension(  
                                TypedValue.COMPLEX_UNIT_DIP, 50f,  
                                getResources().getDisplayMetrics()));// 默认为10DP  
                break;  
            }  
        }  
        a.recycle();  
    }  

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

3、onMeasure

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

[java]
view plain
copy

@Override  
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)  
    {  
        /** 
         * 显示的设置一个宽度 
         */  
        if (!once)  
        {  
  
            mWrapper = (LinearLayout) getChildAt(0);  
            mLeftMenu = (ViewGroup) mWrapper.getChildAt(0);  
            mContent = (ViewGroup) mWrapper.getChildAt(1);  
            mRightMenu = (ViewGroup) mWrapper.getChildAt(2);  
  
            mMenuWidth = mScreenWidth - mMenuRightPadding;  
            mHalfMenuWidth = mMenuWidth / 2;  
            mLeftMenu.getLayoutParams().width = mMenuWidth;  
            mContent.getLayoutParams().width = mScreenWidth;  
            mRightMenu.getLayoutParams().width = mMenuWidth;  
  
        }  
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  
    }  

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

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

4、onLayout

[java]
view plain
copy

@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);  
            once = true;  
        }  
  
    }  

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

5、onTouchEvent

[java]
view plain
copy

@Override  
public boolean onTouchEvent(MotionEvent ev)  
{  
    int action = ev.getAction();  
    switch (action)  
    {  
    // Up时,进行判断,如果显示区域大于菜单宽度一半则完全显示,否则隐藏  
    case MotionEvent.ACTION_UP:  
        int scrollX = getScrollX();  
        // 如果是操作左侧菜单  
        if (isOperateLeft)  
        {  
            // 如果影藏的区域大于菜单一半,则影藏菜单  
            if (scrollX > mHalfMenuWidth)  
            {  
                this.smoothScrollTo(mMenuWidth, 0);  
                // 如果当前左侧菜单是开启状态,且mOnMenuOpenListener不为空,则回调关闭菜单  
                if (isLeftMenuOpen && mOnMenuOpenListener != null)  
                {  
                    // 第一个参数true:打开菜单,false:关闭菜单;第二个参数 0 代表左侧;1代表右侧  
                    mOnMenuOpenListener.onMenuOpen(false, 0);  
                }  
                isLeftMenuOpen = false;  
  
            } else  
            // 关闭左侧菜单  
            {  
                this.smoothScrollTo(0, 0);  
                // 如果当前左侧菜单是关闭状态,且mOnMenuOpenListener不为空,则回调打开菜单  
                if (!isLeftMenuOpen && mOnMenuOpenListener != null)  
                {  
                    mOnMenuOpenListener.onMenuOpen(true, 0);  
                }  
                isLeftMenuOpen = true;  
            }  
        }  
  
        // 操作右侧  
        if (isOperateRight)  
        {  
            // 打开右侧侧滑菜单  
            if (scrollX > mHalfMenuWidth + mMenuWidth)  
            {  
                this.smoothScrollTo(mMenuWidth + mMenuWidth, 0);  
            } else  
            // 关闭右侧侧滑菜单  
            {  
                this.smoothScrollTo(mMenuWidth, 0);  
            }  
        }  
  
        return true;  
    }  
    return super.onTouchEvent(ev);  
}  

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

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

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

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

if (isLeftMenuOpen && mOnMenuOpenListener != null)

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

}

扫一眼我们的回调接口:

[java]
view plain
copy

/** 
     * 回调的接口 
     * @author zhy 
     * 
     */  
    public interface OnMenuOpenListener  
    {  
        /** 
         *  
         * @param isOpen true打开菜单,false关闭菜单 
         * @param flag 0 左侧, 1右侧 
         */  
        void onMenuOpen(boolean isOpen, int flag);  
    }  

右侧菜单我没有添加回调,大家按照左侧的形式自己添加下就ok ; 
好了,接下来,看下我们判断用户操作是左侧还是右侧的代码写在哪。

6、onScrollChanged

[java]
view plain
copy

@Override  
protected void onScrollChanged(int l, int t, int oldl, int oldt)  
{  
    super.onScrollChanged(l, t, oldl, oldt);  
  
    if (l > mMenuWidth)  
    {  
        isOperateRight = true;  
        isOperateLeft = false;  
    } else  
    {  
        isOperateRight = false;  
        isOperateLeft = true;  
    }  
}  

如果看过前两篇,对这个方法应该很眼熟了吧。我们直接通过 l 和 菜单宽度进行比较, 如果大于菜单宽度,那么肯定是想操作右侧菜单,否则那么就是想操作左侧菜单;
到此,我们的双向侧滑菜单已经大功告成了,至于你信不信,反正我有效果图。看效果图前,贴一下MainActivity的代码:

7、MainActivity

[java]
view plain
copy

package com.zhy.zhy_bin_slidingmenu02;  
  
import java.util.ArrayList;  
import java.util.List;  
  
import android.app.ListActivity;  
import android.os.Bundle;  
import android.view.Window;  
import android.widget.ArrayAdapter;  
import android.widget.Toast;  
  
import com.zhy.view.BinarySlidingMenu;  
import com.zhy.view.BinarySlidingMenu.OnMenuOpenListener;  
  
public class MainActivity extends ListActivity  
{  
    private BinarySlidingMenu mMenu;  
    private List<String> mDatas = new ArrayList<String>();  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        requestWindowFeature(Window.FEATURE_NO_TITLE);  
        setContentView(R.layout.activity_main);  
  
        mMenu = (BinarySlidingMenu) findViewById(R.id.id_menu);  
        mMenu.setOnMenuOpenListener(new OnMenuOpenListener()  
        {  
            @Override  
            public void onMenuOpen(boolean isOpen, int flag)  
            {  
                if (isOpen)  
                {  
                      
                    Toast.makeText(getApplicationContext(),  
                            flag == 0 ? "LeftMenu Open" : "RightMenu Open",  
                            Toast.LENGTH_SHORT).show();  
                } else  
                {  
                      
                    Toast.makeText(getApplicationContext(),  
                            flag == 0 ? "LeftMenu Close" : "RightMenu Close",  
                            Toast.LENGTH_SHORT).show();  
                }  
  
            }  
        });  
        // 初始化数据  
        for (int i = 'A'; i <= 'Z'; i++)  
        {  
            mDatas.add((char) i + "");  
        }  
        // 设置适配器  
        setListAdapter(new ArrayAdapter<String>(this, R.layout.item, mDatas));  
    }  
}  

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

效果图:



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

4、打造抽屉式双向侧滑

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

[java]
view plain
copy

@Override  
    protected void onScrollChanged(int l, int t, int oldl, int oldt)  
    {  
        super.onScrollChanged(l, t, oldl, oldt);  
          
        if (l > mMenuWidth)  
        {  
            isOperateRight = true;  
            isOperateLeft = false;  
        } else  
        {  
            isOperateRight = false;  
            isOperateLeft = true;  
        }  
          
        float scale = l * 1.0f / mMenuWidth;  
        ViewHelper.setTranslationX(mContent, mMenuWidth * (scale - 1));  
          
    }  

简单分析一下哈:
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进行了自定义,现在布局文件是这样的:

[html]
view plain
copy

<com.zhy.view.BinarySlidingMenu xmlns:android="http://schemas.android.com/apk/res/android"  
    xmlns:tools="http://schemas.android.com/tools"  
    xmlns:zhy="http://schemas.android.com/apk/res/com.zhy.zhy_bin_slidingmenu03"  
    android:id="@+id/id_menu"  
    android:layout_width="wrap_content"  
    android:layout_height="fill_parent"  
    android:scrollbars="none"  
    zhy:rightPadding="100dp" >  
  
    <com.zhy.view.MyLinearLayout  
        android:layout_width="wrap_content"  
        android:layout_height="fill_parent"  
        android:orientation="horizontal" >  
  
        <include layout="@layout/layout_menu" />  
  
        <LinearLayout  
            android:layout_width="fill_parent"  
            android:layout_height="fill_parent"  
            android:background="@drawable/eee"  
            android:gravity="center"  
            android:orientation="horizontal" >  
  
            <ListView  
                android:id="@android:id/list"  
                android:layout_width="fill_parent"  
                android:layout_height="fill_parent" >  
            </ListView>  
        </LinearLayout>  
  
        <include layout="@layout/layout_menu2" />  
    </com.zhy.view.MyLinearLayout>  
  
</com.zhy.view.BinarySlidingMenu>  

MyLinearlayout的代码:

[java]
view plain
copy

package com.zhy.view;  
  
import android.content.Context;  
import android.util.AttributeSet;  
import android.util.Log;  
import android.widget.LinearLayout;  
  
public class MyLinearLayout extends LinearLayout  
{  
  
    public MyLinearLayout(Context context, AttributeSet attrs)  
    {  
        super(context, attrs);  
//      Log.e("TAG", "MyLinearLayout");  
        setChildrenDrawingOrderEnabled(true);  
    }  
  
    @Override  
    protected int getChildDrawingOrder(int childCount, int i)  
    {  
//      Log.e("tag", "getChildDrawingOrder" + i + " , " + childCount);  
  
        if (i == 0)  
            return 1;  
        if (i == 2)  
            return 2;  
        if (i == 1)  
            return 0;  
        return super.getChildDrawingOrder(childCount, i);  
  
    }  
  
}  

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



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

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

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

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

1、改写onScrollChanged方法

[java]
view plain
copy

@Override  
    protected void onScrollChanged(int l, int t, int oldl, int oldt)  
    {  
        super.onScrollChanged(l, t, oldl, oldt);  
  
        if (l > mMenuWidth)  
        {  
            // 1.0 ~2.0 1.0~0.0  
            // (2-scale)  
            float scale = l * 1.0f / mMenuWidth;  
            isOperateRight = true;  
            isOperateLeft = false;  
            ViewHelper.setTranslationX(mRightMenu, -mMenuWidth * (2 - scale));  
  
        } else  
        {  
            float scale = l * 1.0f / mMenuWidth;  
            isOperateRight = false;  
            isOperateLeft = true;  
            ViewHelper.setTranslationX(mLeftMenu, mMenuWidth * scale);  
  
        }  
    }  

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

2、改写MyLinearLayout

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

看下我们的MyLinearLayout

[java]
view plain
copy

package com.zhy.view;  
  
import android.content.Context;  
import android.util.AttributeSet;  
import android.util.Log;  
import android.widget.LinearLayout;  
  
public class MyLinearLayout extends LinearLayout  
{  
  
    public MyLinearLayout(Context context, AttributeSet attrs)  
    {  
        super(context, attrs);  
        Log.e("TAG", "MyLinearLayout");  
        setChildrenDrawingOrderEnabled(true);  
    }  
  
    @Override  
    protected int getChildDrawingOrder(int childCount, int i)  
    {  
  
        if (i == 0)  
            return 0;  
        if (i == 2)  
            return 1;  
        if (i == 1)  
            return 2;  
        return super.getChildDrawingOrder(childCount, i);  
  
    }  
  
}  

效果图:



到此,我们的形态各异的双向侧滑就结束了~~~

从最普通的双向,到抽屉式,再到我们的菜单在内容之下的侧滑都已经搞定;希望大家通过这三个侧滑,可以举一反三,打造各种变态的侧滑效果~~~~

最后我把3个侧滑的源码都会共享出来,大家自行下载:

Android普通双向侧滑

Android抽屉式双向侧滑

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

ps:本人测试手机,小米2s,尽量真机进行测试。

----------------------------------------------------------------------------------------------------------

博主部分视频已经上线,如果你不喜欢枯燥的文本,请猛戳(初录,期待您的支持):

1、高仿微信5.2.1主界面及消息提醒

2、高仿QQ5.0侧滑
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息