自己動手(二)──PullToRefresh之上拉翻頁(3)

前言

其實,上拉作爲一個手勢可以和很多動作關聯,不僅僅侷限於翻頁。我的實現裏面很多東西寫的比較死,比如其實bottom view其實可以由使用者創建,bottom view在上拉過程中狀態的改變也可以定製,通過addView加入到PullToRefresh容器中,這樣可以提供更多靈活性。即,可以定義一個AbsBottomView的虛基類,它會有一些諸如onFinish,onRelease, onPull之類的函數,供PullToRefresh容器調用。

改進

  • 處理一個手勢分兩半,一半container處理、一半content處理的情況。即,處理權從content變到container,手動給content發個cancel event,處理權從container變到content,手動給conten發個down event。
  • 讓容器繼承自FrameLayout
  • 使得content view可以是任意類型的view,增強通用性
  • 用Scroller工具類替代自定義Animation來實現平滑移動
  • 給變量、函數起個好名字

完整demo

github任意門

相關源碼

public class PTRFrameLayout extends FrameLayout {

    private final static float SCROLL_RATIO = 0.35f;
    private static final long ROTATE_ANIM_DURATION = 180;

    enum STATE {GUIDE_USER_PULL, GUIDE_USER_RELEASE}

    private STATE footerViewState = STATE.GUIDE_USER_PULL;

    private View contentView;
    private View footerView;

    private float lastY = -1;

    private int footerViewHeight;
    private TextView footerTextView;
    private Animation rotateUpAnim;
    private Animation rotateDownAnim;
    private ImageView arrowImageView;

    private Scroller scroller;

    public PTRFrameLayout(Context context) {
        super(context);
        init();
    }

    public PTRFrameLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public PTRFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }


    private void init() {
        scroller = new Scroller(getContext(), new DecelerateInterpolator(1.6F));

        rotateUpAnim = new RotateAnimation(0.0f, -180.0f,
                Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
                0.5f);
        rotateUpAnim.setDuration(ROTATE_ANIM_DURATION);
        rotateUpAnim.setFillAfter(true);
        rotateDownAnim = new RotateAnimation(-180.0f, 0.0f,
                Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
                0.5f);
        rotateDownAnim.setDuration(ROTATE_ANIM_DURATION);
        rotateDownAnim.setFillAfter(true);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        contentView = getChildAt(0);
        footerView = getChildAt(1);

        arrowImageView = (ImageView) findViewById(R.id.iv_ptr_arrow);
        footerTextView = (TextView) findViewById(R.id.tv_ptr_operation_hint);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        footerViewHeight = footerView.getMeasuredHeight();
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        contentView.layout(left, top, right, bottom);
        footerView.layout(left, bottom, right, bottom + footerView.getMeasuredHeight());
    }

    /**
     * should get touch event from here, otherwise you can not scroll out the footerView closely after scroll the scrollview
     *
     * @param event
     * @return
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {

        if (lastY == -1) {
            lastY = event.getY();
        }
        float dy = event.getY() - lastY;
        lastY = event.getY();

        if (getScrollY() != 0 || (dy < 0 && !contentCanScrollUp())) {

            //content scroll end, ptr container scroll begin,
            //send a cancel event to content view, for content view's sake
            if (getScrollY() == 0) {
                MotionEvent cancelEvent = MotionEvent.obtain(event);
                cancelEvent.setAction(MotionEvent.ACTION_CANCEL | (event.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
                super.dispatchTouchEvent(cancelEvent);
            }

            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    break;
                case MotionEvent.ACTION_MOVE:

                    //make sure getScrollY() >= 0, aka never let top show blank area
                    int calibratedScrollY = (int) (-dy * SCROLL_RATIO);
                    if ((getScrollY() - dy * SCROLL_RATIO) < 0) {
                        calibratedScrollY = -getScrollY();
                    }

                    scrollBy(0, calibratedScrollY);
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    if (getScrollY() >= footerViewHeight) {
                        if (PTRListener != null) {
                            PTRListener.onTrigger();
                        }
                    }
                    release();
                    break;
            }

            updateFooterViewState();

            //ptr container scroll end, content  scroll begin,
            //send a down event to content view, so it can recognize this second part of the gesture
            if (getScrollY() == 0) {
                MotionEvent downEvent = MotionEvent.obtain(event);
                downEvent.setAction(MotionEvent.ACTION_DOWN | event.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
                downEvent.setLocation(event.getX(), event.getY());
                super.dispatchTouchEvent(downEvent);
            }
        }

        return super.dispatchTouchEvent(event);
    }

    private void updateFooterViewState() {
        if (getScrollY() >= footerViewHeight && footerViewState != STATE.GUIDE_USER_RELEASE) {
            footerTextView.setText("let it go ~ baby");
            arrowImageView.startAnimation(rotateDownAnim);
            footerViewState = STATE.GUIDE_USER_RELEASE;
        } else if (getScrollY() < footerViewHeight && footerViewState != STATE.GUIDE_USER_PULL) {
            footerTextView.setText("pull ~ baby");
            arrowImageView.startAnimation(rotateUpAnim);
            footerViewState = STATE.GUIDE_USER_PULL;
        }
    }

    private void release() {
        scroller.startScroll(0, getScrollY(), 0, -getScrollY());
        invalidate();//this will make computeScroll() get called
    }

    @Override
    public void computeScroll() {
        if (scroller.computeScrollOffset()){
            scrollTo(0, scroller.getCurrY());
        }
    }

    private boolean contentCanScrollUp() {
        if (contentView instanceof AdapterView<?>) {
            AdapterView<?> adapterView = (AdapterView<?>) contentView;
            if (adapterView.getLastVisiblePosition() == adapterView.getCount() - 1
                    && adapterView.getChildAt(adapterView.getChildCount() - 1).getBottom() <= adapterView.getHeight()) {
                Log.e("", "adapterView content can not scroll up anymore");
                return false;
            } else {
                return true;
            }
        } else {
            if (contentView.getScrollY() >= contentView.getMeasuredHeight() - contentView.getHeight()) {
                return false;
            } else {
                return true;
            }
        }
    }

    private PTRListener PTRListener;

    public void setPTRListener(PTRListener PTRListener) {
        this.PTRListener = PTRListener;
    }

    public static interface PTRListener {
        public void onTrigger();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章