网上例子比较多, 找着试了好几个不是真的行. 所以这必须是真的能用的, 而且效果要棒棒的. 下面例出好几种方案
...
方案一[推荐]: 这是在 github上找的一个star并不高的代码, 但是这效果是我花了大半天时间趴的最棒的! 不多说, 上代码
package com.jhcp.lottery.ui.wedgit;
/**
* Created by suliyea on 16/4/14.
*/
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.AdapterView;
import android.widget.RelativeLayout;
public class BounceScroller extends RelativeLayout {
public static final String TAG = "BounceScroller";
public static final int DEFALUT_DURATION = 400;
public static enum State {
STATE_FIT_CONTENT, STATE_SHOW, STATE_OVER, STATE_FIT_EXTRAS
};
protected State mState = State.STATE_FIT_CONTENT;
private Bouncer mBouncer = new Bouncer();
private BounceListener mListener;
private View mContentView;
private int mLastEventY;
private int mLastTargetTop;
private View mHeaderView;
protected int mHeaderHeight;
private View mFooterView;
private int mFooterHeight;
private boolean overScrolled;
private boolean pullingHeader;
private boolean pullingFooter;
private boolean headerBounce = true;
private boolean footerBounce = true;
private int remainOffset;
private View mTargetView;
private TimeInterpolator mInterpolator;
private long mTimeBase = 0;
public BounceScroller(Context context) {
this(context, null);
}
public BounceScroller(Context context, AttributeSet attrs) {
super(context, attrs);
mInterpolator = new DecelerateInterpolator();
remainOffset = 0;
}
protected void onFinishInflate() {
super.onFinishInflate();
if (getChildCount() > 0) {
mContentView = getChildAt(0);
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (mContentView == null && !eventInView(event, mContentView)) {
return super.dispatchTouchEvent(event);
}
int action = event.getAction();
if (takeTouchEvent(event)) {
Log.d(TAG, System.currentTimeMillis() + " takeTouchEvent " + action);
} else {
boolean result = super.dispatchTouchEvent(event);
Log.d(TAG, System.currentTimeMillis() + " dispatchTouchEvent "
+ result);
}
if (action == MotionEvent.ACTION_CANCEL
|| action == MotionEvent.ACTION_UP) {
overScrolled = false;
mTimeBase = 0;
pullingHeader = false;
pullingFooter = false;
mLastEventY = 0;
mLastTargetTop = 0;
mTargetView = null;
return true;
} else if (action == MotionEvent.ACTION_DOWN) {
// cancel bounce if exists
mBouncer.cancel();
mTargetView = getTargetView(mContentView, event);
mTimeBase = 0;
} else if (action == MotionEvent.ACTION_MOVE) {
int eventOffset = (int) event.getY() - mLastEventY;
if (!overScrolled) {
if (mHeaderView != null && mHeaderView.getBottom() > 0
&& eventOffset < 0) {
overScrolled = true;
} else if (mFooterView != null
&& mFooterView.getTop() < getBottom()
&& eventOffset > 0) {
overScrolled = true;
} else {
overScrolled = false;
}
}
if (mTargetView != null
&& mTargetView.getVisibility() != View.VISIBLE) {
mTargetView = getTargetView(mContentView, event);
Log.d(TAG, "update mTargetView " + mTargetView.getId());
mTimeBase = 0;
overScrolled = false;
} else {
int targetTop = getViewTop(mTargetView);
int viewOffset = targetTop - mLastTargetTop;
Log.d(TAG, "targetTop " + targetTop + " viewOffset "
+ viewOffset + " eventOffset " + eventOffset
+ " mTimeBase " + mTimeBase);
if (eventOffset != 0 && viewOffset == 0 && !overScrolled) {
long currentTime = System.currentTimeMillis();
remainOffset += eventOffset;
if (mTimeBase == 0) {
mTimeBase = currentTime;
} else if (currentTime - mTimeBase > 50) {
overScrolled = true;
mTimeBase = 0;
}
} else if (eventOffset != 0 && viewOffset != 0) {
mTimeBase = 0;
}
if (remainOffset != 0 && overScrolled) {
Log.d(TAG, "do remainOffset " + remainOffset);
onOffset(remainOffset);
remainOffset = 0;
}
}
}
mLastTargetTop = getViewTop(mTargetView);
mLastEventY = (int) event.getY();
return true;
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.d(TAG, System.currentTimeMillis() + " onDraw");
}
private int getViewTop(View view) {
if (view == null) {
return 0;
}
int[] location = new int[2];
view.getLocationOnScreen(location);
return location[1];
}
private boolean takeTouchEvent(MotionEvent event) {
if (!headerBounce && !footerBounce) {
return false;
}
int action = event.getAction();
int contentTop = mContentView.getTop();
if (action == MotionEvent.ACTION_UP
|| action == MotionEvent.ACTION_CANCEL) {
if (contentTop > 0) {
int offset = contentTop;
if (mHeaderView != null && offset > mHeaderHeight / 2) {
offset = offset - mHeaderHeight;
mBouncer.recover(true, offset, State.STATE_FIT_EXTRAS);
} else {
mBouncer.recover(true, offset, State.STATE_FIT_CONTENT);
}
} else if (contentTop < 0) {
int offset = mContentView.getBottom() - getBottom();
if (mFooterView != null && (offset + mFooterHeight / 2) < 0) {
offset = offset + mFooterHeight;
mBouncer.recover(false, offset, State.STATE_FIT_EXTRAS);
} else {
// add by suliyea 防止上拉回弹时候不能回复到原位.
if (offset != contentTop) {
offset = contentTop;
}
mBouncer.recover(false, offset, State.STATE_FIT_CONTENT);
}
}
} else if (action == MotionEvent.ACTION_MOVE) {
int offset = (int) (event.getY() - mLastEventY);
return onOffset(offset);
}
return false;
}
private boolean onOffset(int offset) {
offset = offset / 2;
boolean handled = false;
int contentTop = mContentView.getTop();
if (headerBounce && !handled && contentTop >= 0 && !pullingFooter) {
handled |= pullHeader(offset);
}
if (footerBounce && !handled && contentTop <= 0 && !pullingHeader) {
handled |= pullFooter(offset);
}
return handled;
}
private void setState(boolean header, State newState) {
String position = header ? "header" : "footer";
if (newState == mState) {
return;
}
Log.d(TAG, position + " setState " + mState + " -> " + newState);
mState = newState;
if (mListener != null) {
mListener.onState(header, newState);
}
}
private boolean pullHeader(int offset) {
int scrollY = mContentView.getScrollY();
int curTop = mContentView.getTop();
// pull header
if (!overScrolled || scrollY > 0
|| (offset < 0 && scrollY == 0 && curTop <= 0)) {
return false;
}
pullingHeader = true;
int nextTop = curTop + offset;
if (nextTop <= 0) {
offset = -curTop;
overScrolled = false;
mTimeBase = 0;
nextTop = 0;
pullingHeader = false;
if (mState != State.STATE_FIT_CONTENT) {
setState(true, State.STATE_FIT_CONTENT);
}
} else if (nextTop > 0 && nextTop <= mHeaderHeight) {
if ((mState != State.STATE_SHOW)) {
setState(true, State.STATE_SHOW);
}
} else if (nextTop > mHeaderHeight) {
if (mState != State.STATE_OVER) {
setState(true, State.STATE_OVER);
}
}
Log.d(TAG, "pullHeader " + offset + " nextTop " + nextTop);
offsetContent(offset);
return true;
}
private boolean pullFooter(int offset) {
int curBottom = mContentView.getBottom();
int conBottom = this.getBottom();
// pull footer
if (!overScrolled || (offset > 0 && conBottom <= curBottom)) {
return false;
}
pullingFooter = true;
int nextBottom = curBottom + offset;
if (nextBottom >= conBottom) {
offset = conBottom - curBottom;
overScrolled = false;
mTimeBase = 0;
nextBottom = conBottom;
pullingFooter = false;
if (mState != State.STATE_FIT_CONTENT) {
setState(false, State.STATE_FIT_CONTENT);
}
} else if (nextBottom < conBottom
&& nextBottom >= (conBottom - mFooterHeight)) {
if ((mState != State.STATE_SHOW)) {
setState(false, State.STATE_SHOW);
}
} else if (nextBottom < (conBottom - mFooterHeight)) {
if (mState != State.STATE_OVER) {
setState(false, State.STATE_OVER);
}
}
Log.d(TAG, "pullFooter " + offset + " nextBottom " + nextBottom);
offsetContent(offset);
return true;
}
private class Bouncer implements AnimatorUpdateListener, AnimatorListener {
private ValueAnimator mAnimator;
private int mLastOffset;
private boolean isHeader;
private State mTargetState;
private boolean mCanceled;
public void recover(boolean header, int offset, State state) {
cancel();
Log.d(TAG, "recover offset " + offset);
mCanceled = false;
isHeader = header;
mTargetState = state;
mAnimator = new ValueAnimator();
mAnimator.setIntValues(0, offset);
mLastOffset = 0;
mAnimator.setDuration(500);
mAnimator.setRepeatCount(0);
if (mInterpolator == null) {
mInterpolator = new DecelerateInterpolator();
}
mAnimator.setInterpolator(mInterpolator);
mAnimator.addListener(this);
mAnimator.addUpdateListener(this);
mAnimator.start();
}
public void cancel() {
if (mAnimator != null && mAnimator.isRunning()) {
mAnimator.cancel();
}
mAnimator = null;
}
@Override
public void onAnimationUpdate(ValueAnimator va) {
int currentOffset = (Integer) va.getAnimatedValue();
int delta = mLastOffset - currentOffset;
Log.d(TAG, "recover delta " + delta + " currentOffset "
+ currentOffset);
offsetContent(delta);
mLastOffset = currentOffset;
if (mListener != null) {
int contentOffset = mContentView.getTop();
mListener.onOffset(isHeader, contentOffset);
}
}
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
Log.d(TAG, "onAnimationEnd");
mAnimator = null;
if (!mCanceled) {
setState(isHeader, mTargetState);
}
}
@Override
public void onAnimationCancel(Animator animation) {
Log.d(TAG, "onAnimationCancel");
mCanceled = true;
}
@Override
public void onAnimationRepeat(Animator animation) {
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
int contentTop = 0;
int contentBottom = 0;
if (mContentView != null) {
contentTop = mContentView.getTop();
contentBottom = contentTop + this.getMeasuredHeight();
mContentView.layout(0, contentTop, right, contentBottom);
}
if (mHeaderView != null) {
int headerTop = contentTop - mHeaderHeight;
mHeaderView.layout(0, headerTop, right, headerTop + mHeaderHeight);
}
if (mFooterView != null) {
int footerTop = contentBottom;
mFooterView.layout(0, footerTop, right, footerTop + mFooterHeight);
}
}
private boolean offsetContent(int offset) {
if (mContentView != null) {
mContentView.offsetTopAndBottom(offset);
}
if (mHeaderView != null) {
mHeaderView.offsetTopAndBottom(offset);
}
if (mFooterView != null) {
mFooterView.offsetTopAndBottom(offset);
}
if (mListener != null) {
int contentOffset = mContentView.getTop();
boolean header = contentOffset > 0;
mListener.onOffset(header, contentOffset);
}
invalidate();
return true;
}
public boolean attach(View view) {
if (view == null) {
return false;
}
ViewGroup parent = (ViewGroup) view.getParent();
ViewGroup.LayoutParams params = parent.getLayoutParams();
int index = parent.indexOfChild(view);
parent.removeView(view);
parent.addView(this, index, params);
index = 0;
if (mHeaderView != null) {
index = 1;
}
params = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
addView(view, index, params);
this.mContentView = view;
return true;
}
public void resetState() {
if (mState != State.STATE_FIT_EXTRAS) {
return;
}
if (mContentView == null) {
return;
}
int offset = mContentView.getTop();
if (offset != 0) {
boolean header = true;
if (offset < 0) {
header = false;
}
mBouncer.recover(header, offset, State.STATE_FIT_CONTENT);
}
}
public BounceScroller enableHeader(boolean bounce) {
this.headerBounce = bounce;
return this;
}
public BounceScroller enableFooter(boolean bounce) {
this.footerBounce = bounce;
return this;
}
public BounceScroller setHeaderView(View view) {
if (mHeaderView != null) {
removeView(mHeaderView);
mHeaderView = null;
}
mHeaderView = view;
if (mHeaderView != null) {
mHeaderView.measure(MeasureSpec.UNSPECIFIED,
MeasureSpec.UNSPECIFIED);
mHeaderHeight = mHeaderView.getMeasuredHeight();
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,
mHeaderHeight);
addView(mHeaderView, 0, params);
}
return this;
}
public View getHeaderView() {
return mHeaderView;
}
public BounceScroller setFooterView(View view) {
if (mFooterView != null) {
removeView(mFooterView);
mFooterView = null;
}
mFooterView = view;
if (mFooterView != null) {
mFooterView.measure(MeasureSpec.UNSPECIFIED,
MeasureSpec.UNSPECIFIED);
mFooterHeight = mFooterView.getMeasuredHeight();
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,
mHeaderHeight);
addView(mFooterView, 0, params);
}
return this;
}
public View getFooterView() {
return mFooterView;
}
public BounceScroller setListener(BounceListener listener) {
this.mListener = listener;
return this;
}
public BounceScroller setInterpolator(TimeInterpolator interpolator) {
this.mInterpolator = interpolator;
return this;
}
private View getTargetView(View target, MotionEvent event) {
View view = null;
if (target == null) {
return view;
}
if (!eventInView(event, target)) {
return view;
}
if (!(target instanceof ViewGroup)) {
view = target;
return view;
}
if (target instanceof AdapterView) {
AdapterView> parent = (AdapterView>) target;
int first = parent.getFirstVisiblePosition();
int last = parent.getLastVisiblePosition();
for (int index = 0; index <= (last - first); ++index) {
View child = parent.getChildAt(index);
if (!eventInView(event, child)) {
continue;
}
if (!(child instanceof ViewGroup)) {
view = child;
return view;
}
view = getTargetView(child, event);
// stop search in current view group
return view;
}
} else if (target instanceof ViewGroup) {
ViewGroup parent = (ViewGroup) target;
int childCount = parent.getChildCount();
// with z-order
for (int index = childCount - 1; index >= 0; --index) {
View child = parent.getChildAt(index);
if (!eventInView(event, child)) {
continue;
}
if (!(child instanceof ViewGroup)) {
view = child;
return view;
}
view = getTargetView(child, event);
// stop search in current view group
return view;
}
}
// set view as group self
view = target;
return view;
}
private boolean eventInView(MotionEvent event, View view) {
if (event == null || view == null) {
return false;
}
int eventX = (int) event.getRawX();
int eventY = (int) event.getRawY();
int[] location = new int[2];
view.getLocationOnScreen(location);
int width = view.getWidth();
int height = view.getHeight();
int left = location[0];
int top = location[1];
int right = left + width;
int bottom = top + height;
Rect rect = new Rect(left, top, right, bottom);
boolean contains = rect.contains(eventX, eventY);
return contains;
}
public interface BounceListener {
public void onState(boolean header, BounceScroller.State state);
public void onOffset(boolean header, int offset);
}
}
用例
方案二: 这个是 github上还不错的, 代码很大神, 使用很方便, 但是在测试使用 listview阻尼效果发现, 会有点击效果(selecter)的事件冲突, 没有找到好的解决办法, 怎么用自己看https://github.com/EverythingMe/overscroll-decor
...
方案三: 下面这种是网上流传的重写overScrollBy方式, 体验不好的地方是在快递下拉时, 会出现停止不回弹的 bug
我们使用ListView将位置拖到顶部和底部默认是没有回弹效果的,为了增加这个效果,方法如下:
1、开启overScrollMode为always在布局中android:overScrollMode="always"或在代码中setOverScrollMode(View.OVER_SCROLL_ALWAYS);
2、继承listview 覆盖overScrollBy方法,并且利用反射机制修改阴影效果为透明
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.View;
import android.widget.AbsListView;
import android.widget.ListView;
import java.lang.reflect.Field;
/**
* Created by suliyea on 16/4/12.
*/
public class BounceListView extends ListView {
private static final int MAX_Y_OVERSCROLL_DISTANCE = 100;
private Context mContext;
private int mMaxYOverscrollDistance;
public BounceListView(Context context) {
super(context);
mContext = context;
initBounceListView();
}
public BounceListView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
initBounceListView();
}
public BounceListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext = context;
initBounceListView();
}
private void initBounceListView() {
// get the density of the screen and do some maths with it on the max
// overscroll distance
// variable so that you get similar behaviors no matter what the screen
// size
final DisplayMetrics metrics = mContext.getResources()
.getDisplayMetrics();
final float density = metrics.density;
mMaxYOverscrollDistance = (int) (density * MAX_Y_OVERSCROLL_DISTANCE);
this.setOverScrollMode(View.OVER_SCROLL_ALWAYS);
try {
Class> c = (Class>) Class.forName(AbsListView.class.getName());
Field egtField = c.getDeclaredField("mEdgeGlowTop");
Field egbBottom = c.getDeclaredField("mEdgeGlowBottom");
egtField.setAccessible(true);
egbBottom.setAccessible(true);
Object egtObject = egtField.get(this); // this 指的是ListiVew实例
Object egbObject = egbBottom.get(this);
// egtObject.getClass() 实际上是一个 EdgeEffect 其中有两个重要属性 mGlow mEdge
// 并且这两个属性都是Drawable类型
Class> cc = (Class>) Class.forName(egtObject.getClass()
.getName());
Field mGlow = cc.getDeclaredField("mGlow");
mGlow.setAccessible(true);
mGlow.set(egtObject, new ColorDrawable(Color.TRANSPARENT));
mGlow.set(egbObject, new ColorDrawable(Color.TRANSPARENT));
Field mEdge = cc.getDeclaredField("mEdge");
mEdge.setAccessible(true);
mEdge.set(egtObject, new ColorDrawable(Color.TRANSPARENT));
mEdge.set(egbObject, new ColorDrawable(Color.TRANSPARENT));
} catch (Exception e) {
e.printStackTrace();
}
}
@SuppressLint("NewApi")
@Override
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX,
int scrollY, int scrollRangeX, int scrollRangeY,
int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
// This is where the magic happens, we have replaced the incoming
// maxOverScrollY with our own custom variable mMaxYOverscrollDistance;
return super.overScrollBy(deltaX, deltaY, scrollX, scrollY,
scrollRangeX, scrollRangeY, maxOverScrollX,
mMaxYOverscrollDistance, isTouchEvent);
}
}```
>方案四: 再来一种实现方式, 这种方式能下拉出来, 但是回弹的效果没有, 体验不好
/**
-
这种方式能下拉出来, 但是回弹的效果没有, 体验不好
*/
public class BounceListView extends ListView implements Runnable {// 手指点位置的Y坐标
private float mLastDownY = 0f;
// 移动距离
private int mDistance = 0;
private int mStep = 0;
// 是否移动过
private boolean mPositive = false;/**
- 构造器
*/
public BounceListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public BounceListView(Context context, AttributeSet attrs) {
super(context, attrs);
}public BounceListView(Context context) {
super(context);
}/**
-
TouchEvent事件
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 系列事件,手指第一次按下时触发
if (mLastDownY == 0f && mDistance == 0) {
mLastDownY = event.getY();
return true;
}
break;case MotionEvent.ACTION_CANCEL: break; case MotionEvent.ACTION_UP: // 手指离开之后触发 if (mDistance != 0) { mStep = 1; mPositive = (mDistance >= 0); // 即可把你的Runnable对象增加到UI线程中运行。 this.post(this); return true; } // 重新赋值 mLastDownY = 0f; mDistance = 0; break; case MotionEvent.ACTION_MOVE: // 手指按下之后滑动触发 if (mLastDownY != 0f) { mDistance = (int) (mLastDownY - event.getY()); if ((mDistance < 0 && getFirstVisiblePosition() == 0 && getChildAt( 0).getTop() == 0) || (mDistance > 0 && getLastVisiblePosition() == getCount() - 1)) { // 第一个位置并且是想下拉,就滑动或者最后一个位置向上拉 // 这个判断的作用是在非顶端的部分不会有此滚动 mDistance /= 3; // 这里是为了减少滚动的距离 scrollTo(0, mDistance); // 滚动 return true; } } // 置为0,有自动滑动的效果 mDistance = 0; break;
}
return super.onTouchEvent(event);
}
public void run() {
mDistance += mDistance > 0 ? -mStep : mStep;
scrollTo(0, mDistance);
// 下拉mPositive是false,上拉是true
if ((mPositive && mDistance <= 0) || (!mPositive && mDistance >= 0)) {
scrollTo(0, 0);
mDistance = 0;
mLastDownY = 0f;
return;
}
mStep += 1;
this.post(this);
}
} - 构造器