抽屉式菜单-仿QQ5.0侧滑菜单

为扩展屏幕尺寸所带来的局限性,很多APP都有侧滑菜单。所谓的侧滑菜单就是将一些菜单选项隐藏起来,而不是放置在主屏幕上,然后可以通过滑动的方式将菜单显示出来。

本文将通过android提供的HorizontalScrollView来实现一个仿QQ5.0侧滑菜单的案例,通过自定义控件和属性动画来实现侧滑效果。

1. 最终效果图


2. 布局文件

left_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    
    <Button 
        android:id="@+id/call"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="160dp"
        android:drawableLeft="@drawable/nav_call"
        android:drawablePadding="20dp"
        android:paddingLeft="20dp"
        android:text="Call"
        android:gravity="center_vertical"
        android:background="@null"/>
    
    <Button 
        android:id="@+id/friends"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:drawableLeft="@drawable/nav_friends"
        android:drawablePadding="20dp"
        android:text="Friends"
        android:gravity="center_vertical"
        android:paddingLeft="20dp"
        android:background="@null"/>

    <Button 
        android:id="@+id/location"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:drawableLeft="@drawable/nav_location"
        android:drawablePadding="20dp"
        android:text="Location"
        android:gravity="center_vertical"
        android:paddingLeft="20dp"
        android:background="@null"/>
    
    <Button 
        android:id="@+id/mail"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:drawableLeft="@drawable/nav_mail"
        android:drawablePadding="20dp"
        android:text="Mail"
        android:gravity="center_vertical"
        android:paddingLeft="20dp"
        android:background="@null"/>
    
    <Button 
        android:id="@+id/tasks"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:drawableLeft="@drawable/nav_task"
        android:drawablePadding="20dp"
        android:text="Tasks"
        android:gravity="center_vertical"
        android:paddingLeft="20dp"
        android:background="@null"/>

</LinearLayout>
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:yxman="http://schemas.android.com/apk/res/com.yx.slidingmenu"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <com.yx.slidingmenu.view.SlidingMenu
        android:id="@+id/id_menu"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/sliding_menu_background"
        yxman:rightPadding="80dp" >

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:orientation="horizontal" >

            <include layout="@layout/left_menu" />

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@drawable/home_background_image"
                android:orientation="vertical" >

                <RelativeLayout
                    android:layout_width="match_parent"
                    android:layout_height="50dp"
                    android:background="#13b7f6" >

                    <Button
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:background="@drawable/ic_menu"
                        android:onClick="toggleMenu" />
                </RelativeLayout>
            </LinearLayout>
        </LinearLayout>
    </com.yx.slidingmenu.view.SlidingMenu>

</RelativeLayout>

首先自定义一个用于显示侧滑菜单的left_menu.xml布局文件,然后在activity_main.xml主布局文件中通过一个水平的LinearLayout将子布局文件加载进来,以及将主布局文件编写在其中。

3. 添加自定义属性

a. 首先在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>
b. 在主布局activity_main.xml中声明命名空间和使用自定的属性:
<com.yx.slidingmenu.view.SlidingMenu
        android:id="@+id/id_menu"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/sliding_menu_background"
        yxman:rightPadding="80dp" >
        ......
</com.yx.slidingmenu.view.SlidingMenu>

我的命名空间是xmlns:yxman="http://schemas.android.com/apk/res/com.yx.slidingmenu"。格式是:http://schemas.android.com/apk/res/后面再加上项目的包名。

属性yxman:rightPadding="80dp"右侧内边距设置为80dp。

4. 自定义SlidingMenu

SlidingMenu.java

package com.yx.slidingmenu.view;

import com.nineoldandroids.view.ViewHelper;
import com.yx.slidingmenu.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;

public class SlidingMenu extends HorizontalScrollView {

	private LinearLayout mWapper;
	private ViewGroup mMenu;
	private ViewGroup mContent;
	private int mScreenWidth;
	private int mMenuWidth;

	// 单位是dp
	private int mMenuRightPadding = 50;

	private boolean flag;
	private boolean isOpen;

	public SlidingMenu(Context context) {
		this(context, null);
	}

	/**
	 * 未使用自定义属性时调用
	 * 
	 * @param context
	 * @param attrs
	 */
	public SlidingMenu(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	/**
	 * 当使用了自定义属性时会调用此构造方法
	 * 
	 * @param context
	 * @param attrs
	 * @param defStyle
	 */
	public SlidingMenu(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);

		// 获取自定义定义的属性
		TypedArray attribute = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SlidingMenu, defStyle, 0);

