一直感觉QQ最近联系人那个侧滑删除功能挺高大上的,经过几经波折,终于在新的一年里实现了该功能。实现这个功能真是费了老劲了,好几次有了想法,兴奋的去写代码实现,结果让代码打了自己一个耳光,最终还是用margin的方式实现了这种效果,好吧, 先上效果!

看完效果,就来说一下思路吧:

1、item的左右滑动效果我是用的magin实现的。

2、虽然item布局的时候文本TextView的宽度设置的是match_parent,但在点下去的时候就将这个值设置为了固定值:屏幕的宽度

3、通过提供一个方法来处理滑动和外部itemClick的冲突

主要代码:

public class QQListView extends ListView {private int mScreenWidth;  // 屏幕宽度private int mDownX;          // 按下点的x值private int mDownY;            // 按下点的y值private int mDeleteBtnWidth;// 删除按钮的宽度private boolean isDeleteShown;   // 删除按钮是否正在显示private ViewGroup mPointChild; // 当前处理的itemprivate LinearLayout.LayoutParams mLayoutParams;    // 当前处理的item的LayoutParamspublic QQListView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public QQListView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);// 获取屏幕宽度WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);DisplayMetrics dm = new DisplayMetrics();wm.getDefaultDisplay().getMetrics(dm);mScreenWidth = dm.widthPixels;}@Overridepublic boolean onTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:performActionDown(ev);break;case MotionEvent.ACTION_MOVE:return performActionMove(ev);case MotionEvent.ACTION_UP:performActionUp();break;}return super.onTouchEvent(ev);}// 处理action_down事件private void performActionDown(MotionEvent ev) {if(isDeleteShown) {turnToNormal();}mDownX = (int) ev.getX();mDownY = (int) ev.getY();// 获取当前点的itemmPointChild = (ViewGroup) getChildAt(pointToPosition(mDownX, mDownY)- getFirstVisiblePosition());// 获取删除按钮的宽度mDeleteBtnWidth = mPointChild.getChildAt(1).getLayoutParams().width;mLayoutParams = (LinearLayout.LayoutParams) mPointChild.getChildAt(0).getLayoutParams();// 为什么要重新设置layout_width 等于屏幕宽度// 因为match_parent时,不管你怎么滑,都不会显示删除按钮// why? 因为match_parent时,ViewGroup就不去布局剩下的viewmLayoutParams.width = mScreenWidth;mPointChild.getChildAt(0).setLayoutParams(mLayoutParams);}// 处理action_move事件private boolean performActionMove(MotionEvent ev) {int nowX = (int) ev.getX();int nowY = (int) ev.getY();if(Math.abs(nowX - mDownX) > Math.abs(nowY - mDownY)) {// 如果向左滑动if(nowX < mDownX) {// 计算要偏移的距离int scroll = (nowX - mDownX) / 2;// 如果大于了删除按钮的宽度, 则最大为删除按钮的宽度if(-scroll >= mDeleteBtnWidth) {scroll = -mDeleteBtnWidth;}// 重新设置leftMarginmLayoutParams.leftMargin = scroll;mPointChild.getChildAt(0).setLayoutParams(mLayoutParams);}return true;}return super.onTouchEvent(ev);}// 处理action_up事件private void performActionUp() {// 偏移量大于button的一半,则显示button// 否则恢复默认if(-mLayoutParams.leftMargin >= mDeleteBtnWidth / 2) {mLayoutParams.leftMargin = -mDeleteBtnWidth;isDeleteShown = true;}else {turnToNormal();}mPointChild.getChildAt(0).setLayoutParams(mLayoutParams);}/*** 变为正常状态*/public void turnToNormal() {mLayoutParams.leftMargin = 0;mPointChild.getChildAt(0).setLayoutParams(mLayoutParams);isDeleteShown = false;}/*** 当前是否可点击* @return 是否可点击*/public boolean canClick() {return !isDeleteShown;}
}

很显然, 肯定要选择重写ListView来实现这种效果,并且重写onTouchEvent,通过判断move来达到侧滑效果。

先看看构造方法:

public QQListView(Context context, AttributeSet attrs, int defStyle) {  super(context, attrs, defStyle);  // 获取屏幕宽度  WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);  DisplayMetrics dm = new DisplayMetrics();  wm.getDefaultDisplay().getMetrics(dm);  mScreenWidth = dm.widthPixels;
}  

