本文实例为大家分享了Android自定义View实现滑动回弹的具体代码,供大家参考,具体内容如下
前言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();
}
}
}
二、注意要点
滑动结束的时候要防止动画抖动
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);
}
}
总结
以上就是文章的主要内容,实现了竖向滑动回弹的效果。