package com.smartisanos.sidebar.view; import com.smartisanos.sidebar.R; import android.content.Context; import android.util.AttributeSet; import android.view.DragEvent; import android.view.MotionEvent; import android.widget.ScrollView; public class DragScrollView extends ScrollView { private ScrollController mScrollController; public DragScrollView(Context context) { this(context, null); } public DragScrollView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public DragScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mScrollController = new ScrollController(this); } public void scrollByMotionEvent(MotionEvent event) { mScrollController.scrollByMotionEvent(event); } @Override public boolean dispatchDragEvent(DragEvent event) { boolean intercept = mScrollController.onDragEvent(event); if (intercept) { return true; } return super.dispatchDragEvent(event); } private class ScrollController { // 50 times on second ! private static final int DELAY = 20; private static final int NUMBS = 200; private static final int DIRECTION_DOWN = -1; private static final int DIRECTION_NONE = 0; private static final int DIRECTION_UP = 1; private ScrollView mView; private DragEvent mEvent; private float mRate = 1.0f; private int mInitDel = 0; private int mScrollDirection = 0; private int mTopArea; private int mBottomArea; public ScrollController(ScrollView view) { mView = view; mTopArea = getResources().getDimensionPixelSize(R.dimen.drag_scroll_view_top_area); mBottomArea = getResources().getDimensionPixelSize(R.dimen.drag_scroll_view_bottom_area); } private float getInterpolation(float rate) { return rate * rate; } private void setRate(float rate) { mRate = 1.0f + getInterpolation(rate) * 3; } public boolean scrollByMotionEvent(MotionEvent event) { int action = event.getAction(); float rawX = event.getRawX(); if (action == MotionEvent.ACTION_MOVE && (rawX > getLocationOnScreen()[0] && rawX < getLocationOnScreen()[0] + getWidth())) { float y = event.getRawY() - getLocationOnScreen()[1]; if (y < mTopArea && !isScrollUp()) { setEvent(null); setRate((mTopArea - y) * 1.0f / mTopArea); scroll(DIRECTION_DOWN); return true; } else if (y > mView.getHeight() - mBottomArea && !isScrollBottom()) { setEvent(null); setRate(1 - (mView.getHeight() - y) * 1.0f / mBottomArea); scroll(DIRECTION_UP); return true; } } setEvent(null); setRate(0.0f); scroll(DIRECTION_NONE); return false; } public boolean onDragEvent(DragEvent event){ int action = event.getAction(); if(action == DragEvent.ACTION_DRAG_LOCATION){ float y = event.getY(); if(y < mTopArea && !isScrollUp()){ setEvent(event); setRate((mTopArea - y) * 1.0f / mTopArea); scroll(DIRECTION_DOWN); return true; }else if(y > mView.getHeight() - mBottomArea && !isScrollBottom()){ setEvent(event); setRate(1 - (mView.getHeight() - y) * 1.0f / mBottomArea); scroll(DIRECTION_UP); return true; } } setEvent(null); setRate(0.0f); scroll(DIRECTION_NONE); return false; } private void setEvent(DragEvent event) { if (mEvent != null) { mEvent.recycle(); mEvent = null; } if (event != null) { mEvent = DragEvent.obtain(event); } } private boolean isScrollUp(){ return mView.getScrollY() == 0; } private boolean isScrollBottom(){ if(mView.getChildCount() <= 0){ return true; } return mView.getChildAt(0).getMeasuredHeight() <= mView.getScrollY() + mView.getHeight(); } private void scroll(int direction) { if (mScrollDirection == direction) { return; } mScrollDirection = direction; mView.removeCallbacks(mScrollRunnable); if (mScrollDirection != DIRECTION_NONE) { mInitDel = mView.getHeight() / NUMBS; if (mInitDel <= 0) { mInitDel = 1; } mView.post(mScrollRunnable); } } private Runnable mScrollRunnable = new Runnable() { @Override public void run() { mView.scrollBy(0, (int) (mInitDel * mScrollDirection * mRate)); mView.postDelayed(mScrollRunnable, DELAY); if (mEvent != null) { DragScrollView.super.dispatchDragEvent(mEvent); } } }; } }