Android * 微信对话列表滑动删除效果

作者:singwhatiwanna 时间:2022-10-29 21:12:43 

前言 

用过微信的都知道,微信对话列表滑动删除效果是很不错的,这个效果我们也可以有。思路其实很简单,弄个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做滑动,这种实现的确可以达到效果,而且代码很简单,根本不需要处理什么复杂的滑动冲突。 

效果 

下面分别为微信和 * 效果 

Android * 微信对话列表滑动删除效果

代码分析 

先看SlideView是如何实现的 

看layout xml: 


<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent" >

<LinearLayout
   android:id="@+id/view_content"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="horizontal" >
 </LinearLayout>

<RelativeLayout
   android:id="@+id/holder"
   android:layout_width="120dp"
   android:layout_height="match_parent"
   android:clickable="true"
   android:background="@drawable/holder_bg">

<TextView
     android:id="@+id/delete"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:drawableLeft="@drawable/del_icon_normal"
     android:layout_centerInParent="true"
     android:gravity="center"
     android:textColor="@color/floralwhite"
     android:text="删除" />
 </RelativeLayout>

</merge>

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

再看SlideView.java:


/**
* SlideView 继承自LinearLayout
*/
public class SlideView extends LinearLayout {

private static final String TAG = "SlideView";

private Context mContext;

// 用来放置所有view的容器
 private LinearLayout mViewContent;

// 用来放置内置view的容器,比如删除 按钮
 private RelativeLayout mHolder;

// 弹性滑动对象,提供弹性滑动效果
 private Scroller mScroller;

// 滑动回调接口,用来向上层通知滑动事件
 private OnSlideListener mOnSlideListener;

// 内置容器的宽度 单位:dp
 private int mHolderWidth = 120;

// 分别记录上次滑动的坐标
 private int mLastX = 0;
 private int mLastY = 0;

// 用来控制滑动角度,仅当角度a满足如下条件才进行滑动:tan a = deltaX / deltaY > 2
 private static final int TAN = 2;

public interface OnSlideListener {
   // SlideView的三种状态:开始滑动,打开,关闭
   public static final int SLIDE_STATUS_OFF = 0;
   public static final int SLIDE_STATUS_START_SCROLL = 1;
   public static final int SLIDE_STATUS_ON = 2;

/**
    * @param view
    *      current SlideView
    * @param status
    *      SLIDE_STATUS_ON, SLIDE_STATUS_OFF or
    *      SLIDE_STATUS_START_SCROLL
    */
   public void onSlide(View view, int status);
 }

public SlideView(Context context) {
   super(context);
   initView();
 }

public SlideView(Context context, AttributeSet attrs) {
   super(context, attrs);
   initView();
 }

private void initView() {
   mContext = getContext();
   // 初始化弹性滑动对象
   mScroller = new Scroller(mContext);
   // 设置其方向为横向
   setOrientation(LinearLayout.HORIZONTAL);
   // 将slide_view_merge加载进来
   View.inflate(mContext, R.layout.slide_view_merge, this);
   mViewContent = (LinearLayout) findViewById(R.id.view_content);
   mHolderWidth = Math.round(TypedValue.applyDimension(
       TypedValue.COMPLEX_UNIT_DIP, mHolderWidth, getResources()
           .getDisplayMetrics()));
 }

// 设置按钮的内容,也可以设置图标啥的,我没写
 public void setButtonText(CharSequence text) {
   ((TextView) findViewById(R.id.delete)).setText(text);
 }

// 将view加入到ViewContent中
 public void setContentView(View view) {
   mViewContent.addView(view);
 }

// 设置滑动回调
 public void setOnSlideListener(OnSlideListener onSlideListener) {
   mOnSlideListener = onSlideListener;
 }

// 将当前状态置为关闭
 public void shrink() {
   if (getScrollX() != 0) {
     this.smoothScrollTo(0, 0);
   }
 }

// 根据MotionEvent来进行滑动,这个方法的作用相当于onTouchEvent
 // 如果你不需要处理滑动冲突,可以直接重命名,照样能正常工作
 public void onRequireTouchEvent(MotionEvent event) {
   int x = (int) event.getX();
   int y = (int) event.getY();
   int scrollX = getScrollX();
   Log.d(TAG, "x=" + x + " y=" + y);

switch (event.getAction()) {
   case MotionEvent.ACTION_DOWN: {
     if (!mScroller.isFinished()) {
       mScroller.abortAnimation();
     }
     if (mOnSlideListener != null) {
       mOnSlideListener.onSlide(this,
           OnSlideListener.SLIDE_STATUS_START_SCROLL);
     }
     break;
   }
   case MotionEvent.ACTION_MOVE: {
     int deltaX = x - mLastX;
     int deltaY = y - mLastY;
     if (Math.abs(deltaX) < Math.abs(deltaY) * TAN) {
       // 滑动不满足条件,不做横向滑动
       break;
     }

// 计算滑动终点是否合法,防止滑动越界
     int newScrollX = scrollX - deltaX;
     if (deltaX != 0) {
       if (newScrollX < 0) {
         newScrollX = 0;
       } else if (newScrollX > mHolderWidth) {
         newScrollX = mHolderWidth;
       }
       this.scrollTo(newScrollX, 0);
     }
     break;
   }
   case MotionEvent.ACTION_UP: {
     int newScrollX = 0;
     // 这里做了下判断,当松开手的时候,会自动向两边滑动,具体向哪边滑,要看当前所处的位置
     if (scrollX - mHolderWidth * 0.75 > 0) {
       newScrollX = mHolderWidth;
     }
     // 慢慢滑向终点
     this.smoothScrollTo(newScrollX, 0);
     // 通知上层滑动事件
     if (mOnSlideListener != null) {
       mOnSlideListener.onSlide(this,
           newScrollX == 0 ? OnSlideListener.SLIDE_STATUS_OFF
               : OnSlideListener.SLIDE_STATUS_ON);
     }
     break;
   }
   default:
     break;
   }

mLastX = x;
   mLastY = y;
 }

private void smoothScrollTo(int destX, int destY) {
   // 缓慢滚动到指定位置
   int scrollX = getScrollX();
   int delta = destX - scrollX;
   // 以三倍时长滑向destX,效果就是慢慢滑动
   mScroller.startScroll(scrollX, 0, delta, 0, Math.abs(delta) * 3);
   invalidate();
 }

@Override
 public void computeScroll() {
   if (mScroller.computeScrollOffset()) {
     scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
     postInvalidate();
   }
 }

}

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

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


 @Override
 public boolean onTouchEvent(MotionEvent event) {
   switch (event.getAction()) {
   case MotionEvent.ACTION_DOWN: {
     int x = (int) event.getX();
     int y = (int) event.getY();
     //我们想知道当前点击了哪一行
     int position = pointToPosition(x, y);
     Log.e(TAG, "postion=" + position);
     if (position != INVALID_POSITION) {
       //得到当前点击行的数据从而取出当前行的item。
       //可能有人怀疑,为什么要这么干?为什么不用getChildAt(position)?
       //因为ListView会进行缓存,如果你不这么干,有些行的view你是得不到的。
       MessageItem data = (MessageItem) getItemAtPosition(position);
       mFocusedItemView = data.slideView;
       Log.e(TAG, "FocusedItemView=" + mFocusedItemView);
     }
   }
   default:
     break;
   }

//向当前点击的view发送滑动事件请求,其实就是向SlideView发请求
   if (mFocusedItemView != null) {
     mFocusedItemView.onRequireTouchEvent(event);
   }

return super.onTouchEvent(event);
 }

