美文网首页AndroidAndroidAndroid开发摘录
Android“上拉刷新/下拉加载”与“侧滑菜单”的兼容

Android“上拉刷新/下拉加载”与“侧滑菜单”的兼容

作者: 超低空 | 来源:发表于2015-04-27 22:30 被阅读7006次

    在Android系统中,“上拉刷新/下拉加载更多”和“侧滑菜单”都是非常常用的操作界面,二者都比较容易,网上也有许多牛人做好的库可以直接使用。可是很少有讲解如何让两者并存的方法,前不久在一个项目中需要在已有侧滑菜单的应用中,对其中一个菜单项加入上拉/下拉菜单。由于都要捕捉触摸事件,这两者之间可能会产生一些冲突。这里记录一下我的解决方案和步骤,也希望能够为遇到同样问题的朋友们提供一些思路。

    1、侧滑菜单

    单独加入侧滑菜单还是比较容易的,这里我是参照了网上一个牛人写的一个Demo,源代码可以点击下载
    其中SlidingLayout.java这个是侧滑的布局文件,注释很详细,大部分的代码都不需要修改。需要注意的是在xml文件中,SlidingLayout里只能有两个子元素,左侧为菜单(如ListView),右侧为界面。然后将需要监听侧滑事件的控件通过slidingLayout.setScrollEvent(View view)函数设置好就OK。
    注:这里实际上是通过view的触摸监听器onTouchListener()实现的

    侧滑菜单

    2、上拉刷新/下拉加载更多

    对于上拉/下拉界面,网上比较流传的版本是pull_to_refresh这个库,源代码点击下载。同样,注释非常详细,用法也很简单。需要注意在xml中,RefreshableView标签只能有一个ListView。也就是用于下拉刷新的listview,然后在通过RefreshableView.setOnRefreshListener(PullToRefresh Listener listener, int id)方法设置需要下拉刷新的布局即可完成。
    **注:同样,在内部这里也是通过控件的触摸监听器完成的。

    下拉刷新

    3、二者的冲突原因及解决方案

    以上两种界面分开做都有很多简单易用的库,但是当合在一起的时候会发现容易有冲突。主要原因是两者实现原理都是通过监听控件的触摸事件完成,而大多数时候我们需要下拉的和侧滑的都是同样一个控件,这样就会导致同一个控件被设置了两次setOnTouchListener(),结果后一次的会覆盖掉前一次的,这也就是为什么我们会发现二者无法兼容。我所想到的解决方案有以下几种:

    方法一:分开监听布局

    既然同一个布局只能设置一次触摸监听器,那么只有让下拉刷新和侧滑分别对不同的控件进行监听。这里很明显下拉刷新肯定是要对listview进行操作的,那么我们需要修改的就是侧滑的监听事件。可以将侧滑的setScrollEvent参数设置为listview的父布局,然后在父布局中判断用户的触摸行为。如果判定用户动作为上下滑动,则将触摸事件传递给子布局处理,即下拉刷新。反之如果判定为左右滑动,则在父布局中直接拦截事件,并在父布局中处理事件,即侧滑菜单。具体操作如下:

    1. 新建一个自定义布局,作为下拉的listview父布局。并通过setScrollEvent对父控件加入侧滑监听。
    2. 在父布局中覆盖onInterceptTouchEvent方法。用于拦截触摸事件。
    3. 接着覆盖onTouch方法,当事件被拦截时,调用本类中的onTouch处理触摸事件。
    4. 通过setOnRefreshListener对listview加入下拉刷新功能。

    其中需要了解onInterceptTouchEvent的功能。主要用于拦截事件,当控件被触摸的时此方法第一个被调用,返回true则父布局拦截,事件不会传入子布局(即listview)。而在本布局的onTouch方法中处理。若返回false,则事件被传入子布局处理。
    这样在父布局中判断用户行为,即可将侧滑和下拉分开处理。
    这个方法虽然可以实现功能,但感觉不太灵活,而且本人真机测试后会有明显卡顿现象,最后没有使用方法一,而是使用下面的方法。

    方法二:加入官方支持包

    谷歌官方在android-support-v4支持包中加入了下拉刷新类库SwipeRefreshLayout。查看官方源码后发现底层并非简单的监听onTouch事件完成,可以完美的解决冲突问题。用法也很简单。
    导入v4支持包之后,将要下拉的控件(如listview)布局外再套一个SwipeRefreshLayout布局即可,然后通过refreshableView.setOnRefreshListener()方法设置一个监听内部类即可:

    refreshableView.setOnRefreshListener(new OnRefreshListener()
            {
                @Override
                public void onRefresh()
                {
                    //tbd
                }
            });
    

    4、加入上拉加载更多

    官方的支持包中只有下拉刷新功能,如果需要上拉加载更多,需要对官方包进行扩展。方法如下:
    写一个自定义布局继承自SwipeRefreshLayout(直接使用官方的下拉)。然后再里面加入上拉加载的代码,如下:

    /**
     * 继承自SwipeRefreshLayout,从而实现滑动到底部时上拉加载更多的功能.
     * 
     * @author mrsimple
     */
    public class RefreshLayout extends SwipeRefreshLayout implements
            OnScrollListener
    {
    
        /**
         * 滑动到最下面时的上拉操作
         */
    
        private int mTouchSlop;
        /**
         * listview实例
         */
        private ListView mListView;
    
        /**
         * 上拉监听器, 到了最底部的上拉加载操作
         */
        private OnLoadListener mOnLoadListener;
    
        /**
         * ListView的加载中footer
         */
        private View mListViewFooter;
    
        /**
         * 按下时的y坐标
         */
        private int mYDown;
        /**
         * 抬起时的y坐标, 与mYDown一起用于滑动到底部时判断是上拉还是下拉
         */
        private int mLastY;
        /**
         * 是否在加载中 ( 上拉加载更多 )
         */
        private boolean isLoading = false;
    
        /**
         * @param context
         */
        public RefreshLayout(Context context)
        {
            this(context, null);
        }
    
        public RefreshLayout(Context context, AttributeSet attrs)
        {
            super(context, attrs);
    
            mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    
            mListViewFooter = LayoutInflater.from(context).inflate(
                    R.layout.pull_up_refresh, null, false);
        }
    
        @Override
        protected void onLayout(boolean changed, int left, int top, int right,
                int bottom)
        {
            super.onLayout(changed, left, top, right, bottom);
    
            // 初始化ListView对象
            if (mListView == null)
            {
                getListView();
            }
        }
    
        /**
         * 获取ListView对象
         */
        private void getListView()
        {
            int childs = getChildCount();
            if (childs > 0)
            {
                View childView = getChildAt(0);
                if (childView instanceof ListView)
                {
                    mListView = (ListView) childView;
                    // 设置滚动监听器给ListView, 使得滚动的情况下也可以自动加载
                    mListView.setOnScrollListener(this);
                    Log.d(VIEW_LOG_TAG, "### 找到listview");
                }
            }
        }
    
        /*
         * (non-Javadoc)
         * 
         * @see android.view.ViewGroup#dispatchTouchEvent(android.view.MotionEvent)
         */
        @Override
        public boolean dispatchTouchEvent(MotionEvent event)
        {
            final int action = event.getAction();
    
            switch (action)
            {
            case MotionEvent.ACTION_DOWN:
                // 按下
                mYDown = (int) event.getRawY();
                break;
    
            case MotionEvent.ACTION_MOVE:
                // 移动
                mLastY = (int) event.getRawY();
                break;
    
            case MotionEvent.ACTION_UP:
                // 抬起
                if (canLoad())
                {
                    loadData();
                }
                break;
            default:
                break;
            }
    
            return super.dispatchTouchEvent(event);
        }
    
        /**
         * 是否可以加载更多, 条件是到了最底部, listview不在加载中, 且为上拉操作.
         * 
         * @return
         */
        private boolean canLoad()
        {
            return isBottom() && !isLoading && isPullUp();
        }
    
        /**
         * 判断是否到了最底部
         */
        private boolean isBottom()
        {
    
            if (mListView != null && mListView.getAdapter() != null)
            {
                return mListView.getLastVisiblePosition() == (mListView
                        .getAdapter().getCount() - 1);
            }
            return false;
        }
    
        /**
         * 是否是上拉操作
         * 
         * @return
         */
        private boolean isPullUp()
        {
            return (mYDown - mLastY) >= mTouchSlop;
        }
    
        /**
         * 如果到了最底部,而且是上拉操作.那么执行onLoad方法
         */
        private void loadData()
        {
            if (mOnLoadListener != null)
            {
                // 设置状态
                setLoading(true);
                //
                mOnLoadListener.onLoad();
            }
        }
    
        /**
         * @param loading
         */
        public void setLoading(boolean loading)
        {
            isLoading = loading;
            if (isLoading)
            {
                mListView.addFooterView(mListViewFooter);
            }
            else
            {
                mListView.removeFooterView(mListViewFooter);
                mYDown = 0;
                mLastY = 0;
            }
        }
    
        /**
         * @param loadListener
         */
        public void setOnLoadListener(OnLoadListener loadListener)
        {
            mOnLoadListener = loadListener;
        }
    
        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState)
        {
    
        }
    
        @Override
        public void onScroll(AbsListView view, int firstVisibleItem,
                int visibleItemCount, int totalItemCount)
        {
            // 滚动时到了最底部也可以加载更多
            if (canLoad())
            {
                loadData();
            }
        }
    
        /**
         * 加载更多的监听器
         * 
         * @author mrsimple
         */
        public static interface OnLoadListener
        {
            public void onLoad();
        }
    }
    

    主要是判断下滑过程中是否到了最底部来实现加载更多。最后在通过setOnLoadListener()设置回调监听类即可完成:

    refreshableView.setOnLoadListener(new OnLoadListener()
            {
                @Override
                public void onLoad()
                {
                    currentPage++;
                    new HttpThread(FragmentAbstract.this, handlerLoadMore).start();
                }
            });
    

    效果还是挺不错的。


    上拉加载更多 下拉刷新

    到此,侧滑菜单以及上拉/下拉二者的兼容问题可以得到很好的解决,如果各位朋友有更好的解决方案,欢迎给我留言,相互讨论,共同进步!

    谢谢!!

                                                                                                             ——超低空
    

    相关文章

      网友评论

      • wen_1efd:up主能发个代码源吗?923617189@qq.com
      • running菜:你好,demo可以给我一份么 谢谢 caidongxue1205@163.com 谢谢
      • 果豆928:你好发给我一份源码吧,好吗。1160426759@qq.com谢谢
      • XG不在:好饭不怕晚,xgbuzai@163.com,求demo一份
      • 59498fdfcd0b:楼主给个demo吧,谢谢,3313253544@qq.com
      • 1699eef9a3fc:楼主 给一份demo吧 1102313475@qq.com谢谢
      • e33fc662e094:929371786@qq.com 谢谢
      • e33fc662e094:您好 有dome么>能发一份么?
      • e33fc662e094:dome 是eclipse的? 有studio的么
      • 九月龙卷风:求一份源码,289637248@qq.com,谢谢楼主!
      • 追丶梦:楼主求一份demo,好人一生平安!! 168668715@qq.com
      • 悦目取光:楼主来一份demo,好人一生平安!! 2731657951@qq.com
        超低空:@悦目取光 代码已发送,有什么问题欢迎来留言讨论~
      • abad0b14a950:hi,也寄给我一份源码吧,好吗。windsoul333@hotmail.com谢谢
        超低空:@塞外风 你好, 代码已发送,请注意查收
      • Silence潇湘夜雨:挺好的,继续努力,端午节快乐。
        超低空:@Silence潇湘夜雨 非常感谢,同乐~ :smile:

      本文标题:Android“上拉刷新/下拉加载”与“侧滑菜单”的兼容

      本文链接:https://www.haomeiwen.com/subject/erqvfttx.html