下拉刷新控件包裹下的ListView侧滑出菜单的实现

转帖请注明本文出自skypupil的博客(http://blog.csdn.net/u011956441/article/details/42551805,请尊重他人的辛勤劳动成果,谢谢!

事先声明下本文的目标是解决下拉刷新控件包裹下的ListView在侧滑时遇到的问题,文中的ListView侧滑效果的实现来自网上其他人,到底是谁我也不知道了。

本文实现的最终效果是:从右往左拖动ListView的某个item可以看到item随手指实时移动,并且可以拖出右侧的操作菜单;另外,ListView的单击和长按事件以及下拉刷新还有多个fragment间的相互切换事件都能正常工作。

进入主题,首先是我的布局文件中的代码:


    
    

        
     
     
    
    
    

SwipeRefreshLayout是android官方的下拉刷新控件,具体如何使用,大家可自行谷歌。SlideListView是一个自定义的ListView,用以实现跟随手指实时侧滑的效果。SlideListView的实现代码如下:

package com.moopoo.widget;


import android.content.Context;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Scroller;


/**
 * 侧向滑出菜单的ListView
 * 使用请注意与ListView的Item的布局配合,
 * 该效果的实现是基于在Item的布局中通过设置PaddingLeft和PaddingRight来隐藏左右菜单的,
 * 所以使用此ListView时,请务必在布局Item时使用PaddingLeft和PaddingRight;
 * 或者自己改写此ListView,已达到想要的实现方式
 */
public class SlideListView extends ListView {

    /**下拉刷新view*/
    private SwipeRefreshLayout mSwipeLayout;
    /**禁止侧滑模式*/
    public static int MOD_FORBID = 0;
    /**从左向右滑出菜单模式*/
    public static int MOD_LEFT = 1;
    /**从右向左滑出菜单模式*/
    public static int MOD_RIGHT = 2;
    /**左右均可以滑出菜单模式*/
    public static int MOD_BOTH = 3;
    /**当前的模式*/
    private int mode = MOD_FORBID;
    /**左侧菜单的长度*/
    private int leftLength = 0;
    /**右侧菜单的长度*/
    private int rightLength = 0;

    /**
     * 当前滑动的ListView position
     */
    private int slidePosition;
    /**
     * 手指按下X的坐标
     */
    private int downY;
    /**
     * 手指按下Y的坐标
     */
    private int downX;
    /**
     * ListView的item
     */
    private View itemView;
    /**
     * 滑动类
     */
    private Scroller scroller;
    /**
     * 认为是用户滑动的最小距离
     */
    private int mTouchSlop;

    /**
     * 判断是否可以侧向滑动
     */
    private boolean canMove = false;
    /**
     * 标示是否完成侧滑
     */
    private boolean isSlided = false;
    

    public SlideListView(Context context) {
        this(context, null);
    }

    public SlideListView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SlideListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        scroller = new Scroller(context);
        mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
    }

    /**
     * 初始化菜单的滑出模式
     * @param mode
     */
    public void initSlideMode(int mode){
        this.mode = mode;
    }

    /**
     * 处理我们拖动ListView item的逻辑
     */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {

        final int action = ev.getAction();
        int lastX = (int) ev.getX();

        switch (action) {
            case MotionEvent.ACTION_DOWN:
			/*当前模式不允许滑动,则直接返回,交给ListView自身去处理*/
                if(this.mode == MOD_FORBID){
                    return super.onTouchEvent(ev);
                }

                // 如果处于侧滑完成状态,侧滑回去,并直接返回
                if (isSlided) {
                    scrollBack();
                    return false;
                }
                // 假如scroller滚动还没有结束,我们直接返回
                if (!scroller.isFinished()) {
                    return false;
                }
                downX = (int) ev.getX();
                downY = (int) ev.getY();

                slidePosition = pointToPosition(downX, downY);

                // 无效的position, 不做任何处理
                if (slidePosition == AdapterView.INVALID_POSITION) {
                    return super.onTouchEvent(ev);
                }

                // 获取我们点击的item view
                itemView = getChildAt(slidePosition - getFirstVisiblePosition());

			/*此处根据设置的滑动模式,自动获取左侧或右侧菜单的长度*/
                if(this.mode == MOD_BOTH){
                    this.leftLength = -itemView.getPaddingLeft();
                    this.rightLength = -itemView.getPaddingRight();
                }else if(this.mode == MOD_LEFT){
                    this.leftLength = -itemView.getPaddingLeft();
                }else if(this.mode == MOD_RIGHT){
                    this.rightLength = -itemView.getPaddingRight();
                }

                break;
            case MotionEvent.ACTION_MOVE:
                if (!canMove
                        && slidePosition != AdapterView.INVALID_POSITION
                        && (Math.abs(ev.getX() - downX) > mTouchSlop && Math.abs(ev
                        .getY() - downY) < mTouchSlop)) {
                    // 禁用下拉刷新控件,避免其拦截onTouch事件
                    mSwipeLayout.setEnabled(false);

                    int offsetX = downX - lastX;
                    if(offsetX > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)){
					/*从右向左滑*/
                        canMove = true;
                    }else if(offsetX < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)){
					/*从左向右滑*/
                        canMove = true;
                    }else{
                        canMove = false;
                    }
				/*此段代码是为了避免我们在侧向滑动时同时触发ListView的OnItemClickListener时间*/
                    MotionEvent cancelEvent = MotionEvent.obtain(ev);
                    cancelEvent
                            .setAction(MotionEvent.ACTION_CANCEL
                                    | (ev.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
                    onTouchEvent(cancelEvent);
                }
                if (canMove) {
				/*设置此属性,可以在侧向滑动时,保持ListView不会上下滚动*/
                    requestDisallowInterceptTouchEvent(true);

                    // 手指拖动itemView滚动, deltaX大于0向左滚动,小于0向右滚
                    int deltaX = downX - lastX;
                    if(deltaX < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)){
					/*向左滑*/
                        itemView.scrollTo(deltaX, 0);
                    }else if(deltaX > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)){
					/*向右滑*/
                        itemView.scrollTo(deltaX, 0);
                    }else{
                        itemView.scrollTo(0, 0);
                    }
                    return true; // 拖动的时候ListView不滚动
                }
            case MotionEvent.ACTION_UP:
                mSwipeLayout.setEnabled(true);
                //requestDisallowInterceptTouchEvent(false);
                if (canMove){
                    canMove = false;
                    scrollByDistanceX();
                }
                break;
        }

        // 否则直接交给ListView来处理onTouchEvent事件
        return super.onTouchEvent(ev);
    }

    /**
     * 根据手指滚动itemView的距离来判断是滚动到开始位置还是向左或者向右滚动
     */
    private void scrollByDistanceX() {
		/*当前模式不允许滑动,则直接返回*/
        if(this.mode == MOD_FORBID){
            return;
        }
        if(itemView.getScrollX() > 0 && (this.mode == MOD_BOTH || this.mode == MOD_RIGHT)){
			/*从右向左滑*/
            if (itemView.getScrollX() >= rightLength / 2) {
                scrollLeft();
            }  else {
                // 滚回到原始位置
                scrollBack();
            }
        }else if(itemView.getScrollX() < 0 && (this.mode == MOD_BOTH || this.mode == MOD_LEFT)){
			/*从左向右滑*/
            if (itemView.getScrollX() <= -leftLength / 2) {
                scrollRight();
            } else {
                // 滚回到原始位置
                scrollBack();
            }
        }else{
            // 滚回到原始位置
            scrollBack();
        }

    }

    /**
     * 往右滑动,getScrollX()返回的是左边缘的距离,就是以View左边缘为原点到开始滑动的距离,所以向右边滑动为负值
     */
    private void scrollRight() {
        isSlided = true;
        final int delta = (leftLength + itemView.getScrollX());
        // 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item
        scroller.startScroll(itemView.getScrollX(), 0, -delta, 0,
                Math.abs(delta));
        postInvalidate(); // 刷新itemView
    }

    /**
     * 向左滑动,根据上面我们知道向左滑动为正值
     */
    private void scrollLeft() {
        isSlided = true;
        final int delta = (rightLength - itemView.getScrollX());
        // 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item
        scroller.startScroll(itemView.getScrollX(), 0, delta, 0,
                Math.abs(delta));
        postInvalidate(); // 刷新itemView
    }

    /**
     * 滑动会原来的位置
     */
    private void scrollBack() {
        isSlided = false;
        scroller.startScroll(itemView.getScrollX(), 0, -itemView.getScrollX(),
                0, Math.abs(itemView.getScrollX()));
        postInvalidate(); // 刷新itemView
    }

    @Override
    public void computeScroll() {
        // 调用startScroll的时候scroller.computeScrollOffset()返回true,
        if (scroller.computeScrollOffset()) {
            // 让ListView item根据当前的滚动偏移量进行滚动
            itemView.scrollTo(scroller.getCurrX(), scroller.getCurrY());

            postInvalidate();
        }
    }

    /**
     * 提供给外部调用,用以将侧滑出来的滑回去
     */
    public void slideBack() {
        this.scrollBack();
    }

    /**
     * 外部传进下拉刷新view
     * by skypupil
     */
    public void setParentView(SwipeRefreshLayout swipeLayout){
        mSwipeLayout = swipeLayout;
    }

}

