zoukankan      html  css  js  c++  java
  • 【转】高仿微信对话列表滑动删除效果--不错

    原文网址:http://blog.csdn.net/singwhatiwanna/article/details/17515543

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

    前言

    用过微信的都知道,微信对话列表滑动删除效果是很不错的,这个效果我们也可以有。思路其实很简单,弄个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在CODE上查看代码片派生到我的代码片
     
    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.   
    6.     <LinearLayout  
    7.         android:id="@+id/view_content"  
    8.         android:layout_width="match_parent"  
    9.         android:layout_height="match_parent"  
    10.         android:orientation="horizontal" >  
    11.     </LinearLayout>  
    12.   
    13.     <RelativeLayout  
    14.         android:id="@+id/holder"  
    15.         android:layout_width="120dp"  
    16.         android:layout_height="match_parent"  
    17.         android:clickable="true"  
    18.         android:background="@drawable/holder_bg">  
    19.   
    20.         <TextView  
    21.             android:id="@+id/delete"  
    22.             android:layout_width="wrap_content"  
    23.             android:layout_height="wrap_content"  
    24.             android:drawableLeft="@drawable/del_icon_normal"  
    25.             android:layout_centerInParent="true"  
    26.             android:gravity="center"  
    27.             android:textColor="@color/floralwhite"  
    28.             android:text="删除" />  
    29.     </RelativeLayout>  
    30.   
    31. </merge>  

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

    再看SlideView.java:

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

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

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

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    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.   
    23.     //向当前点击的view发送滑动事件请求,其实就是向SlideView发请求  
    24.     if (mFocusedItemView != null) {  
    25.         mFocusedItemView.onRequireTouchEvent(event);  
    26.     }  
    27.   
    28.     return super.onTouchEvent(event);  
    29. }  

    最后看Activity的代码:

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

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

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

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

  • 相关阅读:
    POJ训练计划1035_Spell checker(串处理/暴力)
    软考-数据库与标准化和知识产权
    分析Cocos2d-x横版ACT手游源码 1、公共
    增强版的RecycleViewAdapter,能够直接使用
    高考志愿,你们想好怎么填了吗?
    win32收不到F10按键消息解决的方法
    Json——使用Json jar包实现Json字符串与Java对象或集合之间的互相转换
    九度OJ #1437 To Fill or Not to Fil
    SQL数据分组后取最大值或者取前几个值(依照某一列排序)
    3.5星|《硅谷产品》:Facebook网红社区产品经理经验谈
  • 原文地址:https://www.cnblogs.com/wi100sh/p/4486322.html
Copyright © 2011-2022 走看看