仿QQ侧滑删除ListView

本篇文章是从网上寻找来的,因为最近应该会用,感觉这篇文章比较实用所以就自己存起来,已备不时之需,另若侵犯到原创的利益,请于本人留言,本人会立即删除。

一直感觉QQ最近联系人那个侧滑删除功能挺高大上的,经过几经波折,终于在新的一年里实现了该功能。实现这个功能真是费了老劲了,好几次有了想法,兴奋的去写代码实现,结果让代码打了自己一个耳光,最终还是用margin的方式实现了这种效果,好吧, 先上效果!!

这里写图片描述功能。具体请参考[Github][2].

看完效果,就来说一下思路吧:

1、item的左右滑动效果我是用的magin实现的。

2、虽然item布局的时候文本TextView的宽度设置的是match_parent,但在点下去的时候就将这个值设置为了固定值:屏幕的宽度

3、通过提供一个方法来处理滑动和外部itemClick的冲突

主要代码:
public class QQListView extends ListView {
private int mScreenWidth; // 屏幕宽度
private int mDownX; // 按下点的x值
private int mDownY; // 按下点的y值
private int mDeleteBtnWidth;// 删除按钮的宽度

private boolean isDeleteShown;  // 删除按钮是否正在显示

private ViewGroup mPointChild;  // 当前处理的item
private LinearLayout.LayoutParams mLayoutParams;    // 当前处理的item的LayoutParams

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

public QQListView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);

    // 获取屏幕宽度
    WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    DisplayMetrics dm = new DisplayMetrics();
    wm.getDefaultDisplay().getMetrics(dm);
    mScreenWidth = dm.widthPixels;
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
    case MotionEvent.ACTION_DOWN:
        performActionDown(ev);
        break;
    case MotionEvent.ACTION_MOVE:
        return performActionMove(ev);
    case MotionEvent.ACTION_UP:
        performActionUp();
        break;
    }

    return super.onTouchEvent(ev);
}

// 处理action_down事件
private void performActionDown(MotionEvent ev) {
    if(isDeleteShown) {
        turnToNormal();
    }

    mDownX = (int) ev.getX();
    mDownY = (int) ev.getY();
    // 获取当前点的item
    mPointChild = (ViewGroup) getChildAt(pointToPosition(mDownX, mDownY)
            - getFirstVisiblePosition());
    // 获取删除按钮的宽度
    mDeleteBtnWidth = mPointChild.getChildAt(1).getLayoutParams().width;
    mLayoutParams = (LinearLayout.LayoutParams) mPointChild.getChildAt(0)
            .getLayoutParams();
    // 为什么要重新设置layout_width 等于屏幕宽度
    // 因为match_parent时,不管你怎么滑,都不会显示删除按钮
    // why? 因为match_parent时,ViewGroup就不去布局剩下的view
    mLayoutParams.width = mScreenWidth;
    mPointChild.getChildAt(0).setLayoutParams(mLayoutParams);
}

// 处理action_move事件
private boolean performActionMove(MotionEvent ev) {
    int nowX = (int) ev.getX();
    int nowY = (int) ev.getY();
    if(Math.abs(nowX - mDownX) > Math.abs(nowY - mDownY)) {
        // 如果向左滑动
        if(nowX < mDownX) {
            // 计算要偏移的距离
            int scroll = (nowX - mDownX) / 2;
            // 如果大于了删除按钮的宽度, 则最大为删除按钮的宽度
            if(-scroll >= mDeleteBtnWidth) {
                scroll = -mDeleteBtnWidth;
            }
            // 重新设置leftMargin
            mLayoutParams.leftMargin = scroll;
            mPointChild.getChildAt(0).setLayoutParams(mLayoutParams);
        }

        return true;
    }
    return super.onTouchEvent(ev);
}

// 处理action_up事件
private void performActionUp() {
    // 偏移量大于button的一半,则显示button
    // 否则恢复默认
    if(-mLayoutParams.leftMargin >= mDeleteBtnWidth / 2) {
        mLayoutParams.leftMargin = -mDeleteBtnWidth;
        isDeleteShown = true;
    }else {
        turnToNormal();
    }

    mPointChild.getChildAt(0).setLayoutParams(mLayoutParams);
}

