Android开发之仿QQ侧滑删除实现(二)
来源:互联网 发布:蜂群算法代码 编辑:程序博客网 时间:2024/06/05 14:12
一、把SlideDelete的简单样式先做出来。
SlideDelete继承自ViewGroup,在引用SlideDelete的xml的位置include进两个layout,一个是内容,一个是删除
一、1、准备两个布局
内容部分
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="60dp" android:background="#97d8da" android:gravity="center" android:orientation="vertical"> <TextView android:id="@+id/mTvContent" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="文本" android:gravity="center" android:textSize="28dp" /></LinearLayout>
删除部分
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="130dp" android:layout_height="60dp" android:background="#ff0000" android:gravity="center" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="删除" android:textSize="22dp" android:textColor="#ffffff" /></LinearLayout>
一.2、在activity_main里面把放进SlideDelete这个控件,然后include进上面两个布局
activity_main
<?xml version="1.0" encoding="utf-8"?><RelativeLayout 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="com.amqr.slidedelete.MainActivity"> <com.amqr.slidedelete.view.SlideDelete android:layout_width="match_parent" android:layout_height="60dp"> <!--文本部分--> <include layout="@layout/slide_content"/> <!--删除部分--> <include layout="@layout/slide_delete"/> </com.amqr.slidedelete.view.SlideDelete></RelativeLayout>
一.3、在自定义控件SlideDelete里面把利用onFinishInflate、onMeasure和onLayout让控件显示出来
public class SlideDelete extends ViewGroup{ private View mContent; // 内容部分 private View mDelete; // 删除部分 public SlideDelete(Context context) { super(context); } public SlideDelete(Context context, AttributeSet attrs) { super(context, attrs); } public SlideDelete(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onFinishInflate() { super.onFinishInflate(); mContent = getChildAt(0); mDelete = getChildAt(1); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 这跟mContent的父亲的大小有关,父亲是宽填充父窗体,高度是和孩子一样是60dp mContent.measure(widthMeasureSpec,heightMeasureSpec); // 测量内容部分的大小 LayoutParams layoutParams = mDelete.getLayoutParams(); int deleteWidth = MeasureSpec.makeMeasureSpec(layoutParams.width,MeasureSpec.EXACTLY); int deleteHeight = MeasureSpec.makeMeasureSpec(layoutParams.height,MeasureSpec.EXACTLY); // 这个参数就需要指定为精确大小 mDelete.measure(deleteWidth,deleteHeight); // 测量删除部分的大小 setMeasuredDimension(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int mContentWidth = mContent.getMeasuredWidth(); int mContentHeight = mContent.getMeasuredHeight(); mContent.layout(0,0,mContentWidth,mContentHeight); // 摆放内容部分的位置 int mDeleteWidth = mDelete.getMeasuredWidth(); mDelete.layout(mContentWidth,0, mContentWidth + mDeleteWidth, mContentHeight); // 摆放删除部分的位置 }}
至此,像下面这样右侧其实 删除部分 已经绘制显示在手机的右侧了,只是屏幕上现在暂时还看不见
在本文中,我们用ViewDragHelper来做动画效果。
ViewDragHelper简介
ViewDragHelper是v4包种提供的用来的做滑动的
ViewDragHelper是在android4.3的时候发布的,所以必须是android4.3以上的v4包才能支持ViewDragHelper
我们这里的Demo要使用ViewDragHelper,所以我们在当面as的项目的gradle里面引入v4包。
compile 'com.android.support:support-v4:23.1.1'
ViewDragHelper怎么用?
简单说,分两步
第一步,
利用ViewDragHelper.create(ViewGroup forParent, Callback cb)创建一个ViewDragHelper的实例。
第二步,弄一个类继承自ViewDragHelper.Callback,作为第一步中create方法的参数,复写一下这么几个方法并且一些逻辑操作
tryCaptureView()
clampViewPositionHorizontal()
clampViewPositionVertical()
onViewPositionChanged()
onViewReleased()
实现动画效果。
第二步中的复写的那几个方法很重要。这几个方法到底分别的有什么用呢?可以大概这么理解:
我们知道TouchEvent大概可以分为三个状态,Down(按下)、Move(移动)和Up(抬起)。
那么在这三个不同的状态里面,与之关联的就是上面的几个方法:
Touch的down事件:
回调tryCaptureView()Touch的move事件
回调
clampViewPositionHorizontal()
clampViewPositionVertical()
onViewPositionChanged()
- Touch的up事件
回调:onViewReleased()
来个代码看一下
继承自ViewDragHelper.Callback的类复写的几个方法。
class MyDrawHelper extends ViewDragHelper.Callback { /** * Touch的down事件会回调这个方法 tryCaptureView * * @Child:指定要动的孩子 (哪个孩子需要动起来) * @pointerId: 点的标记 * @return : ViewDragHelper是否继续分析处理 child的相关touch事件 */ @Override public boolean tryCaptureView(View child, int pointerId) { System.out.println("调用tryCaptureView"); System.out.println("contentView : " + (mContent == child)); return mContent == child || mDelete == child; } // Touch的move事件会回调这面这几个方法 // clampViewPositionHorizontal // clampViewPositionVertical // onViewPositionChanged /** * * 捕获了水平方向移动的位移数据 * @param child 移动的孩子View * @param left 父容器的左上角到孩子View的距离 * @param dx 增量值,其实就是移动的孩子View的左上角距离控件(父亲)的距离,包含正负 * @return 如何动 * * 调用完此方法,在android2.3以上就会动起来了,2.3以及以下是海动不了的 * 2.3不兼容怎么办?没事,我们复写onViewPositionChanged就是为了解决这个问题的 */ @Override public int clampViewPositionHorizontal(View child, int left, int dx) { Log.d("Slide","增量值: "+left); return left; } @Override public int clampViewPositionVertical(View child, int top, int dy) { return super.clampViewPositionVertical(child, top, dy); } /** * 当View的位置改变时的回调 * @param changedView 哪个View的位置改变了 * @param left changedView的left * @param top changedView的top * @param dx x方向的上的增量值 * @param dy y方向上的增量值 */ @Override public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { invalidate(); super.onViewPositionChanged(changedView, left, top, dx, dy); } /** * 相当于Touch的up的事件会回调onViewReleased这个方法 * * @param releasedChild * @param xvel x方向的速率 * @param yvel y方向的速率 */ @Override public void onViewReleased(View releasedChild, float xvel, float yvel) { super.onViewReleased(releasedChild, xvel, yvel); }}
通过上面的详细的备注,我们这些方法的用途都有了相当的了解了。需要注意的是,我们的clampViewPositionHorizontal和clampViewPositionHorizontal所产生的动画效果在2.3以上才会有效果,如果要达到兼容,我们就需要借助onViewPositionChanged方法。
好啦,现在可以开始做动画了。
二.1、当一个孩子动起来另外一个孩子也可以跟随着动起来
/** * 当View的位置改变时的回调 这个方法的价值是结合clampViewPositionHorizontal或者clampViewPositionVertical * @param changedView 哪个View的位置改变了 * @param left changedView的left * @param top changedView的top * @param dx x方向的上的增量值 * @param dy y方向上的增量值 */ @Override public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { //super.onViewPositionChanged(changedView, left, top, dx, dy); invalidate(); if(changedView == mContent){ // 如果移动的是mContent //我们移动mContent的实惠要相应的联动改变mDelete的位置 // 怎么改变mDelete的位置,当然是mDelete的layput方法啦 int tempDeleteLeft = mContentWidth+left; int tempDeleteRight = mContentWidth+left + mDeleteWidth; mDelete.layout(tempDeleteLeft,0,tempDeleteRight,mDeleteHeight); }else{ // touch的是mDelete int tempContentLeft = left - mContentWidth; int tempContentRight = left; mContent.layout(tempContentLeft,0,tempContentRight,mContentHeight); } }
孩子是一起跟着动起来了,出现越界的问题
二.2、解决越界问题
这个越界的问题为什么会产生,是因为现在在clampViewPositionHorizontal方法里面我们简单粗暴地返回了left。这样肯定是不行的。所以我们需要在这个方法上做一些处理
/** * * 捕获了水平方向移动的位移数据 * @param child 移动的孩子View * @param left 父容器的左上角到孩子View的距离 * @param dx 增量值,其实就是移动的孩子View的左上角距离控件(父亲)的距离,包含正负 * @return 如何动 * * 调用完此方法,在android2.3以上就会动起来了,2.3以及以下是海动不了的 * 2.3不兼容怎么办?没事,我们复写onViewPositionChanged就是为了解决这个问题的 */@Overridepublic int clampViewPositionHorizontal(View child, int left, int dx) { Log.d("Slide","增量值: "+left); if(child == mContent){ // 解决内容部分左右拖动的越界问题 if(left>0){ return 0; }else if(-left>mDeleteWidth){ return -mDeleteWidth; } } if(child == mDelete){ // 解决删除部分左右拖动的越界问题 if(left<mContentWidth - mDeleteWidth){ return mContentWidth - mDeleteWidth; }else if(left > mContentWidth){ return mContentWidth; } } return left;}
二、3、释放时位置的归正
/** * 相当于Touch的up的事件会回调onViewReleased这个方法 * * @param releasedChild * @param xvel x方向的速率 * @param yvel y方向的速率 */@Overridepublic void onViewReleased(View releasedChild, float xvel, float yvel) { //super.onViewReleased(releasedChild, xvel, yvel); // 方法的参数里面没有left,那么我们就采用 getLeft()这个方法 int mConLeft = mContent.getLeft(); // 这里没必要分来两个孩子判断 if(-mConLeft>mDeleteWidth/2){ mContent.layout(-mDeleteWidth,0,mContentWidth-mDeleteWidth,mContentHeight); mDelete.layout(mContentWidth-mDeleteWidth,0,mContentWidth,mDeleteHeight); }else{ mContent.layout(0,0,mContentWidth,mContentHeight); mDelete.layout(mContentWidth,0,mContentWidth+mDeleteWidth,mDeleteHeight); } super.onViewReleased(releasedChild, xvel, yvel);}
现在效果是实现了,但是松开手指的一瞬间位置归正得有点突兀,我们需要做一些过渡动画,才显得自然。
二、4、位置归正的过渡动画
ViewDragHelper里面给我们提供了一个方法,smoothSlideViewTo(View child, int finalLeft, int finalTo), smooth是平滑的意思,这个方法就是帮助我们做平滑滑动的。
smoothSlideViewTo(View child, int finalLeft, int finalTop)
public boolean smoothSlideViewTo(View child, int finalLeft, int finalTop) { mCapturedView = child; mActivePointerId = INVALID_POINTER; boolean continueSliding = forceSettleCapturedViewAt(finalLeft, finalTop, 0, 0); if (!continueSliding && mDragState == STATE_IDLE && mCapturedView != null) { // If we're in an IDLE state to begin with and aren't moving anywhere, we // end up having a non-null capturedView with an IDLE dragState mCapturedView = null; } return continueSliding;}
三个参数
child,那个孩子动
finalLeft + finalTop: 孩子运动到最后的左上角的点
通过孩子最后左上角的点就可以确定最后的应该到达的位置
上代码
@Override public void onViewReleased(View releasedChild, float xvel, float yvel) { //super.onViewReleased(releasedChild, xvel, yvel); // 方法的参数里面没有left,那么我们就采用 getLeft()这个方法 int mConLeft = mContent.getLeft(); // 这里没必要分来两个孩子判断 if(-mConLeft>mDeleteWidth/2){ //mContent.layout(-mDeleteWidth,0,mContentWidth-mDeleteWidth,mContentHeight); //mDelete.layout(mContentWidth-mDeleteWidth,0,mContentWidth,mDeleteHeight); //采用ViewDragHelper的 smoothSlideViewTo 方法让移动变得顺滑自然,不会太生硬 //smoothSlideViewTo只是模拟了数据,但是不会真正的动起来,动起来需要调用 invalidate // 而 invalidate 通过调用draw()等方法之后最后还是还是会调用 computeScroll 这个方法 // 所以,使用 smoothSlideViewTo 做过渡动画需要结合 invalidate方法 和 computeScroll方法 // smoothSlideViewTo的动画执行时间没有暴露的参数可以设置,但是这个时间是google给我们经过大量计算给出合理时间 viewDragHelper.smoothSlideViewTo(mContent,-mDeleteWidth,0); viewDragHelper.smoothSlideViewTo(mDelete,mContentWidth-mDeleteWidth,0); }else{ //mContent.layout(0,0,mContentWidth,mContentHeight); //mDelete.layout(mContentWidth, 0, mContentWidth + mDeleteWidth, mDeleteHeight); viewDragHelper.smoothSlideViewTo(mContent, 0, 0); viewDragHelper.smoothSlideViewTo(mDelete,mContentWidth, 0); } invalidate(); super.onViewReleased(releasedChild, xvel, yvel); }}@Overridepublic void computeScroll() { //super.computeScroll(); // 把捕获的View适当的时间移动,其实也可以理解为 smoothSlideViewTo 的模拟过程还没完成 if(viewDragHelper.continueSettling(true)){ invalidate(); } // 其实这个动画过渡的过程大概在怎么走呢? // 1、smoothSlideViewTo方法进行模拟数据,模拟后就就调用invalidate(); // 2、invalidate()最终调用computeScroll,computeScroll做一次细微动画, // computeScroll判断模拟数据是否彻底完成,还没完成会再次调用invalidate // 3、递归调用,知道数据noni完成。} }
到此为止,我们单个的侧滑删除的话的效果就实现了
三、在ListView里面嵌入我们的自定义控件
新建一个Activity,假设名为MyActivity,并且把这个Activity设置为启动页。
MyActivity
public class MyActivity extends Activity{ private ListView mLv; private ArrayList<String> mData; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my); mLv = (ListView) findViewById(R.id.mLv); mData=new ArrayList<>(); for(int i=0;i<200;i++){ mData.add("文本"+i); } mLv.setAdapter(new MyAdapter()); } 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) { ViewHolder viewHolder; if(convertView == null){ viewHolder = new ViewHolder(); convertView = View.inflate(MyActivity.this,R.layout.item,null); viewHolder.mSlideDelete = (SlideDelete) convertView.findViewById(R.id.mSlideDelete); viewHolder.mLlContent = (LinearLayout) convertView.findViewById(R.id.mLlContent); viewHolder.mLlDelete = (LinearLayout) convertView.findViewById(R.id.mLlDelete); convertView.setTag(viewHolder); }else{ viewHolder = (ViewHolder) convertView.getTag(); } viewHolder.mSlideDelete.setOnSlideDeleteListener(new SlideDelete.OnSlideDeleteListener() { @Override public void onOpen(SlideDelete slideDelete) { } @Override public void onClose(SlideDelete slideDelete) { } }); return convertView; } } class ViewHolder{ SlideDelete mSlideDelete; LinearLayout mLlContent; LinearLayout mLlDelete; }}
关联的activity_my
<?xml version="1.0" encoding="utf-8"?><RelativeLayout 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="com.amqr.slidedelete.MainActivity"> <ListView android:id="@+id/mLv" android:layout_width="match_parent" android:layout_height="match_parent"></ListView></RelativeLayout>
adapter里面的item
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <com.amqr.slidedelete.view.SlideDelete android:id="@+id/mSlideDelete" android:layout_width="match_parent" android:layout_height="60dp"> <!--文本部分--> <include layout="@layout/slide_content"/> <!--删除部分--> <include layout="@layout/slide_delete"/> </com.amqr.slidedelete.view.SlideDelete></LinearLayout>
当前效果
简单的效果是实现了,但是这样不好
实际使用中,我们通常是只能一个item处于打开状态,
即A打开,B必须处于关闭,不能A和B都处于打开的状态
怎么办呢。
只让一个item处于打开状态
整理思路:
1、我们给SlideDelete添加接口和回调,接口里面有onOpen(SlideDelete slideDelete)和onClose(SlideDelete slideDelete)两个方法
// SlideDlete的接口 public interface OnSlideDeleteListener { void onOpen(SlideDelete slideDelete); void onClose(SlideDelete slideDelete); }
2、暴露一个setOnSlideDeleteListener方法给外部调用,把SlideDelete的onViewReleased里面的打开和关闭抽取暴露出来,通过参数boolean决定是否显示delete部分。
然后在onViewReleased里面,真正调用接口的onOpen和onClose方法
private OnSlideDeleteListener onSlideDeleteListener; public void setOnSlideDeleteListener(OnSlideDeleteListener onSlideDeleteListener){ this.onSlideDeleteListener = onSlideDeleteListener; }
/** * 相当于Touch的up的事件会回调onViewReleased这个方法 * * @param releasedChild * @param xvel x方向的速率 * @param yvel y方向的速率 */ @Override public void onViewReleased(View releasedChild, float xvel, float yvel) { //super.onViewReleased(releasedChild, xvel, yvel); // 方法的参数里面没有left,那么我们就采用 getLeft()这个方法 int mConLeft = mContent.getLeft(); // 这里没必要分来两个孩子判断 if(-mConLeft>mDeleteWidth/2){ // mDelete展示起来 isShowDelete(true); if(onSlideDeleteListener != null){ onSlideDeleteListener.onOpen(SlideDelete.this); // 调用接口打开的方法 } }else{ // mDetele隐藏起来 isShowDelete(false); if(onSlideDeleteListener != null){ onSlideDeleteListener.onClose(SlideDelete.this); // 调用接口的关闭的方法 } } super.onViewReleased(releasedChild, xvel, yvel); }
3、在MyActivity的Adapter里面调用SlideDelete暴露出来的实现接口的方法。
弄一个集合记录起来已经打开的item,每次getView的执行都先关闭已经打开的item
@Overridepublic View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; if(convertView == null){ viewHolder = new ViewHolder(); convertView = View.inflate(MyActivity.this,R.layout.item,null); viewHolder.mSlideDelete = (SlideDelete) convertView.findViewById(R.id.mSlideDelete); viewHolder.mTvContent = (TextView) convertView.findViewById(R.id.mTvContent); viewHolder.mLlDelete = (LinearLayout) convertView.findViewById(R.id.mLlDelete); convertView.setTag(viewHolder); }else{ viewHolder = (ViewHolder) convertView.getTag(); } viewHolder.mTvContent.setText(mData.get(position)); viewHolder.mSlideDelete.setOnSlideDeleteListener(new SlideDelete.OnSlideDeleteListener() { @Override public void onOpen(SlideDelete slideDelete) { closeOtherItem(); slideDeleteArrayList.add(slideDelete); Log.d("Slide", "slideDeleteArrayList当前数量:" + slideDeleteArrayList.size()); } @Override public void onClose(SlideDelete slideDelete) { slideDeleteArrayList.remove(slideDelete); Log.d("Slide", "slideDeleteArrayList当前数量:" + slideDeleteArrayList.size()); } }); return convertView;}
关闭所有已经打开的item的方法
private void closeOtherItem(){ // 采用Iterator的原因是for是线程不安全的,迭代器是线程安全的 ListIterator<SlideDelete> slideDeleteListIterator = slideDeleteArrayList.listIterator(); while(slideDeleteListIterator.hasNext()){ SlideDelete slideDelete = slideDeleteListIterator.next(); slideDelete.isShowDelete(false); } slideDeleteArrayList.clear();}
至此完成。
附上当前完整的SlideDelete的代码
public class SlideDelete extends ViewGroup{ private View mContent; // 内容部分 private View mDelete; // 删除部分 private ViewDragHelper viewDragHelper; private int mContentWidth; private int mContentHeight; private int mDeleteWidth; private int mDeleteHeight; public SlideDelete(Context context) { super(context); } public SlideDelete(Context context, AttributeSet attrs) { super(context, attrs); } public SlideDelete(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } private OnSlideDeleteListener onSlideDeleteListener; public void setOnSlideDeleteListener(OnSlideDeleteListener onSlideDeleteListener){ this.onSlideDeleteListener = onSlideDeleteListener; } @Override protected void onFinishInflate() { super.onFinishInflate(); mContent = getChildAt(0); mDelete = getChildAt(1); //public static ViewDragHelper create(ViewGroup forParent, Callback cb) viewDragHelper = ViewDragHelper.create(this,new MyDrawHelper()); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 这跟mContent的父亲的大小有关,父亲是宽填充父窗体,高度是和孩子一样是60dp mContent.measure(widthMeasureSpec,heightMeasureSpec); // 测量内容部分的大小 LayoutParams layoutParams = mDelete.getLayoutParams(); int deleteWidth = MeasureSpec.makeMeasureSpec(layoutParams.width, MeasureSpec.EXACTLY); int deleteHeight = MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY); // 这个参数就需要指定为精确大小 mDelete.measure(deleteWidth, deleteHeight); // 测量删除部分的大小 setMeasuredDimension(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { mContentWidth = mContent.getMeasuredWidth(); mContentHeight = mContent.getMeasuredHeight(); mContent.layout(0, 0, mContentWidth, mContentHeight); // 摆放内容部分的位置 mDeleteWidth = mDelete.getMeasuredWidth(); mDeleteHeight = mDelete.getMeasuredHeight(); mDelete.layout(mContentWidth, 0, mContentWidth + mDeleteWidth, mContentHeight); // 摆放删除部分的位置 } class MyDrawHelper extends ViewDragHelper.Callback { /** * Touch的down事件会回调这个方法 tryCaptureView * * @Child:指定要动的孩子 (哪个孩子需要动起来) * @pointerId: 点的标记 * @return : ViewDragHelper是否继续分析处理 child的相关touch事件 */ @Override public boolean tryCaptureView(View child, int pointerId) { System.out.println("调用tryCaptureView"); System.out.println("contentView : " + (mContent == child)); return mContent == child || mDelete == child; } // Touch的move事件会回调这面这几个方法 // clampViewPositionHorizontal // clampViewPositionVertical // onViewPositionChanged /** * * 捕获了水平方向移动的位移数据 * @param child 移动的孩子View * @param left 父容器的左上角到孩子View的距离 * @param dx 增量值,其实就是移动的孩子View的左上角距离控件(父亲)的距离,包含正负 * @return 如何动 * * 调用完此方法,在android2.3以上就会动起来了,2.3以及以下是海动不了的 * 2.3不兼容怎么办?没事,我们复写onViewPositionChanged就是为了解决这个问题的 */ @Override public int clampViewPositionHorizontal(View child, int left, int dx) { //Log.d("Slide", "增量值: " + left); if(child == mContent){ // 解决内容部分左右拖动的越界问题 if(left>0){ return 0; }else if(-left>mDeleteWidth){ return -mDeleteWidth; } } if(child == mDelete){ // 解决删除部分左右拖动的越界问题 if(left<mContentWidth - mDeleteWidth){ return mContentWidth - mDeleteWidth; }else if(left > mContentWidth){ return mContentWidth; } } return left; } @Override public int clampViewPositionVertical(View child, int top, int dy) { return super.clampViewPositionVertical(child, top, dy); } /** * 当View的位置改变时的回调 这个方法的价值是结合clampViewPositionHorizontal或者clampViewPositionVertical * @param changedView 哪个View的位置改变了 * @param left changedView的left * @param top changedView的top * @param dx x方向的上的增量值 * @param dy y方向上的增量值 */ @Override public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { //super.onViewPositionChanged(changedView, left, top, dx, dy); invalidate(); if(changedView == mContent){ // 如果移动的是mContent //我们移动mContent的实惠要相应的联动改变mDelete的位置 // 怎么改变mDelete的位置,当然是mDelete的layput方法啦 int tempDeleteLeft = mContentWidth+left; int tempDeleteRight = mContentWidth+left + mDeleteWidth; mDelete.layout(tempDeleteLeft,0,tempDeleteRight,mDeleteHeight); }else{ // touch的是mDelete int tempContentLeft = left - mContentWidth; int tempContentRight = left; mContent.layout(tempContentLeft,0,tempContentRight,mContentHeight); } } /** * 相当于Touch的up的事件会回调onViewReleased这个方法 * * @param releasedChild * @param xvel x方向的速率 * @param yvel y方向的速率 */ @Override public void onViewReleased(View releasedChild, float xvel, float yvel) { //super.onViewReleased(releasedChild, xvel, yvel); // 方法的参数里面没有left,那么我们就采用 getLeft()这个方法 int mConLeft = mContent.getLeft(); // 这里没必要分来两个孩子判断 if(-mConLeft>mDeleteWidth/2){ // mDelete展示起来 isShowDelete(true); if(onSlideDeleteListener != null){ onSlideDeleteListener.onOpen(SlideDelete.this); // 调用接口打开的方法 } }else{ // mDetele隐藏起来 isShowDelete(false); if(onSlideDeleteListener != null){ onSlideDeleteListener.onClose(SlideDelete.this); // 调用接口的关闭的方法 } } super.onViewReleased(releasedChild, xvel, yvel); } } /** * 是否展示delete部分 * @param isShowDelete */ public void isShowDelete(boolean isShowDelete){ if(isShowDelete){ //mContent.layout(-mDeleteWidth,0,mContentWidth-mDeleteWidth,mContentHeight); //mDelete.layout(mContentWidth-mDeleteWidth,0,mContentWidth,mDeleteHeight); //采用ViewDragHelper的 smoothSlideViewTo 方法让移动变得顺滑自然,不会太生硬 //smoothSlideViewTo只是模拟了数据,但是不会真正的动起来,动起来需要调用 invalidate // 而 invalidate 通过调用draw()等方法之后最后还是还是会调用 computeScroll 这个方法 // 所以,使用 smoothSlideViewTo 做过渡动画需要结合 invalidate方法 和 computeScroll方法 // smoothSlideViewTo的动画执行时间没有暴露的参数可以设置,但是这个时间是google给我们经过大量计算给出合理时间 viewDragHelper.smoothSlideViewTo(mContent,-mDeleteWidth,0); viewDragHelper.smoothSlideViewTo(mDelete,mContentWidth-mDeleteWidth,0); }else{ //mContent.layout(0,0,mContentWidth,mContentHeight); //mDelete.layout(mContentWidth, 0, mContentWidth + mDeleteWidth, mDeleteHeight); viewDragHelper.smoothSlideViewTo(mContent, 0, 0); viewDragHelper.smoothSlideViewTo(mDelete, mContentWidth, 0); } invalidate(); } @Override public void computeScroll() { //super.computeScroll(); // 把捕获的View适当的时间移动,其实也可以理解为 smoothSlideViewTo 的模拟过程还没完成 if(viewDragHelper.continueSettling(true)){ invalidate(); } // 其实这个动画过渡的过程大概在怎么走呢? // 1、smoothSlideViewTo方法进行模拟数据,模拟后就就调用invalidate(); // 2、invalidate()最终调用computeScroll,computeScroll做一次细微动画, // computeScroll判断模拟数据是否彻底完成,还没完成会再次调用invalidate // 3、递归调用,知道数据noni完成。 } @Override public boolean onTouchEvent(MotionEvent event) { //return super.onTouchEvent(event); /**Process a touch event received by the parent view. This method will dispatch callback events as needed before returning. The parent view's onTouchEvent implementation should call this. */ viewDragHelper.processTouchEvent(event); // 使用ViewDragHelper必须复写onTouchEvent并调用这个方法 return true; //消费这个touch } // SlideDlete的接口 public interface OnSlideDeleteListener { void onOpen(SlideDelete slideDelete); void onClose(SlideDelete slideDelete); }}
.
.
附上当前完整的MyActivity代码
public class MyActivity extends Activity{ private ListView mLv; private ArrayList<String> mData; // 继续有多少个条目的delete被展示出来的集合 private List<SlideDelete> slideDeleteArrayList = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my); mLv = (ListView) findViewById(R.id.mLv); mData=new ArrayList<>(); for(int i=0;i<200;i++){ mData.add("文本"+i); } mLv.setAdapter(new MyAdapter()); } class MyAdapter extends BaseAdapter{ @Override public int getCount() { if(mData!=null){ return mData.size(); } return 0; } @Override public Object getItem(int position) { if(mData!=null){ return mData.get(position); } return null; } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; if(convertView == null){ viewHolder = new ViewHolder(); convertView = View.inflate(MyActivity.this,R.layout.item,null); viewHolder.mSlideDelete = (SlideDelete) convertView.findViewById(R.id.mSlideDelete); viewHolder.mTvContent = (TextView) convertView.findViewById(R.id.mTvContent); viewHolder.mLlDelete = (LinearLayout) convertView.findViewById(R.id.mLlDelete); convertView.setTag(viewHolder); }else{ viewHolder = (ViewHolder) convertView.getTag(); } viewHolder.mTvContent.setText(mData.get(position)); viewHolder.mSlideDelete.setOnSlideDeleteListener(new SlideDelete.OnSlideDeleteListener() { @Override public void onOpen(SlideDelete slideDelete) { closeOtherItem(); slideDeleteArrayList.add(slideDelete); Log.d("Slide", "slideDeleteArrayList当前数量:" + slideDeleteArrayList.size()); } @Override public void onClose(SlideDelete slideDelete) { slideDeleteArrayList.remove(slideDelete); Log.d("Slide", "slideDeleteArrayList当前数量:" + slideDeleteArrayList.size()); } }); return convertView; } } class ViewHolder{ SlideDelete mSlideDelete; TextView mTvContent; LinearLayout mLlDelete; } private void closeOtherItem(){ // 采用Iterator的原因是for是线程不安全的,迭代器是线程安全的 ListIterator<SlideDelete> slideDeleteListIterator = slideDeleteArrayList.listIterator(); while(slideDeleteListIterator.hasNext()){ SlideDelete slideDelete = slideDeleteListIterator.next(); slideDelete.isShowDelete(false); } slideDeleteArrayList.clear(); }}
4、滑动屏幕就关闭打开的条目
完善一下,如果当前有item的删除部分是展开的,当这个情况下我们去滑动竖直方向滑动屏幕,那么删除部分就会被隐藏回去。
其实就是做一下ListView的滑动监听而已
mLv.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if(scrollState == SCROLL_STATE_FLING || scrollState == SCROLL_STATE_TOUCH_SCROLL){ closeOtherItem(); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { }});
5、删除按钮按下删除item
viewHolder.mLlDelete.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mData.remove(position); notifyDataSetChanged(); }});
最终效果
原文链接:http://www.jianshu.com/p/5cb27a2ce03d
- Android开发之仿QQ侧滑删除实现(二)
- Android开发之仿QQ侧滑删除实现方式(一)
- Android仿QQ侧滑删除实现
- Android开发之高仿QQ消息侧拉删除
- Android开发学习之仿手机QQ消息列表侧滑删除效果
- Android开发之仿QQ表情实现(上)
- Android开发之仿QQ表情实现(下)
- Android开发之滑动选择菜单(仿QQ滑动删除)
- 仿QQ左滑删除功能实现分析(二)-SwipeListView
- Android仿QQ侧滑删除(ViewDragHelper)
- Android学习之仿QQ侧滑功能的实现
- Android自定义View之仿QQ侧滑菜单实现
- Android仿QQ实现ListView滑动删除
- Android仿QQ实现ListView滑动删除
- android开发之仿QQ拖拽界面效果(侧滑面板)
- android开发之仿QQ拖拽界面效果(侧滑面板)
- 仿QQ侧滑删除
- 仿qq侧滑删除
- linphone-TunnelConfigImpl文件对应的JNI层文件分析
- (三)表的概念
- 旋转数组的最小数字
- 简单的贪心
- Sort(啊哈算法)
- Android开发之仿QQ侧滑删除实现(二)
- hbase 1.1.4增删查改demo
- cdoj 1355 郭大侠与“有何贵干?”
- Tomcat8.x 《设计模式》Facade设计模式的使用->getServletContext()
- NOIP 2006 - 提高组 金明的预算 动态规划(DP)
- linux kernel启动流程
- 总结oninput、onchange与onpropertychange事件的用法和区别
- 基于amoeba实现MySQL读写分离
- 翼发云赵裁:打造企业服务SaaS精品