1. 程式人生 > >Android自定義Scrollview

Android自定義Scrollview

效果:

主要的幾個知識點有 Scroller 、VelocityTracker

主要操作View裡面的幾個方法有  onMeasure、onTouchEvent 、computeScroll 、scrollTo() 、scrollBy()

 

Scroller

是一個專門用於處理滾動效果的工具類,直接呼叫ScrollTo()或者ScrollBy()的方式來移動的話是瞬間完成,使用者體驗感覺不是很好,然後使用Scroller就可以有個一個種平滑的效果

使用的時候也可傳入自定義的插值器,常用的插值器有

AccelerateDecelerateInterpolator  在動畫開始與介紹的地方速率改變比較慢,在中間的時候加速

AccelerateInterpolator   在動畫開始的地方速率改變比較慢,然後開始加速

AnticipateInterpolator   開始的時候向後然後向前甩

AnticipateOvershootInterpolator   開始的時候向後然後向前甩一定值後返回最後的值

BounceInterpolator     動畫結束的時候彈起

CycleInterpolator    動畫迴圈播放特定的次數,速率改變沿著正弦曲線

DecelerateInterpolator 在動畫開始的地方快然後慢

LinearInterpolator     以常量速率改變

OvershootInterpolator   向前甩一定值後再回到原來位置(快速完成動畫,超出再回到結束樣式)

 系統預設是 ViscousFluidInterpolator

用法:

 Scroller mScroller = new Scroller(context, new OvershootInterpolator());
相應方法解釋:
mScroller.getCurrX() //獲取mScroller當前水平滾動的位置 
mScroller.getCurrY() //獲取mScroller當前豎直滾動的位置
mScroller.forceFinished(true); //停止一切滑動
mScroller.computeScrollOffset()//判斷是否還在滑動中, true滑動中 ,false滑動完成


mScroller.startScroll  //設定滑動 ,執行這個方法,之後執行invalidate()才會觸發View裡面的computeScroll方法的回撥的
  /**
     *開始滾動只要提供一個開始位置和結束位置,滾動將使用預設值為250毫秒的持續時間。
     * 
     * @param startX Starting horizontal scroll offset in pixels. Positive
     *        numbers will scroll the content to the left.
     * @param startY Starting vertical scroll offset in pixels. Positive numbers
     *        will scroll the content up.
     * @param dx Horizontal distance to travel. Positive numbers will scroll the
     *        content to the left.
     * @param dy Vertical distance to travel. Positive numbers will scroll the
     *        content up.
     */
    public void startScroll(int startX, int startY, int dx, int dy) {

    }

感覺有點不好理解,要先知道,滑動的開始都是左上角(0,0),最開始時的狀態也就是剛剛說的(0,0),向下滑動getScrollY()的值是負數,向上滑動是正數

 

   mScroller.fling() //慣性滑動的,需要配合VelocityTracker.getYVelocity();來獲取初速度

   /**
         * fling 方法引數註解
         *
         * startX 滾動起始點X座標
         * startY 滾動起始點Y座標
         * velocityX   當滑動螢幕時X方向初速度,以每秒畫素數計算
         * velocityY   當滑動螢幕時Y方向初速度,以每秒畫素數計算
         * minX    X方向的最小值,scroller不會滾過此點。
         * maxX    X方向的最大值,scroller不會滾過此點。
         * minY    Y方向的最小值,scroller不會滾過此點。
         * maxY    Y方向的最大值,scroller不會滾過此點。
         */

 public void fling(int startX, int startY, int velocityX, int velocityY,
            int minX, int maxX, int minY, int maxY) {
 }

用法:

  mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
        float yVelocity = mVelocityTracker.getYVelocity(pointerId);
        mScroller.fling(0, getScrollY(), 0, (int) -yVelocity * 2, 0, 0, 0, measuredHeight - height);

 

VelocityTracker

 追蹤觸控事件速率,實現flinging和其他手勢的幫助類 

  1. 當開始追蹤的時候,使用obtain來獲取VelocityTracker類的例項  
  2. 把接收到的MotionEvent放入到addMovement(android.view.MotionEvent)中  
  3. 當要確定速度時呼叫computeCurrentVelocity(int),使用getXVelocity(int)和getYVelocity(int)來檢測每個觸控點id的速率  

 

onMeasure

主要用於測量控制元件的大小,剛開始直接用getMeasuredHeight()獲取到的高度,其實這個是當前內容可見區域高度,全部內容的滾動長度需要計算的,如下,計算好所有子控制元件的高度之後需要呼叫setMeasuredDimension更改高度