在构造方法中就干了一件事:获取屏幕的宽度, 为什么要获取屏幕宽度呢? 上面已经说过了,这里要改变一下item里的第一个TextView的layout_width为固定值。 match_parent不是很好吗? 为什么要多次一举重新设置为屏幕宽度呢?答案是:当前一个View的layout_width为match_parent时,ViewGroup就不去理会剩下的View了,也就是删除的那个按钮根本没有绘制出来!

下面的onTouchEvent中,分别在ACTION_DOWN、ACTION_MOVE、ACTION_UP三个case中调用了三个方法来处理这三个事件。

首先看看在DOWN的时候做了什么。

// 处理action_down事件
private void performActionDown(MotionEvent ev) {  if(isDeleteShown) {  turnToNormal();  }  mDownX = (int) ev.getX();  mDownY = (int) ev.getY();  // 获取当前点的item  mPointChild = (ViewGroup) getChildAt(pointToPosition(mDownX, mDownY)  - getFirstVisiblePosition());  // 获取删除按钮的宽度  mDeleteBtnWidth = mPointChild.getChildAt(1).getLayoutParams().width;  mLayoutParams = (LinearLayout.LayoutParams) mPointChild.getChildAt(0)  .getLayoutParams();  // 为什么要重新设置layout_width 等于屏幕宽度  // 因为match_parent时,不管你怎么滑,都不会显示删除按钮  // why? 因为match_parent时,ViewGroup就不去布局剩下的view  mLayoutParams.width = mScreenWidth;  mPointChild.getChildAt(0).setLayoutParams(mLayoutParams);
} 

3~5行,如果某一个item的deleteButton正在显示,则调用turnToNormal方法去恢复现场,turnToNormal方法其实很简单,稍候说明一下。

7~11行的任务就是确定当前按下的点所在哪个item上,并获取这个item。这里需要注意的pointToPosition方法获取的是当前点在所有item中第几个,而getChildAt()获取的是距离第一个可见项的第几个,所以要减去getFirstVisiblePosition()才能得到正确的item。

13行,获取了deleteButton的宽度,这个宽度是在下面判断deleteButton是否要全部显示或隐藏用的。

最主要的14~25行,重新设置第一个TextView的layout_width,至于为什么,上面已经说过了。

处理move事件要稍微麻烦点。

