高仿微信对话列表滑动删除效果(转)

前言

用过微信的都知道,微信对话列表滑动删除效果是很不错的,这个效果我们也可以有。思路其实很简单,弄个ListView,然后里面的每个item做成一个可以滑动的自定义控件即可。由于ListView是上下滑动而item是左右滑动,因此会有滑动冲突,也许你需要了解下android中点击事件的派发流程,请参考Android源码分析-点击事件派发机制。我的解决思路是这样的:重写ListView的onInterceptTouchEvent方法,在move的时候做判断,如果是左右滑动就返回false,否则返回true;重写SlideView(即自定义item控件)的onTouchEvent方法来处理滑动。整个思路没有问题,滑动冲突也解决了,可是ListView无法得到焦点了,也就是ListView无法处理点击事件了。让我们回想下问题出在哪里:我的理解是这样的,上述处理滑动本身没有问题,但是有一个副作用,就是会让外层View失去焦点且无法处理点击事件。常见的滑动冲突场景,比如launcher内部嵌入ListView却是没有问题的,因为这个时候launcher不需要获得焦点,需要获得焦点的是内部的ListView。因此,上述处理方式对于外部需要获得焦点的情况(比如外部是ListView)就不太适合了。于是我就和ttdevs探讨,发现他采用了另外一种思路,我从来没有想过还可以这样玩。下面介绍他的思路。

新的思路

不考虑那么复杂,不采用主流玩法,所有的事件均由外层的ListView做拦截,同时把事件传递给SlideView做滑动,这种实现的确可以达到效果,而且代码很简单,根本不需要处理什么复杂的滑动冲突。

效果

下面分别为微信和高仿效果

代码分析

先看SlideView是如何实现的

看layout xml:

[html] view plaincopy

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <merge xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent" >
  5. <LinearLayout
  6. android:id="@+id/view_content"
  7. android:layout_width="match_parent"
  8. android:layout_height="match_parent"
  9. android:orientation="horizontal" >
  10. </LinearLayout>
  11. <RelativeLayout
  12. android:id="@+id/holder"
  13. android:layout_width="120dp"
  14. android:layout_height="match_parent"
  15. android:clickable="true"
  16. android:background="@drawable/holder_bg">
  17. <TextView
  18. android:id="@+id/delete"
  19. android:layout_width="wrap_content"
  20. android:layout_height="wrap_content"
  21. android:drawableLeft="@drawable/del_icon_normal"
  22. android:layout_centerInParent="true"
  23. android:gravity="center"
  24. android:textColor="@color/floralwhite"
  25. android:text="删除" />
  26. </RelativeLayout>
  27. </merge>

上述xml文件中,所有的view都会被放在view_content中,而holder是放置诸如删除按钮之类的东西,我们的SlideView会加载这个布局。

再看SlideView.java:

