package com.seal.ui.views;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ScrollView;
/**
* 具有上下弹性滚动的ScrollView<br>
* <br>
* <strong>策略:</strong> 获取ScrollView的子视图并添加到自定义的{@link OverScrollWarpLayout}滚动视图中 将滚动视图添加到ScrollView作为子视图,所有的弹性滚动都由{@link OverScrollWarpLayout}来完成
*/
public class OverScrollView extends ScrollView {
/**
* 滚动系数, 视图滚动距离与手指滑动距离的比值
*/
private static final float ELASTICITY_COEFFICIENT = 0.25f;
/**
* 无弹性滚动状态
*/
private static final int NO_OVERSCROLL_STATE = 0;
/**
* 上方弹性滚动状态
*/
private static final int TOP_OVERSCROLL_STATE = 1;
/**
* 下方弹性滚动状态
*/
private static final int BOTTOM_OVERSCROLL_STATE = 2;
/**
* 滚动最大高度,超过此高度不再滚动
*/
private static final int OVERSCROLL_MAX_HEIGHT = 1200;
/**
* Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
*/
private static final int INVALID_POINTER = -1;
/**
* 触发事件的高度默认阀值
*/
private static final int TRIGGER_HEIGHT = 120;
/**
* 弹性滚动状态
*/
private int overScrollSate;
/**
* 属性标志位:是否可以弹性滚动
*/
private boolean mIsUseOverScroll = true;
/**
* 是否标记可以滚动
*/
private boolean isRecord;
/**
* 自定义的弹性滚动视图
*/
private OverScrollWarpLayout mContentLayout;
/**
* OverScroll监听器
*/
private OverScrollListener mOverScrollListener;
/**
* OverScroll细致监听器
*/
private OverScrollTinyListener mOverScrollTinyListener;
/**
* Scroll细致监听器
*/
private OnScrollListener mScrollListener;
/**
* 最新一次的手指触摸位置
*/
private float mLastMotionY;
/**
* 弹性滚动总距离,向下为负,向上为正
*/
private int overScrollDistance;
/**
* 按在屏幕上的手指的id
*/
private int mActivePointerId = INVALID_POINTER;
/**
* 是否触摸
*/
private boolean isOnTouch;
/**
* 是否具有惯性
*/
private boolean isInertance;
/**
* 是否使用惯性
*/
private boolean mIsUseInertance = true;
/**
* 是否禁用快速滚动
*/
private boolean mIsBanQuickScroll;
/**
* 惯性距离(与滑动速率有关)
*/
private int inertanceY;
/**
* 触发事件的高度阀值,最小值为30
*/
private int mOverScrollTrigger = TRIGGER_HEIGHT;
public OverScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initScrollView();
}
public OverScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public OverScrollView(Context context) {
this(context, null);
}
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
private void initScrollView() {
// 设置滚动无阴影( API Level 9 )
if (Build.VERSION.SDK_INT >= 9) {
setOverScrollMode(View.OVER_SCROLL_NEVER);
} else {
ViewCompat.setOverScrollMode(this, ViewCompat.OVER_SCROLL_NEVER);
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 如果禁用,不做任何处理
if (!mIsUseOverScroll) {
return super.onInterceptTouchEvent(ev);
}
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
if (isOverScrolled()) {
isRecord = true;
// Remember where the motion event started
mLastMotionY = (int) ev.getY();
mActivePointerId = ev.getPointerId(0);
}
break;
case MotionEvent.ACTION_MOVE:
if (isRecord && Math.abs(ev.getY() - mLastMotionY) > 20) {
return true;
}
break;
case MotionEvent.ACTION_CANCEL:
if (isRecord) {
isRecord = false;
}
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
isOnTouch = true;
if (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL) {
if (mOverScrollTinyListener != null) {
mOverScrollTinyListener.scrollLoosen();
}
isOnTouch = false;
}
// 如果禁用,不做任何处理
if (!mIsUseOverScroll) {
return super.onTouchEvent(ev);
}
if (!isOverScrolled()) {
mLastMotionY = (int) ev.getY();
return super.onTouchEvent(ev);
}
switch (ev.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mActivePointerId = ev.getPointerId(0);
mLastMotionY = (int) ev.getY();
break;
case MotionEvent.ACTION_POINTER_DOWN:
final int index = ev.getActionIndex();
mLastMotionY = (int) ev.getY(index);
mActivePointerId = ev.getPointerId(index);
break;
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
if (mActivePointerId != INVALID_POINTER) {
mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
}
break;
case MotionEvent.ACTION_MOVE:
if (isRecord) {
final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
if (activePointerIndex == -1) {
break;
}
final float y = ev.getY(activePointerIndex);
// 滚动距离
int deltaY = (int) (mLastMotionY - y);
// 记录新的触摸位置
mLastMotionY = y;
if (Math.abs(overScrollDistance) >= OVERSCROLL_MAX_HEIGHT && overScrollDistance * deltaY > 0) {
deltaY = 0;
}
// 如果滚动到ScrollView自身滚动边界,直接调用自身滚动
if (overScrollDistance * (overScrollDistance + deltaY) < 0) {
mContentLayout.smoothScrollToNormal();
overScrollDistance = 0;
break;
}
// 如果处于ScrollView滚动状态,直接调用ScrollView自身滚动
if ((!isOnBottom() && overScrollDistance > 0) || (!isOnTop() && overScrollDistance < 0)) {
mContentLayout.smoothScrollToNormal();
overScrollDistance = 0;
break;
}
if (overScrollDistance * deltaY > 0) {
deltaY = (int) (deltaY * ELASTICITY_COEFFICIENT);
}
if (overScrollDistance == 0) {
deltaY = (int) (deltaY * ELASTICITY_COEFFICIENT * 0.5f);
}
if (overScrollDistance == 0 && deltaY == 0) {
break;
}
// 检测最终滚动距离,最大为20
if (Math.abs(deltaY) > 20) {
deltaY = deltaY > 0 ? 20 : -20;
}
// 记录滚动总距离
overScrollDistance += deltaY;
if (isOnTop() && overScrollDistance > 0 && !isOnBottom()) {
overScrollDistance = 0;
break;
}
if (isOnBottom() && overScrollDistance < 0 && !isOnTop()) {
overScrollDistance = 0;
break;
}
// 滚动视图
mContentLayout.smoothScrollBy(0, deltaY);
if (mOverScrollTinyListener != null) {
mOverScrollTinyListener.scrollDistance(deltaY, overScrollDistance);
}
return true;
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mContentLayout.smoothScrollToNormal();
overScrollTrigger();
// 重置滑动总距离
overScrollDistance = 0;
// 重置标记
isRecord = false;
// 重置手指触摸id
mActivePointerId = INVALID_POINTER;
break;
default:
break;
}
return super.onTouchEvent(ev);
}
/**
* 功能描述: 防止出现pointerIndex out of range异常<br>
*
* @param ev
*/
private void onSecondaryPointerUp(MotionEvent ev) {
final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
// Make this decision more intelligent.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastMotionY = (int) ev.getY(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
}
}
public boolean isOverScrolled() {
return isOnTop() || isOnBottom();
}
private boolean isOnTop() {
return getScrollY() == 0;
}
private boolean isOnBottom() {
return getScrollY() + getHeight() == mContentLayout.getHeight();
}
/**
* 功能描述:初始化滚动视图 <br>
* <br>
* <strong>策略:</strong> 获取ScrollView的子视图并添加到自定义的{@link OverScrollWarpLayout}滚动视图中 将滚动视图添加到ScrollView作为子视图
*/
private void initOverScrollLayout() {
// 必须设置为true,否则添加子视图时高度不会填充到整个ScrollView的高度
setFillViewport(true);
if (mContentLayout == null) {
// 获取ScrollView的子视图
View child = getChildAt(0);
// 初始化弹性滚动视图
mContentLayout = new OverScrollWarpLayout(getContext());
// 移除ScrollView所有视图
this.removeAllViews();
// 将原先ScrollView子视图加入到弹性滚动视图中
mContentLayout.addView(child);
// 添加弹性滚动视图,作为ScrollView子视图
this.addView(mContentLayout, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
}
// mIsUseOverScroll = true;
}
/**
* 功能描述:设置是否可以弹性滚动 <br>
*
* @param isOverScroll
*/
public void setOverScroll(boolean isOverScroll) {
mIsUseOverScroll = isOverScroll;
}
/**
* 功能描述: 设置是否使用惯性<br>
*
* @param isInertance
*/
public void setUseInertance(boolean isInertance) {
mIsUseInertance = isInertance;
}
@Override
protected void onAttachedToWindow() {
initOverScrollLayout();
super.onAttachedToWindow();
}
/**
* 功能描述: 获取弹性状态<br>
*
* @return
*/
public int getScrollState() {
invalidateState();
return overScrollSate;
}
/**
* 功能描述: 刷新弹性滚动状态<br>
*/
private void invalidateState() {
if (mContentLayout.getScrollerCurrY() == 0) {
overScrollSate = NO_OVERSCROLL_STATE;
}
if (mContentLayout.getScrollerCurrY() < 0) {
overScrollSate = TOP_OVERSCROLL_STATE;
}
if (mContentLayout.getScrollerCurrY() > 0) {
overScrollSate = BOTTOM_OVERSCROLL_STATE;
}
}
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
@Override
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX,
int maxOverScrollY, boolean isTouchEvent) {
// Log.v("test", "deltaY "+deltaY+" scrollY "+scrollY);
return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
if (mScrollListener != null && overScrollDistance == 0) {
mScrollListener.onScroll(l, t, oldl, oldt);
}
super.onScrollChanged(l, t, oldl, oldt);
}
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
@Override
protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
if (mIsUseInertance && !isInertance && scrollY != 0) {
isInertance = true;
}
if (clampedY && !isOnTouch && isInertance) {
mContentLayout.smoothScrollBy(0, inertanceY);
mContentLayout.smoothScrollToNormal();
inertanceY = 0;
}
super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
}
/**
* 功能描述: 设置OverScroll滚动监听器<br>
*
* @param listener
*/
public void setOverScrollListener(OverScrollListener listener) {
mOverScrollListener = listener;
}
/**
* 功能描述: 设置OverScroll滚动监听器<br>
*
* @param listener
*/
public void setOverScrollTinyListener(OverScrollTinyListener listener) {
mOverScrollTinyListener = listener;
}
/**
* 功能描述: 设置Scroll滚动监听器<br>
*
* @param listener
*/
public void setOnScrollListener(OnScrollListener listener) {
mScrollListener = listener;
}
/**
* 设置OverScrollListener出发阀值
*
* @param height
*/
public void setOverScrollTrigger(int height) {
if (height >= 30) {
mOverScrollTrigger = height;
}
}
private void overScrollTrigger() {
if (mOverScrollListener == null) {
return;
}
if (overScrollDistance > mOverScrollTrigger && isOnBottom()) {
mOverScrollListener.footerScroll();
}
if (overScrollDistance < -mOverScrollTrigger && isOnTop()) {
mOverScrollListener.headerScroll();
}
}
public void setQuickScroll(boolean isEnable) {
mIsBanQuickScroll = !isEnable;
}
@Override
public void computeScroll() {
if (!mIsBanQuickScroll) {
super.computeScroll();
}
}
/**
* 获取ScrollView可滚动高度
*
* @return
*/
public int getScrollHeight() {
return mContentLayout.getHeight() - getHeight();
}
@Override
public void fling(int velocityY) {
inertanceY = 50 * velocityY / 5000;
super.fling(velocityY);
}
/**
* 当OverScroll超出一定值时,调用此监听
*
* @author King
* @since 2014-4-9 下午4:36:29
*/
public interface OverScrollListener {
/**
* 顶部
*/
void headerScroll();
/**
* 底部
*/
void footerScroll();
}
/**
* 每当OverScroll时,都能触发的监听
*
* @author King
* @since 2014-4-9 下午4:39:06
*/
public interface OverScrollTinyListener {
/**
* 滚动距离
*
* @param tinyDistance
* 当前滚动的细小距离
* @param totalDistance
* 滚动的总距离
*/
void scrollDistance(int tinyDistance, int totalDistance);
/**
* 滚动松开
*/
void scrollLoosen();
}
/**
* 普通滚动监听器<br>
* overScroll距离为0切无惯性时调用
*
* @author king
*
*/
public interface OnScrollListener {
void onScroll(int l, int t, int oldl, int oldt);
}
}