android完美slidingmenu滑动按钮

本文介绍了一种自定义滑动菜单的实现方法,通过重写FrameLayout并控制两个View的相对位置,实现了类似网易新闻的右滑出菜单效果。

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


想实现类似网易新闻的右滑出menu的那种效果(youTube也有那种效果),网上找了种种方法和代码例子,发现都达不到想要的效果。发现他们大部分方法,不是重写HorizontalScrollView, 就是重写SlidingDrawer。

其实想像一下那个交互的直实情况,完全不用绕圏子,原理就可以是将上面的一个View拨开,将下面的View显示出来而以。 不如自己重写一个基本布局.


demo下载


 先看一下做出来的效果吧,有人说没图说真相, 这个自己多嵌套了一层。(每滑完一层,右边就变灰,表示不可操作了

右滑---------->再右滑---------->

 最大的特点是如果你要优化自己的代码成这个效果,代码改动量非常小,只需要调整一下布局,改改View的位置就可以了。甚至代码可以不用修改。


 下面看看实现过程

首先,两个View是可以重叠的。我们使用FrameLayout作为继承类。命名为ScrollDrawerView

这里要保证FrameLayout里有两个子View, 做很多事情先都要先检测一下。

	/**
	 * 检查设置top, bottom
	 * @return
	 */
	private boolean checkTopBottomOk() {
		if (mBottomView != null && mTopView != null) {
			return mBottomView != mTopView;
		}
		
		int count = getChildCount();
		if (mBottomView == null && mTopView == null) {
			if (count != 2) {
				return false;
			} else {
				mBottomView = getChildAt(0);
				mTopView = getChildAt(1);
				return mBottomView != mTopView;
			}
		} else {
			if (count != 1) {
				return false;
			} else {
				View v = getChildAt(0);
				if (mBottomView != null) {
					mTopView = v;
				} else {
					mBottomView = v;
				}
				return mBottomView != mTopView;
			}
		}
	}

    /**
     * 设置(替换)抽屉的下层
     * @param v
     */
    public void setBottomView(View v) {
        this.addView(v, 0);
        mBottomView = v;
    }
    
    /**
     * 设置(替换)抽屉的上层
     * @param v
     */
    public void setTopView(View v) {
        this.addView(v, 1);
        mTopView = v;
    }

需要灵活一点的话,就是继可以在layout文件中去配置View的位置,也可以在代码中进行设置见上面的代码。


2, 要写View的话onInterceptTouchEvent和onTouchEvent是必须要处理的。因为之前对这些只是一知半解, 也懒得去细想。于是用一个偷懒的办法从ViewPager那里“借”点东西喽。因为ViewPager跟我们的事件拦截非常相似。如果子View里面内容可以滑动,则不会触发父View的onTouchEvent事件。脆将onInterceptTouchEvent抄过来,稍作修改,嘿嘿大功告成注意canScroll方法是核心


   private boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
    	if (v instanceof ViewGroup) {
            final ViewGroup group = (ViewGroup) v;
            final int scrollX = v.getScrollX();
            final int scrollY = v.getScrollY();
            final int count = group.getChildCount();
            // Count backwards - let topmost views consume scroll distance first.
            for (int i = count - 1; i >= 0; i--) {
                // TODO: Add versioned support here for transformed views.
                // This will not work for transformed views in Honeycomb+
                final View child = group.getChildAt(i);
                if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
                        y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
                        canScroll(child, true, dx, x + scrollX - child.getLeft(),
                                y + scrollY - child.getTop())) {
                    return true;
                }
            }
        }
        return checkV && v.canScrollHorizontally(-dx) && v.isEnabled();
    }
另外拓展性想稍强一点, 可以自嵌套的话。也要重canScrollHorizontally


	@Override
	public boolean canScrollHorizontally(int direction) {
		if (checkTopBottomOk()) {
			int postion = mTopView.getLeft() - direction;
			return postion >= 0 && postion <= mBottomView.getWidth(); 
		} else {
			return false;
		}
	}
面的意思就是mTopView的位置只能在0 到mBottomView.getWidth()的范围,不能越界。


看onTouchEvent的处理。ACTION_DOWN只是一个初始化的理。而且很多情况不会运行到,原因是事件的起源是从onInterceptTouchEvent的ACTION_MOVE那里才决定到要去拦截处理这个事件。因此onTouchEvent的很大机率是从ACTION_MOVE开始接收的。

				final int activePointerIndex = event.findPointerIndex(mActivePointerId);
				final int x = (int) event.getX(activePointerIndex);
				int deltaX = mLastMotionX - x;
				
				if (!mIsBeingDragged) {
					final int y = (int) event.getY(activePointerIndex);
					int yDiff = Math.abs(y - mLastMotionY);
					
					if (Math.abs(deltaX) > mTouchSlop && Math.abs(deltaX) > yDiff) {
						final ViewParent parent = getParent();
						if (parent != null) {
							parent.requestDisallowInterceptTouchEvent(true);
						}
						mIsBeingDragged = true;
						if (deltaX > 0) {
							deltaX -= mTouchSlop;
						} else {
							deltaX += mTouchSlop;
						}
					}
				}
				
				if (mIsBeingDragged) {
					mLastMotionX = x;
					
					int bottomWidth = mBottomView.getWidth();
					int left = mTopView.getLeft() - deltaX;
					if (left < 0) {
						left = 0;
					} else if (left > bottomWidth) {
						left = bottomWidth;
					}
					
					if (left == 0 || left == bottomWidth) {
						mVelocityTracker.clear();
					}
					//位置随手指变动
					mTopView.setLeft(left);
				}
			}
		
上面是ACTION_MOVE的处理,其分为两部分,if(!mIsBeingDragged){}里面是去决定是否真的去执行拖动事件。  if(mIsBeingDragged){} 里面是让mTopView的位置随手的移动而变动。


看下ACTION_UP的处理。这里决定了动过后的开关状态。

				final VelocityTracker velocityTracker = mVelocityTracker;
				velocityTracker.computeCurrentVelocity(500, mMaximumVelocity);
				int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId);
				
				final int activePointerIndex = event.findPointerIndex(mActivePointerId);
				final int x = (int) event.getX(activePointerIndex);
				int xDiff = Math.abs(mInitialMotionX - x );
				
				if ((Math.abs(initialVelocity) > mDirectVelocity)) {
					//速度足够大,执行切换
					opener(initialVelocity > 0);
				} else if ((Math.abs(initialVelocity) > mMinimumVelocity) && (xDiff > mBottomView.getWidth() / 4)) {
					//速度不太大,但移动距离够长,也执行切换
					opener(initialVelocity > 0);
				}else {
					//按现在位置,不到一半就返回,到了一半就过去
					opener(mTopView.getLeft() >= mBottomView.getWidth()/2);
				}

根据速度,移动位置,最终位置三个条件来决定状态的开关, 这里很好理解了。

然后再mScroller处理一下滑动的动画(关函数opener, computeScroll)。

再处理一下滑开后使mTopView无效(关联函数setViewEnabled)。

再处理一下Layout后mTopView的复问问题(onLayout).再修理一下边边角角。

OK,搞定了,效果不错。还可以嵌套使用可以延伸出很多有意思的效果来


我比较懒很多东西也说不清楚。。上面只是一个大概思路。详细的看代码就OK



评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值