package com.pan.simplepicture.widget.scrollview; 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}����� * * @author King */ 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); } 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. // TODO: 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; } } @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); } @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); } }