ListView滑动删除效果极简实现O(∩_∩)O哈哈~

无图无真相,上图。
这里写图片描述

图片太大了,截掉了点。
这里写图片描述
界面很简单,就两个view。

咳咳

首先,要想很容易的理解这部分的代码,应该熟悉android的消息派发机制(尤其是dispatchTouchEvent、onTouchEvent这两个方法),自顶向下树形分发,我转载的上一篇文章对此写的清楚明白,超级赞。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="li.slidelist.MainActivity">

    <li.slidelist.SlideListView
        android:id="@+id/listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></li.slidelist.SlideListView>

</LinearLayout>

主布局很简单,就是上面那样,SlideListView就是我写的listview。然后是listview的每个item的布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="60dp">


        <TextView
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:text="拉我"
            android:id="@+id/textView"
            android:gravity="center"
            android:layout_alignParentLeft="true" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="60dp"
            android:text="叫我?"
            android:id="@+id/button"
            android:layout_alignParentRight="true"
            android:visibility="gone"/>
</RelativeLayout>

通过visibility属性看得出来button是隐藏的。这里说明一下,布局不能套的很多,根布局下面最好直接放item要用的组件,如果用各种布局套来套去,会使树(整个xml文件就是一个树)变得复杂,这和下面的滑动实现有很大的关系。
看最重要的代码(关键地方的注释非常详细):

package li.slidelist;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListView;

/**
 * Created by Lee Y on 2016/5/19.
 */
public class SlideListView extends ListView{
    /*
       代表每个item的根布局
     */
    private ViewGroup itemView;
    /*
     代表被隐藏滑动要显示出来的view
     */
    private View appearView;
    /*
     代表需要滑动的view
     */
    private View slideView;
    /*
     downx 代表 手指刚按下的x坐标
     downy 代表 手指刚按下的y坐标
     坐标不懂得可以去百度下,很简单。
     currentPosition 代表 手指按住的是listview中的哪个item
     */
    private int downX, downY, currentPosition;
    /*
    代表屏幕宽度
     */
    private int srceenWidth;
    /*
    构造方法不解释,固定的
     */
    public SlideListView(Context context){
        this(context, null);
    }
    public SlideListView(Context context, AttributeSet attr){
        this(context, attr, 0);
    }
    public SlideListView(Context context, AttributeSet attr, int defStyle){
        super(context, attr, defStyle);
    }
    /*
    设置屏幕宽度
     */
    public void setSrceenWidth(int s){
        srceenWidth = s;
    }
    /*
    listview的消息分派方法(实际上是View的),返回true则消息分派到此结束(不会再往下寻找),消息由该listview来处理
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                downX = (int) ev.getX();
                downY = (int) ev.getY();
                currentPosition = pointToPosition(downX, downY);
                /*
                下面的三句非常重要,之前做过实验,通过自写Adapter然后将需要显示的view传过来以便响应滑动事件,可是发现穿过来的view(就是要出现的按钮)
                显示的位置是不对的(比如:滑动第三个item,应该在第三个item后面出现,可确实在第六个或第八个item后面出现,很奇葩),不光是这一个问题
                我想滑动的是布局里面的那个TextView而不是整个布局,如果不分别获得TextView和Button的对象引用,那么滑动将是整个item(这达不到图上的效果)
                因此我想到的办法,就是用viewgroup对象把textview和button对象索引出来,viewgroup对象就是下面的itemView(可以把它看成每个item的根布局)。
                这样问题也就随之而来了,如果item的布局过于复杂单靠根布局对象去寻找需要的view对象引用是很费力的。其实这也有解决办法,这里先不写了,
                注释有点长了=。=
                 */
                itemView = (ViewGroup) getChildAt(currentPosition - getFirstVisiblePosition());
                slideView = itemView.getChildAt(0);  // item中的textview
                appearView = itemView.getChildAt(1);  // item中被隐藏的button
                appearView.setOnClickListener(new OnClickListener() {
                    /*
                    button点击事件,让自己消失,并且使textview恢复原位
                     */
                    @Override
                    public void onClick(View v) {
                        v.setVisibility(View.GONE);
                        slideView.scrollTo(0,0);
                    }
                });
                break;
        }
        return super.dispatchTouchEvent(ev);
    }
    /*
    listview的触摸消息处理方法,返回true则此次触摸事件到此结束,这里注意此方法里面并没有处理listview纵向滑动的代码,
    如果返会true,listview的纵向滑动会失效,所以应该最后调用父方法
     */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                // item的位置是否合法
                if(currentPosition == AdapterView.INVALID_POSITION){
                    return super.onTouchEvent(ev);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                // 通过手指移动的x坐标 计算textview滑动的距离
                int x = (int)ev.getX();
                int moveX = downX - x;
                downX = x;
                slideView.scrollBy(moveX, 0);
                break;
            case MotionEvent.ACTION_UP:
                // 如果左滑超过屏幕的三分之一就显示button,右滑超过三分之一隐藏button,如果都不是怎么滑动都会恢复原位
                if(slideView.getScrollX() > srceenWidth/3){
                    appearView.setVisibility(View.VISIBLE);
                }else if(slideView.getScrollX() > -srceenWidth/3){
                    appearView.setVisibility(View.GONE);
                    slideView.scrollTo(0,0);
                }else{
                    slideView.scrollTo(0,0);
                }
                break;
        }
        return super.onTouchEvent(ev);
    }
}