/**
 * 变为正常状态
 */
public void turnToNormal() {
    mLayoutParams.leftMargin = 0;
    mPointChild.getChildAt(0).setLayoutParams(mLayoutParams);
    isDeleteShown = false;
}

/**
 * 当前是否可点击
 * @return 是否可点击
 */
public boolean canClick() {
    return !isDeleteShown;
}

}

很显然, 肯定要选择重写ListView来实现这种效果,并且重写onTouchEvent,通过判断move来达到侧滑效果。

先看看构造方法:

public QQListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);

// 获取屏幕宽度  
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);  
DisplayMetrics dm = new DisplayMetrics();  
wm.getDefaultDisplay().getMetrics(dm);  
mScreenWidth = dm.widthPixels;  

}

在构造方法中就干了一件事:获取屏幕的宽度, 为什么要获取屏幕宽度呢? 上面已经说过了,这里要改变一下item里的第一个TextView的layout_width为固定值。 match_parent不是很好吗? 为什么要多次一举重新设置为屏幕宽度呢?答案是:当前一个View的layout_width为match_parent时,ViewGroup就不去理会剩下的View了,也就是删除的那个按钮根本没有绘制出来!

下面的onTouchEvent中,分别在ACTION_DOWN、ACTION_MOVE、ACTION_UP三个case中调用了三个方法来处理这三个事件。

首先看看在DOWN的时候做了什么。

// 处理action_down事件
private void performActionDown(MotionEvent ev) {
if(isDeleteShown) {
turnToNormal();
}

mDownX = (int) ev.getX();  
mDownY = (int) ev.getY();  
// 获取当前点的item  
mPointChild = (ViewGroup) getChildAt(pointToPosition(mDownX, mDownY)  
        - getFirstVisiblePosition());  
// 获取删除按钮的宽度  
mDeleteBtnWidth = mPointChild.getChildAt(1).getLayoutParams().width;  
mLayoutParams = (LinearLayout.LayoutParams) mPointChild.getChildAt(0)  
        .getLayoutParams();  
// 为什么要重新设置layout_width 等于屏幕宽度  
// 因为match_parent时,不管你怎么滑,都不会显示删除按钮  
// why? 因为match_parent时,ViewGroup就不去布局剩下的view  
mLayoutParams.width = mScreenWidth;  
mPointChild.getChildAt(0).setLayoutParams(mLayoutParams);  

}

3~5行,如果某一个item的deleteButton正在显示,则调用turnToNormal方法去恢复现场,turnToNormal方法其实很简单,稍候说明一下。

7~11行的任务就是确定当前按下的点所在哪个item上,并获取这个item。这里需要注意的pointToPosition方法获取的是当前点在所有item中第几个,而getChildAt()获取的是距离第一个可见项的第几个,所以要减去getFirstVisiblePosition()才能得到正确的item。

13行,获取了deleteButton的宽度,这个宽度是在下面判断deleteButton是否要全部显示或隐藏用的。

最主要的14~25行,重新设置第一个TextView的layout_width,至于为什么,上面已经说过了。

处理move事件要稍微麻烦点。

// 处理action_move事件
private boolean performActionMove(MotionEvent ev) {
int nowX = (int) ev.getX();
int nowY = (int) ev.getY();
if(Math.abs(nowX - mDownX) > Math.abs(nowY - mDownY)) {
// 如果向左滑动
if(nowX < mDownX) {
// 计算要偏移的距离
int scroll = (nowX - mDownX) / 2;
// 如果大于了删除按钮的宽度, 则最大为删除按钮的宽度
if(-scroll >= mDeleteBtnWidth) {
scroll = -mDeleteBtnWidth;
}
// 重新设置leftMargin
mLayoutParams.leftMargin = scroll;
mPointChild.getChildAt(0).setLayoutParams(mLayoutParams);
}
return true;
}
return super.onTouchEvent(ev);
}

