`
king_tt
  • 浏览: 2260255 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

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,两侧菜单都包含按钮,基本的冲突都检测过~~~当然如果有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 ;也就是说,整个滑动的过程,我们强制让内容区域固定了。


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

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

起初,我准备使用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,尽量真机进行测试。
















分享到:
评论

相关推荐

    Android自定义相机Camera

    在Android开发中,自定义相机(Camera)是一个常见的需求,特别是在开发涉及图像处理或拍照功能的应用时。Android原生提供了Camera API,但直接使用可能会遇到一些问题,如图像拉伸、拍照翻转等。本教程将详细介绍...

    Android-Android属性动画弹幕自定义弹幕布局

    本教程将深入探讨如何利用Android属性动画和自定义布局实现弹幕效果。首先,让我们理解这两个核心概念。 **属性动画(Property Animation)** 属性动画是Android 3.0(API level 11)引入的一个强大工具,它允许...

    vue-video:vue移动端简单自定义控件播放器

    3. **自定义控件**:为了实现移动端友好的用户体验,"vue-video"提供了自定义的控制条组件,包括播放/暂停按钮、进度条、音量控制等。这些控件通常需要响应触摸事件,确保在手机和平板上的操作流畅。Vue的事件绑定...

    Android-tv-widget, Android tv,盒子,投影仪 控件.zip

    这个压缩包“Android-tv-widget, Android tv,盒子,投影仪 控件.zip”似乎包含了一个开源项目,名为“Android-tv-widget-master”,这可能是用来帮助开发者设计和实现Android TV应用中的自定义控件。 开源项目通常...

    形态各异的不规则窗体

    在计算机编程领域,尤其是Windows平台下的...总的来说,实现形态各异的不规则窗体是一个结合了图形学、窗口管理和用户交互的挑战,但通过熟练掌握VC++和GDI函数,开发者可以创造出引人注目的应用程序,提升用户体验。

    android 仿Path菜单

    总的来说,实现"android 仿Path菜单"需要对Android的自定义View、布局管理、动画系统以及事件处理有深入的理解。通过这个项目,开发者可以提升自己的Android UI设计和实现能力,同时也为用户提供了更具特色的交互...

    Android例子源码HerilyAlertDialog完全自定义的Dialog

    7. **适配不同屏幕尺寸**:由于Android设备的屏幕尺寸各异,自定义Dialog应考虑到不同屏幕的适配问题。确保Dialog在大屏和小屏设备上都能正常显示和使用。 在“Android例子源码HerilyAlertDialog完全自定义的Dialog...

    android日期时间选择器

    6. 兼容性处理:由于Android设备众多,系统版本各异,自定义控件可能需要考虑兼容性问题。使用Support Library或AndroidX可以帮助解决这个问题,确保控件能在不同Android版本上正常工作。 7. 国际化支持:对于面向...

    Android自定义不规则七巧板布局

    本主题聚焦于“Android自定义不规则七巧板布局”,这是一种基于七巧板概念的布局方式,能够帮助开发者构建出形状各异、交互丰富的UI元素。 七巧板布局的核心思想是将屏幕空间分割成若干个可重排的不规则形状,这些...

    Android自定义组件开发详解自制书签目录版方便看.pdf

    Android自定义组件开发是一项高级技术,它允许开发者创建具有特定功能...总之,自定义组件开发是一项挑战和机遇并存的技术领域,对于有志于成为Android领域专家的开发者来说,深入学习和实践自定义组件开发将大有裨益。

    android漂亮的圆形旋转菜单

    8. 兼容性测试:Android设备众多,屏幕尺寸和分辨率各异,因此确保圆形旋转菜单在各种设备上都能正常工作非常重要。进行广泛的兼容性测试,以确保良好的用户体验。 总之,"android漂亮的圆形旋转菜单"是一个展示...

    数字标识控件.zip 移动开发 / Android

    9. **适配不同屏幕尺寸**:由于Android设备的屏幕尺寸各异,开发者需要确保数字标识控件在各种屏幕尺寸上都能正常显示。这涉及到对布局进行响应式设计,如使用约束布局(ConstraintLayout)或百分比布局...

    在菜单上放置多种控件.rar

    在不同的编程环境中,实现菜单控件的方法各异。以下以常见的几种为例: 1. Windows Forms:使用MenuStrip控件,通过添加ToolStripMenuItem并设置其属性来创建菜单,然后可以添加Button、CheckBox等控件到菜单项。 2....

    Android自定义预定日历,并且显示阴历源码(可扩展)

    1. **自定义View**: 在Android中,自定义View是通过继承`View`或`ViewGroup`类来实现的。在这个项目中,开发者可能创建了一个自定义的`CalendarView`类,该类扩展了`View`或`ViewGroup`,并重写了`onDraw()`方法以...

    asp.net 编辑控件

    这可以通过继承现有的控件类,或者利用用户控件(User Control)和自定义控件(Composite Control)来实现。自定义控件可以封装特定的功能,提高代码复用性,并保持界面一致性。 在实际应用中,我们可能需要根据...

    利用c#绘制异形控件

    本文将深入探讨如何利用C#绘制异形控件,即那些形状不规则、非矩形的控件,通过Region属性来实现。 首先,我们要理解什么是Region。在Windows API中,Region是一个表示图形区域的对象,它可以是任意形状,如圆形、...

    java和android通用的方法总结

    9. **安卓自定义控件使用**: 自定义控件可以满足特定的设计需求,这部分可能涵盖了自定义View的基本步骤,如onDraw()方法的重写,以及如何在XML布局中使用自定义控件。 以上这些内容涵盖了Java和Android开发的关键...

    Android项目中实现弹幕功能

    在Android中,我们通常会使用`MediaPlayer`类来播放视频,或者更高级的`ExoPlayer`库,它提供了更多的自定义选项和更好的性能。`ExoPlayer`支持多种媒体格式,并允许开发者控制播放速度、音轨选择等。为了实现弹幕与...

    安卓UI自定义定制开发

    9. 多分辨率适配:Android设备屏幕尺寸和分辨率各异,因此UI设计必须考虑到不同设备的适配问题。通过使用相对布局、百分比布局或ConstraintLayout,以及dp单位,可以实现更好的屏幕适配。 10. accessibility支持:...

    Android模仿人人界面

    在Android中,这些可以通过ImageView、Button、EditText和ListView等控件来实现。开发者可能需要自定义这些控件的样式,比如设置圆角头像、透明背景的按钮等,以贴近人人网的视觉风格。 3. **自定义视图**:对于...

Global site tag (gtag.js) - Google Analytics