demand

  1. ViewPager+Fragment vertical direction, ViewPager can not slide, each Fragment slides to the top or bottom, similar to IOS elastic effect, slide a certain distance to jump to the last or next Fragment.
  2. Actual effect: see the effect of the TAB list page.

code

public class PullSlideParent extends ViewGroup { private final ViewDragHelper mDragHelper; private final ViewGroup mHeader; private final ViewGroup mFooter; private int mHeaderMeasuredHeight; private int mFooterMeasuredHeight; private View mDragContentView; private float lastY; private float deltaY; private OnPullListener onPullListener; private boolean up = false; Private Boolean down = false; // dropdown private Boolean canUp = true; private boolean canDown = true; Private static final float SWIPE_BACK_FACTOR = 0.9f; private float swipeBackFraction; public PullSlideParent(Context context) { this(context, null); } public PullSlideParent(Context context, AttributeSet attrs) { this(context, attrs, 0); } public PullSlideParent(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mDragHelper = ViewDragHelper.create(this, new PullSlideParent.DragHelperCallback()); mHeader = (ViewGroup) LayoutInflater.from(getContext()).inflate(R.layout.pull_side_header, this, false); mFooter = (ViewGroup) LayoutInflater.from(getContext()).inflate(R.layout.pull_side_footer, this, false); addView(mHeader); addView(mFooter); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int childCount = getChildCount(); if (childCount ! = 3) { throw new IllegalStateException("PullSlideParent must contains only three direct child."); } measureChildren(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (getChildCount() ! = 3) { return; } mHeaderMeasuredHeight = mHeader.getMeasuredHeight(); mFooterMeasuredHeight = mFooter.getMeasuredHeight(); mHeader.layout(0, -mHeaderMeasuredHeight, getMeasuredWidth(), 0); mFooter.layout(0, getMeasuredHeight(), getMeasuredWidth(), getMeasuredHeight() + mFooterMeasuredHeight); int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); if (child instanceof RecyclerView) { mDragContentView = child; mDragContentView.layout(0, 0, mDragContentView.getMeasuredWidth(), mDragContentView.getMeasuredHeight()); } } } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: lastY = ev.getY(); deltaY = 0; mDragHelper.shouldInterceptTouchEvent(ev); break; case MotionEvent.ACTION_MOVE: deltaY = ev.getY() - lastY; lastY = ev.getY(); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mDragHelper.shouldInterceptTouchEvent(ev); deltaY = 0; break; default: } if (canDown && deltaY > 0 && ! mDragContentView.canScrollVertically(-1)) { return mDragHelper.shouldInterceptTouchEvent(ev); } if (canUp && deltaY < 0 && ! mDragContentView.canScrollVertically(1)) { return mDragHelper.shouldInterceptTouchEvent(ev); } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { mDragHelper.processTouchEvent(event); return true; } @Override public void computeScroll() { if (mDragHelper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this); } } public void setCanUp(boolean canUp) { this.canUp = canUp; } public void setCanDown(boolean canDown) { this.canDown = canDown; } public void setOnPullListener(PullSlideParent.OnPullListener onPullListener) { this.onPullListener = onPullListener; } public interface OnPullListener { void onPullUp(); void onPullDown(); } private class DragHelperCallback extends ViewDragHelper.Callback { @Override public boolean tryCaptureView(@NonNull View child, int pointerId) { return child == mDragContentView; } @Override public int getViewVerticalDragRange(@NonNull View child) { return Math.max(mHeaderMeasuredHeight, mFooterMeasuredHeight); } @Override public void onViewPositionChanged(@NonNull View changedView, int left, int top, int dx, Int dy) {if (top > 0) {swipeBackFraction = 1.0f * top/mHeaderMeasuredHeight; mHeader.setTranslationY(top); } else if (top < 0) {swipeBackFraction = -1.0f * top/mFooterMeasuredHeight; mFooter.setTranslationY(top); } else { swipeBackFraction = 0f; down = false; up = false; mHeader.setTranslationY(top); mFooter.setTranslationY(top); } } @Override public int clampViewPositionVertical(@NonNull View child, int top, int dy) { if (top > 0) { down = true; return Math.min(mHeaderMeasuredHeight, top); } else if (top < 0) { up = true; return Math.max(top, -mFooterMeasuredHeight); } return 0; } @Override public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) { if (onPullListener ! = null && swipeBackFraction > SWIPE_BACK_FACTOR) { if (up) { onPullListener.onPullUp(); } else if (down) { onPullListener.onPullDown(); } } mDragHelper.smoothSlideViewTo(releasedChild, 0, 0); ViewCompat.postInvalidateOnAnimation(PullSlideParent.this); }}}Copy the code