代码并没有添加根据速度的大小显示按钮的代码,所以怎么快速滑动都不会出现按钮(除非满足上面的条件)。

package li.slidelist;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.Window;

public class MainActivity extends Activity{

    private SlideListView slideListView;
    private MyAdapter adapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);

        slideListView = (SlideListView) findViewById(R.id.listview);
        slideListView.setSrceenWidth(getWindowManager().getDefaultDisplay().getWidth());
        adapter = new MyAdapter(this, R.layout.item);
        slideListView.setAdapter(adapter);

    }
}

再附上主活动代码

package li.slidelist;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.TextView;

/**
 * Created by Lee Y on 2016/5/19.
 */
public class MyAdapter extends BaseAdapter{

    private Context context;
    private int rescourseID;
    private ViewHolder holder;
    public MyAdapter(Context c, int r){
        context = c;
        rescourseID = r;
    }
    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

    @Override
    public int getCount() {
        return 15;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        holder = null;
        if(convertView == null){
            holder = new ViewHolder();
            convertView = LayoutInflater.from(context).inflate(rescourseID, null);
            holder.v = (TextView) convertView.findViewById(R.id.textView);
            holder.b = (Button) convertView.findViewById(R.id.button);
            convertView.setTag(holder);
        }else{
            holder = (ViewHolder) convertView.getTag();
        }
        return convertView;
    }

    private class ViewHolder{
        TextView v;
        Button b;
    }
}

好吧,还有适配器的代码,不过适配器用系统自带的就可以。
把速度代码添加上去也是很简单的,这里就不写了。
但是代码是有瑕疵的,主要是由于事件判断不严谨,纵向滑动时有可能横向滑动也会跟着动。解决办法也很容易, 把它们互相屏蔽就可以了。
总共三类事件,点击事件、纵向滑动事件、横向滑动事件,让他们彼此互不相见,办法很简单。首先,在滑动时屏蔽点击事件:

    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
            //只要按钮处于消失状态,就截断所有DOWN动作
                if(appearView.getVisibility() == View.GONE){
                    return true;
                }
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

只要重写onInterceptTouchEvent方法,并在DOWN动作时截断事件分发就可以了,这样写岂不是不是把ListView的单击事件弄没了?其实不会的,ListView的单击事件是响应在ListView的层次上,不会下发。
然后把纵向滑动和横向滑动分开,这只需置一个判断值:

    private boolean isHorizontal = false;
    // 横向滑动时 置为true

接着在分发MOVE动作的时候确定isHorizontal的值:

public boolean dispatchTouchEvent(MotionEvent ev) {
                case MotionEvent.ACTION_MOVE:
                    int x = (int) ev.getRawX() - local[0];
                    int y = (int) ev.getRawY() - local[1];
                    // 下面是新添加的代码,判断是横向滑动还是纵向滑动
                    if(Math.abs(downX - x) > minDistance && Math.abs(downY - y) < minDistance){
                        isHorizontal = true;
                    }
                    break;
}

最后在执行动作的时候做出判断:

    public boolean onTouchEvent(MotionEvent ev) {

        if(isHorizontal) {  // 在这里做出判断
            switch (ev.getAction()) {
                case MotionEvent.ACTION_MOVE:
                    Log.e("SlideList", "MOVE");
                    int x = (int) ev.getX();
                    int moveX = downX - x;
                    downX = x;
                    if(moveX > 0 || appearView.getVisibility() == VISIBLE){
                        slideView.scrollBy(moveX, 0);
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    Log.e("SlideList", "UP");
                    if (slideView.getScrollX() > srceenWidth / 3 ) {
                        appearView.setVisibility(View.VISIBLE);
                    } else if (slideView.getScrollX() > -srceenWidth / 3 ) {
                        appearView.setVisibility(View.GONE);
                        slideView.scrollTo(0, 0);
                    } else {
                        slideView.scrollTo(0, 0);
                    }
                    isHorizontal = false;
                    break;
            }
            return true;  // 直接返回true 不去触发纵向滑动
        }
        return super.onTouchEvent(ev);
    }

这样所有冲突就都被解决了。

猜你喜欢

转载自blog.csdn.net/nvnnv/article/details/51456354
o