[java] view plaincopy

  1. /**
  2. * SlideView 继承自LinearLayout
  3. */
  4. public class SlideView extends LinearLayout {
  5. private static final String TAG = "SlideView";
  6. private Context mContext;
  7. // 用来放置所有view的容器
  8. private LinearLayout mViewContent;
  9. // 用来放置内置view的容器,比如删除 按钮
  10. private RelativeLayout mHolder;
  11. // 弹性滑动对象,提供弹性滑动效果
  12. private Scroller mScroller;
  13. // 滑动回调接口,用来向上层通知滑动事件
  14. private OnSlideListener mOnSlideListener;
  15. // 内置容器的宽度 单位:dp
  16. private int mHolderWidth = 120;
  17. // 分别记录上次滑动的坐标
  18. private int mLastX = 0;
  19. private int mLastY = 0;
  20. // 用来控制滑动角度,仅当角度a满足如下条件才进行滑动:tan a = deltaX / deltaY > 2
  21. private static final int TAN = 2;
  22. public interface OnSlideListener {
  23. // SlideView的三种状态:开始滑动,打开,关闭
  24. public static final int SLIDE_STATUS_OFF = 0;
  25. public static final int SLIDE_STATUS_START_SCROLL = 1;
  26. public static final int SLIDE_STATUS_ON = 2;
  27. /**
  28. * @param view
  29. *            current SlideView
  30. * @param status
  31. *            SLIDE_STATUS_ON, SLIDE_STATUS_OFF or
  32. *            SLIDE_STATUS_START_SCROLL
  33. */
  34. public void onSlide(View view, int status);
  35. }
  36. public SlideView(Context context) {
  37. super(context);
  38. initView();
  39. }
  40. public SlideView(Context context, AttributeSet attrs) {
  41. super(context, attrs);
  42. initView();
  43. }
  44. private void initView() {
  45. mContext = getContext();
  46. // 初始化弹性滑动对象
  47. mScroller = new Scroller(mContext);
  48. // 设置其方向为横向
  49. setOrientation(LinearLayout.HORIZONTAL);
  50. // 将slide_view_merge加载进来
  51. View.inflate(mContext, R.layout.slide_view_merge, this);
  52. mViewContent = (LinearLayout) findViewById(R.id.view_content);
  53. mHolderWidth = Math.round(TypedValue.applyDimension(
  54. TypedValue.COMPLEX_UNIT_DIP, mHolderWidth, getResources()
  55. .getDisplayMetrics()));
  56. }
  57. // 设置按钮的内容,也可以设置图标啥的,我没写
  58. public void setButtonText(CharSequence text) {
  59. ((TextView) findViewById(R.id.delete)).setText(text);
  60. }
  61. // 将view加入到ViewContent中
  62. public void setContentView(View view) {
  63. mViewContent.addView(view);
  64. }
  65. // 设置滑动回调
  66. public void setOnSlideListener(OnSlideListener onSlideListener) {
  67. mOnSlideListener = onSlideListener;
  68. }
  69. // 将当前状态置为关闭
  70. public void shrink() {
  71. if (getScrollX() != 0) {
  72. this.smoothScrollTo(0, 0);
  73. }
  74. }
  75. // 根据MotionEvent来进行滑动,这个方法的作用相当于onTouchEvent
  76. // 如果你不需要处理滑动冲突,可以直接重命名,照样能正常工作
  77. public void onRequireTouchEvent(MotionEvent event) {
  78. int x = (int) event.getX();
  79. int y = (int) event.getY();
  80. int scrollX = getScrollX();
  81. Log.d(TAG, "x=" + x + "  y=" + y);
  82. switch (event.getAction()) {
  83. case MotionEvent.ACTION_DOWN: {
  84. if (!mScroller.isFinished()) {
  85. mScroller.abortAnimation();
  86. }
  87. if (mOnSlideListener != null) {
  88. mOnSlideListener.onSlide(this,
  89. OnSlideListener.SLIDE_STATUS_START_SCROLL);
  90. }
  91. break;
  92. }
  93. case MotionEvent.ACTION_MOVE: {
  94. int deltaX = x - mLastX;
  95. int deltaY = y - mLastY;
  96. if (Math.abs(deltaX) < Math.abs(deltaY) * TAN) {
  97. // 滑动不满足条件,不做横向滑动
  98. break;
  99. }
  100. // 计算滑动终点是否合法,防止滑动越界
  101. int newScrollX = scrollX - deltaX;
  102. if (deltaX != 0) {
  103. if (newScrollX < 0) {
  104. newScrollX = 0;
  105. } else if (newScrollX > mHolderWidth) {
  106. newScrollX = mHolderWidth;
  107. }
  108. this.scrollTo(newScrollX, 0);
  109. }
  110. break;
  111. }
  112. case MotionEvent.ACTION_UP: {
  113. int newScrollX = 0;
  114. // 这里做了下判断,当松开手的时候,会自动向两边滑动,具体向哪边滑,要看当前所处的位置
  115. if (scrollX - mHolderWidth * 0.75 > 0) {
  116. newScrollX = mHolderWidth;
  117. }
  118. // 慢慢滑向终点
  119. this.smoothScrollTo(newScrollX, 0);
  120. // 通知上层滑动事件
  121. if (mOnSlideListener != null) {
  122. mOnSlideListener.onSlide(this,
  123. newScrollX == 0 ? OnSlideListener.SLIDE_STATUS_OFF
  124. : OnSlideListener.SLIDE_STATUS_ON);
  125. }
  126. break;
  127. }
  128. default:
  129. break;
  130. }
  131. mLastX = x;
  132. mLastY = y;
  133. }
  134. private void smoothScrollTo(int destX, int destY) {
  135. // 缓慢滚动到指定位置
  136. int scrollX = getScrollX();
  137. int delta = destX - scrollX;
  138. // 以三倍时长滑向destX,效果就是慢慢滑动
  139. mScroller.startScroll(scrollX, 0, delta, 0, Math.abs(delta) * 3);
  140. invalidate();
  141. }
  142. @Override
  143. public void computeScroll() {
  144. if (mScroller.computeScrollOffset()) {
  145. scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
  146. postInvalidate();
  147. }
  148. }
  149. }

