本来准备在ListView的每个Item的布局上设置一个隐藏的Button,当滑动的时候显示。但是因为每次只要存在一个Button,发现每个Item上的Button相互间不好控制。所以决定继承ListView然后结合PopupWindow。
首先是布局文件:
delete_btn.xml:这里只需要一个Button
1 26 20
主布局文件:activity_main.xml,ListView的每个Item的样式直接使用了系统的android.R.layout.simple_list_item_1
15 6 10 11 12
接下来看看QQListView的实现:
1 package com.example.listviewitemslidedeletebtnshow; 2 3 import android.content.Context; 4 import android.util.AttributeSet; 5 import android.view.Gravity; 6 import android.view.LayoutInflater; 7 import android.view.MotionEvent; 8 import android.view.View; 9 import android.view.ViewConfiguration; 10 import android.widget.Button; 11 import android.widget.LinearLayout; 12 import android.widget.ListView; 13 import android.widget.PopupWindow; 14 15 public class QQListView extends ListView 16 { 17 18 private static final String TAG = "QQlistView"; 19 20 // private static final int VELOCITY_SANP = 200; 21 // private VelocityTracker mVelocityTracker; 22 /** 23 * 用户滑动的最小距离 24 */ 25 private int touchSlop; 26 27 /** 28 * 是否响应滑动 29 */ 30 private boolean isSliding; 31 32 /** 33 * 手指按下时的x坐标 34 */ 35 private int xDown; 36 /** 37 * 手指按下时的y坐标 38 */ 39 private int yDown; 40 /** 41 * 手指移动时的x坐标 42 */ 43 private int xMove; 44 /** 45 * 手指移动时的y坐标 46 */ 47 private int yMove; 48 49 private LayoutInflater mInflater; 50 51 private PopupWindow mPopupWindow; 52 private int mPopupWindowHeight; 53 private int mPopupWindowWidth; 54 55 private Button mDelBtn; 56 /** 57 * 为删除按钮提供一个回调接口 58 */ 59 private DelButtonClickListener mListener; 60 61 /** 62 * 当前手指触摸的View 63 */ 64 private View mCurrentView; 65 66 /** 67 * 当前手指触摸的位置 68 */ 69 private int mCurrentViewPos; 70 71 /** 72 * 必要的一些初始化 73 * 74 * @param context 75 * @param attrs 76 */ 77 public QQListView(Context context, AttributeSet attrs) 78 { 79 super(context, attrs); 80 81 mInflater = LayoutInflater.from(context); 82 touchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 83 84 View view = mInflater.inflate(R.layout.delete_btn, null); 85 mDelBtn = (Button) view.findViewById(R.id.id_item_btn); 86 mPopupWindow = new PopupWindow(view, LinearLayout.LayoutParams.WRAP_CONTENT, 87 LinearLayout.LayoutParams.WRAP_CONTENT); 88 /** 89 * 先调用下measure,否则拿不到宽和高 90 */ 91 mPopupWindow.getContentView().measure(0, 0); 92 mPopupWindowHeight = mPopupWindow.getContentView().getMeasuredHeight(); 93 mPopupWindowWidth = mPopupWindow.getContentView().getMeasuredWidth(); 94 } 95 96 @Override 97 public boolean dispatchTouchEvent(MotionEvent ev) 98 { 99 int action = ev.getAction(); 100 int x = (int) ev.getX(); 101 int y = (int) ev.getY(); 102 switch (action) 103 { 104 105 case MotionEvent.ACTION_DOWN: 106 xDown = x; 107 yDown = y; 108 /** 109 * 如果当前popupWindow显示,则直接隐藏,然后屏蔽ListView的touch事件的下传 110 */ 111 if (mPopupWindow.isShowing()) 112 { 113 dismissPopWindow(); 114 return false; 115 } 116 // 获得当前手指按下时的item的位置 117 mCurrentViewPos = pointToPosition(xDown, yDown); 118 // 获得当前手指按下时的item 119 View view = getChildAt(mCurrentViewPos - getFirstVisiblePosition()); 120 mCurrentView = view; 121 break; 122 case MotionEvent.ACTION_MOVE: 123 xMove = x; 124 yMove = y; 125 int dx = xMove - xDown; 126 int dy = yMove - yDown; 127 /** 128 * 判断是否是从右到左的滑动 129 */ 130 if (xMove < xDown && Math.abs(dx) > touchSlop && Math.abs(dy) < touchSlop) 131 { 132 // Log.e(TAG, "touchslop = " + touchSlop + " , dx = " + dx + 133 // " , dy = " + dy); 134 isSliding = true; 135 } 136 break; 137 } 138 return super.dispatchTouchEvent(ev); 139 } 140 141 @Override 142 public boolean onTouchEvent(MotionEvent ev) 143 { 144 int action = ev.getAction(); 145 /** 146 * 如果是从右到左的滑动才相应 147 */ 148 if (isSliding) 149 { 150 switch (action) 151 { 152 case MotionEvent.ACTION_MOVE: 153 154 int[] location = new int[2]; 155 // 获得当前item的位置x与y 156 mCurrentView.getLocationOnScreen(location); 157 // 设置popupWindow的动画 158 mPopupWindow.setAnimationStyle(R.style.popwindow_delete_btn_anim_style); 159 mPopupWindow.update(); 160 mPopupWindow.showAtLocation(mCurrentView, Gravity.LEFT | Gravity.TOP, 161 location[0] + mCurrentView.getWidth(), location[1] + mCurrentView.getHeight() / 2 162 - mPopupWindowHeight / 2); 163 // 设置删除按钮的回调 164 mDelBtn.setOnClickListener(new OnClickListener() 165 { 166 @Override 167 public void onClick(View v) 168 { 169 if (mListener != null) 170 { 171 mListener.clickHappend(mCurrentViewPos); 172 mPopupWindow.dismiss(); 173 } 174 } 175 }); 176 // Log.e(TAG, "mPopupWindow.getHeight()=" + mPopupWindowHeight); 177 178 break; 179 case MotionEvent.ACTION_UP: 180 isSliding = false; 181 182 } 183 // 相应滑动期间屏幕itemClick事件,避免发生冲突 184 return true; 185 } 186 187 return super.onTouchEvent(ev); 188 } 189 190 /** 191 * 隐藏popupWindow 192 */ 193 private void dismissPopWindow() 194 { 195 if (mPopupWindow != null && mPopupWindow.isShowing()) 196 { 197 mPopupWindow.dismiss(); 198 } 199 } 200 201 public void setDelButtonClickListener(DelButtonClickListener listener) 202 { 203 mListener = listener; 204 } 205 206 interface DelButtonClickListener 207 { 208 public void clickHappend(int position); 209 } 210 211 }
代码注释写得很详细,简单说一下,在dispatchTouchEvent中设置当前是否响应用户滑动,然后在onTouchEvent中判断是否响应,如果响应则popupWindow以动画的形式展示出来。当然屏幕上如果存在PopupWindow则屏幕ListView的滚动与Item的点击,以及从右到左滑动时屏幕Item的click事件。
接下来是MainActivity.java,这里代码很简单不做介绍了。
1 package com.example.listviewitemslidedeletebtnshow; 2 3 import java.util.ArrayList; 4 import java.util.Arrays; 5 import java.util.List; 6 7 import android.app.Activity; 8 import android.os.Bundle; 9 import android.view.View; 10 import android.widget.AdapterView; 11 import android.widget.AdapterView.OnItemClickListener; 12 import android.widget.ArrayAdapter; 13 import android.widget.Toast; 14 15 import com.example.listviewitemslidedeletebtnshow.QQListView.DelButtonClickListener; 16 17 public class MainActivity extends Activity 18 { 19 private QQListView mListView; 20 private ArrayAdaptermAdapter; 21 private List mDatas; 22 23 @Override 24 protected void onCreate(Bundle savedInstanceState) 25 { 26 super.onCreate(savedInstanceState); 27 setContentView(R.layout.activity_main); 28 29 mListView = (QQListView) findViewById(R.id.id_listview); 30 // 不要直接Arrays.asList 31 mDatas = new ArrayList (Arrays.asList("HelloWorld", "Welcome", "Java", "Android", "Servlet", "Struts", 32 "Hibernate", "Spring", "HTML5", "Javascript", "Lucene")); 33 mAdapter = new ArrayAdapter (this, android.R.layout.simple_list_item_1, mDatas); 34 mListView.setAdapter(mAdapter); 35 36 mListView.setDelButtonClickListener(new DelButtonClickListener() 37 { 38 @Override 39 public void clickHappend(final int position) 40 { 41 Toast.makeText(MainActivity.this, position + " : " + mAdapter.getItem(position), 1).show(); 42 mAdapter.remove(mAdapter.getItem(position)); 43 } 44 }); 45 46 mListView.setOnItemClickListener(new OnItemClickListener() 47 { 48 @Override 49 public void onItemClick(AdapterView parent, View view, int position, long id) 50 { 51 Toast.makeText(MainActivity.this, position + " : " + mAdapter.getItem(position), 1).show(); 52 } 53 }); 54 } 55 }
效果图如下:楼主使用asm.jar以及gifcamera截的gif,由于button的动画很短感觉截图效果很卡不流畅,大家有什么好的截图,还望推荐。有兴趣的还是下载源码看看效果i。
源码下载:http://download.csdn.net/detail/lmj623565791/7148325
上述文章实现的功能是:在ListView的Item上从右向左滑时,出现删除按钮,点击删除按钮把Item删除。
看过文章后,感觉没有必要把dispatchTouchEvent()和onTouchEvent()两个方法都重写,只要重写onTouchEvent就好了。于是对代码作了一些调整:
1 public class MyListView extends ListView { 2 private static final String TAG = "MyListView"; 3 private int mTouchSlop; 4 private int mXDown; 5 private int mYDown; 6 private int mCurrentPosition; 7 private View mCurrentView; 8 private PopupWindow mPopupWindow; 9 private LayoutInflater mInflater; 10 private boolean isSliding = false; 11 // 为删除按钮提供一个回调接口 12 private DelButtonClickListener mListener; 13 private Button mDelBtn; 14 private int mPopupWindowHeight; 15 private int mPopupWindowWidth; 16 17 public MyListView(Context context, AttributeSet attrs) { 18 super(context, attrs); 19 mInflater = LayoutInflater.from(context); 20 mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 21 22 View view = mInflater.inflate(R.layout.delete_btn, null); 23 mDelBtn = (Button) view.findViewById(R.id.id_item_btn); 24 mPopupWindow = new PopupWindow(view, LinearLayout.LayoutParams.WRAP_CONTENT, 25 LinearLayout.LayoutParams.WRAP_CONTENT); 26 // 如果需要通过点击PopupWindow之外的地方使其消失,则需要setFocusable(true). 27 mPopupWindow.setFocusable(true); 28 // Android 6.0以前的版本需要setBackgroundDrawable(), 29 // 才能实现通过点击PopupWindow之外的地方使其消失的功能。 30 mPopupWindow.setBackgroundDrawable(new ColorDrawable(0)); 31 // 先调用下measure,否则拿不到宽和高 32 mPopupWindow.getContentView().measure(0, 0); 33 mPopupWindowHeight = mPopupWindow.getContentView().getMeasuredHeight(); 34 mPopupWindowWidth = mPopupWindow.getContentView().getMeasuredWidth(); 35 } 36 37 @Override 38 public boolean onTouchEvent(MotionEvent ev) { 39 int action = ev.getAction(); 40 int x = (int) ev.getX(); 41 int y = (int) ev.getY(); 42 43 switch (action){ 44 case MotionEvent.ACTION_DOWN: 45 isSliding = false; 46 mXDown = x; 47 mYDown = y; 48 mCurrentPosition = pointToPosition(mXDown, mYDown); 49 View view = getChildAt(mCurrentPosition - getFirstVisiblePosition()); 50 mCurrentView = view; 51 break; 52 case MotionEvent.ACTION_MOVE: 53 int dx = x - mXDown; 54 int dy = y - mYDown; 55 56 Log.d(TAG, "mTouchSlop = " + mTouchSlop + ", dx = " + dx + ", dy = " + dy); 57 58 if(mXDown > x && Math.abs(dx) > mTouchSlop && Math.abs(dy) < mTouchSlop){ 59 Log.d(TAG, "isSliding"); 60 isSliding = true; 61 int[] location = new int[2]; 62 mCurrentView.getLocationOnScreen(location); 63 mPopupWindow.setAnimationStyle(R.style.popwindow_delete_btn_anim_style); 64 mPopupWindow.update(); 65 Log.d(TAG, "Height: " + mCurrentView.getHeight() + "," + mPopupWindow.getHeight()); 66 mPopupWindow.showAtLocation(mCurrentView, Gravity.NO_GRAVITY, 67 location[0] + mCurrentView.getWidth(), 68 location[1] + mCurrentView.getHeight() / 2 - mPopupWindowHeight / 2); 69 mDelBtn.setOnClickListener(new OnClickListener() { 70 @Override 71 public void onClick(View v) { 72 mListener.clickHappend(mCurrentPosition); 73 mPopupWindow.dismiss(); 74 } 75 }); 76 } 77 case MotionEvent.ACTION_UP: 78 // isSliding 如果这里恢复为false,则后面会执行super.onTouchEvent事件, 79 // 而AbsListView的onTouchEvent调用了onTouchUp方法,在onTouchUp方法中有可能执行 80 // performClick.run() --> performItemClick() --> super.performItemClick 81 // --> mOnItemClickListener.onItemClick,这样最终触发Item的点击。 82 // 因此此处依旧保持isSliding为true的状态,而在ACTION_DOWN事件中恢复isSliding为false, 83 // 毕竟每个事件都以ACTION_DOWN开始。 84 //isSliding = false; 85 } 86 87 if(isSliding){ 88 return true; 89 } 90 91 return super.onTouchEvent(ev); 92 } 93 94 public void setDelButtonClickListener(DelButtonClickListener listener){ 95 mListener = listener; 96 } 97 98 interface DelButtonClickListener{ 99 public void clickHappend(int position);100 }101 }102 103 MyListView.java
通过这个例子学习到:
1、ListView的Item点击事件的触发过程:
自定义ListView的onTouchEvent() ---调用super.onTouchEvent()---> AbsListView.onTouchEvent() ---MotionEvent.ACTION_UP---> AbsListView.onTouchUp()
---(有可能)调用performClick.run()---> AbsListView.PerformClick.run() ---调用performItemClick()--->AbsListView.performItemClick()
---(有可能)调用super.performItemClick()---> AdapterView.performItemClick() ---mOnItemClickListener.onItemClick---> OnItemClickListener.onItemClick()
也就是Item的点击事件是在MotionEvent.ACTION_UP事件完成的,这样在自定义ListView的onTouchEvent()中,对MotionEvent.ACTION_UP直接return true消费掉事件,而不要调用super.onTouchEvent。这样就避免了删除按钮与Item点击事件的冲突。
2、PopupWindow--通过点击PopupWindow之外的地方使其消失
a、需要调用setFocusable()方法(PopupWindow中showAtLocation() --> createPopupLayoutParams() -->computeFlags() --> 设置FLAG_NOT_FOCUSABLE);
b、Android 6.0以前的版本需要setBackgroundDrawable()(具体原因见:)。
原文:http://blog.csdn.net/lmj623565791/article/details/22961279
http://www.cnblogs.com/yarightok/p/5666127.html