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

版权声明:Love Open Source —— 超低空 https://blog.csdn.net/MC_hust/article/details/45318087

在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,从而实现滑动到底部时上拉加载更多的功能.
 */
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();
        }
    }

    /**
     * 加载更多的监听器
     */
    public static interface OnLoadListener
    {
        public void onLoad();
    }
}

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

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

效果还是挺不错的。
上拉加载更多

下拉刷新

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

谢谢!!

                                                                                                         ——超低空

猜你喜欢

转载自blog.csdn.net/MC_hust/article/details/45318087