通过自定义ViewGroup来实现侧滑菜单效果,解决滑动冲

本文详细介绍了如何在Android中实现侧滑菜单效果,并解决自定义ViewGroup与ListView的滑动冲突问题。通过事件分发、自定义ViewGroup和源码分析,提供了一个完整解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言:

在去年跟同事做一个项目的时候遇到了一个问题,那就是根据需求要求实现一个侧滑菜单的效果,那时候感觉这个好牛逼,跟QQ的效果差不多,感觉有点难做,所以今天我决定写一篇博客来分析下该如何做这个侧滑菜单,同时,在自定义ViewGroup中如何解决侧滑菜单与ListView冲突的问题。
      View的事件分发,很多博客上都有,跟大家推荐下的是郭霖的博客,他写的那个事件分发的博客简单易懂,同时也可以看看任玉刚的《android艺术探索》,里面讲解的很深入,当然最好的是一边看博客或者书籍,一边自己对照着源码来分析。源码上虽然有些东西难懂,但是很多代码都是可以忽略的,选取其中的重点来看。
这是效果图:



这两个界面的整体是一个ViewGroup,在android中,View是可以看成无限大的,只是手机屏幕只有那么大,所以显示的区域只有灰色的那一部分,通过控制View的ScrollX来控制View在手机屏幕中显示的位置。


上图就是整个ViewGroup的布局,这个应该都不难理解,手机屏幕就是灰色的ListView的区域,而手机屏幕左侧的就是白色的ListView的区域,通过控制ViewGroup的偏移量ScrollX 来达到这个在手机上滑动的效果。上图自己在写代码的时候最好是自己手动画一下,这样方便自己理解。
  好了,基本的东西基本都介绍了,那下面就来自定义我们自己的侧滑菜单了,先看布局代码,这样方便理解,虽然最开始做的部分是自定义ViewGroup。
activity_main.xml的布局代码:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#aaffaa"
    tools:context="com.example.slidegroup.MainActivity" >

    <com.example.slidegroup.SlideView
        android:layout_width="wrap_content"
        android:layout_height="match_parent" >

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

        <include layout="@layout/content" />
    </com.example.slidegroup.SlideView>

</RelativeLayout>
两个<include>包含的就是两个listView而已,很简单我就不贴代码了.
可以看出我们自定义的ViewGroup有两个子View,下面就是我们的ViewGroup的代码:
public class SlideView extends ViewGroup {
	/**
	 * 整个ViewGroup 由一个menu, 一个content组成,都是一个简单的listView。
	 */
	//屏幕宽度
	private int mScreenWidth;
	//屏幕高度
	private int mScreenHeight;
	//menu的宽度,左侧的菜单
	private int mMenuWidth;
	//左侧的menu
	private ViewGroup mMenu;
	//屏幕中默认显示的content
	private ViewGroup mContent;
	//上一次手指接触屏幕的X坐标
	private float mLastX;
	//上一次手指接触屏幕的Y坐标
	private float mLastY;
	//当手指点击屏幕的时候的X坐标,
	private float mDownX;
	//当menu完全显示的时候右侧content的显示的宽度
	private int mMenuMargin = 80;
	//帮助实现View滑动的类
	private Scroller mScroller;
	//当事件分发到ViewGroup的dispatchTouchEvent(MotionEvent ev)的时候,
	//因为onInterceptTouchEvent是在dispatchTouchEvent中被调用的
	//上次手指所处屏幕的X的坐标
	private int mLastInterceptX = 0;
	//上次手指所处屏幕的Y的坐标
	private int mLastInterceptY = 0;
	
