自己动手(二)──PullToRefresh之上拉翻页(2)

效果图

这里写图片描述
这里写图片描述
这里写图片描述

改进

  • 解决先上拉再下拉时的问题
  • 加入箭头动画

原理

解决先上拉再下拉时的问题关键在于如何在 custom view 的滑动和 scroll view 的滑动之间切换。从上向下滑动的时候,应该可以从 custom view 的滑动无缝过渡到 scroll view 的滑动。如最后一张图所示。这点豆瓣一刻没有做到。我通过插入一个 touch down event实现。体验更佳。箭头动画相对简单,RotateAnimation。

关键源码

public class PtrLinearLayout extends LinearLayout {

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

    enum STATE {CAN_TURN_PAGE, CAN_NOT_TURN_PAGE};

    private STATE state = STATE.CAN_NOT_TURN_PAGE;

    private View contentView;
    private View footerView;

    private float lastY = -1;

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


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

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

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

    private void init() {

        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);

        footerView.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));

        footerViewHeight = footerView.getMeasuredHeight();
        LayoutParams layoutParams = (LayoutParams) footerView.getLayoutParams();
        layoutParams.height = footerViewHeight;
        footerView.setLayoutParams(layoutParams);

        scrollView = (ScrollView) contentView;

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

    /**
     * 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();

        boolean linearLayoutScrolling = false;
        float scrollViewContinueScrollDy = 0;

        if (getScrollY() != 0 || (isScrollViewReachBottom() && dy < 0)) {
            linearLayoutScrolling = true;

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

                    //never let getScrollY < 0
                    int calibrateDScrollY = (int) (-dy * SCROLL_RATIO);
                    if ((getScrollY() - dy * SCROLL_RATIO) < 0){
                        calibrateDScrollY = -getScrollY();
                        scrollViewContinueScrollDy = dy * SCROLL_RATIO - getScrollY();
                    }

                    scrollBy(0, calibrateDScrollY);
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    if (getScrollY() >= footerViewHeight) {
                        if (footerListener != null) {
                            footerListener.onTrigger();
                        }
                    } else {
//                        scrollTo(0, 0);
                        smoothScrollTo(0, 0);
                    }
                    break;
            }

            if (getScrollY() >= footerViewHeight && state == STATE.CAN_NOT_TURN_PAGE) {
                footerTextView.setText("松开翻页");
                arrowImageView.startAnimation(rotateDownAnim);
                state = STATE.CAN_TURN_PAGE;
            } else if (getScrollY() < footerViewHeight && state == STATE.CAN_TURN_PAGE) {
                footerTextView.setText("上拉翻页");
                arrowImageView.startAnimation(rotateUpAnim);
                state = STATE.CAN_NOT_TURN_PAGE;
            }


        }

        if (getScrollY() == 0) {
            if (linearLayoutScrolling){
                MotionEvent downEvent = MotionEvent.obtain(event);
                downEvent.setAction(MotionEvent.ACTION_DOWN | event.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
                downEvent.setLocation(event.getX(), event.getY()-scrollViewContinueScrollDy);
                super.dispatchTouchEvent(downEvent);
            }
            return super.dispatchTouchEvent(event);
        } else {
//            if (getScrollY() < 0) {
//                scrollTo(0, 0);
//            }
            MotionEvent cancelEvent = MotionEvent.obtain(event);
            cancelEvent.setAction(MotionEvent.ACTION_CANCEL|(event.getActionIndex()<< MotionEvent.ACTION_POINTER_INDEX_SHIFT));
            super.dispatchTouchEvent(cancelEvent);
            return true;
        }

    }

    private void smoothScrollTo(int toScrollX, int toScrollY) {
        Animation scrollYAnimation = new ScrollYAnimation(getScrollY(), toScrollY);
        scrollYAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
        scrollYAnimation.setDuration(DURATION);
        startAnimation(scrollYAnimation);
    }

    private class ScrollYAnimation extends Animation{
        private int fromScrollY, toScrollY, scrollYDelta;

        private ScrollYAnimation(int fromScrollY, int toScrollY) {
            this.fromScrollY = fromScrollY;
            this.toScrollY = toScrollY;
            scrollYDelta = toScrollY - fromScrollY;
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            if (interpolatedTime == 1){
                scrollTo(0, toScrollY);
                return;
            }
            scrollTo(0, (int) (fromScrollY + scrollYDelta*interpolatedTime));
        }
    }

    private boolean isScrollViewReachBottom() {
        return scrollView.getScrollY() >= scrollView.getChildAt(0).getMeasuredHeight() - scrollView.getHeight();
    }

    private FooterListener footerListener;

    public void setFooterListener(FooterListener footerListener) {
        this.footerListener = footerListener;
    }

    public static interface FooterListener {
        public void onTrigger();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值