// 处理action_move事件
private boolean performActionMove(MotionEvent ev) {  int nowX = (int) ev.getX();  int nowY = (int) ev.getY();  if(Math.abs(nowX - mDownX) > Math.abs(nowY - mDownY)) {  // 如果向左滑动  if(nowX < mDownX) {  // 计算要偏移的距离  int scroll = (nowX - mDownX) / 2;  // 如果大于了删除按钮的宽度, 则最大为删除按钮的宽度  if(-scroll >= mDeleteBtnWidth) {  scroll = -mDeleteBtnWidth;  }  // 重新设置leftMargin  mLayoutParams.leftMargin = scroll;  mPointChild.getChildAt(0).setLayoutParams(mLayoutParams);  }  return true;  }  return super.onTouchEvent(ev);
}  

performActionMove是有返回值的,而且我们在onTouchEvent中return了它的返回值,有返回值的目的就是让横向滑动的时候在我们的onTouchEvent中消费了事件,竖屏滑动的时候交由ListView的onTouchEvent处理事件,从而避免屏蔽掉了ListView的上下滑动机制。

3~4行,首先获取当前手指所在的点。

然后第5行去判断x轴方向的位移是否大于y轴方向的位移,如果不大于,move事件直接交由super处理。

再来看看if内部,接着又是一个if, 这里主要是判断是不是向左滑动,如果向左滑动, 在第9行计算需要的偏移量,这里取的是手指偏移量的一半。

10~13行主要是为了防止滑动过界,右边出现空白的情况。

15~16行,就是改变第一个TextView的leftMargin的值,从而达到向左移动的效果。

处理up就简单多了。

// 处理action_up事件
private void performActionUp() {  // 偏移量大于button的一半,则显示button  // 否则恢复默认  if(-mLayoutParams.leftMargin >= mDeleteBtnWidth / 2) {  mLayoutParams.leftMargin = -mDeleteBtnWidth;  isDeleteShown = true;  }else {  turnToNormal();  }  mPointChild.getChildAt(0).setLayoutParams(mLayoutParams);
}  

主要就是通过判断view的leftMargin来确定deletebutton是要进入显示状态还是不显示状态。

很多地方都用到了turnToNormal这个方法,那我们就来看看这个自定义的方法。

public void turnToNormal() {  mLayoutParams.leftMargin = 0;  mPointChild.getChildAt(0).setLayoutParams(mLayoutParams);  isDeleteShown = false;
} 

这个自定义方法是一个public的,并不是我没有注意代码的封装性,而是这个方法在外部也会使用到, turnToNormal要做的事也很简单,就是“恢复现场”。

还一个简单的自定义方法,在外部需要itemClick的时候,通过该方法判断是否当前是否处于item可点击状态。

public boolean canClick() {  return !isDeleteShown;
}  

好啦, 接下来是应用环节了,看两个布局文件,一个是main布局,一个是ListView的item布局

<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=".MainActivity" >  <org.loader.qqlist.QQListView  android:id="@+id/list"  android:layout_width="wrap_content"  android:layout_height="wrap_content"  android:cacheColorHint="@android:color/transparent"  android:listSelector="@android:color/transparent"  android:divider="@android:color/darker_gray"  android:dividerHeight="2dp" />  </RelativeLayout>  

这个没什么好说的了, 就是引用了自定义的ListView。

来看看item的布局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  android:layout_width="match_parent"  android:layout_height="wrap_content"  android:orientation="horizontal" >  <TextView  android:id="@+id/tv"  android:layout_width="match_parent"  android:layout_height="wrap_content"  android:paddingBottom="20dp"  android:paddingLeft="10dp"  android:paddingTop="20dp"  android:background="@android:color/white"/>  <TextView  android:id="@+id/delete"  android:layout_width="80dp"  android:layout_height="match_parent"  android:background="#FFFF0000"  android:gravity="center"  android:paddingLeft="20dp"  android:textColor="@android:color/white"  android:paddingRight="20dp"  android:text="删除" />  </LinearLayout> 

哎? 不是说第一个TextView的layout_width要动态设置成固定值:屏幕的宽度吗? 这里应该wrap_content也可以吧? 答案是不可以! 回想一下,去设置固定值是在处理到该item的事件时才干的活,那你从没有点击的item呢? 肯定还是需要match_parent的。

最后看看Activity

public class MainActivity extends Activity {  private QQListView mListView;  private ArrayList<String> mData = new ArrayList<String>() {  {  for(int i=0;i<50;i++) {  add("hello world, hello android  " + i);  }  }  };  @Override  protected void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  setContentView(R.layout.activity_main);  mListView = (QQListView) findViewById(R.id.list);  mListView.setAdapter(new MyAdapter());  mListView.setOnItemClickListener(new OnItemClickListener() {  @Override  public void onItemClick(AdapterView<?> parent, View view,  int position, long id) {  if(mListView.canClick()) {  Toast.makeText(MainActivity.this, mData.get(position), Toast.LENGTH_SHORT).show();  }  }  });  }  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) {  if(null == convertView) {  convertView = View.inflate(MainActivity.this, R.layout.item, null);  }  TextView tv = (TextView) convertView.findViewById(R.id.tv);  TextView delete = (TextView) convertView.findViewById(R.id.delete);  tv.setText(mData.get(position));  final int pos = position;  delete.setOnClickListener(new OnClickListener() {  @Override  public void onClick(View v) {  mData.remove(pos);  notifyDataSetChanged();  mListView.turnToNormal();  }  });  return convertView;  }  }
}  

62行, 我们在deleteButton的onClick事件中调用了自定义方法turnToNormal,这样就保证了删除后,deleteButton不会继续存在下一个item上。

22~24行, 多了一个判断,通过canClick方法来判断当前item是否可点击。

最后是源码下载地址:http://git.oschina.net/qibin/horizontalScrollListView

