自定义View学习之12/5(侧滑删除实现,ViewDragHelper)

今天我们准备做侧滑删除的自定义视图,我采用了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代码(代码里有详细注释,这里就不再描述):

package com.wyw.slide;

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

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值