自定义view实现侧滑删除功能

来源:互联网 发布:政府的顶级域名 编辑:程序博客网 时间:2024/06/06 09:25

今天我们准备做侧滑删除的自定义视图,我采用了v4包里面ViewDragHelper。2013年谷歌i/o大会上介绍了两个新的layout: SlidingPaneLayout和DrawerLayout也是用的ViewDragHelper来处理拖动。

其实ViewDragHelper并不是第一个用于分析手势处理的类,gesturedetector也是,但是在和拖动相关的手势分析方面gesturedetector只能说是勉为其难。

关于ViewDragHelper有如下几点:

ViewDragHelper.Callback是连接ViewDragHelper与view之间的桥梁(这个view一般是指拥子view的容器即parentView);

ViewDragHelper的实例是通过静态工厂方法创建的;

你能够指定拖动的方向;

ViewDragHelper可以检测到是否触及到边缘;

ViewDragHelper并不是直接作用于要被拖动的View,而是使其控制的视图容器中的子View可以被拖动,如果要指定某个子view的行为,需要在Callback中想办法;

ViewDragHelper的本质其实是分析onInterceptTouchEvent和onTouchEvent的MotionEvent参数,然后根据分析的结果去改变一个容器中被拖动子View的位置( 通过offsetTopAndBottom(int offset)和offsetLeftAndRight(int offset)方法 ),他能在触摸的时候判断当前拖动的是哪个子View;

虽然ViewDragHelper的实例方法 ViewDragHelper create(ViewGroup forParent, Callback cb) 可以指定一个被ViewDragHelper处理拖动事件的对象 ,但ViewDragHelper类的设计决定了其适用于被包含在一个自定义ViewGroup之中,而不是对任意一个布局上的视图容器使用ViewDragHelper。

好,咱们先来看下我们要做的效果: 
这里写图片描述

这里开始贴MainActivity代码(代码里有详细注释,这里就不再描述):

