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


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

    1、概述

    关于自己定义控件側滑已经写了两篇了~~今天决定把之前的单向改成双向,当然了。单纯的修改之前的代码也没意思。今天不仅会把之前的单向改为双向,还会多加入一种側滑效果,给大家带来若干种形态各异的双向側滑菜单,只是请放心。代码会非常简单~~然后依据这若干种。仅仅要你喜欢。相信你能够打造不论什么绚(bian)丽(tai)效果的双向側滑菜单~~

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

    1、最普通的側滑效果。请參考:Android 自己定义控件打造史上最简单的側滑菜单

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

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

    2、目标效果


    1、最普通的双向側滑


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

    2、抽屉式双向側滑


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


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

    全部的代码的内容区域都是一个ListView,两側菜单都包括button。主要的冲突都检測过~~~当然假设有bug在所难免,请直接留言;假设你攻克了某些未知bug,希望你也能够留言,也许能够帮助到其它人~~

    以下就開始我们的代码了。


    3、代码是最好的老师

    1、布局文件

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

    <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的构造方法

    /**
    	 * 屏幕宽度
    	 */
    	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中肯定是对側滑菜单的宽度、高度等进行设置:

    @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

    @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

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

    扫一眼我们的回调接口:

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

    右側菜单我没有加入回调,大家依照左側的形式自己加入下就ok ; 

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

    6、onScrollChanged

    	@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

    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设置一个属性动画

    @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 。也就是说,整个滑动的过程,我们强制让内容区域固定了。


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

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

    起初,我准备使用bringToFont方法,在拖动的时候,让菜单在上面~~~只是呢,问题大大的,有兴趣能够试试~~

    于是乎,我换了个方法,我将BinarySlidingMenu内部的Linearlayout进行了自己定义,如今布局文件是这种:

    <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的代码:

    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方法

    @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

    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側滑





  • 相关阅读:
    python基础十一之装饰器进阶
    python基础十之装饰器
    python基础九之函数
    python基础八之文件操作
    python基础七之copy
    python基础七之集合
    python基础数据类型汇总
    python基础六之编码
    synchronized关键字的内存语义
    对于this和当前线程的一些理解
  • 原文地址:https://www.cnblogs.com/mfmdaoyou/p/6823247.html
Copyright © 2020-2023  润新知