自定义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
- 自定义view实现侧滑删除功能
- 自定义view实现侧滑删除效果
- 自定义View(仿QQ侧滑删除实现,ViewDragHelper)
- 自定义 View 实现秒表功能
- 自定义View实现倒计时功能
- 自定义view实现圆环功能
- android自定义View之自定义EditText(添加删除功能)
- Android自定义View之自定义EditText(添加删除功能)
- android自定义View之自定义EditText(添加删除功能)
- android自定义View之自定义侧滑删除效果
- 自定义View学习之12/5(侧滑删除实现,ViewDragHelper)
- 自定义view系列(5)--99.99%实现QQ侧滑删除效果
- 自定义view系列(5)--99.99%实现QQ侧滑删除效果
- 自定义view系列(5)--99.99%实现QQ侧滑删除效果
- 自定义View 实现 TextView 的功能:
- 自定义view,实现手势密码功能
- 自定义view实现涂鸦(画板)功能
- 自定义view实现涂鸦(画板)功能(二)
- UIKit框架各个类的简要说明
- 行百里者半九十,华为开发者大赛各参赛团队渐入佳境
- 协议简介之 应用层协议
- Matlab保存图片的几种方法
- Mouseenter/ mouseleave和mouseover/mouseout
- 自定义view实现侧滑删除功能
- C# winform使用log4net步骤
- Ubuntu 系统引导(grub)修复
- 今天所讲的内容以及已制作完成的游戏
- 【NOIP2014提高组T5】寻找道路-双spfa
- iphone修改手机铃声
- jquery bind
- 宽电压大电流单节锂电池充电管理方案
- mysql check table脚本