import java.util.ArrayList;import android.app.Activity;import android.os.Bundle;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.AbsListView;import android.widget.AbsListView.OnScrollListener;import android.widget.BaseAdapter;import android.widget.LinearLayout;import android.widget.ListView;import android.widget.TextView;import android.widget.Toast;public class MainActivity extends Activity {    /**列表控件*/    private ListView listview;    /**列表数据集*/    private ArrayList<String> list = new ArrayList<String>();    /**当前滑动的下标*/    private int index=-1;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        listview = (ListView) findViewById(R.id.listview);        for (int i = 0; i < 30; i++) {            list.add(i+"");        }        listview.setAdapter(new listAdapter());        //listivew滑动监听        listview.setOnScrollListener(new OnScrollListener() {            @Override            public void onScrollStateChanged(AbsListView view, int scrollState) {                //滚动的时候,把侧滑还原                if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {                    if (index != -1) {                        if (listview.getChildAt(index - listview.getFirstVisiblePosition()) != null) {                            SwipeLayout swipeLayout = (SwipeLayout) listview.getChildAt(index - listview.getFirstVisiblePosition()).findViewById(R.id.swipelayout);                            //还原滑动                            swipeLayout.revert();                            index = -1;                        }                    }                }                           }            @Override            public void onScroll(AbsListView view, int firstVisibleItem,                    int visibleItemCount, int totalItemCount) {                // TODO Auto-generated method stub            }        });    }    //listview适配器    private class listAdapter extends BaseAdapter {        @Override        public int getCount() {            return list.size();        }        @Override        public Object getItem(int position) {            return list.get(position);        }        @Override        public long getItemId(int position) {            return position;        }        @Override        public View getView(final int position, View convertView, ViewGroup parent) {            final ViewHolder viewHolder;            if (convertView == null) {                viewHolder = new ViewHolder();                convertView  = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_main, null);                viewHolder.swipeLayout = (SwipeLayout) convertView.findViewById(R.id.swipelayout);                viewHolder.txt_content = (TextView) convertView.findViewById(R.id.text);                viewHolder.txt_delete = (TextView) convertView.findViewById(R.id.text_delete);                viewHolder.txts = (TextView) convertView.findViewById(R.id.texts);                viewHolder.linear = (LinearLayout) convertView.findViewById(R.id.linear);                convertView.setTag(viewHolder);            } else {                viewHolder = (ViewHolder) convertView.getTag();            }            viewHolder.linear.setVisibility(View.GONE);            //设置内容            viewHolder.txt_content.setText(list.get(position));             //删除按钮            viewHolder.txt_delete.setOnClickListener(new View.OnClickListener() {                @Override                public void onClick(View v) {                    list.remove(position);                    notifyDataSetChanged();                }            });            //已读按钮            viewHolder.txts.setOnClickListener(new View.OnClickListener() {                @Override                public void onClick(View v) {                    Toast.makeText(MainActivity.this, "已读", Toast.LENGTH_SHORT).show();                }            });            //设置自定义监听            viewHolder.swipeLayout.setOnSlide(new SwipeLayout.onSlideListener() {                //侧滑完了之后调用 true已经侧滑,false还未侧滑                @Override                public void onSlided(boolean isSlide) {                    if (isSlide) {//是否滑动成功(包括侧滑之后的返回滑动)                        if (index != -1) {                            //当第一个已经侧滑了,在侧滑第二个的时候,就把第一个还原                            if (listview.getChildAt(index - listview.getFirstVisiblePosition()) != null) {                                SwipeLayout swipeLayout = (SwipeLayout) listview.getChildAt(index - listview.getFirstVisiblePosition()).findViewById(R.id.swipelayout);                                swipeLayout.revert();                            }                        }                        index = position;                    }                }                //未侧滑状态下的默认显示整体的点击事件                @Override                public void onClick() {                    Toast.makeText(MainActivity.this, list.get(position), Toast.LENGTH_SHORT).show();                }            });            return convertView;        }        private class ViewHolder {            /** 滑动父控件 */            private SwipeLayout swipeLayout;            /** 内容按钮 */            private TextView txt_content;            /** 删除按钮 */            private TextView txt_delete;            /** 已读按钮 */            private TextView txts;            /** 右边试图*/            private LinearLayout linear;        }    }}



自定义侧滑view代码(代码里有详细注释):

package com.wyw.slide;import android.annotation.SuppressLint;import android.content.Context;import android.support.v4.view.ViewCompat;import android.support.v4.widget.ViewDragHelper;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import android.widget.LinearLayout;/** * 这个类本身是个layout,所以处理在他下面的包含的子控件 *  * Created by wyw */@SuppressLint("NewApi")public class SwipeLayout extends LinearLayout {    // 分析手势处理的类    private ViewDragHelper viewDragHelper;    //第一个view    private View contentView;    //第二个view    private View actionView;    private int dragDistance;    private final double AUTO_OPEN_SPEED_LIMIT = 400.0;    private int draggedX;    /**     * 滑动监听     */    private onSlideListener onSlide;    //按下的x    private float downX;    private float downY;    public SwipeLayout(Context context) {        this(context, null);    }    public SwipeLayout(Context context, AttributeSet attrs) {        this(context, attrs, -1);    }    public SwipeLayout(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        // 创建一个带有回调接口的ViewDragHelper        viewDragHelper = ViewDragHelper.create(this, new DragHelperCallback());    }    // 当View中所有的子控件 均被映射成xml后触发    @Override    protected void onFinishInflate() {        // 拿到第一个内容显示视图        contentView = getChildAt(0);        // 拿到第二个内容显示视图(即删除视图)        actionView = getChildAt(1);        // 默认不显示        actionView.setVisibility(GONE);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        dragDistance = actionView.getMeasuredWidth();    }    /**     * 还原     */    public void revert() {        if (viewDragHelper != null) {            viewDragHelper.smoothSlideViewTo(contentView, 0, 0);            invalidate();        }    }    /**     * 手势处理的监听实现     */    private class DragHelperCallback extends ViewDragHelper.Callback {        // tryCaptureView如何返回ture则表示可以捕获该view,你可以根据传入的第一个view参数决定哪些可以捕获        @Override        public boolean tryCaptureView(View view, int i) {            return view == contentView || view == actionView;        }        // 当captureview的位置发生改变时回调        @Override        public void onViewPositionChanged(View changedView, int left, int top,                int dx, int dy) {            //左边移动了多少            draggedX = left;            // 拦截父视图事件,不让父试图事件影响            getParent().requestDisallowInterceptTouchEvent(true);            if (changedView == contentView) {                actionView.offsetLeftAndRight(dx);            } else {                contentView.offsetLeftAndRight(dx);            }            if (actionView.getVisibility() == View.GONE) {                actionView.setVisibility(View.VISIBLE);            }            //刷新视图            invalidate();        }        /**         * clampViewPositionHorizontal,         * clampViewPositionVertical可以在该方法中对child移动的边界进行控制, left , top         * 分别为即将移动到的位置,比如横向的情况下,我希望只在ViewGroup的内部移动,即:最小>=paddingleft,         * 最大<=ViewGroup.getWidth()-paddingright-child.getWidth。就可以按照如下代码编写:         */        @Override        public int clampViewPositionHorizontal(View child, int left, int dx) {            if (child == contentView) {                final int leftBound = getPaddingLeft();                final int minLeftBound = -leftBound - dragDistance;                final int newLeft = Math.min(Math.max(minLeftBound, left), 0);                return newLeft;            } else {                final int minLeftBound = getPaddingLeft()                        + contentView.getMeasuredWidth() - dragDistance;                final int maxLeftBound = getPaddingLeft()                        + contentView.getMeasuredWidth() + getPaddingRight();                final int newLeft = Math.min(Math.max(left, minLeftBound),                        maxLeftBound);                return newLeft;            }        }        /**         * 原因是什么呢?主要是因为,如果子View不消耗事件,那么整个手势(DOWN-MOVE*-UP)         * 都是直接进入onTouchEvent,在onTouchEvent的DOWN的时候就确定了captureView。         * 如果消耗事件,那么就会先走onInterceptTouchEvent方法,判断是否可以捕获, 而在判断的过程中会去判断另外两个回调的方法:         * getViewHorizontalDragRange和getViewVerticalDragRange,         * 只有这两个方法返回大于0的值才能正常的捕获。所以, 如果你用Button测试,或者给TextView添加了clickable = true         * ,都要记得重写下面这两个方法:         */        @Override        public int getViewHorizontalDragRange(View child) {            return dragDistance;        }        // 手指释放的时候回调        @Override        public void onViewReleased(View releasedChild, float xvel, float yvel) {            super.onViewReleased(releasedChild, xvel, yvel);            boolean settleToOpen = false;            if (xvel > AUTO_OPEN_SPEED_LIMIT) {                settleToOpen = false;            } else if (xvel < -AUTO_OPEN_SPEED_LIMIT) {                settleToOpen = true;            } else if (draggedX <= -dragDistance / 2) {                settleToOpen = true;            } else if (draggedX > -dragDistance / 2) {                settleToOpen = false;            }            final int settleDestX = settleToOpen ? -dragDistance : 0;            if (onSlide != null) {                if (settleDestX == 0) {                    onSlide.onSlided(false);                } else {                    onSlide.onSlided(true);                }            }            viewDragHelper.smoothSlideViewTo(contentView, settleDestX, 0);            ViewCompat.postInvalidateOnAnimation(SwipeLayout.this);        }    }    public void setOnSlide(onSlideListener onSlide) {        this.onSlide = onSlide;    }    /**     * 由于整个视图都用了ViewDragHelper手势处理,     * 所以导致不滑动的视图点击事件不可用,所以需要自己处理点击事件     */    public interface onSlideListener {        /**         * 侧滑完了之后调用 true已经侧滑,false还未侧滑         */        void onSlided(boolean isSlide);        /**         * 未侧滑状态下的默认显示整体的点击事件         */        void onClick();    }    @Override    public boolean onInterceptTouchEvent(MotionEvent event) {        // 刚开始开启父视图事件.让onTouchEvent监听是移动还是点击        getParent().requestDisallowInterceptTouchEvent(false);        if (viewDragHelper.shouldInterceptTouchEvent(event)) {            return true;        }        return super.onInterceptTouchEvent(event);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        //记录按下的坐标        if (event.getAction() == MotionEvent.ACTION_DOWN) {            downX = event.getRawX();            downY = event.getRawY();        }        if (event.getAction() == MotionEvent.ACTION_UP) {            //x,y移动的距离小于10就出发点击事件            if (Math.abs(downX - event.getRawX()) < 10                    && Math.abs(downY - event.getRawY()) < 10) {                if (onSlide != null) {                    onSlide.onClick();                }            }        }//      处理拦截到的事件,这个方法会在返回前分发事件        viewDragHelper.processTouchEvent(event);//      表示消费了事件,不会再往下传递        return true;    }    @Override    public void computeScroll() {        super.computeScroll();        if (viewDragHelper.continueSettling(true)) {            /**             * 导致失效发生在接下来的动画时间步,通常下显示帧。 这个方法可以从外部的调用UI线程只有当这种观点是附加到一个窗口。             */            ViewCompat.postInvalidateOnAnimation(this);        }    }}


最后贴下布局文件:

<?xml version="1.0" encoding="utf-8"?><com.wyw.slide.SwipeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/swipelayout"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:gravity="center_vertical"    android:orientation="horizontal" >    <TextView        android:id="@+id/text"        android:layout_width="match_parent"        android:layout_height="50dp"        android:background="@android:color/white"        android:gravity="center"        android:textColor="@android:color/black" />    <LinearLayout        android:id="@+id/linear"        android:layout_width="160dp"        android:layout_height="50dp"        android:orientation="horizontal" >        <TextView            android:id="@+id/text_delete"            android:layout_width="0dp"            android:layout_weight="1"            android:layout_height="match_parent"            android:background="@android:color/black"            android:gravity="center"            android:text="删除"            android:textColor="@android:color/white" />        <TextView            android:id="@+id/texts"            android:layout_width="0dp"            android:layout_weight="1"            android:layout_height="match_parent"            android:background="@android:color/black"            android:gravity="center"            android:text="已读"            android:textColor="@android:color/white" />    </LinearLayout></com.wyw.slide.SwipeLayout>


本篇博客就到这里,如果对ViewDragHelper有兴趣的朋友可以去下下demo看看,研究下,会发现这个东西确实不错。

尊重原创转载请注明:(http://blog.csdn.net/u013895206) !

下面是地址传送门: 
http://download.csdn.net/detail/u013895206/9302511

0 0
原创粉丝点击