Android自定义View实现滑动回弹(竖)

tech2022-07-09  156

 


前言

Android 页面滑动的时候的回弹效果:


 

一、关键代码

 

public class UniversalBounceView extends FrameLayout implements IPull { private static final String TAG = "UniversalBounceView"; //default. private static final int SCROLL_DURATION = 200; private static final float SCROLL_FRACTION = 0.4f; private static final int VIEW_TYPE_NORMAL = 0; private static final int VIEW_TYPE_ABSLISTVIEW = 1; private static final int VIEW_TYPE_SCROLLVIEW = 2; private static float VIEW_SCROLL_MAX = 720; private int viewHeight; private AbsListView alv; private OnBounceStateListener onBounceStateListener; private View child; private Scroller scroller; private boolean pullEnabled = true; private boolean pullPaused; private int touchSlop = 8; private int mPointerId; private float downY, lastDownY, tmpY; private int lastPointerIndex; private float moveDiffY; private boolean isNotJustInClickMode; private int moveDelta; private int viewType = VIEW_TYPE_NORMAL; public UniversalBounceView(Context context) { super(context); init(context); } public UniversalBounceView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public UniversalBounceView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } private void init(Context context) { scroller = new Scroller(context, new CustomDecInterpolator()); touchSlop = (int) (ViewConfiguration.get(context).getScaledTouchSlop() * 1.5); } class CustomDecInterpolator extends DecelerateInterpolator { public CustomDecInterpolator() { super(); } public CustomDecInterpolator(float factor) { super(factor); } public CustomDecInterpolator(Context context, AttributeSet attrs) { super(context, attrs); } @Override public float getInterpolation(float input) { return (float) Math.pow(input, 6.0 / 12); } } private void checkCld() { int cnt = getChildCount(); if (1 <= cnt) { child = getChildAt(0); } else if (0 == cnt) { pullEnabled = false; child = new View(getContext()); } else { throw new ArrayIndexOutOfBoundsException("child count can not be less than 0."); } } @Override protected void onFinishInflate() { checkCld(); super.onFinishInflate(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); viewHeight = h; VIEW_SCROLL_MAX = h * 1 / 3; } private boolean isTouch = true; public void setTouch(boolean isTouch) { this.isTouch = isTouch; } @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (!isTouch) { return true; } else { try { if (isPullEnable()) { if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) { if (Math.abs(ev.getY() - tmpY) < touchSlop) { return super.dispatchTouchEvent(ev); } else { tmpY = Integer.MIN_VALUE; } } return takeEvent(ev); } } catch (IllegalArgumentException | IllegalStateException e) { e.printStackTrace(); } if (getVisibility() != View.VISIBLE) { return true; } return super.dispatchTouchEvent(ev); } } private boolean takeEvent(MotionEvent ev) { int action = ev.getActionMasked(); switch (action) { case MotionEvent.ACTION_DOWN: mPointerId = ev.getPointerId(0); downY = ev.getY(); tmpY = downY; scroller.setFinalY(scroller.getCurrY()); setScrollY(scroller.getCurrY()); scroller.abortAnimation(); pullPaused = true; isNotJustInClickMode = false; moveDelta = 0; break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: pullPaused = false; smoothScrollTo(0); if (isNotJustInClickMode) { ev.setAction(MotionEvent.ACTION_CANCEL); } postDelayed(new Runnable() { @Override public void run() { if (getScrollY() == 0 && onBounceStateListener != null) { onBounceStateListener.overBounce(); } } }, 200); break; case MotionEvent.ACTION_MOVE: lastPointerIndex = ev.findPointerIndex(mPointerId); lastDownY = ev.getY(lastPointerIndex); moveDiffY = Math.round((lastDownY - downY) * getScrollFraction()); downY = lastDownY; boolean canStart = isCanPullStart(); boolean canEnd = isCanPullEnd(); int scroll = getScrollY(); float total = scroll - moveDiffY; if (canScrollInternal(scroll, canStart, canEnd)) { handleInternal(); break; } if (Math.abs(scroll) > VIEW_SCROLL_MAX) { return true; } if ((canStart && total < 0) || (canEnd && total > 0)) { if (moveDelta < touchSlop) { moveDelta += Math.abs(moveDiffY); } else { isNotJustInClickMode = true; } if (onBounceStateListener != null) { onBounceStateListener.onBounce(); } scrollBy(0, (int) -moveDiffY); return true; } // else if ((total > 0 && canStart) || (total < 0 && canEnd)) { // if (moveDelta < touchSlop) { // moveDelta += Math.abs(moveDiffY); // } else { // isNotJustInClickMode = true; // } // scrollBy(0, -scroll); // return true; // } break; case MotionEvent.ACTION_POINTER_UP: handlePointerUp(ev, 1); break; default: break; } return super.dispatchTouchEvent(ev); } private boolean canScrollInternal(int scroll, boolean canStart, boolean canEnd) { boolean result = false; if ((child instanceof RecyclerView) || (child instanceof AbsListView) || child instanceof ScrollView) { viewType = VIEW_TYPE_ABSLISTVIEW; result = canStart && canEnd; } else if (child instanceof ScrollView || child instanceof NestedScrollView) { viewType = VIEW_TYPE_SCROLLVIEW; } else { return false; } if (result) { isNotJustInClickMode = true; if (moveDelta < touchSlop) { moveDelta += Math.abs(moveDiffY); return true; } return false; } if (((scroll == 0 && canStart && moveDiffY < 0) || (scroll == 0 && canEnd && moveDiffY > 0) || (!canStart && !canEnd))) { return true; } if (moveDelta < touchSlop) { moveDelta += Math.abs(moveDiffY); return true; } else { isNotJustInClickMode = true; } return false; } private void handleInternal() { } private void handlePointerUp(MotionEvent event, int type) { int pointerIndexLeave = event.getActionIndex(); int pointerIdLeave = event.getPointerId(pointerIndexLeave); if (mPointerId == pointerIdLeave) { int reIndex = pointerIndexLeave == 0 ? 1 : 0; mPointerId = event.getPointerId(reIndex); // 调整触摸位置,防止出现跳动 downY = event.getY(reIndex); } } @Override public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); } private void smoothScrollTo(int value) { int scroll = getScrollY(); scroller.startScroll(0, scroll, 0, value - scroll, SCROLL_DURATION); postInvalidate(); } @Override public void computeScroll() { super.computeScroll(); if (!pullPaused && scroller.computeScrollOffset()) { scrollTo(scroller.getCurrX(), scroller.getCurrY()); postInvalidate(); } } private float getScrollFraction() { float ratio = Math.abs(getScrollY()) / VIEW_SCROLL_MAX; ratio = ratio < 1 ? ratio : 1; float fraction = (float) (-2 * Math.cos((ratio + 1) * Math.PI) / 5.0f) + 0.1f; return fraction < 0.10f ? 0.10f : fraction; } @Override public boolean isPullEnable() { return pullEnabled; } @Override public boolean isCanPullStart() { if (child instanceof RecyclerView) { RecyclerView recyclerView = (RecyclerView) child; return !recyclerView.canScrollVertically(-1); } if (child instanceof AbsListView) { AbsListView lv = (AbsListView) child; return !lv.canScrollVertically(-1); } if (child instanceof RelativeLayout || child instanceof FrameLayout || child instanceof LinearLayout || child instanceof WebView || child instanceof View) { return child.getScrollY() == 0; } return false; } @Override public boolean isCanPullEnd() { if (child instanceof RecyclerView) { RecyclerView recyclerView = (RecyclerView) child; return !recyclerView.canScrollVertically(1); } if (child instanceof AbsListView) { AbsListView lv = (AbsListView) child; int first = lv.getFirstVisiblePosition(); int last = lv.getLastVisiblePosition(); View view = lv.getChildAt(last - first); if (null == view) { return false; } else { return (lv.getCount() - 1 == last) && (view.getBottom() <= lv.getHeight()); } } if (child instanceof ScrollView) { View v = ((ScrollView) child).getChildAt(0); if (null == v) { return true; } else { return child.getScrollY() >= v.getHeight() - child.getHeight(); } } if (child instanceof NestedScrollView) { View v = ((NestedScrollView) child).getChildAt(0); if (null == v) { return true; } else { return child.getScrollY() >= v.getHeight() - child.getHeight(); } } if (child instanceof WebView) { return (((WebView) child).getContentHeight() * ((WebView) child).getScale()) - (((WebView) child).getHeight() + ((WebView) child).getScrollY()) <= 10; } if (child instanceof RelativeLayout || child instanceof FrameLayout || child instanceof LinearLayout || child instanceof View) { return (child.getScrollY() == 0); } return false; } /** * 通过addView实现效果回弹效果 * * @param replaceChildView 需要替换的View */ public void replaceAddChildView(View replaceChildView) { if (replaceChildView != null) { removeAllViews(); child = replaceChildView; addView(replaceChildView); } } public void setPullEnabled(boolean enable) { pullEnabled = enable; } public interface OnBounceStateListener { public void onBounce(); public void overBounce(); } public void setOnBounceStateListener(OnBounceStateListener onBounceStateListener) { this.onBounceStateListener = onBounceStateListener; } @Override public boolean dispatchKeyEvent(KeyEvent event) { try { return super.dispatchKeyEvent(event); } catch (IllegalArgumentException | IllegalStateException e) { e.printStackTrace(); } return false; } @Override public void dispatchWindowFocusChanged(boolean hasFocus) { try { super.dispatchWindowFocusChanged(hasFocus); } catch (IllegalArgumentException | IllegalStateException e) { e.printStackTrace(); } } }

 

二、注意要点

1.滑动结束的时候要防止动画抖动

 

private void handlePointerUp(MotionEvent event, int type) { int pointerIndexLeave = event.getActionIndex(); int pointerIdLeave = event.getPointerId(pointerIndexLeave); if (mPointerId == pointerIdLeave) { int reIndex = pointerIndexLeave == 0 ? 1 : 0; mPointerId = event.getPointerId(reIndex); // 调整触摸位置,防止出现跳动 downY = event.getY(reIndex); } }  

总结

以上就是文章的主要内容,实现了竖向滑动回弹的效果。

最新回复(0)