注意:上面代码中的“mSwipeLayout“关键词相关的代码是我后加进去的。没有这些代码,在侧滑ListView某个item时(这里是从右往左滑),若手指往下方滑动一点就很容易触发SwipeRefreshLayout,就是下图中上方的蓝色圆片,后果就是侧滑过程被阻断,item的侧滑终止,停在某个位置,显然不是我们想要的效果。

      

我们期望中的应该是item在侧滑的时候,SwipeRefreshLayout不被触发,侧滑完成后SwipeRefreshLayout又能被正常触发。怎么办呢?这时候代码中的”mSwipeLayout.setEnabled“就起作用了,思路是:在判断用户在item上发生有效的手指移动时,就失能SwipeRefreshLayout,当完成侧滑,手指抬起离开屏幕时,重新使能SwipeRefreshLayout。这样就成功地解决了SlideListView和SwipeRefreshLayout的冲突。

实际上,还有一个问题,在我的应用”moopoo“中我使用了Viewpager制作两个可以左右滑动的fragment(如上图中所示),而我的侧滑ListView是在第二个fragment(第二个page里),那么此时还会遇到Listview的向左侧滑事件被Viewpager拦截的问题。这时我是通过自定义一个Viewpager,重写onInterceptTouchEvent方法来实现的(大家可谷歌android事件的分发机制),具体原理看我的如下代码:

