热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

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

这篇文章主要为大家详细介绍了Android高仿微信对话列表滑动删除效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

前言 

用过微信的都知道,微信对话列表滑动删除效果是很不错的,这个效果我们也可以有。思路其实很简单,弄个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: 

<&#63;xml version="1.0" encoding="utf-8"&#63;>


  
  

  

    
  


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

再看SlideView.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
/**
 * 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)" (newscrollx="" <0)="" else=""> 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 &#63; 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();
    }
  }
 
}
</math.abs(deltay)>

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@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的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
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<&#63;> 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);
    }
  }
}
</mainactivity.messageitem></messageitem>

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

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • Android 九宫格布局详解及实现:人人网应用示例
    本文深入探讨了人人网Android应用中独特的九宫格布局设计,解析其背后的GridView实现原理,并提供详细的代码示例。这种布局方式不仅美观大方,而且在现代Android应用中较为少见,值得开发者借鉴。 ... [详细]
  • 深入解析Android自定义View面试题
    本文探讨了Android Launcher开发中自定义View的重要性,并通过一道经典的面试题,帮助开发者更好地理解自定义View的实现细节。文章不仅涵盖了基础知识,还提供了实际操作建议。 ... [详细]
  • 国内BI工具迎战国际巨头Tableau,稳步崛起
    尽管商业智能(BI)工具在中国的普及程度尚不及国际市场,但近年来,随着本土企业的持续创新和市场推广,国内主流BI工具正逐渐崭露头角。面对国际品牌如Tableau的强大竞争,国内BI工具通过不断优化产品和技术,赢得了越来越多用户的认可。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 本文详细介绍了如何使用Spring Boot进行高效开发,涵盖了配置、实例化容器以及核心注解的使用方法。 ... [详细]
  • Android 渐变圆环加载控件实现
    本文介绍了如何在 Android 中创建一个自定义的渐变圆环加载控件,该控件已在多个知名应用中使用。我们将详细探讨其工作原理和实现方法。 ... [详细]
  • Android LED 数字字体的应用与实现
    本文介绍了一种适用于 Android 应用的 LED 数字字体(digital font),并详细描述了其在 UI 设计中的应用场景及其实现方法。这种字体常用于视频、广告倒计时等场景,能够增强视觉效果。 ... [详细]
  • RecyclerView初步学习(一)
    RecyclerView初步学习(一)ReCyclerView提供了一种插件式的编程模式,除了提供ViewHolder缓存模式,还可以自定义动画,分割符,布局样式,相比于传统的ListVi ... [详细]
  • 2023年京东Android面试真题解析与经验分享
    本文由一位拥有6年Android开发经验的工程师撰写,详细解析了京东面试中常见的技术问题。涵盖引用传递、Handler机制、ListView优化、多线程控制及ANR处理等核心知识点。 ... [详细]
  • 本文介绍如何在 Unity 的 XML 配置文件中,将参数传递给自定义生命周期管理器的构造函数。我们将详细探讨 CustomLifetimeManager 类的实现及其配置方法。 ... [详细]
  • SQLite 动态创建多个表的需求在网络上有不少讨论,但很少有详细的解决方案。本文将介绍如何在 Qt 环境中使用 QString 类轻松实现 SQLite 表的动态创建,并提供详细的步骤和示例代码。 ... [详细]
  • 解决JAX-WS动态客户端工厂弃用问题并迁移到XFire
    在处理Java项目中的JAR包冲突时,我们遇到了JaxWsDynamicClientFactory被弃用的问题,并成功将其迁移到org.codehaus.xfire.client。本文详细介绍了这一过程及解决方案。 ... [详细]
  • 探讨如何真正掌握Java EE,包括所需技能、工具和实践经验。资深软件教学总监李刚分享了对毕业生简历中常见问题的看法,并提供了详尽的标准。 ... [详细]
  • 本文介绍如何使用布局文件在Android应用中排列多行TextView和Button,使其占据屏幕的特定比例,并提供示例代码以帮助理解和实现。 ... [详细]
  • 本文介绍了Android开发中Intent的基本概念及其在不同Activity之间的数据传递方式,详细展示了如何通过Intent实现Activity间的跳转和数据传输。 ... [详细]
author-avatar
mobiledu2502912817
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有