Android仿小米商城商品详情界面UI,ScrollView嵌套ScrollView/WebView/ListView

最近公司没事,研究了下多嵌套滚动组件的事件分发,虽然以前也接触过,但都是拿网上的用,也是特别简单的,正好朋友也需要,就研究了下

这个Demo也不是很完善,放上来也是让各位大牛给指点一下,优化优化

使用情景:

小米商城商品详情界面,界面看似ScrollView,但当正常滚动到底部时,提示继续上拉显示更多详情,上拉后直接滚动到第二屏,第二屏是个ViewPager,ViewPager里面的各个pager有的是WebView有的是ListView,有的是ScrollView,一开始想想就特别头晕,后来理清思路后,实现起来却处处碰壁,不是ViewPager不能左右滑动就是ListView不能上拉,网上也搜索了很多相关Demo,但都没有完善一点的,也许根本没几个人使用这样的无脑嵌套吧,好吧,既然这样,就只有自己动手了。

花了1周时间,总算出来点效果了,重写了几个组件:InnerScrollView、InnerWebView、InnerListView


一、InnerScrollView.java

  1. 思路:

    如果内部ScrollView是固定高度,那么需要滚动,外部的当然也需要滚动,所以要判断当内部滚动到顶部并且手指继续下滑时,把事件交父类处理,同样当滚动到底部并继续上滑时也要交出去,如果InnerScrollView的ChildView高度小于等于InnerScrollView高度(就是不出现滚动条)时,把事件交给父类处理。

  2. 实现:

    只需要在onTouchEvent()里做判断即可,其他不重写
    package com.wuguangxin.morescrolldemo.view;
    
    import android.content.Context;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.MotionEvent;
    import android.widget.ScrollView;
    
    /**
     * 内部ScrollView,解决滑动内部ScrollView时,触发外部滚动问题
     *
     * @author wuguangxin
     * @date 16/7/1 上午10:34
     */
    public class XinInnerScrollView extends ScrollView {
        private final String TAG = "XinInnerScrollView";
        private float childHeight = 0;
        private float downX, downY; // 按下时
        private float currX, currY; // 移动时
        private float moveY; // 从按下到移动的Y距离
        private float scrollViewHeight;
        private boolean isOnTop; // ScrollView是否处于屏幕顶端
        private boolean isOnBottom; // ScrollView是否处于屏幕底端
        private boolean debug = true;
        private Position position = Position.NONE;
    
        public XinInnerScrollView(Context context) {
            this(context, null);
        }
    
        public XinInnerScrollView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public XinInnerScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                getParent().getParent().requestDisallowInterceptTouchEvent(true);
                downX = ev.getX();
                downY = ev.getY();
                childHeight = getChildAt(0).getMeasuredHeight();
                scrollViewHeight = getHeight();
                break;
            case MotionEvent.ACTION_MOVE:
                currX = ev.getX();
                currY = ev.getY();
                moveY = Math.abs(currY - downY);
                isOnTop = getScrollY() == 0;
                isOnBottom = (getScrollY() + scrollViewHeight) == childHeight;
                // 垂直滑动
                if (moveY > Math.abs(currX - downX)) {
                    if (childHeight <= scrollViewHeight) {
                        printLog("onTouchEvent ACTION_MOVE 不能滚动 父处理");
                        getParent().getParent().requestDisallowInterceptTouchEvent(false);
                    } else if (isOnTop) { // 当前处于ScrollView顶部
                        if (currY - downY > 0) {
                            printLog("onTouchEvent ACTION_MOVE 已到顶部 下滑 父处理");
                            getParent().getParent().requestDisallowInterceptTouchEvent(false);
                        } else {
                            printLog("onTouchEvent ACTION_MOVE 已到顶部 上滑 子处理");
                        }
                    } else if (isOnBottom) {
                        // 当前处于ScrollView底部
                        if (currY - downY < 0) {
                            printLog("onTouchEvent ACTION_MOVE 已到底部 上滑 父处理");
                            getParent().getParent().requestDisallowInterceptTouchEvent(false);
                        } else {
                            printLog("onTouchEvent ACTION_MOVE 已到底部 下滑 子处理");
                        }
                    } else {
                        // 当前处于ScrollView中间
                        printLog("onTouchEvent ACTION_MOVE 在中间 子处理");
                    }
                }
                // 水平滚动
                else {
                    if(position.equals(Position.TOP)){
                        printLog("onTouchEvent ACTION_MOVE 水平滚动 position=TOP 子处理");
                    } else {
                        if(Math.abs(currX - downX) > 30){
                            printLog("onTouchEvent ACTION_MOVE 水平滚动 position!=TOP 横向滑动距离>30 父处理");
                            getParent().getParent().requestDisallowInterceptTouchEvent(false);
                        } else {
                            printLog("onTouchEvent ACTION_MOVE 水平滚动 position!=TOP 横向滑动距离<=30 子处理");
                        }
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                printLog("onTouchEvent ACTION_UP ========================");
                getParent().getParent().requestDisallowInterceptTouchEvent(true);
                break;
            }
            return super.onTouchEvent(ev);
        }
    
        /**
         * 为了更好的处理手势滑动事件,设置该组件所处的位置;
         * 比如只有上下两屏时,如果该View是在第一屏,那么设置为Position.TOP,如果在第二屏,则设置为Position.BOTTOM
         *
         * @param position
         */
        public void setPosition(Position position) {
            this.position = position;
        }
    
        public static enum Position {
            /**
             * 顶部View,横向滑动时将不考虑将事件交给父View。(该设计只为第一屏为纯ScrollView考虑)
             */
            TOP,
            /**
             * 底部View, 横向滑动时,将把事件交给父View处理
             */
            BOTTOM,
            /**
             * 不设置,将自动判断(自动判断并不是很精准)
             */
            NONE
        }
    
        public void printLog(String msg) {
            if (debug) {
                Log.d(TAG, msg);
            }
        }
    }

     说一下Position,因为第一屏或者第二屏中的ViewPager里面也可能用到InnerScrollView,ViewPager里面的需要考虑左右滑动的事件,但第一屏是不需要的,为了在第一屏做横向滑动时(一般第一屏应该只有一个ScrollView),不把事件交给父类,所以需要知道该InnerScrollView是在哪里使用的,设置该标记,做更好的判断。日志中子处理”处只打日志,不设置getParent().getParent().requestDisallowInterceptTouchEvent(true);是因为在ACTION_DOWN时已经告诉父类不要拦截,只需要在移动时在适合的条件下通知父类自己不再处理。这就是重写的内部ScrollView。
  3. 还需要解决的问题

    如果准备滚动到底部时,这时不抬起手指继续往回滑,这时事件已经交出去了,往回滑动时,内部ScrollView已经无法滚动了,手势如图:

二、InnerWebView.java

  1. 思路:

    垂直滑动:      
    • 当处于顶部,继续下滑时,交出事件;
    • 当处于底部,继续上滑时,交出事件;
    水平滑动:      
    • 当处于左侧,继续右滑时,交出事件;
    • 当处于右侧,继续左滑时,交出事件;
  2. 实现

    package com.wuguangxin.morescrolldemo.view;
    
    import android.content.Context;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.MotionEvent;
    import android.webkit.WebView;
    
    /**
     * 内部WebView, 该View只适合放在最后一屏
     * 
     * @author wuguangxin
     * @date 16/7/1 上午10:34
     */
    public class XinInnerWebView extends WebView {
        private final String TAG = "XinInnerScrollView";
        private boolean debug = true;
        private float downX, downY; // 按下时
        private float currX, currY; // 移动时
        private float moveX; // 移动长度-横向
    
        public XinInnerWebView(Context context) {
            super(context);
        }
    
        public XinInnerWebView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public XinInnerWebView(Context context, AttributeSet attrs, int defStyleAtt
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值