		int n =  attribute.getIndexCount();
		for (int i = 0; i < n; i++) {
			int attr = attribute.getIndex(i);

			switch (attr) {
			case R.styleable.SlidingMenu_rightPadding:
				int defValue = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, context.getResources().getDisplayMetrics());
				mMenuRightPadding = attribute.getDimensionPixelSize(attr, defValue);
				break;
			}
		}
		attribute.recycle();

		WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
		DisplayMetrics outMetrics = new DisplayMetrics();
		wm.getDefaultDisplay().getMetrics(outMetrics);
		mScreenWidth = outMetrics.widthPixels;
	}

	/**
	 * 设置子View的宽和高,设置自己的宽和高
	 */
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		if (!flag) {
			mWapper = (LinearLayout) getChildAt(0);
			mMenu = (ViewGroup) mWapper.getChildAt(0);
			mContent = (ViewGroup) mWapper.getChildAt(1);
			mMenuWidth = mMenu.getLayoutParams().width = mScreenWidth - mMenuRightPadding;
			mContent.getLayoutParams().width = mScreenWidth;
			flag = true;
		}
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
	}

	/**
	 * 通过设置偏移量,将menu隐藏
	 */
	@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);
		}
	}

	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		int action = ev.getAction();
		switch (action) {
		case MotionEvent.ACTION_UP:
			// 隐藏在左边的宽度
			int scrollX = getScrollX();
			if (scrollX >= mMenuWidth / 2) {
				this.smoothScrollTo(mMenuWidth, 0);
				isOpen = false;
			} else {
				this.smoothScrollTo(0, 0);
				isOpen = true;
			}
			return true;
		}
		return super.onTouchEvent(ev);
	}

	/**
	 * 切换菜单
	 */
	public void toggle() {
		if (isOpen) {
			closeMenu();
		} else {
			openMenu();
		}
	}

	/**
	 * 打开菜单
	 */
	public void openMenu() {
		if (isOpen)
			return;
		this.smoothScrollTo(0, 0);
		isOpen = true;
	}

	/**
	 * 关闭菜单
	 */
	public void closeMenu() {
		if (!isOpen)
			return;
		this.smoothScrollTo(mMenuWidth, 0);
		isOpen = false;
	}

	/**
	 * 滚动发生时
	 */
	@Override
	protected void onScrollChanged(int l, int t, int oldl, int oldt) {
		super.onScrollChanged(l, t, oldl, oldt);
		
		// scale取值范围:0~1
		float scale = l * 1.0f / mMenuWidth; 

		/**
		 * 1. 内容区域0.7~1.0 缩放的效果 scale: (0.7 + 0.3 * scale)
		 * 2. 菜单的偏移量需要修改
		 * 3. 菜单的显示时有缩放以及透明度变化:
		 *      缩放: 0.7 ~1.0 -> 1.0 - scale * 0.3 
		 *      透明度: 0.6 ~ 1.0 -> 1.0 - scale * 0.4
		 */
		float rightScale = 0.7f + 0.3f * scale;
		float leftScale = 1.0f - scale * 0.3f;
		float leftAlpha = 1.0f - scale * 0.4f;

		// 调用属性动画,设置TranslationX
		ViewHelper.setTranslationX(mMenu, mMenuWidth * scale * 0.8f);
		ViewHelper.setScaleX(mMenu, leftScale);
		ViewHelper.setScaleY(mMenu, leftScale);
		ViewHelper.setAlpha(mMenu, leftAlpha);
		// 设置content的缩放的中心点
		ViewHelper.setPivotX(mContent, 0);
		ViewHelper.setPivotY(mContent, mContent.getHeight() / 2);
		ViewHelper.setScaleX(mContent, rightScale);
		ViewHelper.setScaleY(mContent, rightScale);
	}
}

以上代码看着很复杂,但都有详细的注释,主要是用于处理滑动事件,不过多解释。

5. MainActivity.java

package com.yx.slidingmenu;

import com.yx.slidingmenu.view.SlidingMenu;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;

public class MainActivity extends Activity {

	private SlidingMenu mLeftMenu;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		mLeftMenu = (SlidingMenu) findViewById(R.id.id_menu);
	}

	public void toggleMenu(View view) {
		mLeftMenu.toggle();
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}
}

至此,核心代码和主要演示过程以完毕。是不是感觉很炫酷,就是这么简单。如有疑问下载源码自行研究。

源码下载:https://github.com/yuanxindev/qq_5_0_slidingmenu.git



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值