最后看Activity的代码:


public class MainActivity extends Activity implements OnItemClickListener,
   OnClickListener, OnSlideListener {

private static final String TAG = "MainActivity";

private ListViewCompat mListView;

private List<MessageItem> mMessageItems = new ArrayList<MainActivity.MessageItem>();

private SlideAdapter mSlideAdapter;

// 上次处于打开状态的SlideView
 private SlideView mLastSlideViewWithStatusOn;

@Override
 protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);
   initView();
 }

private void initView() {
   mListView = (ListViewCompat) findViewById(R.id.list);

for (int i = 0; i < 20; i++) {
     MessageItem item = new MessageItem();
     if (i % 3 == 0) {
       item.iconRes = R.drawable.default_qq_avatar;
       item.title = "腾讯新闻";
       item.msg = "青岛 * 满月:大量鱼虾死亡";
       item.time = "晚上18:18";
     } else {
       item.iconRes = R.drawable.wechat_icon;
       item.title = "微信团队";
       item.msg = "欢迎你使用微信";
       item.time = "12月18日";
     }
     mMessageItems.add(item);
   }
   mSlideAdapter = new SlideAdapter();
   mListView.setAdapter(mSlideAdapter);
   mListView.setOnItemClickListener(this);
 }

private class SlideAdapter extends BaseAdapter {

private LayoutInflater mInflater;

SlideAdapter() {
     super();
     mInflater = getLayoutInflater();
   }

@Override
   public int getCount() {
     return mMessageItems.size();
   }

@Override
   public Object getItem(int position) {
     return mMessageItems.get(position);
   }

@Override
   public long getItemId(int position) {
     return position;
   }

@Override
   public View getView(int position, View convertView, ViewGroup parent) {
     ViewHolder holder;
     SlideView slideView = (SlideView) convertView;
     if (slideView == null) {
       // 这里是我们的item
       View itemView = mInflater.inflate(R.layout.list_item, null);

slideView = new SlideView(MainActivity.this);
       // 这里把item加入到slideView
       slideView.setContentView(itemView);
       // 下面是做一些数据缓存
       holder = new ViewHolder(slideView);
       slideView.setOnSlideListener(MainActivity.this);
       slideView.setTag(holder);
     } else {
       holder = (ViewHolder) slideView.getTag();
     }
     MessageItem item = mMessageItems.get(position);
     item.slideView = slideView;
     item.slideView.shrink();

holder.icon.setImageResource(item.iconRes);
     holder.title.setText(item.title);
     holder.msg.setText(item.msg);
     holder.time.setText(item.time);
     holder.deleteHolder.setOnClickListener(MainActivity.this);

return slideView;
   }

}

