AndroidSwipeLayout的使用(listview,gridview,view中滑动显示隐藏按钮的使用)

首先感谢类库作者。

这个控件比较强大,强大之处就在于SwipeLayout这个类

SwipeLayout:

里面封装了对拖动事件的处理类ViewDragHelper,以及内嵌了两个参数类,使用了枚举类:

public static enum DragEdge {Left, Right, Top, Bottom;};public static enum ShowMode {LayDown, PullOut}

DragEdge类枚举了按钮从上下左右哪个位置出现。ShowMode类枚举了按钮是以被拉出的,还是隐藏在底部显示出来的。

默认是使用DragEdge.Right.ordinal(),ShowMode.PullOut.ordinal(),就是从右边拉出

构造函数:

public SwipeLayout(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);mDragHelper = ViewDragHelper.create(this, mDragHelperCallback);mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SwipeLayout);int dragEdgeChoices = a.getInt(R.styleable.SwipeLayout_drag_edge, DRAG_RIGHT);mEdgeSwipesOffset[DragEdge.Left.ordinal()] = a.getDimension(R.styleable.SwipeLayout_leftEdgeSwipeOffset, 0);mEdgeSwipesOffset[DragEdge.Right.ordinal()] = a.getDimension(R.styleable.SwipeLayout_rightEdgeSwipeOffset, 0);mEdgeSwipesOffset[DragEdge.Top.ordinal()] = a.getDimension(R.styleable.SwipeLayout_topEdgeSwipeOffset, 0);mEdgeSwipesOffset[DragEdge.Bottom.ordinal()] = a.getDimension(R.styleable.SwipeLayout_bottomEdgeSwipeOffset, 0);setClickToClose(a.getBoolean(R.styleable.SwipeLayout_clickToClose, mClickToClose));if ((dragEdgeChoices & DRAG_LEFT) == DRAG_LEFT) {mDragEdges.put(DragEdge.Left, null);}if ((dragEdgeChoices & DRAG_TOP) == DRAG_TOP) {mDragEdges.put(DragEdge.Top, null);}if ((dragEdgeChoices & DRAG_RIGHT) == DRAG_RIGHT) {mDragEdges.put(DragEdge.Right, null);}if ((dragEdgeChoices & DRAG_BOTTOM) == DRAG_BOTTOM) {mDragEdges.put(DragEdge.Bottom, null);}int ordinal = a.getInt(R.styleable.SwipeLayout_show_mode, ShowMode.PullOut.ordinal());mShowMode = ShowMode.values()[ordinal];a.recycle();}

先获取属性里DragEdge的int值,如果没有就使用默认的 DragEdge.Right.ordinal()的int值,他的int值表示该枚举类在构造器中的position,然后再通过

mDragEdge = DragEdge.values()[ordinal];来获得真正的枚举值。(ShowMode的参数也是同样的方法获得)

1.首先来分析view的初始化过程

