转载: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:
- <span class="tag"><?</span><span class="tag-name">xml</span> <span class="attribute">version</span>=<span class="attribute-value">“1.0”</span> <span class="attribute">encoding</span>=<span class="attribute-value">“utf-8”</span><span class="tag">?></span>
- <span class="tag"><</span><span class="tag-name">merge</span> <span class="attribute">xmlns:android</span>=<span class="attribute-value">“http://schemas.android.com/apk/res/android”</span>
- <span class="attribute">android:layout_width</span>=<span class="attribute-value">“match_parent”</span>
- <span class="attribute">android:layout_height</span>=<span class="attribute-value">“match_parent”</span> <span class="tag">></span>
-
- <span class="tag"><</span><span class="tag-name">LinearLayout</span>
- <span class="attribute">android:id</span>=<span class="attribute-value">“@+id/view_content”</span>
- <span class="attribute">android:layout_width</span>=<span class="attribute-value">“match_parent”</span>
- <span class="attribute">android:layout_height</span>=<span class="attribute-value">“match_parent”</span>
- <span class="attribute">android:orientation</span>=<span class="attribute-value">“horizontal”</span> <span class="tag">></span>
- <span class="tag"></</span><span class="tag-name">LinearLayout</span><span class="tag">></span>
-
- <span class="tag"><</span><span class="tag-name">RelativeLayout</span>
- <span class="attribute">android:id</span>=<span class="attribute-value">“@+id/holder”</span>
- <span class="attribute">android:layout_width</span>=<span class="attribute-value">“120dp”</span>
- <span class="attribute">android:layout_height</span>=<span class="attribute-value">“match_parent”</span>
- <span class="attribute">android:clickable</span>=<span class="attribute-value">“true”</span>
- <span class="attribute">android:background</span>=<span class="attribute-value">“@drawable/holder_bg”</span><span class="tag">></span>
-
- <span class="tag"><</span><span class="tag-name">TextView</span>
- <span class="attribute">android:id</span>=<span class="attribute-value">“@+id/delete”</span>
- <span class="attribute">android:layout_width</span>=<span class="attribute-value">“wrap_content”</span>
- <span class="attribute">android:layout_height</span>=<span class="attribute-value">“wrap_content”</span>
- <span class="attribute">android:drawableLeft</span>=<span class="attribute-value">“@drawable/del_icon_normal”</span>
- <span class="attribute">android:layout_centerInParent</span>=<span class="attribute-value">“true”</span>
- <span class="attribute">android:gravity</span>=<span class="attribute-value">“center”</span>
- <span class="attribute">android:textColor</span>=<span class="attribute-value">“@color/floralwhite”</span>
- <span class="attribute">android:text</span>=<span class="attribute-value">“删除”</span> <span class="tag">/></span>
- <span class="tag"></</span><span class="tag-name">RelativeLayout</span><span class="tag">></span>
-
- <span class="tag"></</span><span class="tag-name">merge</span><span class="tag">></span>
上述xml文件中,所有的view都会被放在view_content中,而holder是放置诸如删除按钮之类的东西,我们的SlideView会加载这个布局。
再看SlideView.java:
- <span class="comment">/**</span>
- <span class="comment"> * SlideView 继承自LinearLayout</span>
- <span class="comment"> */</span>
- <span class="keyword">public</span> <span class="keyword">class</span> SlideView <span class="keyword">extends</span> LinearLayout {
-
- <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String TAG = <span class="string">“SlideView”</span>;
-
- <span class="keyword">private</span> Context mContext;
-
- <span class="comment">// 用来放置所有view的容器</span>
- <span class="keyword">private</span> LinearLayout mViewContent;
-
- <span class="comment">// 用来放置内置view的容器,比如删除 按钮</span>
- <span class="keyword">private</span> RelativeLayout mHolder;
-
- <span class="comment">// 弹性滑动对象,提供弹性滑动效果</span>
- <span class="keyword">private</span> Scroller mScroller;
-
- <span class="comment">// 滑动回调接口,用来向上层通知滑动事件</span>
- <span class="keyword">private</span> OnSlideListener mOnSlideListener;
-
- <span class="comment">// 内置容器的宽度 单位:dp</span>
- <span class="keyword">private</span> <span class="keyword">int</span> mHolderWidth = <span class="number">120</span>;
-
- <span class="comment">// 分别记录上次滑动的坐标</span>
- <span class="keyword">private</span> <span class="keyword">int</span> mLastX = <span class="number"></span>;
- <span class="keyword">private</span> <span class="keyword">int</span> mLastY = <span class="number"></span>;
-
- <span class="comment">// 用来控制滑动角度,仅当角度a满足如下条件才进行滑动:tan a = deltaX / deltaY > 2</span>
- <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> TAN = <span class="number">2</span>;
-
- <span class="keyword">public</span> <span class="keyword">interface</span> OnSlideListener {
- <span class="comment">// SlideView的三种状态:开始滑动,打开,关闭</span>
- <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> SLIDE_STATUS_OFF = <span class="number"></span>;
- <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> SLIDE_STATUS_START_SCROLL = <span class="number">1</span>;
- <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> SLIDE_STATUS_ON = <span class="number">2</span>;
-
- <span class="comment">/**</span>
- <span class="comment"> * @param view</span>
- <span class="comment"> * current SlideView</span>
- <span class="comment"> * @param status</span>
- <span class="comment"> * SLIDE_STATUS_ON, SLIDE_STATUS_OFF or</span>
- <span class="comment"> * SLIDE_STATUS_START_SCROLL</span>
- <span class="comment"> */</span>
- <span class="keyword">public</span> <span class="keyword">void</span> onSlide(View view, <span class="keyword">int</span> status);
- }
-
- <span class="keyword">public</span> SlideView(Context context) {
- <span class="keyword">super</span>(context);
- initView();
- }
-
- <span class="keyword">public</span> SlideView(Context context, AttributeSet attrs) {
- <span class="keyword">super</span>(context, attrs);
- initView();
- }
-
- <span class="keyword">private</span> <span class="keyword">void</span> initView() {
- mContext = getContext();
- <span class="comment">// 初始化弹性滑动对象</span>
- mScroller = <span class="keyword">new</span> Scroller(mContext);
- <span class="comment">// 设置其方向为横向</span>
- setOrientation(LinearLayout.HORIZONTAL);
- <span class="comment">// 将slide_view_merge加载进来</span>
- View.inflate(mContext, R.layout.slide_view_merge, <span class="keyword">this</span>);
- mViewContent = (LinearLayout) findViewById(R.id.view_content);
- mHolderWidth = Math.round(TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP, mHolderWidth, getResources()
- .getDisplayMetrics()));
- }
-
- <span class="comment">// 设置按钮的内容,也可以设置图标啥的,我没写</span>
- <span class="keyword">public</span> <span class="keyword">void</span> setButtonText(CharSequence text) {
- ((TextView) findViewById(R.id.delete)).setText(text);
- }
-
- <span class="comment">// 将view加入到ViewContent中</span>
- <span class="keyword">public</span> <span class="keyword">void</span> setContentView(View view) {
- mViewContent.addView(view);
- }
-
- <span class="comment">// 设置滑动回调</span>
- <span class="keyword">public</span> <span class="keyword">void</span> setOnSlideListener(OnSlideListener onSlideListener) {
- mOnSlideListener = onSlideListener;
- }
-
- <span class="comment">// 将当前状态置为关闭</span>
- <span class="keyword">public</span> <span class="keyword">void</span> shrink() {
- <span class="keyword">if</span> (getScrollX() != <span class="number"></span>) {
- <span class="keyword">this</span>.smoothScrollTo(<span class="number"></span>, <span class="number"></span>);
- }
- }
-
- <span class="comment">// 根据MotionEvent来进行滑动,这个方法的作用相当于onTouchEvent</span>
- <span class="comment">// 如果你不需要处理滑动冲突,可以直接重命名,照样能正常工作</span>
- <span class="keyword">public</span> <span class="keyword">void</span> onRequireTouchEvent(MotionEvent event) {
- <span class="keyword">int</span> x = (<span class="keyword">int</span>) event.getX();
- <span class="keyword">int</span> y = (<span class="keyword">int</span>) event.getY();
- <span class="keyword">int</span> scrollX = getScrollX();
- Log.d(TAG, <span class="string">“x=”</span> + x + <span class="string">” y=”</span> + y);
-
- <span class="keyword">switch</span> (event.getAction()) {
- <span class="keyword">case</span> MotionEvent.ACTION_DOWN: {
- <span class="keyword">if</span> (!mScroller.isFinished()) {
- mScroller.abortAnimation();
- }
- <span class="keyword">if</span> (mOnSlideListener != <span class="keyword">null</span>) {
- mOnSlideListener.onSlide(<span class="keyword">this</span>,
- OnSlideListener.SLIDE_STATUS_START_SCROLL);
- }
- <span class="keyword">break</span>;
- }
- <span class="keyword">case</span> MotionEvent.ACTION_MOVE: {
- <span class="keyword">if</span> (Math.abs(deltaX) < Math.abs(deltaY) * TAN) {
- <span class="comment">// 滑动不满足条件,不做横向滑动</span>
- <span class="keyword">break</span>;
- }
-
- <span class="comment">// 计算滑动终点是否合法,防止滑动越界</span>
- <span class="keyword">if</span> (deltaX != <span class="number"></span>) {
- <span class="keyword">if</span> (newScrollX < <span class="number"></span>) {
- newScrollX = <span class="number"></span>;
- } <span class="keyword">else</span> <span class="keyword">if</span> (newScrollX > mHolderWidth) {
- newScrollX = mHolderWidth;
- }
- <span class="keyword">this</span>.scrollTo(newScrollX, <span class="number"></span>);
- }
- <span class="keyword">break</span>;
- }
- <span class="keyword">case</span> MotionEvent.ACTION_UP: {
- <span class="keyword">int</span> newScrollX = <span class="number"></span>;
- <span class="comment">// 这里做了下判断,当松开手的时候,会自动向两边滑动,具体向哪边滑,要看当前所处的位置</span>
- newScrollX = mHolderWidth;
- }
- <span class="comment">// 慢慢滑向终点</span>
- <span class="keyword">this</span>.smoothScrollTo(newScrollX, <span class="number"></span>);
- <span class="comment">// 通知上层滑动事件</span>
- <span class="keyword">if</span> (mOnSlideListener != <span class="keyword">null</span>) {
- mOnSlideListener.onSlide(<span class="keyword">this</span>,
- newScrollX == <span class="number"></span> ? OnSlideListener.SLIDE_STATUS_OFF
- : OnSlideListener.SLIDE_STATUS_ON);
- }
- <span class="keyword">break</span>;
- }
- <span class="keyword">default</span>:
- <span class="keyword">break</span>;
- }
-
- mLastX = x;
- mLastY = y;
- }
-
- <span class="keyword">private</span> <span class="keyword">void</span> smoothScrollTo(<span class="keyword">int</span> destX, <span class="keyword">int</span> destY) {
- <span class="comment">// 缓慢滚动到指定位置</span>
- <span class="keyword">int</span> scrollX = getScrollX();
- <span class="comment">// 以三倍时长滑向destX,效果就是慢慢滑动</span>
- mScroller.startScroll(scrollX, <span class="number"></span>, delta, <span class="number"></span>, Math.abs(delta) * <span class="number">3</span>);
- invalidate();
- }
-
- <span class="annotation">@Override</span>
- <span class="keyword">public</span> <span class="keyword">void</span> computeScroll() {
- <span class="keyword">if</span> (mScroller.computeScrollOffset()) {
- scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
- postInvalidate();
- }
- }
-
- }
上述代码做了很详细的说明,这就是滑动控件的完整代码,大家要明白的是:你所添加的view都是加在SlideView的子View : view_content中的,而不是直接加在SlideView中,只有这样我们才方便做滑动效果。
接着看ListView的代码:核心就是下面这一个方法,将点击事件发送给SlideView处理。
- <span class="annotation">@Override</span>
- <span class="keyword">public</span> <span class="keyword">boolean</span> onTouchEvent(MotionEvent event) {
- <span class="keyword">switch</span> (event.getAction()) {
- <span class="keyword">case</span> MotionEvent.ACTION_DOWN: {
- <span class="keyword">int</span> x = (<span class="keyword">int</span>) event.getX();
- <span class="keyword">int</span> y = (<span class="keyword">int</span>) event.getY();
- <span class="comment">//我们想知道当前点击了哪一行</span>
- <span class="keyword">int</span> position = pointToPosition(x, y);
- Log.e(TAG, <span class="string">“postion=”</span> + position);
- <span class="keyword">if</span> (position != INVALID_POSITION) {
- <span class="comment">//得到当前点击行的数据从而取出当前行的item。</span>
- <span class="comment">//可能有人怀疑,为什么要这么干?为什么不用getChildAt(position)?</span>
- <span class="comment">//因为ListView会进行缓存,如果你不这么干,有些行的view你是得不到的。</span>
- MessageItem data = (MessageItem) getItemAtPosition(position);
- mFocusedItemView = data.slideView;
- Log.e(TAG, <span class="string">“FocusedItemView=”</span> + mFocusedItemView);
- }
- }
- <span class="keyword">default</span>:
- <span class="keyword">break</span>;
- }
-
- <span class="comment">//向当前点击的view发送滑动事件请求,其实就是向SlideView发请求</span>
- <span class="keyword">if</span> (mFocusedItemView != <span class="keyword">null</span>) {
- mFocusedItemView.onRequireTouchEvent(event);
- }
-
- <span class="keyword">return</span> <span class="keyword">super</span>.onTouchEvent(event);
- }
最后看Activity的代码:
- <span class="keyword">public</span> <span class="keyword">class</span> MainActivity <span class="keyword">extends</span> Activity <span class="keyword">implements</span> OnItemClickListener,
- OnClickListener, OnSlideListener {
-
- <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String TAG = <span class="string">“MainActivity”</span>;
-
- <span class="keyword">private</span> ListViewCompat mListView;
-
- <span class="keyword">private</span> List<MessageItem> mMessageItems = <span class="keyword">new</span> ArrayList<MainActivity.MessageItem>();
-
- <span class="keyword">private</span> SlideAdapter mSlideAdapter;
-
- <span class="comment">// 上次处于打开状态的SlideView</span>
- <span class="keyword">private</span> SlideView mLastSlideViewWithStatusOn;
-
- <span class="annotation">@Override</span>
- <span class="keyword">protected</span> <span class="keyword">void</span> onCreate(Bundle savedInstanceState) {
- <span class="keyword">super</span>.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- initView();
- }
-
- <span class="keyword">private</span> <span class="keyword">void</span> initView() {
- mListView = (ListViewCompat) findViewById(R.id.list);
-
- <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number"></span>; i < <span class="number">20</span>; i++) {
- MessageItem item = <span class="keyword">new</span> MessageItem();
- <span class="keyword">if</span> (i % <span class="number">3</span> == <span class="number"></span>) {
- item.iconRes = R.drawable.default_qq_avatar;
- item.title = <span class="string">“腾讯新闻”</span>;
- item.msg = <span class="string">“青岛爆炸满月:大量鱼虾死亡”</span>;
- item.time = <span class="string">“晚上18:18”</span>;
- } <span class="keyword">else</span> {
- item.iconRes = R.drawable.wechat_icon;
- item.title = <span class="string">“微信团队”</span>;
- item.msg = <span class="string">“欢迎你使用微信”</span>;
- item.time = <span class="string">“12月18日”</span>;
- }
- mMessageItems.add(item);
- }
- mSlideAdapter = <span class="keyword">new</span> SlideAdapter();
- mListView.setAdapter(mSlideAdapter);
- mListView.setOnItemClickListener(<span class="keyword">this</span>);
- }
-
- <span class="keyword">private</span> <span class="keyword">class</span> SlideAdapter <span class="keyword">extends</span> BaseAdapter {
-
- <span class="keyword">private</span> LayoutInflater mInflater;
-
- SlideAdapter() {
- <span class="keyword">super</span>();
- mInflater = getLayoutInflater();
- }
-
- <span class="annotation">@Override</span>
- <span class="keyword">public</span> <span class="keyword">int</span> getCount() {
- <span class="keyword">return</span> mMessageItems.size();
- }
-
- <span class="annotation">@Override</span>
- <span class="keyword">public</span> Object getItem(<span class="keyword">int</span> position) {
- <span class="keyword">return</span> mMessageItems.get(position);
- }
-
- <span class="annotation">@Override</span>
- <span class="keyword">public</span> <span class="keyword">long</span> getItemId(<span class="keyword">int</span> position) {
- <span class="keyword">return</span> position;
- }
-
- <span class="annotation">@Override</span>
- <span class="keyword">public</span> View getView(<span class="keyword">int</span> position, View convertView, ViewGroup parent) {
- ViewHolder holder;
- SlideView slideView = (SlideView) convertView;
- <span class="keyword">if</span> (slideView == <span class="keyword">null</span>) {
- <span class="comment">// 这里是我们的item</span>
- View itemView = mInflater.inflate(R.layout.list_item, <span class="keyword">null</span>);
-
- slideView = <span class="keyword">new</span> SlideView(MainActivity.<span class="keyword">this</span>);
- <span class="comment">// 这里把item加入到slideView</span>
- slideView.setContentView(itemView);
- <span class="comment">// 下面是做一些数据缓存</span>
- holder = <span class="keyword">new</span> ViewHolder(slideView);
- slideView.setOnSlideListener(MainActivity.<span class="keyword">this</span>);
- slideView.setTag(holder);
- } <span class="keyword">else</span> {
- 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.<span class="keyword">this</span>);
-
- <span class="keyword">return</span> slideView;
- }
-
- }
-
- <span class="keyword">public</span> <span class="keyword">class</span> MessageItem {
- <span class="keyword">public</span> <span class="keyword">int</span> iconRes;
- <span class="keyword">public</span> String title;
- <span class="keyword">public</span> String msg;
- <span class="keyword">public</span> String time;
- <span class="keyword">public</span> SlideView slideView;
- }
-
- <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> ViewHolder {
- <span class="keyword">public</span> ImageView icon;
- <span class="keyword">public</span> TextView title;
- <span class="keyword">public</span> TextView msg;
- <span class="keyword">public</span> TextView time;
- <span class="keyword">public</span> 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);
- }
- }
-
- <span class="annotation">@Override</span>
- <span class="keyword">public</span> <span class="keyword">void</span> onItemClick(AdapterView<?> parent, View view, <span class="keyword">int</span> position,
- <span class="keyword">long</span> id) {
- <span class="comment">// 这里处理ListItem的点击事件</span>
- Log.e(TAG, <span class="string">“onItemClick position=”</span> + position);
- }
-
- <span class="annotation">@Override</span>
- <span class="keyword">public</span> <span class="keyword">void</span> onSlide(View view, <span class="keyword">int</span> status) {
- <span class="comment">// 如果当前存在已经打开的SlideView,那么将其关闭</span>
- <span class="keyword">if</span> (mLastSlideViewWithStatusOn != <span class="keyword">null</span>
- && mLastSlideViewWithStatusOn != view) {
- mLastSlideViewWithStatusOn.shrink();
- }
- <span class="comment">// 记录本次处于打开状态的view</span>
- <span class="keyword">if</span> (status == SLIDE_STATUS_ON) {
- mLastSlideViewWithStatusOn = (SlideView) view;
- }
- }
-
- <span class="annotation">@Override</span>
- <span class="keyword">public</span> <span class="keyword">void</span> onClick(View v) {
- <span class="comment">// 这里处理删除按钮的点击事件,可以删除对话</span>
- <span class="keyword">if</span> (v.getId() == R.id.holder) {
- <span class="keyword">int</span> position = mListView.getPositionForView(v);
- <span class="keyword">if</span> (position != ListView.INVALID_POSITION) {
- mMessageItems.remove(position);
- mSlideAdapter.notifyDataSetChanged();
- }
- Log.e(TAG, <span class="string">“onClick v=”</span> + v);
- }
- }
- }
代码我都特意写了注释,就不多说了。
代码下载:http://download.csdn.net/detail/singwhatiwanna/6760085
另外此博文采用了 ttdevs 所提供代码的部分思想(他的博客是http://blog.csdn.net/ttdevs)
💬 评论