	public SlideView(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
		WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
		DisplayMetrics metrics = new DisplayMetrics();
		wm.getDefaultDisplay().getMetrics(metrics);
		mScreenWidth = metrics.widthPixels;
		mScreenHeight = metrics.heightPixels;
		mMenuMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mMenuMargin, context.getResources().getDisplayMetrics());
		mMenuWidth = mScreenWidth - mMenuMargin;
		mScroller = new Scroller(context);
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// TODO Auto-generated method stub
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		mMenu = (ViewGroup) getChildAt(0);
		mMenu.getLayoutParams().width = (int) mMenuWidth;
		mContent = (ViewGroup) getChildAt(1);
		mContent.getLayoutParams().width = (int) mScreenWidth;
		measureChild(mMenu, widthMeasureSpec, heightMeasureSpec);
		measureChild(mContent, widthMeasureSpec, heightMeasureSpec);
		setMeasuredDimension(mMenuWidth + mScreenWidth, mScreenHeight);
	}

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		if (changed) {
			//android的机制,View在显示到屏幕的时候,会进行最少2次的OnMeasure,OnLayout...
			//所以这里只在第一次的时候进行ViewGroup在手机屏幕中显示的位置的设定
			mMenu.layout(-mMenuWidth, 0, 0, mScreenHeight);
			mContent.layout(0, 0, mScreenWidth, mScreenHeight);
		}
	}
	
	//通过重写ViewGroup的事件拦截方法来解决ViewGroup与ListView的滑动冲突
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		// return super.onInterceptTouchEvent(ev);
		boolean intercept = false;
		int x = (int) ev.getRawX();
		int y = (int) ev.getRawY();
		switch (ev.getAction()) {
		case MotionEvent.ACTION_DOWN:
			if(!mScroller.isFinished()){
				mScroller.abortAnimation();
				intercept = true;
			}
			break;

		case MotionEvent.ACTION_MOVE:
			//判断滑动的时候,横向的距离与纵向的距离只差
			//如果横向的距离大于纵向的距离,那就拦截,ViewGroup滑动(消费点击事件);
			//如果横向的距离小于纵向的距离,那就不拦截,ListView滑动(消费点击事件);
			int deltaX = (int) ev.getRawX() - mLastInterceptX;
			int deltaY = (int) ev.getRawY() - mLastInterceptY;
			Log.d("deltaX", deltaX+"");
			Log.d("deltaY", deltaY+"");
			if(Math.abs(deltaX) > Math.abs(deltaY)){
				intercept = true;
			}else{
				intercept = false;
			}
			break;
			
		case MotionEvent.ACTION_UP:
			intercept = false;
			break;

		}
		mLastX = x;
		mLastY = y;
		mLastInterceptX = x;
		mLastInterceptY = y;
		return intercept;
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			mDownX = event.getRawX();
			mLastX = mDownX;
			break;
		case MotionEvent.ACTION_MOVE:
			float mCurrentX = event.getRawX();
			float mCurrentY = event.getRawY();
			//获取滑动时上一个点到现在手指所处的点的距离
			float scrolledX = mLastX - mCurrentX;
			
			//边界值判断(这里最好是自己手动画图,便于理解)
			if (-(getScrollX() + scrolledX) > mMenuWidth) {
				scrollTo(-mMenuWidth, 0);
				return true;
			} else if (getScrollX() + scrolledX > 0) {
				scrollTo(0, 0);
				return true;
			}

			scrollBy((int) scrolledX, 0);
			mLastX = mCurrentX;
			mLastY = mCurrentY;
			break;
		case MotionEvent.ACTION_UP:
			//当手指离开屏幕的时候,判断滑动的距离是否大于Menu宽度的1/2
			//如果大于则显示menu,如果小于则不显示
			if (-getScrollX() >= mMenuWidth / 2) {
				mScroller.startScroll(getScrollX(), 0,
						-(mMenuWidth + getScrollX()), 0);
				invalidate();
			} else {
				mScroller.startScroll(getScrollX(), 0, -getScrollX(), 0);
				invalidate();
			}
			break;

		}
		return super.onTouchEvent(event);
	}
	
	//这里是实现View缓慢滑动所要重写的方法
	@Override
	public void computeScroll() {
		// TODO Auto-generated method stub
		if (mScroller.computeScrollOffset()) {
			scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
			invalidate();
		}
	}
	
}

代码中都有注释,这里就不详解了,基本上原理就是这样,通过不断的获取ViewGroup的ScrollX来达到滑动的效果,同时判定ViewGroup在屏幕中显示的区域,当ViewGroup与子View都有滑动的需求的时候,这里要要考虑他们俩是否存在滑动冲突问题,(View的事件分发网上有很多资料,解决方法也多种多样,最主要的还是要了解自己的业务需求,这样才能定制好合适自己的方法)。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值