如果是xml中的view,在加载布局的时候会调用addView方法,在这个方法中如果view有设置Gravity属性,会将该view添加到mDragEdges的map中,并设置相应的方向的key:

 @Overridepublic void addView(View child, int index, ViewGroup.LayoutParams params) {if (child == null) return;int gravity = Gravity.NO_GRAVITY;try {gravity = (Integer) params.getClass().getField("gravity").get(params);} catch (Exception e) {e.printStackTrace();}if (gravity > 0) {//兼容LTR和RTLgravity = GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(this));//根据Gravity的值匹配对应的DragEdgekey值,并将view以value值放入到map中if ((gravity & Gravity.LEFT) == Gravity.LEFT) {mDragEdges.put(DragEdge.Left, child);}if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) {mDragEdges.put(DragEdge.Right, child);}if ((gravity & Gravity.TOP) == Gravity.TOP) {mDragEdges.put(DragEdge.Top, child);}if ((gravity & Gravity.BOTTOM) == Gravity.BOTTOM) {mDragEdges.put(DragEdge.Bottom, child);}} else {//如果gravity值小于0,就取寻找map中是否只有key没有value的键值,有就把当前的view和没有值的键组合在一起for (Map.Entry entry : mDragEdges.entrySet()) {if (entry.getValue() == null) {//means used the drag_edge attr, the no gravity child should be use setmDragEdges.put(entry.getKey(), child);break;}}}if (child.getParent() == this) {return;}super.addView(child, index, params);}
然后就是view的绘制,接着手动布局之前加入到mDragEdges中的隐藏在底部的view:

@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {updateBottomViews();if (mOnLayoutListeners != null) for (int i = 0; i < mOnLayoutListeners.size(); i++) {mOnLayoutListeners.get(i).onLayout(this);}}
在updateBottomViews方法中通过getCurrentBottomView方法获得当前隐藏在底部的view,由于默认的mCurrentDragEdge设置是DragEdge.Right,所以初始化阶段,currentBottomView默认就是mDragEdges中设置了right标记的view,并计算得出可以拖动的最大偏移量mDragDistance:

// 布局隐藏在下面的viewprivate void updateBottomViews() {//获得当前底部的view,并计算可以拖动的距离View currentBottomView = getCurrentBottomView();if (currentBottomView != null) {if (mCurrentDragEdge == DragEdge.Left || mCurrentDragEdge == DragEdge.Right) {mDragDistance = currentBottomView.getMeasuredWidth() - dp2px(getCurrentOffset());} else {mDragDistance = currentBottomView.getMeasuredHeight() - dp2px(getCurrentOffset());}}//布局两种情况下的viewif (mShowMode == ShowMode.PullOut) {layoutPullOut();} else if (mShowMode == ShowMode.LayDown) {layoutLayDown();}safeBottomView();}
并且只会针对该view(在PullOut和LayDown)的情况下进行layout:

 void layoutPullOut() {Rect rect = computeSurfaceLayoutArea(false);View surfaceView = getSurfaceView();//布局显示的viewif (surfaceView != null) {surfaceView.layout(rect.left, rect.top, rect.right, rect.bottom);bringChildToFront(surfaceView);}//布局隐藏在下面的viewrect = computeBottomLayoutAreaViaSurface(ShowMode.PullOut, rect);View currentBottomView = getCurrentBottomView();if (currentBottomView != null) {currentBottomView.layout(rect.left, rect.top, rect.right, rect.bottom);}}void layoutLayDown() {Rect rect = computeSurfaceLayoutArea(false);View surfaceView = getSurfaceView();if (surfaceView != null) {surfaceView.layout(rect.left, rect.top, rect.right, rect.bottom);bringChildToFront(surfaceView);}rect = computeBottomLayoutAreaViaSurface(ShowMode.LayDown, rect);View currentBottomView = getCurrentBottomView();if (currentBottomView != null) {currentBottomView.layout(rect.left, rect.top, rect.right, rect.bottom);}}/*** a helper function to compute the Rect area that surface will hold in.* 计算显示在前面的view的layout位置** @param open open status or close status.*/private Rect computeSurfaceLayoutArea(boolean open) {int l = getPaddingLeft(), t = getPaddingTop();if (open) {if (mCurrentDragEdge == DragEdge.Left)l = getPaddingLeft() + mDragDistance;else if (mCurrentDragEdge == DragEdge.Right)l = getPaddingLeft() - mDragDistance;else if (mCurrentDragEdge == DragEdge.Top)t = getPaddingTop() + mDragDistance;else t = getPaddingTop() - mDragDistance;}
//        return new Rect(l, t, l + getMeasuredWidth(), t + getMeasuredHeight());return new Rect(l, t, getMeasuredWidth() - l, getMeasuredHeight() - t);}/*** 通过计算获得当前隐藏的view的布局区域** @param mode* @param surfaceArea* @return*/private Rect computeBottomLayoutAreaViaSurface(ShowMode mode, Rect surfaceArea) {Rect rect = surfaceArea;View bottomView = getCurrentBottomView();int bl = rect.left, bt = rect.top, br = rect.right, bb = rect.bottom;if (mode == ShowMode.PullOut) {if (mCurrentDragEdge == DragEdge.Left)bl = rect.left - mDragDistance;else if (mCurrentDragEdge == DragEdge.Right)bl = rect.right;else if (mCurrentDragEdge == DragEdge.Top)bt = rect.top - mDragDistance;else bt = rect.bottom;if (mCurrentDragEdge == DragEdge.Left || mCurrentDragEdge == DragEdge.Right) {bb = rect.bottom;br = bl + (bottomView == null ? 0 : bottomView.getMeasuredWidth());} else {bb = bt + (bottomView == null ? 0 : bottomView.getMeasuredHeight());br = rect.right;}} else if (mode == ShowMode.LayDown) {if (mCurrentDragEdge == DragEdge.Left)br = bl + mDragDistance;else if (mCurrentDragEdge == DragEdge.Right)bl = br - mDragDistance;else if (mCurrentDragEdge == DragEdge.Top)bb = bt + mDragDistance;else bt = bb - mDragDistance;}return new Rect(bl, bt, br, bb);}/*** prevent bottom view get any touch event. Especially in LayDown mode.* 把不需要的view设置为INVISIBLE,以避免触摸事件冲突*/private void safeBottomView() {Status status = getOpenStatus();List bottoms = getBottomViews();if (status == Status.Close) {for (View bottom : bottoms) {if (bottom != null && bottom.getVisibility() != INVISIBLE) {bottom.setVisibility(INVISIBLE);}}} else {View currentBottomView = getCurrentBottomView();if (currentBottomView != null && currentBottomView.getVisibility() != VISIBLE) {currentBottomView.setVisibility(VISIBLE);}}}
2.接下来分析拖动的过程:

 @Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {if (!isSwipeEnabled()) {return false;}if (mClickToClose && getOpenStatus() == Status.Open && isTouchOnSurface(ev)) {return true;}for (SwipeDenier denier : mSwipeDeniers) {if (denier != null && denier.shouldDenySwipe(ev)) {return false;}}switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:mDragHelper.processTouchEvent(ev);mIsBeingDragged = false;sX = ev.getRawX();sY = ev.getRawY();//if the swipe is in middle state(scrolling), should intercept the touchif (getOpenStatus() == Status.Middle) {mIsBeingDragged = true;}break;case MotionEvent.ACTION_MOVE:boolean beforeCheck = mIsBeingDragged;checkCanDrag(ev);if (mIsBeingDragged) {ViewParent parent = getParent();if (parent != null) {parent.requestDisallowInterceptTouchEvent(true);}}if (!beforeCheck && mIsBeingDragged) {//let children has one chance to catch the touch, and request the swipe not intercept//useful when swipeLayout wrap a swipeLayout or other gestural layoutreturn false;}break;case MotionEvent.ACTION_CANCEL:case MotionEvent.ACTION_UP:mIsBeingDragged = false;mDragHelper.processTouchEvent(ev);break;default://handle other action, such as ACTION_POINTER_DOWN/UPmDragHelper.processTouchEvent(ev);}return mIsBeingDragged;}
触摸拦截方法中重要的是ACTION_DOWN和ACTION_MOVE,在ACTION_DOWN需要先调用mDragHelper.processTouchEvent(ev);方法在ViewDragHelp中记录点击的位置,其实一般是用mDragHelper.shouldInterceptTouchEvent(ev)方法。在ACTION_MOVE方法中通过局部变量来记录mIsBeingDragged的状态,是为了刚开始拖动的时候,让子的
swipeLayout(就是swipeLayout嵌套swipeLayout的情况)布局可以有机会捕捉到触摸事件,并通过checkCanDrag(ev):

/*** 检验当前的拖动动作能否触发拖动* 如果可以拖动就通过setCurrentDragEdge(dragEdge)设置当前的* dragEdge** @param ev*/private void checkCanDrag(MotionEvent ev) {if (mIsBeingDragged) return;if (getOpenStatus() == Status.Middle) {mIsBeingDragged = true;return;}Status status = getOpenStatus();float distanceX = ev.getRawX() - sX;float distanceY = ev.getRawY() - sY;float angle = Math.abs(distanceY / distanceX);angle = (float) Math.toDegrees(Math.atan(angle));if (getOpenStatus() == Status.Close) {DragEdge dragEdge;if (angle < 45) {if (distanceX > 0 && isLeftSwipeEnabled()) {dragEdge = DragEdge.Left;} else if (distanceX < 0 && isRightSwipeEnabled()) {dragEdge = DragEdge.Right;} else return;} else {if (distanceY > 0 && isTopSwipeEnabled()) {dragEdge = DragEdge.Top;} else if (distanceY < 0 && isBottomSwipeEnabled()) {dragEdge = DragEdge.Bottom;} else return;}setCurrentDragEdge(dragEdge);}boolean doNothing = false;if (mCurrentDragEdge == DragEdge.Right) {boolean suitable = (status == Status.Open && distanceX > mTouchSlop)|| (status == Status.Close && distanceX < -mTouchSlop);suitable = suitable || (status == Status.Middle);if (angle > 30 || !suitable) {doNothing = true;}}if (mCurrentDragEdge == DragEdge.Left) {boolean suitable = (status == Status.Open && distanceX < -mTouchSlop)|| (status == Status.Close && distanceX > mTouchSlop);suitable = suitable || status == Status.Middle;if (angle > 30 || !suitable) {doNothing = true;}}if (mCurrentDragEdge == DragEdge.Top) {boolean suitable = (status == Status.Open && distanceY < -mTouchSlop)|| (status == Status.Close && distanceY > mTouchSlop);suitable = suitable || status == Status.Middle;if (angle < 60 || !suitable) {doNothing = true;}}if (mCurrentDragEdge == DragEdge.Bottom) {boolean suitable = (status == Status.Open && distanceY > mTouchSlop)|| (status == Status.Close && distanceY < -mTouchSlop);suitable = suitable || status == Status.Middle;if (angle < 60 || !suitable) {doNothing = true;}}mIsBeingDragged = !doNothing;}
来判断底部是否有隐藏的view,是否有可以拖动的行为,如果可以,就通过如下代码来请求父布局不打断触摸事件,这样以后的移动事件都将由子swipeLayout布局的onTouchEvent方法来处理(如果不是为了这个用途,就不用自己记录mIsBeingDragged的标记,因为在ViewDragHelper中可以通过getViewDragState()来获得拖动的状态):

if (mIsBeingDragged) {ViewParent parent = getParent();if (parent != null) {parent.requestDisallowInterceptTouchEvent(true);}}
然后真正处理事件是放在onTouchEvent方法中进行的:

@Overridepublic boolean onTouchEvent(MotionEvent event) {if (!isSwipeEnabled()) return super.onTouchEvent(event);int action = event.getActionMasked();gestureDetector.onTouchEvent(event);switch (action) {case MotionEvent.ACTION_DOWN:mDragHelper.processTouchEvent(event);sX = event.getRawX();sY = event.getRawY();case MotionEvent.ACTION_MOVE: {//the drag state and the direction are already judged at onInterceptTouchEventcheckCanDrag(event);if (mIsBeingDragged) {getParent().requestDisallowInterceptTouchEvent(true);mDragHelper.processTouchEvent(event);}break;}case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:mIsBeingDragged = false;mDragHelper.processTouchEvent(event);break;default://handle other action, such as ACTION_POINTER_DOWN/UPmDragHelper.processTouchEvent(event);}return super.onTouchEvent(event) || mIsBeingDragged || action == MotionEvent.ACTION_DOWN;}
重点就是调用了mDragHelper.processTouchEvent(event);方法,这个方法会在ViewDragHelper中帮我们处理很多东西,并通过回调类mDragHelperCallback的各种方法来将需要的参数回调给我们。

接下来看mDragHelperCallback对象:

 private ViewDragHelper.Callback mDragHelperCallback = new ViewDragHelper.Callback() {@Overridepublic int clampViewPositionHorizontal(View child, int left, int dx) {if (child == getSurfaceView()) {//在拖动显示在前面的view的情况switch (mCurrentDragEdge) {//上下拖动的时候,设置左右不滑动case Top:case Bottom:return getPaddingLeft();//隐藏在左边,可以向右拖动,case Left://向右再往回拉的时候最多只能拉到PaddingLeft的位置if (left < getPaddingLeft()) return getPaddingLeft();//向右可以拉倒PaddingLeft加上mDragDistance的位置if (left > getPaddingLeft() + mDragDistance)return getPaddingLeft() + mDragDistance;break;case Right:if (left > getPaddingLeft()) return getPaddingLeft();if (left < getPaddingLeft() - mDragDistance)return getPaddingLeft() - mDragDistance;break;}} else if (getCurrentBottomView() == child) {//在拖动显示隐藏的view的情况switch (mCurrentDragEdge) {case Top:case Bottom:return getPaddingLeft();case Left://左边隐藏的view在向右边拖动的时候最多只能拖到PaddingLeft位置if (mShowMode == ShowMode.PullOut) {if (left > getPaddingLeft()) return getPaddingLeft();}break;case Right:if (mShowMode == ShowMode.PullOut) {if (left < getMeasuredWidth() - mDragDistance) {return getMeasuredWidth() - mDragDistance;}}break;}}return left;}@Overridepublic int clampViewPositionVertical(View child, int top, int dy) {if (child == getSurfaceView()) {switch (mCurrentDragEdge) {case Left:case Right:return getPaddingTop();case Top:if (top < getPaddingTop()) return getPaddingTop();if (top > getPaddingTop() + mDragDistance)return getPaddingTop() + mDragDistance;break;case Bottom:if (top < getPaddingTop() - mDragDistance) {return getPaddingTop() - mDragDistance;}if (top > getPaddingTop()) {return getPaddingTop();}}} else {View surfaceView = getSurfaceView();int surfaceViewTop = surfaceView == null ? 0 : surfaceView.getTop();switch (mCurrentDragEdge) {case Left:case Right:return getPaddingTop();case Top:if (mShowMode == ShowMode.PullOut) {if (top > getPaddingTop()) return getPaddingTop();} else {if (surfaceViewTop + dy < getPaddingTop())return getPaddingTop();if (surfaceViewTop + dy > getPaddingTop() + mDragDistance)return getPaddingTop() + mDragDistance;}break;case Bottom:if (mShowMode == ShowMode.PullOut) {if (top < getMeasuredHeight() - mDragDistance)return getMeasuredHeight() - mDragDistance;} else {if (surfaceViewTop + dy >= getPaddingTop())return getPaddingTop();if (surfaceViewTop + dy <= getPaddingTop() - mDragDistance)return getPaddingTop() - mDragDistance;}}}return top;}@Overridepublic boolean tryCaptureView(View child, int pointerId) {// 如果显示是前面的view或者是集合中隐藏的viewboolean result = child == getSurfaceView() || getBottomViews().contains(child);if (result) {isCloseBeforeDrag = getOpenStatus() == Status.Close;}return result;}@Overridepublic int getViewHorizontalDragRange(View child) {return mDragDistance;}@Overridepublic int getViewVerticalDragRange(View child) {return mDragDistance;}boolean isCloseBeforeDrag = true;@Overridepublic void onViewReleased(View releasedChild, float xvel, float yvel) {super.onViewReleased(releasedChild, xvel, yvel);processHandRelease(xvel, yvel, isCloseBeforeDrag);for (SwipeListener l : mSwipeListeners) {l.onHandRelease(SwipeLayout.this, xvel, yvel);}invalidate();}@Overridepublic void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {View surfaceView = getSurfaceView();if (surfaceView == null) return;View currentBottomView = getCurrentBottomView();int evLeft = surfaceView.getLeft(), evRight = surfaceView.getRight(), evTop = surfaceView.getTop(), evBottom = surfaceView.getBottom();//如果移动的是上面的viewif (changedView == surfaceView) {//如果是PullOut模式且存在隐藏的viewif (mShowMode == ShowMode.PullOut && currentBottomView != null) {//设置隐藏的view跟随移动if (mCurrentDragEdge == DragEdge.Left || mCurrentDragEdge == DragEdge.Right) {currentBottomView.offsetLeftAndRight(dx);} else {currentBottomView.offsetTopAndBottom(dy);}}} else if (getBottomViews().contains(changedView)) {//如果移动的是隐藏的viewif (mShowMode == ShowMode.PullOut) {//设置PullOut模式下的上面的view跟随移动surfaceView.offsetLeftAndRight(dx);surfaceView.offsetTopAndBottom(dy);} else {//需要控制底部的view始终保持不动Rect rect = computeBottomLayDown(mCurrentDragEdge);if (currentBottomView != null) {currentBottomView.layout(rect.left, rect.top, rect.right, rect.bottom);}//而surfaceView则需要向拖动的方向移动surfaceView.offsetLeftAndRight(dx);surfaceView.offsetTopAndBottom(dy);
//
//                    int newLeft = surfaceView.getLeft() + dx, newTop = surfaceView.getTop() + dy;
//
//                    if (mCurrentDragEdge == DragEdge.Left && newLeft < getPaddingLeft())
//                        newLeft = getPaddingLeft();
//                    else if (mCurrentDragEdge == DragEdge.Right && newLeft > getPaddingLeft())
//                        newLeft = getPaddingLeft();
//                    else if (mCurrentDragEdge == DragEdge.Top && newTop < getPaddingTop())
//                        newTop = getPaddingTop();
//                    else if (mCurrentDragEdge == DragEdge.Bottom && newTop > getPaddingTop())
//                        newTop = getPaddingTop();
//
//                    surfaceView.layout(newLeft, newTop, newLeft + getMeasuredWidth(), newTop + getMeasuredHeight());}}dispatchRevealEvent(evLeft, evTop, evRight, evBottom);dispatchSwipeEvent(evLeft, evTop, dx, dy);invalidate();}};



tryCaptureView:

由于拖动的可能是显示的view,也可能是子view,所以只要是这两种view,都会返回true.

clampViewPositionHorizontal(View child, int left, int dx):

参数中view就是当前拖动的view,返回的left就是当前view的最左边将要移动到的位置,所以在该方法中是为了防止view被拖出边界即可。

clampViewPositionVertical(View child, int top, int dy):

如上

onViewPositionChanged(View changedView, int left, int top, int dx, int dy):

如果在拖动view的时候会引起另一些view的相应移动,就要在此方法中作相应的操作,如果拖动的view是surfaceView,那么当前隐藏在下面的view就要通过

currentBottomView.offsetLeftAndRight(dx)
方法来设置相应的跟随移动;那如果拖动的view是bottomView:在PullOut模式下还是通过

//设置PullOut模式下的上面的view跟随移动surfaceView.offsetLeftAndRight(dx);surfaceView.offsetTopAndBottom(dy);
来移动,但假如是LayDown模式,假设在左边现在有一个隐藏的view,这时候通过拖动bottomView使其恢复到原来的位置,但是拖动的时候会发现bottomView往左边移动了,而实际需要的效果是bottomView不动,surfaceView向左边移动。所以需要通过

//需要控制底部的view始终保持不动Rect rect = computeBottomLayDown(mCurrentDragEdge);if (currentBottomView != null) {currentBottomView.layout(rect.left, rect.top, rect.right, rect.bottom);}
设置底部的view的位置不变,而surfaceView则需要作相应的移动:

 //而surfaceView则需要向拖动的方向移动surfaceView.offsetLeftAndRight(dx);surfaceView.offsetTopAndBottom(dy);
然后在拖动的过程中会调用这三个方法:

            dispatchRevealEvent(evLeft, evTop, evRight, evBottom);dispatchSwipeEvent(evLeft, evTop, dx, dy);invalidate();

onViewReleased(View releasedChild, float xvel, float yvel):

如果在拖动的过程中,手指移开了,拖动的view会自动回到对应的位置,这是通过processHandRelease方法来执行的,他里面通过判断手指拖动的时候的速度值xvel和获取的最小速度150.0作比较,如果速度是在-150和150两边的速度,会相应的调用open()或者close()方法,如果速度是在这两个数值中间的,就通过移动的距离来判断应该open()还是close():

 /*** Process the surface release event.** @param xvel                 xVelocity* @param yvel                 yVelocity* @param isCloseBeforeDragged the open state before drag*/protected void processHandRelease(float xvel, float yvel, boolean isCloseBeforeDragged) {//获得最小速度,150.0float minVelocity = mDragHelper.getMinVelocity();Log.i("llj","minVelocity:"+minVelocity+"xvel:"+xvel);View surfaceView = getSurfaceView();DragEdge currentDragEdge = mCurrentDragEdge;if (currentDragEdge == null || surfaceView == null) {return;}//如果在拖动前是关着的,设置临界值是0.25,否则是0.75float willOpenPercent = (isCloseBeforeDragged ? .25f : .75f);if (currentDragEdge == DragEdge.Left) {//如果速度比150大if (xvel > minVelocity) open();//如果速度比-150小else if (xvel < -minVelocity) close();else {//速度在-150到150之间的,通过移动的距离判断是否open()或者close()float openPercent = 1f * getSurfaceView().getLeft() / mDragDistance;if (openPercent > willOpenPercent) open();else close();}} else if (currentDragEdge == DragEdge.Right) {if (xvel > minVelocity) close();else if (xvel < -minVelocity) open();else {float openPercent = 1f * (-getSurfaceView().getLeft()) / mDragDistance;if (openPercent > willOpenPercent) open();else close();}} else if (currentDragEdge == DragEdge.Top) {if (yvel > minVelocity) open();else if (yvel < -minVelocity) close();else {float openPercent = 1f * getSurfaceView().getTop() / mDragDistance;if (openPercent > willOpenPercent) open();else close();}} else if (currentDragEdge == DragEdge.Bottom) {if (yvel > minVelocity) close();else if (yvel < -minVelocity) open();else {float openPercent = 1f * (-getSurfaceView().getTop()) / mDragDistance;if (openPercent > willOpenPercent) open();else close();}}}
open()方法有两种移动的方式,一种是平滑的,一种是瞬移的,平滑的就先通过computeSurfaceLayoutArea方法获取打开后surfaceView应该处于的位置,然后通过smoothSlideViewTo滑到相应的位置,不用管bottomView,因为他会在onViewPositionChanged中作相应的跟随移动;而如果是非平滑的分别通过computeSurfaceLayoutArea和computeBottomLayoutAreaViaSurface方法获取surfaceView和bottomView的最终位置,并通过layout来设置位置,invalidate方法来执行重绘:

 public void open(boolean smooth, boolean notify) {View surface = getSurfaceView(), bottom = getCurrentBottomView();if (surface == null) {return;}int dx, dy;//获取surfaceView的相应的位置Rect rect = computeSurfaceLayoutArea(true);//如果是平滑移动if (smooth) {mDragHelper.smoothSlideViewTo(surface, rect.left, rect.top);} else {dx = rect.left - surface.getLeft();dy = rect.top - surface.getTop();surface.layout(rect.left, rect.top, rect.right, rect.bottom);//如果是PullOut模式,也需要设置bottomView的重新layout//获取的位置应该是比rect.left往左边偏移mDragDistance的距离if (getShowMode() == ShowMode.PullOut) {Rect bRect = computeBottomLayoutAreaViaSurface(ShowMode.PullOut, rect);if (bottom != null) {bottom.layout(bRect.left, bRect.top, bRect.right, bRect.bottom);}}if (notify) {dispatchRevealEvent(rect.left, rect.top, rect.right, rect.bottom);dispatchSwipeEvent(rect.left, rect.top, dx, dy);} else {safeBottomView();}}invalidate();}
close()方法的过程和上面的open()类似:

 /*** close surface** @param smooth smoothly or not.* @param notify if notify all the listeners.*/public void close(boolean smooth, boolean notify) {View surface = getSurfaceView();if (surface == null) {return;}int dx, dy;if (smooth)//如果是平滑移动,调用如下方法,然后bottomView会在onViewPositionChanged滑到对应的位置mDragHelper.smoothSlideViewTo(getSurfaceView(), getPaddingLeft(), getPaddingTop());else {//如果是瞬回到初始化位置,直接通过layout设置,// 不用管底部隐藏的view,因为在重新拖动的时候底部的view会重新layout位置Rect rect = computeSurfaceLayoutArea(false);dx = rect.left - surface.getLeft();dy = rect.top - surface.getTop();surface.layout(rect.left, rect.top, rect.right, rect.bottom);if (notify) {dispatchRevealEvent(rect.left, rect.top, rect.right, rect.bottom);dispatchSwipeEvent(rect.left, rect.top, dx, dy);} else {safeBottomView();}}invalidate();}

3.接下来分析两个事件的分发过程

dispatchRevealEvent(evLeft, evTop, evRight, evBottom):

首先通过getRelativePosition(child)来计算出拖动过程中指定的view应该出现的位置,因为拖动的view可以是一个layout,而指定的view可能只是其中的子view,所以通过此方法得出子view在拖动过程中的对应位置,然后通过isViewShowing方法来判断当前子view是否被用户所看见。假设是left得到隐藏布局,就是通过子view右边的边界和paddingLeft这个临界点来作对比(if (childRight >= getPaddingLeft() && childLeft < getPaddingLeft()) return true;),如果是可见的就通过计算得出参数distance(移动的距离)和fraction(移动的距离占子view宽度的比重)的值,然后通过(l.onReveal(child, mCurrentDragEdge, Math.abs(fraction), distance);)方法回调给监听器;如果fraction比重的绝对值为1,说明该子view已经完全显示出来,接着就加到mShowEntirely的一个map中:

//分发揭示事件protected void dispatchRevealEvent(final int surfaceLeft, final int surfaceTop, final int surfaceRight, final int surfaceBottom) {if (mRevealListeners.isEmpty()) return;for (Map.Entry> entry : mRevealListeners.entrySet()) {View child = entry.getKey();//通过计算得出此时该view应该出现的位置Rect rect = getRelativePosition(child);if (isViewShowing(child, rect, mCurrentDragEdge, surfaceLeft, surfaceTop, surfaceRight, surfaceBottom)) {//如果指定的view显示在外面mShowEntirely.put(child, false);int distance = 0;float fraction = 0f;if (getShowMode() == ShowMode.LayDown) {switch (mCurrentDragEdge) {case Left:
//                            Log.i("llj", "rect.left:" + rect.left + "surfaceLeft:" + surfaceLeft);distance = rect.left - surfaceLeft;fraction = distance / (float) child.getWidth();break;case Right:distance = rect.right - surfaceRight;fraction = distance / (float) child.getWidth();break;case Top:distance = rect.top - surfaceTop;fraction = distance / (float) child.getHeight();break;case Bottom:distance = rect.bottom - surfaceBottom;fraction = distance / (float) child.getHeight();break;}} else if (getShowMode() == ShowMode.PullOut) {switch (mCurrentDragEdge) {case Left://计算向右移动了多少距离distance = rect.right - getPaddingLeft();//计算向右移动的距离占了指定view的宽度的比重fraction = distance / (float) child.getWidth();break;case Right:distance = rect.left - getWidth();fraction = distance / (float) child.getWidth();break;case Top:distance = rect.bottom - getPaddingTop();fraction = distance / (float) child.getHeight();break;case Bottom:distance = rect.top - getHeight();fraction = distance / (float) child.getHeight();break;}}//遍历分发监听器,如果已经完全打开就把view添加到map中for (OnRevealListener l : entry.getValue()) {l.onReveal(child, mCurrentDragEdge, Math.abs(fraction), distance);if (Math.abs(fraction) == 1) {mShowEntirely.put(child, true);}}}//如果已经完全打开就回调监听器if (isViewTotallyFirstShowed(child, rect, mCurrentDragEdge, surfaceLeft, surfaceTop, surfaceRight, surfaceBottom)) {mShowEntirely.put(child, true);for (OnRevealListener l : entry.getValue()) {if (mCurrentDragEdge == DragEdge.Left || mCurrentDragEdge == DragEdge.Right)l.onReveal(child, mCurrentDragEdge, 1, child.getWidth());elsel.onReveal(child, mCurrentDragEdge, 1, child.getHeight());}}}}
dispatchSwipeEvent(evLeft, evTop, dx, dy):

首先通过dx和dy来判断当前的拖动的view是正在打开还是正在关闭的状态:

//分发SwipeListener监听器,判断正在打开还是正在关闭protected void dispatchSwipeEvent(int surfaceLeft, int surfaceTop, int dx, int dy) {DragEdge edge = getDragEdge();boolean open = true;if (edge == DragEdge.Left) {if (dx < 0) open = false;} else if (edge == DragEdge.Right) {if (dx > 0) open = false;} else if (edge == DragEdge.Top) {if (dy < 0) open = false;} else if (edge == DragEdge.Bottom) {if (dy > 0) open = false;}dispatchSwipeEvent(surfaceLeft, surfaceTop, open);}
然后在刚开始打开,打开的过程中,完全打开,刚开始关闭,关闭的过程,完全关闭的几个情况下都做了相应的回调:

 //分发SwipeListener监听器protected void dispatchSwipeEvent(int surfaceLeft, int surfaceTop, boolean open) {
//        Log.i("llj", "surfaceLeft - getPaddingLeft():" + (surfaceLeft - getPaddingLeft()));safeBottomView();Status status = getOpenStatus();if (!mSwipeListeners.isEmpty()) {mEventCounter++;for (SwipeListener l : mSwipeListeners) {//将要打开或者关闭,只回调一次if (mEventCounter == 1) {if (open) {l.onStartOpen(this);} else {l.onStartClose(this);}}//打开或者关闭的中间过程l.onUpdate(SwipeLayout.this, surfaceLeft - getPaddingLeft(), surfaceTop - getPaddingTop());}//完全关闭if (status == Status.Close) {for (SwipeListener l : mSwipeListeners) {l.onClose(SwipeLayout.this);}mEventCounter = 0;}// 完全打开if (status == Status.Open) {View currentBottomView = getCurrentBottomView();if (currentBottomView != null) {currentBottomView.setEnabled(true);}for (SwipeListener l : mSwipeListeners) {l.onOpen(SwipeLayout.this);}mEventCounter = 0;}}}

我们简单的通过拆分成三块来分析了该类库核心类的作用。



本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部