AndroidSwipeLayout的使用(listview,gridview,view中滑动显示隐藏按钮的使用)
2015-11-06 12:02
681 查看
首先感谢类库作者。
这个控件比较强大,强大之处就在于SwipeLayout这个类
SwipeLayout:
里面封装了对拖动事件的处理类ViewDragHelper,以及内嵌了两个参数类,使用了枚举类:
DragEdge类枚举了按钮从上下左右哪个位置出现。ShowMode类枚举了按钮是以被拉出的,还是隐藏在底部显示出来的。
默认是使用DragEdge.Right.ordinal(),ShowMode.PullOut.ordinal(),就是从右边拉出
构造函数:
先获取属性里DragEdge的int值,如果没有就使用默认的 DragEdge.Right.ordinal()的int值,他的int值表示该枚举类在构造器中的position,然后再通过
mDragEdge = DragEdge.values()[ordinal];来获得真正的枚举值。(ShowMode的参数也是同样的方法获得)
1.首先来分析view的初始化过程
如果是xml中的view,在加载布局的时候会调用addView方法,在这个方法中如果view有设置Gravity属性,会将该view添加到mDragEdges的map中,并设置相应的方向的key:
swipeLayout(就是swipeLayout嵌套swipeLayout的情况)布局可以有机会捕捉到触摸事件,并通过checkCanDrag(ev):
接下来看mDragHelperCallback对象:
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就要通过
onViewReleased(View releasedChild, float xvel, float yvel):
如果在拖动的过程中,手指移开了,拖动的view会自动回到对应的位置,这是通过processHandRelease方法来执行的,他里面通过判断手指拖动的时候的速度值xvel和获取的最小速度150.0作比较,如果速度是在-150和150两边的速度,会相应的调用open()或者close()方法,如果速度是在这两个数值中间的,就通过移动的距离来判断应该open()还是close():
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中:
首先通过dx和dy来判断当前的拖动的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:
@Override public 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和RTL gravity = 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<DragEdge, View> entry : mDragEdges.entrySet()) { if (entry.getValue() == null) { //means used the drag_edge attr, the no gravity child should be use set mDragEdges.put(entry.getKey(), child); break; } } } if (child.getParent() == this) { return; } super.addView(child, index, params); }然后就是view的绘制,接着手动布局之前加入到mDragEdges中的隐藏在底部的view:
@Override protected 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:
// 布局隐藏在下面的view private 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()); } } //布局两种情况下的view if (mShowMode == ShowMode.PullOut) { layoutPullOut(); } else if (mShowMode == ShowMode.LayDown) { layoutLayDown(); } safeBottomView(); }并且只会针对该view(在PullOut和LayDown)的情况下进行layout:
void layoutPullOut() { Rect rect = computeSurfaceLayoutArea(false); View surfaceView = getSurfaceView(); //布局显示的view if (surfaceView != null) { surfaceView.layout(rect.left, rect.top, rect.right, rect.bottom); bringChildToFront(surfaceView); } //布局隐藏在下面的view rect = 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<View> 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.接下来分析拖动的过程:
@Override public 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 touch if (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 layout return 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/UP mDragHelper.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方法中进行的:
@Override public 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 onInterceptTouchEvent checkCanDrag(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/UP mDragHelper.processTouchEvent(event); } return super.onTouchEvent(event) || mIsBeingDragged || action == MotionEvent.ACTION_DOWN; }重点就是调用了mDragHelper.processTouchEvent(event);方法,这个方法会在ViewDragHelper中帮我们处理很多东西,并通过回调类mDragHelperCallback的各种方法来将需要的参数回调给我们。
接下来看mDragHelperCallback对象:
private ViewDragHelper.Callback mDragHelperCallback = new ViewDragHelper.Callback() { @Override public 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; } @Override public 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; } @Override public boolean tryCaptureView(View child, int pointerId) { // 如果显示是前面的view或者是集合中隐藏的view boolean result = child == getSurfaceView() || getBottomViews().contains(child); if (result) { isCloseBeforeDrag = getOpenStatus() == Status.Close; } return result; } @Override public int getViewHorizontalDragRange(View child) { return mDragDistance; } @Override public int getViewVerticalDragRange(View child) { return mDragDistance; } boolean isCloseBeforeDrag = true; @Override public 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(); } @Override public 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(); //如果移动的是上面的view if (changedView == surfaceView) { //如果是PullOut模式且存在隐藏的view if (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)) { //如果移动的是隐藏的view if (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.0 float 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.75 float 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<View, ArrayList<OnRevealListener>> 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()); else l.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; } } }
我们简单的通过拆分成三块来分析了该类库核心类的作用。
相关文章推荐
- Android 使用Fragment界面向下跳转并一级级返回
- 吃掉Android混淆——ProGuard第一篇
- Android 使用Fragment实现底部菜单栏
- android 遍历assert文件夹下的文件
- android toolbar
- Android开发之创建ActionBar
- Cannot reload AVD list:
- android中invalidate()的自动清屏含义以及屏幕刷新
- 使用Intent.ACTION_EDIT 调用系统编辑联系人
- Android开发&多媒体控件
- Android Mvvm入门教程
- android中 AIDL的使用
- Android NOTE
- 解决Android的TextView排版问题
- android:gravity与android:layout_gravity的区别,以及代码设置对应属性
- android自定义带返回功能的标题
- Android中画廊视图Gallery和ImageSwitcher组件的使用(十三)
- Android SIM内ECC文件存储超过5个就无法匹配service category
- android studio 学习-如何打包apk
- Android FDN号码完全匹配