研究了很久的拖拽ListView的實現,受益良多,特此與爾共饗。
鑑於這部分內容網上的資料少而簡陋,而具體的實現過程或許對大家纔有幫助,爲了詳盡而不失真,我們一步一步分析,分成兩篇文章。
一、準備。
1.需求問題
初步:實現列表的拖拽效果(可參考Android源碼下packages/apps/Music中的播放列表TouchInterceptor.java源碼)。
(提前說明一下,本文不是完全按照Music中實現的,代碼實現方式做了一些調整,去掉來很多無關的東西,方便大家理解,效果上也修改成了另外一種 個人認爲是更簡單更高效的一套。)
拓展:借鑑上一篇文章Android學習系列(9)--App列表之分組ListView,實現分組列表的拖拽效果。
下面以初步實現爲例子,逐步展開實現步驟。
2.搭建主界面DragListActivity.java和主佈局drag_list_activity.xml。
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
|
public
class DragListActivity extends
Activity { //數據列表 private
List<String> list = null ; //數據適配器 private
DragListAdapter adapter = null ; //存放分組標籤 public
static List<String> groupKey=
new ArrayList<String>(); //分組一 private
List<String> navList = new
ArrayList<String>(); //分組二 private
List<String> moreList = new
ArrayList<String>(); @Override public
void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.drag_list_activity); //初始化樣本數據 initData(); //後面會介紹DragListView DragListView dragListView = (DragListView)findViewById(R.id.drag_list); adapter =
new DragListAdapter( this , list); dragListView.setAdapter(adapter); } } |
3.列表項的佈局drag_list_item.xml。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
<? xml
version = "1.0"
encoding = "utf-8" ?> <!-- 強調一點,使用相對佈局 --> android:layout_width = "fill_parent" android:layout_height = "wrap_content" > < TextView android:id = "@+id/drag_list_item_text" android:layout_width = "wrap_content" android:layout_height = "@dimen/drag_item_normal_height" android:paddingLeft = "5dip" android:layout_alignParentLeft = "true" android:layout_centerVertical = "true" android:gravity = "center_vertical" /> < ImageView
android:id = "@+id/drag_list_item_image" android:src = "@drawable/list_icon" android:layout_alignParentRight = "true" android:layout_centerVertical = "true" android:layout_width = "wrap_content" android:layout_height = "@dimen/drag_item_normal_height" /> </ RelativeLayout > |
4.準備樣本數據。
我已經準備好了兩組數據,在前面提到的initData()方法中執行初始化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public
void initData(){ //數據結果 list =
new ArrayList<String>(); //groupKey存放的是分組標籤 groupKey.add( "A組" ); groupKey.add( "B組" ); for ( int
i= 0 ; i< 5 ; i++){ navList.add( "A選項" +i); } list.add( "A組" ); list.addAll(navList); for ( int
i= 0 ; i< 8 ; i++){ moreList.add( "B選項" +i); } list.add( "B組" ); list.addAll(moreList); } |
這裏定義了分組標籤集合groupKey後面分組的時候會用到。
5.自定義適配器類DragListAdapter。
接着我們搭建數據適配器,負責把list的數據填充到ListView中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public
static class DragListAdapter
extends ArrayAdapter<String>{ public
DragListAdapter(Context context, List<String> objects) { super (context,
0 , objects); } @Override public
View getView( int
position, View convertView, ViewGroup parent) { View view = convertView; if (view== null ){ //加載列表項模板 view = LayoutInflater.from(getContext()).inflate(R.layout.drag_list_item,
null ); }
TextView textView = (TextView)view.findViewById(R.id.drag_list_item_text); textView.setText(getItem(position)); return
view; } } |
注意getItem(position)會取得數組適配器中position位置的T(這裏是字符串),比較好用的一個方法。
至此,我們準備了一個正常的數據列表,效果如下:
二、實現
上面部分是我們的一個 準備工作,接下來我們通過自定義ListView,重寫ListView中onInterceptTouchEvent(),onTouchEvent()方法來響應觸控事件做相應的界面調整(選中,拖動,數據更改後刷新界面)等等。
6.自定義視圖類。
1
2
3
4
5
6
7
8
9
10
11
|
//自定義ListView,準備改造成自己想要的ListView //這樣的好處是我們不僅可以直接使用ListView很多現成的穩定的方法,而且可以重寫方法改寫ListView的行爲(利用的是java面向對象的繼承特性,本人喜歡在任何代碼中分析面向對象的特性、原則和模式) public
class DragListView extends
ListView { private
int scaledTouchSlop; //判斷滑動的一個距離,scroll的時候會用到 public
DragListView(Context context, AttributeSet attrs) { super (context, attrs); scaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } } |
7.重寫觸控攔截事件方法onInterceptTouchEvent()。
爲了能在子控件響應觸摸事件的情況下此ListView也能監聽到觸摸事件,我們把重寫這個方法,做一些初始化工作。我們在這裏捕獲down事件,在down事件中,我們做一些拖動的準備工作:
1)獲取點擊數據項,初始化一些變量;
2)判斷是否是拖動還是僅僅點擊;
3)如果是拖動,建立拖動影像;
這些工作是我們後面拖動的一個執行基礎,非常重要。
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
|
//下面定義要使用的所有變量 private
ImageView dragImageView; //被拖拽項的影像,其實就是一個ImageView private
int dragSrcPosition; //手指拖動項原始在列表中的位置 private
int dragPosition; //手指拖動的時候,當前拖動項在列表中的位置 private
int dragPoint; //在當前數據項中的位置 private
int dragOffset; //當前視圖和屏幕的距離(這裏只使用了y方向上) private
WindowManager windowManager; //windows窗口控制類 private
WindowManager.LayoutParams windowParams; //用於控制拖拽項的顯示的參數 private
int scaledTouchSlop; //判斷滑動的一個距離 private
int upScrollBounce; //拖動的時候,開始向上滾動的邊界 private
int downScrollBounce; //拖動的時候,開始向下滾動的邊界 @Override public
boolean onInterceptTouchEvent(MotionEvent ev) { //捕獲down事件 if (ev.getAction()==MotionEvent.ACTION_DOWN){ int
x = ( int )ev.getX(); int
y = ( int )ev.getY(); //選中的數據項位置,使用ListView自帶的pointToPosition(x, y)方法 dragSrcPosition = dragPosition = pointToPosition(x, y); //如果是無效位置(超出邊界,分割線等位置),返回 if (dragPosition==AdapterView.INVALID_POSITION){ return
super .onInterceptTouchEvent(ev); } //獲取選中項View //getChildAt(int position)顯示display在界面的position位置的View //getFirstVisiblePosition()返回第一個display在界面的view在adapter的位置position,可能是0,也可能是4 ViewGroup itemView = (ViewGroup) getChildAt(dragPosition-getFirstVisiblePosition()); //dragPoint點擊位置在點擊View內的相對位置 //dragOffset屏幕位置和當前ListView位置的偏移量,這裏只用到y座標上的值 //這兩個參數用於後面拖動的開始位置和移動位置的計算 dragPoint = y - itemView.getTop(); dragOffset = ( int ) (ev.getRawY() - y); //獲取右邊的拖動圖標,這個對後面分組拖拽有妙用 View dragger = itemView.findViewById(R.id.drag_list_item_image); //如果在右邊位置(拖拽圖片左邊的20px的右邊區域) if (dragger!= null &&x>dragger.getLeft()- 20 ){ //準備拖動 //初始化拖動時滾動變量 //scaledTouchSlop定義了拖動的偏差位(一般+-10) //upScrollBounce當在屏幕的上部(上面1/3區域)或者更上的區域,執行拖動的邊界,downScrollBounce同理定義 upScrollBounce = Math.min(y-scaledTouchSlop, getHeight()/ 3 ); downScrollBounce = Math.max(y+scaledTouchSlop, getHeight()* 2 / 3 ); //設置Drawingcache爲true,獲得選中項的影像bm,就是後面我們拖動的哪個頭像 itemView.setDrawingCacheEnabled( true ); Bitmap bm = Bitmap.createBitmap(itemView.getDrawingCache()); //準備拖動影像(把影像加入到當前窗口,並沒有拖動,拖動操作我們放在onTouchEvent()的move中執行) startDrag(bm, y); } return
false ; } return
super .onInterceptTouchEvent(ev); } |
開始拖動影像startDrag()方法:
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
|
/** * 準備拖動,初始化拖動項的圖像 * @param bm * @param y */ public
void startDrag(Bitmap bm , int
y){ //釋放影像,在準備影像的時候,防止影像沒釋放,每次都執行一下
stopDrag(); windowParams =
new WindowManager.LayoutParams(); //從上到下計算y方向上的相對位置, windowParams.gravity = Gravity.TOP; windowParams.x =
0 ; windowParams.y = y - dragPoint + dragOffset; windowParams.width = WindowManager.LayoutParams.WRAP_CONTENT; windowParams.height = WindowManager.LayoutParams.WRAP_CONTENT; //下面這些參數能夠幫助準確定位到選中項點擊位置,照抄即可 windowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; windowParams.format = PixelFormat.TRANSLUCENT; windowParams.windowAnimations =
0 ; //把影像ImagView添加到當前視圖中 ImageView imageView =
new ImageView(getContext()); imageView.setImageBitmap(bm); windowManager = (WindowManager)getContext().getSystemService( "window" ); windowManager.addView(imageView, windowParams); //把影像ImageView引用到變量drawImageView,用於後續操作(拖動,釋放等等) dragImageView = imageView; } |
1
2
3
4
5
6
7
8
9
|
/** * 停止拖動,去除拖動項的頭像 */ public
void stopDrag(){ if (dragImageView!= null ){ windowManager.removeView(dragImageView); dragImageView =
null ; } } |
未完待續...