package com.moopoo.widget;

import android.content.Context;
import android.graphics.Color;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.TextView;

public class myViewPager extends ViewPager {

	private boolean page2 = false;
    private int downX;
	
	public myViewPager(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
	}

	public myViewPager(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
	}

    /*
    * 用以标志当前是否为第2个页面
    * 在viewpager的setOnPageChangeListener的onPageSelected方法中调用
    */
    public void setPageTag(){
        if(page2) page2 = false;
        else page2 = true;
    }
	
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		/*记录手指在屏幕上的移动方向,如果是从右往左滑则不拦截Touch事件,交由下级子View处理;
		相反,则正常处理从左往右滑事件,可以从第2个页面切换回第1个页面*/
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = (int)ev.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                // 判断是否是在第2个页面发生了从右往左滑的事件
                if (page2 && ev.getX() - downX < 0) {
                    return false;
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.onInterceptTouchEvent(ev);
	}

}

最后是在activity里对SlideListView和SwipeRefreshLayout的设置代码,如下:

    // Viewpager的第二页(第二个fragment)的onCreate()方法中的相关控件初始化代码
    listView = (SlideListView)getActivity().findViewById(R.id.listView_mp2);
    listView.initSlideMode(SlideListView.MOD_RIGHT);
    
    swipeLayout = (SwipeRefreshLayout)getActivity().findViewById(R.id.swipe_mp2);
    swipeLayout.setColorSchemeResources(color.white,
                R.color.yellow,
                R.color.red);
    swipeLayout.setProgressBackgroundColor(color.cyan);
    swipeLayout.setSize(SwipeRefreshLayout.LARGE);
    
    listView.setParentView(swipeLayout);

如果想要亲手感受下本文实现的侧滑效果,可以下载应用:moopoo,在”我的泡池“页里试试哦~ 


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值