public class MessageItem {
   public int iconRes;
   public String title;
   public String msg;
   public String time;
   public SlideView slideView;
 }

private static class ViewHolder {
   public ImageView icon;
   public TextView title;
   public TextView msg;
   public TextView time;
   public ViewGroup deleteHolder;

ViewHolder(View view) {
     icon = (ImageView) view.findViewById(R.id.icon);
     title = (TextView) view.findViewById(R.id.title);
     msg = (TextView) view.findViewById(R.id.msg);
     time = (TextView) view.findViewById(R.id.time);
     deleteHolder = (ViewGroup) view.findViewById(R.id.holder);
   }
 }

@Override
 public void onItemClick(AdapterView<?> parent, View view, int position,
     long id) {
   // 这里处理ListItem的点击事件
   Log.e(TAG, "onItemClick position=" + position);
 }

@Override
 public void onSlide(View view, int status) {
   // 如果当前存在已经打开的SlideView,那么将其关闭
   if (mLastSlideViewWithStatusOn != null
       && mLastSlideViewWithStatusOn != view) {
     mLastSlideViewWithStatusOn.shrink();
   }
   // 记录本次处于打开状态的view
   if (status == SLIDE_STATUS_ON) {
     mLastSlideViewWithStatusOn = (SlideView) view;
   }
 }

@Override
 public void onClick(View v) {
   // 这里处理删除按钮的点击事件,可以删除对话
   if (v.getId() == R.id.holder) {
     int position = mListView.getPositionForView(v);
     if (position != ListView.INVALID_POSITION) {
       mMessageItems.remove(position);
       mSlideAdapter.notifyDataSetChanged();
     }
     Log.e(TAG, "onClick v=" + v);
   }
 }
}

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

代码下载:http://xiazai.jb51.net/201608/yuanma/androidSlide(jb51.net).rar

来源:http://blog.csdn.net/singwhatiwanna/article/details/17515543

标签:Android,微信,列表,滑动
0
投稿

猜你喜欢

  • 详解Android中实现ListView左右滑动删除条目的方法

    2021-08-15 03:38:53
  • java数据结构基础:栈

    2022-11-16 09:30:21
  • android 分辨率适配的方法

    2023-03-09 09:21:47
  • Java C++ 算法题解leetcode652寻找重复子树

    2022-08-17 23:58:09
  • 深入了解c# 迭代器和列举器

    2022-04-24 16:32:37
  • Android编程之SMS读取短信并保存到SQLite的方法

    2022-01-30 18:02:45
  • VisualStudio Community2019在安装的过程中无法进入安装界面的解决方法

    2023-02-09 11:24:34
  • 实例讲解Java的Spring框架中的控制反转和依赖注入

    2023-04-24 17:44:49
  • C#实现合并多张图片为GIF动态图

    2022-12-13 04:16:35
  • 深入理解C#之枚举

    2023-01-02 14:08:33
  • Spring Boot整合Redis的完整步骤

    2023-06-03 03:21:56
  • SpringBoot @SpringBootTest加速单元测试的小诀窍

    2022-12-17 13:05:06
  • JavaWeb 中Cookie实现记住密码的功能示例

    2023-04-06 05:06:48
  • Spring注解配置实现过程详解

    2022-04-06 11:12:52
  • 浅谈Java中的Filter过滤器

    2023-07-23 10:00:08
  • Spring Data Jpa的四种查询方式详解

    2021-10-10 10:35:26
  • C#实现微信跳一跳小游戏的自动跳跃助手开发实战

    2022-12-11 02:49:08
  • 详解Servlet3.0新特性(从注解配置到websocket编程)

    2023-08-08 14:29:48
  • C#开发WinForm根据条件改变DataGridView行颜色

    2022-05-06 05:38:56
  • Android网络数据开关用法简单示例

    2021-07-20 00:06:06
  • asp之家 软件编程 m.aspxhome.com