仿格瓦拉双层拖拽布局,格瓦拉电影详情界面

这是一个复杂的控件,双层布局,可滑动,可拖拽


Demo下载:我的Github DoublePull


格瓦拉实际效果图:


简单分析:

  1. 根部局使用了RelativeLayout,有两个子布局:外层布局与内层布局。
  2. 外层布局。根布局为自定义ScrollView,有两子布局:HeaderFrameLayout与PullRelativeLayout
  3. 内层布局。根部局为RelativeLayout,有两子布局:RecyclerView与ImageButton

外层布局如图:

内层布局如图:


实现难点:

  1. 初始化,事件由ScrollView处理和消耗。当滚动到顶部,若继续往下滑动,事件由子View PullRelativeLayout处理和消耗
  2. 若PullRelativeLayout处理和消耗事件,拖拽距离过小,移动到原来位置。反之,则向下滑动隐藏布局
  3. HeaderFrameLayout随着PullRelativeLayout的变化而变化。隐藏则一起隐藏,打开则一起打开
  4. PullRelativeLayout隐藏后,ScrollView将不能处理和消耗事件,事件应由RecyclerView处理和消耗
  5. RecyclerView头布局滚动高度超过,头的70%,PullRelativeLayout做动画。HeaderFrameLayout做打开动画
  6. HeaderFrameLayout自定义View内部需要有打开功能,可使用Scroller来完成
  7. PullRelativeLayout自定义View内部需要有打开和隐藏功能,可使用Scroller来完成
  8. View的滑动也可使用动画的形式,但是由于需要设置一些Visibility属性,这里就使用Scroller来完成滑动
  9. 自定义View之间的状态获取和数据交互,使用了对外提供回调接口的形式和对象直接注入的形式
  10. 调试布局效果会花掉一些时间

自定义View分析:

OutScrollView



    public class OutScrollView extends ScrollView {

        private int mPullRelativeLayoutState = PullRelativeLayout.SHOW;
        private int mDownY;
        private int mMoveY;

        private OnScrollStateChangeListener mOnScrollStateChangeListener;

        public OutScrollView(Context context) {
            super(context);
        }

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

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

        public void setPullRelativeLayoutState(int state) {
            mPullRelativeLayoutState = state;
        }

        @Override
        protected void onScrollChanged(int l, int t, int oldl, int oldt) {
            super.onScrollChanged(l, t, oldl, oldt);
            if (mOnScrollStateChangeListener != null) {
                mOnScrollStateChangeListener.onScrollChange(l, t, oldl, oldt);
            }
        }

        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mDownY = (int) ev.getRawY();
                    break;

                case MotionEvent.ACTION_MOVE:
                    mMoveY = (int) ev.getRawY();
                    break;

                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_UP:
                    mDownY = 0;
                    mMoveY = 0;
                    break;
            }

            if(mMoveY - mDownY < 0) {
                return super.onInterceptTouchEvent(ev);
            }

            if (getScrollY() == 0) {
                if (mPullRelativeLayoutState == PullRelativeLayout.SHOW) {
                    return false;
                }

                if (mPullRelativeLayoutState == PullRelativeLayout.MOVE) {
                    return false;
                }
            }
            return super.onInterceptTouchEvent(ev);
        }

        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            if (mPullRelativeLayoutState == PullRelativeLayout.HIDE) {
                return false;
            }
            return super.onTouchEvent(ev);
        }

        public interface OnScrollStateChangeListener {
            void onScrollChange(int l, int t, int oldl, int oldt);
        }

        public void setOnScrollStateChangeListener(OnScrollStateChangeListener listener) {
            mOnScrollStateChangeListener = listener;
        }
    }

  1. 获取PullRelativeLayoutState的状态值,根据它来判断是否处理事件:自己处理,子View处理,不处理等
  2. OnScrollStateChangeListener把ScrollView滚动高度状态回调出去,给外面使用

HeaderFrameLayout



    public class HeaderFrameLayout extends FrameLayout {

        private Scroller mScroller;
        private int mHeight;
        private boolean isOpen;

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

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

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

        private void init() {
            mScroller = new Scroller(getContext());
        }

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

        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            mHeight = getMeasuredHeight();
        }

        public void setOpen(boolean flag) {
            isOpen = flag;
        }

        public boolean isOpen() {
            return isOpen;
        }

        public void open() {
            if (!isOpen) {
                return;
            }
            smoothScrollTo(0, mHeight, 0, -mHeight, 800);
            isOpen = false;
        }

        private void smoothScrollTo(int startX, int startY,
                                    int dx, int dy, int duration) {
            mScroller.startScroll(startX, startY, dx, dy, duration);
            invalidate();
        }
    }



  1. 使用Scroller来滑动,让自己拥有打开滑动的效果,初始化位移高度等
  2. 一个boolean值来限制是否做打开滑动的效果