上述代码做了很详细的说明,这就是滑动控件的完整代码,大家要明白的是:你所添加的view都是加在SlideView的子View : view_content中的,而不是直接加在SlideView中,只有这样我们才方便做滑动效果。

接着看ListView的代码:核心就是下面这一个方法,将点击事件发送给SlideView处理。

[java] view plaincopy

  1. @Override
  2. public boolean onTouchEvent(MotionEvent event) {
  3. switch (event.getAction()) {
  4. case MotionEvent.ACTION_DOWN: {
  5. int x = (int) event.getX();
  6. int y = (int) event.getY();
  7. //我们想知道当前点击了哪一行
  8. int position = pointToPosition(x, y);
  9. Log.e(TAG, "postion=" + position);
  10. if (position != INVALID_POSITION) {
  11. //得到当前点击行的数据从而取出当前行的item。
  12. //可能有人怀疑,为什么要这么干?为什么不用getChildAt(position)?
  13. //因为ListView会进行缓存,如果你不这么干,有些行的view你是得不到的。
  14. MessageItem data = (MessageItem) getItemAtPosition(position);
  15. mFocusedItemView = data.slideView;
  16. Log.e(TAG, "FocusedItemView=" + mFocusedItemView);
  17. }
  18. }
  19. default:
  20. break;
  21. }
  22. //向当前点击的view发送滑动事件请求,其实就是向SlideView发请求
  23. if (mFocusedItemView != null) {
  24. mFocusedItemView.onRequireTouchEvent(event);
  25. }
  26. return super.onTouchEvent(event);
  27. }

转载请注明出处:http://blog.csdn.net/singwhatiwanna/article/details/17515543

最后看Activity的代码:

