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

来源:互联网 发布:fedora 安装软件 编辑:程序博客网 时间:2024/05/16 19:31

前言:

在去年跟同事做一个项目的时候遇到了一个问题,那就是根据需求要求实现一个侧滑菜单的效果,那时候感觉这个好牛逼,跟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;//左侧的menuprivate ViewGroup mMenu;//屏幕中默认显示的contentprivate 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 stubWindowManager 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);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// TODO Auto-generated method stubsuper.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);}@Overrideprotected 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的滑动冲突@Overridepublic 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;}@Overridepublic 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缓慢滑动所要重写的方法@Overridepublic void computeScroll() {// TODO Auto-generated method stubif (mScroller.computeScrollOffset()) {scrollTo(mScroller.getCurrX(), mScroller.getCurrY());invalidate();}}}

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


0 0
原创粉丝点击