andorid自定義view Scroller詳解含Demo 記錄學習

最近在看自定義view相關的內容 在Scroller 這裏的確是卡了一些時間 故寫下來自己的心得總結
聲明:此文章demo轉載於guolin大神的Scroller完全解析 詳情請戳這裏
廢話不多說
Scroller 的作用:是一個專門用於處理滾動效果的工具類
Scroller的使用方法 (代碼中也會將詳細的使用步驟進行註釋)
1.創建Scroller實例
2.調用startScroller()方法初始化滾動數據並刷新界面
3.重寫computescroll()方法,並在內部完成平滑滾動邏輯
其中的scrollTo() ,scrollBy()這裏就不多做介紹 詳情可以看guolin作者的小demo

好 現在開始一點點的摳代碼
效果圖:
在這裏插入圖片描述

public class ScrollerLayout extends ViewGroup {

 private Scroller mScroller;
 private int mTouchSlop;//爲拖動的最小移動像素數
public ScrollerLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 第一步,創建Scroller的實例
        mScroller = new Scroller(context);
        // 獲取TouchSlop值
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        Log.i("minnum","@"+mTouchSlop);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            // 爲ScrollerLayout中的每一個子控件測量大小
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
        }
    }


}

這裏比較簡單 就是對於我們自定義view的初始化 在ScrollerLayout 中我們先創建了Scroller的實例 又拿到最小偏移量 然後再onMeasure 方法中定義了每一個子容器的寬高 這裏就是父容器的寬高

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (changed) {
            int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                View childView = getChildAt(i);
                // 爲ScrollerLayout中的每一個子控件在水平方向上進行佈局
                //childview.layout(l,t,r,b)
                childView.layout(i * childView.getMeasuredWidth(), 0, (i + 1) * childView.getMeasuredWidth(), childView.getMeasuredHeight());
            }
            // 初始化左右邊界值
            leftBorder = getChildAt(0).getLeft();
            rightBorder = getChildAt(getChildCount() - 1).getRight();

        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mXDown = ev.getRawX();
                mXLastMove = mXDown;
                break;
            case MotionEvent.ACTION_MOVE:
                mXMove = ev.getRawX();
                float diff = Math.abs(mXMove - mXDown);
                Log.i("diff","@"+diff);
                mXLastMove = mXMove;
                Log.i("inter","@"+mXLastMove);
                // 當手指拖動值大於TouchSlop值時,認爲應該進行滾動,攔截子控件的事件
                if (diff > mTouchSlop) {
                    return true;
                }
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

每一個子容器的寬高定義好了 下一步就是確定孩子顯示的位置 分別拿到每一個孩子 然後使用child Viewlayout()確定孩子顯示的位置 然後再onInterceptTouchEvent()中對事件進行攔截 不讓事件傳遞到子控件上
event.getRawX:表示的是觸摸點距離屏幕左邊界的距離 當移動的距離大於最小偏移量時 就開始攔截 就會將事件傳遞到onTouchEvent中

@Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:

                mXMove = event.getRawX();
                Log.i("inters","@"+mXMove+"last"+mXLastMove);
                int scrolledX = (int) (mXLastMove - mXMove);
                Log.i("imports","scrllx"+getScrollX()+"juli"+scrolledX);
                if (getScrollX() + scrolledX < leftBorder) {
                    scrollTo(leftBorder, 0);
                    return true;
                } else if (getScrollX() + getWidth() + scrolledX > rightBorder) {
                    scrollTo(rightBorder - getWidth(), 0);
                    return true;
                }
                scrollBy(scrolledX, 0);
                mXLastMove = mXMove;
                break;
            case MotionEvent.ACTION_UP:
                // 當手指擡起時,根據當前的滾動值來判定應該滾動到哪個子控件的界面
                int targetIndex = (getScrollX() + getWidth() / 2) / getWidth();
                Log.i("import","scrllx"+getScrollX()+"width"+getWidth());
                int dx = targetIndex * getWidth() - getScrollX();
                // 第二步,調用startScroll()方法來初始化滾動數據並刷新界面
                mScroller.startScroll(getScrollX(), 0, dx, 0);
                invalidate();
                break;
        }
        return super.onTouchEvent(event);
    }

    @Override
    public void computeScroll() {
        // 第三步,重寫computeScroll()方法,並在其內部完成平滑滾動的邏輯
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        }
    }

本文的難點就來了 其中getScrollX()確實是我捉摸不透 先來看下圖 假設我們的屏幕在當前1的位置上
在這裏插入圖片描述
那getScrollX()值是如何獲取的呢
公式:當前view視圖x的座標減去初始位置X座標
其中當前view視圖x的座標時是隨着滑動當前x座標發生改變的 初始位置x,y座標是不變的 在我們這個demo中就是(0,0)
當我們屏幕現在在第二個圖形中 當我們向右移動100 我們的視圖x的座標就會從該開始的400 變成現在的500
getScrollX() = 500-0 = 500
假設我們在第二個圖形上 不管你的手指放在圖形的那個位置 他的左上角x座標就是400 同理 當我們在第1個或者第三個時他的初始getScrollX()默認就是當前view視圖的x座標

然後在onTouchEvent 的移動事件中 int scrolledX = (int) (mXLastMove - mXMove); 用上從次的值減去當前移動的值 因爲scrollTo scrollBy他們移動時取反的 這點需要牢記 值爲正代表向右移動
然後用當前位置和 移動的位置進行判斷 是否小於左邊界和大於右邊界 如果條件成立 則說明已超出邊界然後讓其移動到左右邊界的位置 不超出的話就在當前位置上進行距離的增量
在手指擡起的事件中 算出移動的位置dx 然後調用Scroller.startScroll();進行移動
重寫computeScroll()拿到當前mScroller 的x,y值 一點點的移動到最後目的值

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章