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

在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();
            }
        });

效果还是挺不错的。


上拉加载更多
下拉刷新

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

谢谢!!

                                                                                                         ——超低空
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,333评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,812评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,016评论 0 246
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,293评论 0 214
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,650评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,788评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,003评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,741评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,462评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,681评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,168评论 1 262
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,528评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,169评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,119评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,902评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,846评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,719评论 2 274

推荐阅读更多精彩内容