performActionMove是有返回值的,而且我们在onTouchEvent中return了它的返回值,有返回值的目的就是让横向滑动的时候在我们的onTouchEvent中消费了事件,竖屏滑动的时候交由ListView的onTouchEvent处理事件,从而避免屏蔽掉了ListView的上下滑动机制。

3~4行,首先获取当前手指所在的点。

然后第5行去判断x轴方向的位移是否大于y轴方向的位移,如果不大于,move事件直接交由super处理。

再来看看if内部,接着又是一个if, 这里主要是判断是不是向左滑动,如果向左滑动, 在第9行计算需要的偏移量,这里取的是手指偏移量的一半。

10~13行主要是为了防止滑动过界,右边出现空白的情况。

15~16行,就是改变第一个TextView的leftMargin的值,从而达到向左移动的效果。

处理up就简单多了。

// 处理action_up事件
private void performActionUp() {
// 偏移量大于button的一半,则显示button
// 否则恢复默认
if(-mLayoutParams.leftMargin >= mDeleteBtnWidth / 2) {
mLayoutParams.leftMargin = -mDeleteBtnWidth;
isDeleteShown = true;
}else {
turnToNormal();
}

mPointChild.getChildAt(0).setLayoutParams(mLayoutParams);  

}

主要就是通过判断view的leftMargin来确定deletebutton是要进入显示状态还是不显示状态。

很多地方都用到了turnToNormal这个方法,那我们就来看看这个自定义的方法。

public void turnToNormal() {
mLayoutParams.leftMargin = 0;
mPointChild.getChildAt(0).setLayoutParams(mLayoutParams);
isDeleteShown = false;
}

这个自定义方法是一个public的,并不是我没有注意代码的封装性,而是这个方法在外部也会使用到, turnToNormal要做的事也很简单,就是“恢复现场”。

还一个简单的自定义方法,在外部需要itemClick的时候,通过该方法判断是否当前是否处于item可点击状态。

public boolean canClick() {
return !isDeleteShown;
}

好啦, 接下来是应用环节了,看两个布局文件,一个是main布局,一个是ListView的item布局

代码块

public class MainActivity extends Activity {
private QQListView mListView;
private ArrayList mData = new ArrayList() {
{
for(int i=0;i<50;i++) {
add(“hello world, hello android ” + i);
}
}
};

@Override  
protected void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
    setContentView(R.layout.activity_main);  

    mListView = (QQListView) findViewById(R.id.list);  
    mListView.setAdapter(new MyAdapter());  
    mListView.setOnItemClickListener(new OnItemClickListener() {  
        @Override  
        public void onItemClick(AdapterView<?> parent, View view,  
                int position, long id) {  
            if(mListView.canClick()) {  
                Toast.makeText(MainActivity.this, mData.get(position), Toast.LENGTH_SHORT).show();  
            }  
        }  
    });  
}  

class MyAdapter extends BaseAdapter {  

    @Override  
    public int getCount() {  
        return mData.size();  
    }  

    @Override  
    public Object getItem(int position) {  
        return mData.get(position);  
    }  

    @Override  
    public long getItemId(int position) {  
        return position;  
    }  

    @Override  
    public View getView(int position, View convertView, ViewGroup parent) {  
        if(null == convertView) {  
            convertView = View.inflate(MainActivity.this, R.layout.item, null);  
        }  
        TextView tv = (TextView) convertView.findViewById(R.id.tv);  
        TextView delete = (TextView) convertView.findViewById(R.id.delete);  

        tv.setText(mData.get(position));  

        final int pos = position;  
        delete.setOnClickListener(new OnClickListener() {  
            @Override  
            public void onClick(View v) {  
                mData.remove(pos);  
                notifyDataSetChanged();  
                mListView.turnToNormal();  
            }  
        });  

        return convertView;  
    }  
}  

}

62行, 我们在deleteButton的onClick事件中调用了自定义方法turnToNormal,这样就保证了删除后,deleteButton不会继续存在下一个item上。

22~24行, 多了一个判断,通过canClick方法来判断当前item是否可点击。

最后是源码下载地址:http://git.oschina.net/qibin/horizontalScrollListView

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值