PullRelativeLayout


    public class PullRelativeLayout extends RelativeLayout {

        public static final int SHOW = 1000;
        public static final int HIDE = 2000;
        public static final int MOVE = 3000;
        public static final int OPEN_START = 4000;
        public static final int OPEN_FINISH = 5000;

        private static final int NORMAL_TIME = 600;
        private int mMaxOffset;
        private int mState = SHOW;
        private float mLastY;
        private int mMoveY;
        private Scroller mScroller;
        private OnStateChangeListener mOnStateChangeListener;

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

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

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


        private void init() {
            mScroller = new Scroller(getContext());
        }

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

        public void setMaxOffset(int offset) {
            mMaxOffset = offset;
        }

        public void setState(int state) {
            mState = state;
        }

        public int getState() {
            return mState;
        }

        @Override
        public boolean onTouchEvent(MotionEvent event) {
            if (mState == HIDE) {
                return false;
            }
            float y = event.getY();
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mLastY = y;
                    break;

                case MotionEvent.ACTION_MOVE:
                    int moveY = (int) (y - mLastY);
                    if (getScrollY() <= 0 && moveY > 0) {
                        int offset = moveY / 2;
                        move(offset);
                    }
                    mLastY = y;
                    break;

                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_UP:
                    changeState();
                    break;
            }
            return true;
        }

        private void move(int offset) {
            mState = MOVE;
            if (mOnStateChangeListener != null) {
                mOnStateChangeListener.pullViewMove(mState, -offset);
            }
            scrollBy(0, -offset);
        }

        private void hide() {
            mState = HIDE;
            if (mOnStateChangeListener != null) {
                mOnStateChangeListener.pullViewHide(mState);
            }
            mMoveY = getMeasuredHeight() + Math.abs(getScrollY());
            smoothScrollTo(0, getScrollY(), 0, -mMoveY, NORMAL_TIME * 3);
        }

        public void hide(int time) {
            mState = HIDE;
            if (mOnStateChangeListener != null) {
                mOnStateChangeListener.pullViewHide(mState);
            }
            mMoveY = getMeasuredHeight() + Math.abs(getScrollY());
            smoothScrollTo(0, getScrollY(), 0, -mMoveY, time);
        }

        private void show() {
            mState = SHOW;
            if (mOnStateChangeListener != null) {
                mOnStateChangeListener.pullViewShow(mState);
            }
            smoothScrollTo(0, getScrollY(), 0, -getScrollY(), getScrollY());
        }

        private void changeState() {
            if (Math.abs(getScrollY()) > mMaxOffset + 50) {
                hide();
            } else {
                show();
            }
        }

        public void open() {
            mState = OPEN_START;
            if (mOnStateChangeListener != null) {
                mOnStateChangeListener.pullViewOpenStart();
            }
            smoothScrollTo(0, -mMoveY, 0, mMoveY, NORMAL_TIME);
            postDelayed(new Runnable() {
                @Override
                public void run() {
                    mState = OPEN_FINISH;
                    if (mOnStateChangeListener != null) {
                        mOnStateChangeListener.pullViewOpenFinish();
                    }
                }
            }, NORMAL_TIME);
        }

        private void smoothScrollTo(int startX, int startY,
                                    int dx, int dy, int duration) {
            mScroller.startScroll(startX, startY, dx, dy, duration);
            invalidate();
        }

        public interface OnStateChangeListener {
            void pullViewShow(int state);

            void pullViewHide(int state);

            void pullViewMove(int state, int offset);

            void pullViewOpenStart();

            void pullViewOpenFinish();
        }

        public void setOnStateChangeListener(OnStateChangeListener listener) {
            mOnStateChangeListener = listener;
        }
    }


  1. 使用Scroller来滑动,设置打开,隐藏,手指拖拽的方法。将View的各种状态回调出去,用于同步其他View的状态
  2. 设置状态监听器

再来看一次效果图:

Demo下载:我的Github DoublePull

2016年7月09日 03:57:12

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值