[java] view plaincopy

  1. public class MainActivity extends Activity implements OnItemClickListener,
  2. OnClickListener, OnSlideListener {
  3. private static final String TAG = "MainActivity";
  4. private ListViewCompat mListView;
  5. private List<MessageItem> mMessageItems = new ArrayList<MainActivity.MessageItem>();
  6. private SlideAdapter mSlideAdapter;
  7. // 上次处于打开状态的SlideView
  8. private SlideView mLastSlideViewWithStatusOn;
  9. @Override
  10. protected void onCreate(Bundle savedInstanceState) {
  11. super.onCreate(savedInstanceState);
  12. setContentView(R.layout.activity_main);
  13. initView();
  14. }
  15. private void initView() {
  16. mListView = (ListViewCompat) findViewById(R.id.list);
  17. for (int i = 0; i < 20; i++) {
  18. MessageItem item = new MessageItem();
  19. if (i % 3 == 0) {
  20. item.iconRes = R.drawable.default_qq_avatar;
  21. item.title = "腾讯新闻";
  22. item.msg = "青岛爆炸满月:大量鱼虾死亡";
  23. item.time = "晚上18:18";
  24. } else {
  25. item.iconRes = R.drawable.wechat_icon;
  26. item.title = "微信团队";
  27. item.msg = "欢迎你使用微信";
  28. item.time = "12月18日";
  29. }
  30. mMessageItems.add(item);
  31. }
  32. mSlideAdapter = new SlideAdapter();
  33. mListView.setAdapter(mSlideAdapter);
  34. mListView.setOnItemClickListener(this);
  35. }
  36. private class SlideAdapter extends BaseAdapter {
  37. private LayoutInflater mInflater;
  38. SlideAdapter() {
  39. super();
  40. mInflater = getLayoutInflater();
  41. }
  42. @Override
  43. public int getCount() {
  44. return mMessageItems.size();
  45. }
  46. @Override
  47. public Object getItem(int position) {
  48. return mMessageItems.get(position);
  49. }
  50. @Override
  51. public long getItemId(int position) {
  52. return position;
  53. }
  54. @Override
  55. public View getView(int position, View convertView, ViewGroup parent) {
  56. ViewHolder holder;
  57. SlideView slideView = (SlideView) convertView;
  58. if (slideView == null) {
  59. // 这里是我们的item
  60. View itemView = mInflater.inflate(R.layout.list_item, null);
  61. slideView = new SlideView(MainActivity.this);
  62. // 这里把item加入到slideView
  63. slideView.setContentView(itemView);
  64. // 下面是做一些数据缓存
  65. holder = new ViewHolder(slideView);
  66. slideView.setOnSlideListener(MainActivity.this);
  67. slideView.setTag(holder);
  68. } else {
  69. holder = (ViewHolder) slideView.getTag();
  70. }
  71. MessageItem item = mMessageItems.get(position);
  72. item.slideView = slideView;
  73. item.slideView.shrink();
  74. holder.icon.setImageResource(item.iconRes);
  75. holder.title.setText(item.title);
  76. holder.msg.setText(item.msg);
  77. holder.time.setText(item.time);
  78. holder.deleteHolder.setOnClickListener(MainActivity.this);
  79. return slideView;
  80. }
  81. }
  82. public class MessageItem {
  83. public int iconRes;
  84. public String title;
  85. public String msg;
  86. public String time;
  87. public SlideView slideView;
  88. }
  89. private static class ViewHolder {
  90. public ImageView icon;
  91. public TextView title;
  92. public TextView msg;
  93. public TextView time;
  94. public ViewGroup deleteHolder;
  95. ViewHolder(View view) {
  96. icon = (ImageView) view.findViewById(R.id.icon);
  97. title = (TextView) view.findViewById(R.id.title);
  98. msg = (TextView) view.findViewById(R.id.msg);
  99. time = (TextView) view.findViewById(R.id.time);
  100. deleteHolder = (ViewGroup) view.findViewById(R.id.holder);
  101. }
  102. }
  103. @Override
  104. public void onItemClick(AdapterView<?> parent, View view, int position,
  105. long id) {
  106. // 这里处理ListItem的点击事件
  107. Log.e(TAG, "onItemClick position=" + position);
  108. }
  109. @Override
  110. public void onSlide(View view, int status) {
  111. // 如果当前存在已经打开的SlideView,那么将其关闭
  112. if (mLastSlideViewWithStatusOn != null
  113. && mLastSlideViewWithStatusOn != view) {
  114. mLastSlideViewWithStatusOn.shrink();
  115. }
  116. // 记录本次处于打开状态的view
  117. if (status == SLIDE_STATUS_ON) {
  118. mLastSlideViewWithStatusOn = (SlideView) view;
  119. }
  120. }
  121. @Override
  122. public void onClick(View v) {
  123. // 这里处理删除按钮的点击事件,可以删除对话
  124. if (v.getId() == R.id.holder) {
  125. int position = mListView.getPositionForView(v);
  126. if (position != ListView.INVALID_POSITION) {
  127. mMessageItems.remove(position);
  128. mSlideAdapter.notifyDataSetChanged();
  129. }
  130. Log.e(TAG, "onClick v=" + v);
  131. }
  132. }
  133. }

代码我都特意写了注释,就不多说了。

代码下载:http://download.csdn.net/detail/singwhatiwanna/6760085

