android完美slidingmenu滑动按钮

来源:互联网 发布:网络主播经纪公司合同 编辑:程序博客网 时间:2024/06/05 15:52


想实现类似网易新闻的右滑出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


@Overridepublic 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