int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            //測量子控制元件的大小
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
            MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams();
            measuredHeight += (childView.getMeasuredHeight() + layoutParams.bottomMargin + layoutParams.topMargin);
        }
        //呼叫此方法 重新更改高度
        setMeasuredDimension(getMeasuredWidth(), measuredHeight);

 

computeScroll

用scollTo/scollBy/startScroll方法來進行滑動時,都需要執行invalidate()才會觸發其的回撥,從而才會看到效果

@Override  
public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        } 
    }

 

下面是全部程式碼

public class ScrollerViewLayout extends LinearLayout {
    
    private int measuredHeight;//全部item高度

    private int height; //可見內容高度

    private Scroller mScroller;
    private VelocityTracker mVelocityTracker;
    private float mMaxVelocity;

    public ScrollerViewLayout(Context context) {
        this(context, null, 0);
    }

    public ScrollerViewLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ScrollerViewLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setOrientation(VERTICAL);
        mScroller = new Scroller(context, new OvershootInterpolator());
        ViewConfiguration vc = ViewConfiguration.get(getContext());
        mMaxVelocity = vc.getScaledMaximumFlingVelocity();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measuredHeight = 0;
        //得到控制元件原始顯示高度
        height = getMeasuredHeight();

        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            //測量子控制元件的大小
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
            MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams();
            measuredHeight += (childView.getMeasuredHeight() + layoutParams.bottomMargin + layoutParams.topMargin);
        }
        //呼叫此方法 重新更改高度
        setMeasuredDimension(getMeasuredWidth(), measuredHeight);
    }

    private float downY;
    private int pointerId;
    private boolean isSilde = false;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        handlerScroll(event);
        return true;
    }

    /**
     * 處理滾動事件
     * @param event
     */
    private void handlerScroll(MotionEvent event) {
        float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (mVelocityTracker == null) {
                    mVelocityTracker = VelocityTracker.obtain();
                }
                pointerId = event.getPointerId(0);
                //停止一切滾動
                mScroller.forceFinished(true);
                mVelocityTracker.clear();
                mVelocityTracker.addMovement(event);
                downY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                mVelocityTracker.addMovement(event);
                int move = (int) (downY - y);
                if (isSilde || move == 0) {
                    return;
                }
                //向下滑動
                if (move < 0) {
                    scrollBy(0, move);
                    downY = y;
                }
                //向上滑動
                else if (move > 0) {
                    scrollBy(0, move);
                    downY = y;
                }

                Log.e("kawa", ">>>>move:" + move
                        + ">>>downY:" + downY
                        + ">>>y:" + y
                        + ">>>height:" + height
                        + ">>>measuredHeight:" + measuredHeight
                        + ">>>getScrollY:" + getScrollY());
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if ((measuredHeight - height) < getScrollY() || getScrollY() < 0) {
                    scrollReset();
                } else {
                    scrollFling();
                }
                break;
        }
    }

    @Override  //每次執行draw都會執行,獲取當前的滾動位置進行重繪製
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            isSilde = true;
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        } else {
            isSilde = false;
        }
    }

    /**
     *  超出頂部/底部的進行復位
     */
    private void scrollReset() {
        int scrollY = getScrollY();
        if (scrollY < 0) {
            int startY = scrollY;
            int endY = -scrollY;
            mScroller.startScroll(0, startY, 0, endY);
            invalidate();
        } else {
            //向上滑動超出底部界限時才進行復位
            if ((measuredHeight - height) < getScrollY()) {
                int startY = scrollY;
                int endY = -(scrollY - (measuredHeight - height));
                mScroller.startScroll(0, startY, 0, endY);
                invalidate();
            }
        }
    }

    /**
     * 慣性滾動
     */
    private void scrollFling() {
        mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
        float yVelocity = mVelocityTracker.getYVelocity(pointerId);
        /**
         * fling 方法引數註解
         *
         * startX 滾動起始點X座標
         * startY 滾動起始點Y座標
         * velocityX   當滑動螢幕時X方向初速度,以每秒畫素數計算
         * velocityY   當滑動螢幕時Y方向初速度,以每秒畫素數計算
         * minX    X方向的最小值,scroller不會滾過此點。
         * maxX    X方向的最大值,scroller不會滾過此點。
         * minY    Y方向的最小值,scroller不會滾過此點。
         * maxY    Y方向的最大值,scroller不會滾過此點。
         */
        mScroller.fling(0, getScrollY(), 0, (int) -yVelocity * 2, 0, 0, 0, measuredHeight - height);
        invalidate();
    }
}

這裡有張圖可以更好的理解使用Scroller (圖來源網路)