public SwipeBackLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs); mDragHelper = ViewDragHelper.create(this, new ViewDragCallback()); TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.SwipeBackLayout, defStyle, R.style.SwipeBackLayout); int edgeSize = a.getDimensionPixelSize(R.styleable.SwipeBackLayout_edge_size, -1); if (edgeSize > 0) setEdgeSize(edgeSize); int mode = EDGE_FLAGS[a.getInt(R.styleable.SwipeBackLayout_edge_flag, 0)]; setEdgeTrackingEnabled(mode); int shadowLeft = a.getResourceId(R.styleable.SwipeBackLayout_shadow_left, R.drawable.shadow_left); int shadowRight = a.getResourceId(R.styleable.SwipeBackLayout_shadow_right, R.drawable.shadow_right); int shadowBottom = a.getResourceId(R.styleable.SwipeBackLayout_shadow_bottom, R.drawable.shadow_bottom); setShadow(shadowLeft, EDGE_LEFT); setShadow(shadowRight, EDGE_RIGHT); setShadow(shadowBottom, EDGE_BOTTOM); a.recycle(); final float density = getResources().getDisplayMetrics().density; final float minVel = MIN_FLING_VELOCITY * density; mDragHelper.setMinVelocity(minVel); mDragHelper.setMaxVelocity(minVel * 2f); }
public CardSlidePanel(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.card); bottomMarginTop = (int) a.getDimension(R.styleable.card_bottomMarginTop, bottomMarginTop); yOffsetStep = (int) a.getDimension(R.styleable.card_yOffsetStep, yOffsetStep); // 滑动相关类 mDragHelper = ViewDragHelper.create(this, 10f, new DragHelperCallback()); mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT); a.recycle(); }
@Override public void computeScroll() { if (mDragHelper != null && mDragHelper.continueSettling(true)) { if (!isEnabled()) { mDragHelper.abort(); return; } ViewCompat.postInvalidateOnAnimation(this); } }
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { final int action = MotionEventCompat.getActionMasked(ev); if (!isEnabled() || !isTouchEnabled() || (mIsUnableToDrag && action != MotionEvent.ACTION_DOWN)) { mDragHelper.cancel(); return super.onInterceptTouchEvent(ev); } if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { mDragHelper.cancel(); return false; } final float x = ev.getX(); final float y = ev.getY(); switch (action) { case MotionEvent.ACTION_DOWN: { mIsUnableToDrag = false; mInitialMotionX = x; mInitialMotionY = y; break; } case MotionEvent.ACTION_MOVE: { final float adx = Math.abs(x - mInitialMotionX); final float ady = Math.abs(y - mInitialMotionY); final int dragSlop = mDragHelper.getTouchSlop(); // Handle any horizontal scrolling on the drag view. if (mIsUsingDragViewTouchEvents && adx > dragSlop && ady < dragSlop) { return super.onInterceptTouchEvent(ev); } if ((ady > dragSlop && adx > ady) || !isDragViewUnder((int) mInitialMotionX, (int) mInitialMotionY)) { mDragHelper.cancel(); mIsUnableToDrag = true; return false; } break; } } return mDragHelper.shouldInterceptTouchEvent(ev); }
@Override public void computeScroll() { if (mDragHelper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this); } else { // 动画结束 synchronized (this) { if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) { orderViewStack(); btnLock = false; } } } }
/* touch事件的拦截与处理都交给mDraghelper来处理 */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean shouldIntercept = mDragHelper.shouldInterceptTouchEvent(ev); int action = ev.getActionMasked(); if (action == MotionEvent.ACTION_DOWN) { // ACTION_DOWN的时候就对view重新排序 orderViewStack(); // 保存初次按下时arrowFlagView的Y坐标 // action_down时就让mDragHelper开始工作,否则有时候导致异常 mDragHelper.processTouchEvent(ev); } return shouldIntercept; }
/** 点击按钮消失动画 */ private void vanishOnBtnClick(int type) { synchronized (obj1) { View animateView = viewList.get(0); if (animateView.getVisibility() != View.VISIBLE || releasedViewList.contains(animateView)) { return; } int finalX = 0; if (type == VANISH_TYPE_LEFT) { finalX = -childWith; } else if (type == VANISH_TYPE_RIGHT) { finalX = allWidth; } if (finalX != 0) { releasedViewList.add(animateView); if (mDragHelper.smoothSlideViewTo(animateView, finalX, initCenterViewY + allHeight)) { ViewCompat.postInvalidateOnAnimation(this); } } if (type >= 0 && cardSwitchListener != null) { cardSwitchListener.onCardVanish(isShowing, type); } } }
@Override public void computeScroll() { mScrimOpacity = 1 - mScrollPercent; if (mDragHelper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this); } }
@Override public boolean onTouchEvent(MotionEvent ev) { if (!isEnabled() || !isTouchEnabled()) { return super.onTouchEvent(ev); } mDragHelper.processTouchEvent(ev); return true; }
@Override public boolean onTouchEvent(MotionEvent event) { if (!mEnable) { return false; } mDragHelper.processTouchEvent(event); return true; }
/** * 将拦截的到事件给ViewDragHelper进行处理 * * @param e * @return */ @Override public boolean onTouchEvent(MotionEvent e) { try { dragHelper.processTouchEvent(e); } catch (Exception ex) { ex.printStackTrace(); } return false; }
public void close(boolean animate) { if (animate) { // 继续滑动 if (dragHelper.smoothSlideViewTo(vg_main, 0, 0)) { ViewCompat.postInvalidateOnAnimation(this); } } else { vg_main.layout(0, 0, width, height); dispatchDragEvent(0); } }
public void open(boolean animate) { if (animate) { // 继续滑动 if (dragHelper.smoothSlideViewTo(vg_main, range, 0)) { ViewCompat.postInvalidateOnAnimation(this); } } else { vg_main.layout(range, 0, range * 2, height); dispatchDragEvent(range); } }
@Override public boolean onTouchEvent(MotionEvent e) { try { // 统一交给mDragHelper处理,由DragHelperCallback实现拖动效果 // 该行代码可能会抛异常,正式发布时请将这行代码加上try catch mDragHelper.processTouchEvent(e); } catch (Exception ex) { ex.printStackTrace(); } return true; }
@Override public boolean onInterceptTouchEvent(MotionEvent event) { if (!mEnable) { return false; } try { return mDragHelper.shouldInterceptTouchEvent(event); } catch (ArrayIndexOutOfBoundsException e) { // FIXME: handle exception // issues #9 return false; } }
@Override public boolean onTouchEvent(@NonNull MotionEvent ev) { if (!isEnabled() || !isTouchEnabled()) { return super.onTouchEvent(ev); } try { mDragHelper.processTouchEvent(ev); return true; } catch (Exception ex) { // Ignore the pointer out of range exception return false; } }
@Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { final boolean drawContent = child == mContentView; boolean ret = super.drawChild(canvas, child, drawingTime); if (mScrimOpacity > 0 && drawContent && mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE) { drawShadow(canvas, child); drawScrim(canvas, child); } return ret; }
/** * Smoothly animate mDraggingPane to the target X position within its range. * * @param slideOffset position to animate to * @param velocity initial velocity in case of fling, or 0. */ boolean smoothSlideTo(float slideOffset, int velocity) { if (!isEnabled()) { // Nothing to do. return false; } int panelTop = computePanelTopPosition(slideOffset); if (mDragHelper.smoothSlideViewTo(mSlideableView, mSlideableView.getLeft(), panelTop)) { setAllChildrenVisible(); ViewCompat.postInvalidateOnAnimation(this); return true; } return false; }
/** * 松手时处理滑动到边缘的动画 * * @param xvel X方向上的滑动速度 */ private void animToSide(View changedView, float xvel, float yvel) { int finalX = initCenterViewX; int finalY = initCenterViewY; int flyType = -1; // 1. 下面这一坨计算finalX和finalY,要读懂代码需要建立一个比较清晰的数学模型才能理解,不信拉倒 int dx = changedView.getLeft() - initCenterViewX; int dy = changedView.getTop() - initCenterViewY; if (dx == 0) { // 由于dx作为分母,此处保护处理 dx = 1; } if (xvel > X_VEL_THRESHOLD || dx > X_DISTANCE_THRESHOLD) { finalX = allWidth; finalY = dy * (childWith + initCenterViewX) / dx + initCenterViewY; flyType = VANISH_TYPE_RIGHT; } else if (xvel < -X_VEL_THRESHOLD || dx < -X_DISTANCE_THRESHOLD) { finalX = -childWith; finalY = dy * (childWith + initCenterViewX) / (-dx) + dy + initCenterViewY; flyType = VANISH_TYPE_LEFT; } // 如果斜率太高,就折中处理 if (finalY > allHeight) { finalY = allHeight; } else if (finalY < -allHeight / 2) { finalY = -allHeight / 2; } // 如果没有飞向两侧,而是回到了中间,需要谨慎处理 if (finalX != initCenterViewX) { releasedViewList.add(changedView); } // 2. 启动动画 if (mDragHelper.smoothSlideViewTo(changedView, finalX, finalY)) { ViewCompat.postInvalidateOnAnimation(this); } // 3. 消失动画即将进行,listener回调 if (flyType >= 0 && cardSwitchListener != null) { cardSwitchListener.onCardVanish(isShowing, flyType); } }
/** Scroll out contentView and finish the activity */ public void scrollToFinishActivity() { final int childWidth = mContentView.getWidth(); final int childHeight = mContentView.getHeight(); int left = 0, top = 0; if ((mEdgeFlag & EDGE_LEFT) != 0) { left = childWidth + mShadowLeft.getIntrinsicWidth() + OVERSCROLL_DISTANCE; mTrackingEdge = EDGE_LEFT; } else if ((mEdgeFlag & EDGE_RIGHT) != 0) { left = -childWidth - mShadowRight.getIntrinsicWidth() - OVERSCROLL_DISTANCE; mTrackingEdge = EDGE_RIGHT; } else if ((mEdgeFlag & EDGE_BOTTOM) != 0) { top = -childHeight - mShadowBottom.getIntrinsicHeight() - OVERSCROLL_DISTANCE; mTrackingEdge = EDGE_BOTTOM; } mDragHelper.smoothSlideViewTo(mContentView, left, top); invalidate(); }
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { // If the scrollable view is handling touch, never intercept if (mIsScrollableViewHandlingTouch) { mDragHelper.cancel(); return false; } final int action = MotionEventCompat.getActionMasked(ev); final float x = ev.getX(); final float y = ev.getY(); switch (action) { case MotionEvent.ACTION_DOWN: { mIsUnableToDrag = false; mInitialMotionX = x; mInitialMotionY = y; break; } case MotionEvent.ACTION_MOVE: { final float adx = Math.abs(x - mInitialMotionX); final float ady = Math.abs(y - mInitialMotionY); final int dragSlop = mDragHelper.getTouchSlop(); if ((ady > dragSlop && adx > ady) || !isViewUnder(mDragView, (int) mInitialMotionX, (int) mInitialMotionY)) { mDragHelper.cancel(); mIsUnableToDrag = true; return false; } break; } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: // If the dragView is still dragging when we get here, we need to call processTouchEvent // so that the view is settled // Added to make scrollable views work (tokudu) if (mDragHelper.isDragging()) { mDragHelper.processTouchEvent(ev); return true; } break; } return mDragHelper.shouldInterceptTouchEvent(ev); }
/** * 拦截触摸事件 * * @param ev * @return */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return dragHelper.shouldInterceptTouchEvent(ev) && gestureDetector.onTouchEvent(ev); }
public SlidingUpPanelLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); if (isInEditMode()) { mShadowDrawable = null; mDragHelper = null; return; } if (attrs != null) { TypedArray defAttrs = context.obtainStyledAttributes(attrs, DEFAULT_ATTRS); if (defAttrs != null) { int gravity = defAttrs.getInt(0, Gravity.NO_GRAVITY); setGravity(gravity); } defAttrs.recycle(); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlidingUpPanelLayout); if (ta != null) { mPanelHeight = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_umanoPanelHeight, -1); mShadowHeight = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_umanoShadowHeight, -1); mParallaxOffset = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_umanoParalaxOffset, -1); mMinFlingVelocity = ta.getInt( R.styleable.SlidingUpPanelLayout_umanoFlingVelocity, DEFAULT_MIN_FLING_VELOCITY); mCoveredFadeColor = ta.getColor(R.styleable.SlidingUpPanelLayout_umanoFadeColor, DEFAULT_FADE_COLOR); mDragViewResId = ta.getResourceId(R.styleable.SlidingUpPanelLayout_umanoDragView, -1); mOverlayContent = ta.getBoolean(R.styleable.SlidingUpPanelLayout_umanoOverlay, DEFAULT_OVERLAY_FLAG); mClipPanel = ta.getBoolean(R.styleable.SlidingUpPanelLayout_umanoClipPanel, DEFAULT_CLIP_PANEL_FLAG); mAnchorPoint = ta.getFloat(R.styleable.SlidingUpPanelLayout_umanoAnchorPoint, DEFAULT_ANCHOR_POINT); mSlideState = PanelState.values()[ ta.getInt( R.styleable.SlidingUpPanelLayout_umanoInitialState, DEFAULT_SLIDE_STATE.ordinal())]; } ta.recycle(); } final float density = context.getResources().getDisplayMetrics().density; if (mPanelHeight == -1) { mPanelHeight = (int) (DEFAULT_PANEL_HEIGHT * density + 0.5f); } if (mShadowHeight == -1) { mShadowHeight = (int) (DEFAULT_SHADOW_HEIGHT * density + 0.5f); } if (mParallaxOffset == -1) { mParallaxOffset = (int) (DEFAULT_PARALAX_OFFSET * density); } // If the shadow height is zero, don't show the shadow if (mShadowHeight > 0) { if (mIsSlidingUp) { mShadowDrawable = getResources().getDrawable(R.drawable.above_shadow); } else { mShadowDrawable = getResources().getDrawable(R.drawable.below_shadow); } } else { mShadowDrawable = null; } setWillNotDraw(false); mDragHelper = ViewDragHelper.create(this, 0.5f, new DragHelperCallback()); mDragHelper.setMinVelocity(mMinFlingVelocity * density); mIsTouchEnabled = true; }
@Override public boolean dispatchTouchEvent(@NonNull MotionEvent ev) { final int action = MotionEventCompat.getActionMasked(ev); if (!isEnabled() || !isTouchEnabled() || (mIsUnableToDrag && action != MotionEvent.ACTION_DOWN)) { mDragHelper.cancel(); return super.dispatchTouchEvent(ev); } final float y = ev.getY(); if (action == MotionEvent.ACTION_DOWN) { mIsScrollableViewHandlingTouch = false; mPrevMotionY = y; } else if (action == MotionEvent.ACTION_MOVE) { float dy = y - mPrevMotionY; mPrevMotionY = y; // If the scroll view isn't under the touch, pass the // event along to the dragView. if (!isViewUnder(mScrollableView, (int) mInitialMotionX, (int) mInitialMotionY)) { return super.dispatchTouchEvent(ev); } // Which direction (up or down) is the drag moving? if (dy * (mIsSlidingUp ? 1 : -1) > 0) { // Collapsing // Is the child less than fully scrolled? // Then let the child handle it. if (mScrollableViewHelper.getScrollableViewScrollPosition(mScrollableView, mIsSlidingUp) > 0) { mIsScrollableViewHandlingTouch = true; return super.dispatchTouchEvent(ev); } // Was the child handling the touch previously? // Then we need to rejigger things so that the // drag panel gets a proper down event. if (mIsScrollableViewHandlingTouch) { // Send an 'UP' event to the child. MotionEvent up = MotionEvent.obtain(ev); up.setAction(MotionEvent.ACTION_CANCEL); super.dispatchTouchEvent(up); up.recycle(); // Send a 'DOWN' event to the panel. (We'll cheat // and hijack this one) ev.setAction(MotionEvent.ACTION_DOWN); } mIsScrollableViewHandlingTouch = false; return this.onTouchEvent(ev); } else if (dy * (mIsSlidingUp ? 1 : -1) < 0) { // Expanding // Is the panel less than fully expanded? // Then we'll handle the drag here. if (mSlideOffset < 1.0f) { mIsScrollableViewHandlingTouch = false; return this.onTouchEvent(ev); } // Was the panel handling the touch previously? // Then we need to rejigger things so that the // child gets a proper down event. if (!mIsScrollableViewHandlingTouch && mDragHelper.isDragging()) { mDragHelper.cancel(); ev.setAction(MotionEvent.ACTION_DOWN); } mIsScrollableViewHandlingTouch = true; return super.dispatchTouchEvent(ev); } } else if (action == MotionEvent.ACTION_UP && mIsScrollableViewHandlingTouch) { // If the scrollable view was handling the touch and we receive an up // we want to clear any previous dragging state so we don't intercept a touch stream // accidentally mDragHelper.setDragState(ViewDragHelper.STATE_IDLE); } // In all other cases, just let the default behavior take over. return super.dispatchTouchEvent(ev); }
/** 有加速度,当我们停止滑动的时候,该不会立即停止动画效果 */ @Override public void computeScroll() { if (dragHelper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this); } }
public DragLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); gestureDetector = new GestureDetectorCompat(context, new YScrollDetector()); dragHelper = ViewDragHelper.create(this, dragHelperCallback); }
/** * Set the size of an edge. This is the range in pixels along the edges of this view that will * actively detect edge touches or drags if edge tracking is enabled. * * @param size The size of an edge in pixels */ public void setEdgeSize(int size) { mDragHelper.setEdgeSize(size); }
/** * Enable edge tracking for the selected edges of the parent view. The callback's * * <p>methods will only be invoked for edges for which edge tracking has been enabled. * * @param edgeFlags Combination of edge flags describing the edges to watch * @see #EDGE_LEFT * @see #EDGE_RIGHT * @see #EDGE_BOTTOM */ public void setEdgeTrackingEnabled(int edgeFlags) { mEdgeFlag = edgeFlags; mDragHelper.setEdgeTrackingEnabled(mEdgeFlag); }
/** * Sets the sensitivity of the NavigationLayout. * * @param context The application context. * @param sensitivity value between 0 and 1, the final value for touchSlop = * ViewConfiguration.getScaledTouchSlop * (1 / s); */ public void setSensitivity(Context context, float sensitivity) { mDragHelper.setSensitivity(context, sensitivity); }