另外此博文采用了 ttdevs 所提供代码的部分思想(他的博客是http://blog.csdn.net/ttdevs)

时间: 2024-10-11 21:01:14

高仿微信对话列表滑动删除效果(转)的相关文章

关于高仿微信对话列表滑动删除效果代码优化

原文:http://blog.csdn.net/singwhatiwanna/article/details/17515543 最近公司项目需用到微信滑动拉出按钮的效果,发现一位牛人已经实现了相关效果,但控件仍与业务代码存有耦合,于是花了点时间做了些去耦合,并于此进行记录,以防遗忘. 个人认为耦合主要在于两点: 第一点是SlideListView中的onTouchEvent 通过获取item间接得到SlideView,但这样会引入外部数据类MessageItem.          @Overr

【转】高仿微信对话列表滑动删除效果--不错

原文网址:http://blog.csdn.net/singwhatiwanna/article/details/17515543 转载请注明出处:http://blog.csdn.net/singwhatiwanna/article/details/17515543 前言 用过微信的都知道,微信对话列表滑动删除效果是很不错的,这个效果我们也可以有.思路其实很简单,弄个ListView,然后里面的每个item做成一个可以滑动的自定义控件即可.由于ListView是上下滑动而item是左右滑动,因

高仿微信对话列表滑动删除效果

前言 用过微信的都知道.微信对话列表滑动删除效果是非常不错的,这个效果我们也能够有. 思路事实上非常easy,弄个ListView.然后里面的每一个item做成一个能够滑动的自己定义控件就可以.由于ListView是上下滑动而item是左右滑动,因此会有滑动冲突.或许你须要了解下android中点击事件的派发流程,请參考Android源代码分析-点击事件派发机制.我的解决思路是这种:重写ListView的onInterceptTouchEvent方法,在move的时候做推断,假设是左右滑动就返回

高仿 美团 向左滑动删除条目

1.效果图             2.功能实现 2.1 布局结构 <?xml version="1.0" encoding="UTF-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height=&quo

Android仿微信SlideView聊天列表滑动删除效果

package com.ryg.slideview; import com.ryg.slideview.MainActivity.MessageItem; //Download by http://www.okbase.net import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import andro

android高仿微信拍照、多选、预览、删除(去除相片)相册功能

先声明授人与鱼不如授人与渔,只能提供一个思路,当然需要源码的同学可以私下有偿问我要源码:QQ:508181017 工作了将近三年时间了,一直没正儿八经的研究系统自带的相册和拍照,这回来个高仿微信的拍照.多选.预览.删除(去除相片)相册功能,之前开发的所有应用都带有这需求,但是一直都不实用!废话就不多说了,先来捋一下思路: 1.拍照能实时保存到本地并实时查询(不必用广播或者服务) 2.拍照保存到到自定义路径并根据不同文件夹显示文件夹下的相片 3.多选规定张数图片 4.用到的集合有: (1).所有相

SwipeListView 详解 实现微信,QQ等滑动删除效果

Linux的shell编程 1.什么是shell? 当一个用户登录Linux系统之后,系统初始化程序init就为每一个用户运行一个称为shell(外壳)的程序. shell就是一个命令行解释器,它为用户提供了一个向Linux内核发送请求以便运行程序的界面系统级程序,用户可以用shell来启动.挂起.停止甚至是编写一些程序.一般的Linux系统都将bash作为默认的shell. 2.几种流行的shell 目前流行的shell有ash.bash.ksh.csh.zsh等,可以用下面的命令来查看she

Android-通过SlidingMenu高仿微信6.2最新版手势滑动返回(二)

转载请标明出处: http://blog.csdn.net/hanhailong726188/article/details/46453627 本文出自:[海龙的博客] 一.概述 在上一篇博文中,博文地址Android-通过SlidingPaneLayout高仿微信6.2最新版手势滑动返回(一),我们是通过官方自带的SlidingPaneLayout来实现的手势滑动返回.在这篇博文中,我们将採用SlidingMenu来高仿. 事实上实现的原理都一样.仅仅只是是把SlidingPaneLayout

Android高仿微信图片选择上传工具

源码托管地址:https://github.com/SleepyzzZ/photo-selector 话不多说,先上效果图(高仿微信图片选择器): 图片选择界面: 图片预览界面: 批量上传图片: 实现的功能介绍: 1.图片异步加载,使用Glide开源库实现加载; 2.图片的预览界面,支持左右滑动,双击放大浏览; 3.图片批量上传,使用OkHttp来实现与Servlet服务器的通信; 使用方法(Android Studio): 新建工程,File->New->Import Module导入pho