仿QQ侧滑删除ListView——2015第一博相关推荐

  1. 仿QQ侧滑删除,Listview上下滑动,Listview的iteam的点击事件等bug的解决

         网上ListView横向滑动删除Item这样的介绍也很多,但实用性不强,没有解决横向滑动和item的点击事件的冲突,废话少说,有图有真相,下面直接上代码 1:侧滑 2:侧滑的点击事件 3:i ...

  2. 高仿 QQ 侧滑删除 Item 的效果

    <span style="font-size:24px;"><span style="white-space:pre"> </sp ...

  3. 仿QQ拖动删除未读消息个数气泡之二

    在仿QQ拖动删除未读消息个数气泡这篇文章中,模仿了QQ的删除未读消息气泡,不过也遗留了一个问题,当时为了让气泡能够在全屏范围内拖动,不能将其放在布局文件xml中,而是采用了在主布局加载完成后用addV ...

  4. Android学习之仿QQ侧滑功能的实现

    现在项目越来越多的应用了滑动删除的功能,Android本来遵循的是长按删除,IOS定制的是滑动删除,不可否认滑动删除确实在客户体验上要好一点,所以看了很多关于仿QQ滑动删除的例子,还是感觉代码家的An ...

  5. Android 仿QQ侧滑菜单

    前言 集成方式 兼容超强的BaseRecyclerViewAdapterHelper 方法及属性介绍 THANKS 侧滑的雏形 测绘布局 onLayout onMeasure MotionEvent事 ...

  6. Android仿QQ侧滑菜单

    先上效果图: GIF图有点模糊,源码已上传Github:Android仿QQ侧滑菜单 ####整体思路: 自定义ItemView的根布局(SwipeMenuLayout extends LinearL ...

  7. 名片夹android布局代码,Android自定义布局实现仿qq侧滑部分代码

    自定义布局实现仿qq侧滑部分Android代码,供大家参考,具体内容如下 实现说明: 通过自定义布局实现: SlidingLayout继承于 HorizontalScrollView /** * Cr ...

  8. Android高仿QQ侧滑菜单

    文章目录 效果图 整体思路 实现过程 先分析SwipeMenuLayout 再分析下SwipeRecycleView 踩过的坑 后记 效果图 GIF图有点模糊,源码已上传Github:Android仿 ...

  9. android仿qq布局,Android自定义布局实现仿qq侧滑部分代码

    自定义布局实现仿qq侧滑部分android代码,供大家参考,具体内容如下 实现说明: 通过自定义布局实现: slidinglayout继承于 horizontalscrollview /** * cr ...

最新文章

  1. vlookup 2张表 显示na_Vlookup函数的这7个应用技巧都不掌握,那就真的Out了
  2. UA MATH563 概率论的数学基础 中心极限定理12 强大数定律 版本2:Etemadi定理
  3. 神策数据荣获 36 氪 「2020 中国新经济之王」之「最具影响力企业」和「最具竞争力企业」双奖 !
  4. 运算符面试题(剑指offer,面试宝典,牛客网)
  5. 154. Find Minimum in Rotated Sorted Array II
  6. java useragent 360 遨游 火狐_各种浏览器UserAgent一览表(桌面+移动)
  7. c语言的四个函数,C语言学习之动态内存分配的四个函数
  8. VGG16关于学习率如何影响精度效应
  9. MongoDB学习笔记一:MongoDB的下载和安装
  10. java https请求 证书_java发https请求,证书配置
  11. qq空间显示手机型号android,qq说说显示手机型号 qq说说显示手机型号在哪里设置...
  12. AutoCAD实用功能之查看xdata扩展属性
  13. 什么是数据库?什么是数据库管理系统?
  14. Div中嵌套一个div,怎么是里面的div居中?
  15. 阿里巴巴高级技术专家李浩:千牛开放从云到端
  16. Problem - 1335E2 - Codeforces(暴力+优化)
  17. CSDN日报190318:简单爬虫:给女友定时发送睡前小故事
  18. 灵机一栋团队alpha冲刺 Ⅰ
  19. 【老鸟进阶】deepfacelab合成参数详解
  20. log4j漏洞,jndi侵入验证复现

热门文章

  1. iOS-OC-语音播放之切换听筒和扬声器的方法解决方案(红外感应)
  2. AIIC学习日记-十进制浮点数预处理为二进制定点数
  3. 百度或360搜索:地震姜常宏图片,求助?!!
  4. 三个案例告诉你微信营销成功要素有哪些转载
  5. C# MethodInvoker委托的使用
  6. 优秀的HDR渲染软件:Topaz Adjust AI for Mac
  7. 舵机内部结及工作原理浅析
  8. 5G手机的“高端”战事
  9. HDU 3129 The Brave Sir Robin’s cAsE cOrReCtOr(字符串处理)
  10. QT 加载外部CAN dll库