ListView滑動刪除實現之三——建立可滑動刪除的ListView
前言:這幾天房子真是把我搞得頭大,天天也睡不好,奶奶個熊,不知道決策對不對,聽天由命吧。
相關文章:
今天就要來實現我們開頭所說的那個滑動刪除的效果了,首先來看看如何讓檢視跟隨手指移動而移動。然後再進一步看看在ListView中又該如何做。
知識補充
這裡先補充一個知識。這裡會用到一個函式:
View.getScrollX()
它返回當前的view視角橫向移動的座標,即我們呼叫View.scrollX(x,y),getScrollX()返回的就是這裡的xView.getScrollY()同理。
一、如何讓VIEW跟隨手指反向移動
先看下面效果圖:
這個圖大家估計很容易看懂,就是讓裡面的View(藍色框和黃色框)跟著我們的手指移動。但大家注意了,我們呼叫scrollTo移動的它們父容器(LinearLayout)的視角。所以我們LinearLayout的移動方向與手指移動方向正好反過來。你比如,開始時,我們手指向左滑動。這時候我們的LinearLayout的視角要向右走,才能看到刪除的黃框!後來,當手指向右滑動時,我們的LinearLayout的視角只有向左走才能回到藍色框檢視!所以,從效果圖裡可以看到的最重要一點是:父視窗(LinearLayout)的視角移動向方與手指的移動方向正好是相反的!!!!這一點大家一定要理解出來,不然程式碼上就會出錯。這裡看不懂也沒關係,下面會結合程式碼和檢視具體講解。
然後我們看一下具體實現:
1、首先,我們看看佈局(acitivty_main.xml)
先列出程式碼:
從效果圖中,我們也可以看到,主佈局是一個LinearLayout的垂直佈局,我們要滑動的佈局頁面是其中的第二個LinearLayout,它是一個水平佈局,我們在前一節講過它的佈局方式:<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <TextView android:text="@string/hello_world" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <LinearLayout android:id="@+id/lin_root" android:layout_width="fill_parent" android:layout_height="200dp" android:orientation="horizontal"> <TextView android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#0000ff" android:text="ITEM" android:gravity="center" android:textSize="25dp" /> <TextView android:layout_width="200dp" android:layout_height="fill_parent" android:background="#ffff00" android:text="刪除" android:textSize="25dp" android:textColor="#ffffff" android:gravity="center" /> </LinearLayout> </LinearLayout>
首先,將第一個TextView(藍色塊)width_layout設定為fill_parent或者match_parent來填滿整個視窗。然後旁邊再附一個黃色塊的TextView;由於父容器LinearLayout是水平佈局,所以藍色框會佔滿整個父容器,黃色框會被擠到右邊看不到的位置。下面我們看看操作程式碼.<LinearLayout android:id="@+id/lin_root" android:layout_width="fill_parent" android:layout_height="200dp" android:orientation="horizontal"> <TextView android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#0000ff" android:text="ITEM" android:gravity="center" android:textSize="25dp" /> <TextView android:layout_width="200dp" android:layout_height="fill_parent" android:background="#ffff00" android:text="刪除" android:textSize="25dp" android:textColor="#ffffff" android:gravity="center" /> </LinearLayout>
2、MainActivity程式碼
操作程式碼如下:
public class MainActivity extends Activity {
private LinearLayout itemRoot;
private int mlastX = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
itemRoot = (LinearLayout)findViewById(R.id.lin_root);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int scrollX = itemRoot.getScrollX();
int x = (int)event.getX();
if (event.getAction() == MotionEvent.ACTION_MOVE){
int newScrollX = scrollX + mlastX - x;
itemRoot.scrollTo(newScrollX,0);
}
mlastX = x;
return super.onTouchEvent(event);
}
}
這裡最難的就是如何計算移動距離了。我們下面用一個圖來講解移動距離的計算方法。先看下面這個圖,表示的是根部的LinearLayout(lin_root)的視角跟隨手指的移動方向。下面為了講解方便起見,就用根佈局的ID:lin_root來代表根佈局的檢視。
圖(1),表示程式的初始化情況:黑色框表示螢幕,從這張圖中可以看到,藍色塊顯示在螢幕中,但黃色塊在螢幕外。
圖(2),這個圖模擬此時lin_root.scrollTo到了一個點,其中紅色框表示移動後的lin_root視角。它對應的顯示效果圖就是它下面的那個圖。
圖(3),在圖(2)的基礎上,使用者手指又滑動的一段距離,此時lin_root視角繼續往後移動,在圖(3)中的綠色框就表示這時候移動後的lin_root視角。它對應的顯示效果是它下面的那個圖。
但這裡有個問題必須注意:我們的手指是向左移動的,而lin_root的視角是向右移動的。也就是說,手指的移動方向與lin_root視角的移動方向是反過來的。
理解了上面的視角的移動方向與手指的移動方向是反過來的之後,然後我們再看下面這個圖:
在這個圖中,顯示瞭如何計算出手指的移動距離
要計算出手指的移動距離,必須利用lin_root的父容器,即MainActivity的根佈局原點來算。這是因為lin_root的視角是實時移動的,也就是說lin_root的原點是一直變的,即隨著手動的位置在變。如果我們仍然以lin_root的左上角為原點來計算手指的位置那必然是不準確的。所以我們必須找一個不動的座標點來準確的定位手指的位置。進而計算出手指的移動的距離。這個可以用螢幕座標,也可以利用它的父輩或者祖輩的座標系來算。而在父類中攔截OnTouchEvent()事件是非常容易的,所以,我們就利用它的父容器來做。這也就是為什麼在MainActivity中重寫onTouchEvent()的原因。因為lin_root自身的OnTouchEvent是無法準確獲得手指位置的。
有了上面的鋪墊,下面就是動真格的了,下面來看如何來計算新一輪的scrollX
從上面圖中,我們可以很容易看出來:
newScrollX = scrollX + 手動移動距離
其中手指移動距離 = lastX - getX();
所以newScrollX = scrollX + lastX - getX();
然後再看下面的程式碼:
public boolean onTouchEvent(MotionEvent event) {
int scrollX = itemRoot.getScrollX();
int x = (int)event.getX();
if (event.getAction() == MotionEvent.ACTION_MOVE){
int newScrollX = scrollX + mlastX - x;
itemRoot.scrollTo(newScrollX,0);
}
mlastX = x;
return super.onTouchEvent(event);
}
這裡首先通過itemRoot.getScrollX();獲得lin_root上次的視角移動X座標,然後利用int newScrollX = scrollX + mlastX - x;計算出這次應該移動的座標。最後利用itemRoot.scrollTo(newScrollX,0);將lin_root的視角移動到這個位置去。到這裡,大家應該對這個公式的計算就比較理解了,下面我們進一步優化。
3、優化
(1)滑動距離優化
在上面的程式碼中,我們讓lin_root的視角跟隨手指移動,從效果圖中也可以看到,移動距離左右可以移意移動。這明顯不是我們想要的,我們想讓lin_root的視角只能向右移動,顯示出右邊的刪除框,而且不能超過刪除框,所以總結下來就是下面兩點:
- 1、lin_root視角只能向右移動,即newScrollX必須大於0
- 2、lin_root視角向右移動距離不能超過黃色刪除框的寬度
private final int MAX_WIDTH = 200;
private int dipToPx(Context context, int dip) {
return (int) (dip * context.getResources().getDisplayMetrics().density + 0.5f);
}
首先,定義一個向右移動的最大距離,因為在acitivity_main.xml中定義了黃色刪除框的寬度是200dip,所以我們這裡MAX_WIDTH也設為200。在程式碼中我們利用dipToPx()函式將DIP轉換成PX;下面是具體的操作程式碼:public boolean onTouchEvent(MotionEvent event) {
int maxLength = dipToPx(this,MAX_WIDTH);
int scrollX = itemRoot.getScrollX();
int x = (int) event.getX();
if (event.getAction() == MotionEvent.ACTION_MOVE) {
int newScrollX = scrollX + mlastX - x;
if (newScrollX<0){
newScrollX = 0;
}else if (newScrollX >maxLength){
newScrollX = maxLength;
}
itemRoot.scrollTo(newScrollX, 0);
}
mlastX = x;
return super.onTouchEvent(event);
}
先摘出核心的改動程式碼:if (newScrollX<0){
newScrollX = 0;
}else if (newScrollX >maxLength){
newScrollX = maxLength;
}
itemRoot.scrollTo(newScrollX, 0);
從上面的程式碼中可以看到,當newScrollX小於0時,即lin_root向左移動時,這是我們不允許的,所以直接將它復位。當newScrollX >maxLength時,即向右移動的距離超過了黃色框的總寬度,就不應允許他繼續滑動了,所以這時候就應該將它的最大移動距離定死在maxLength的位置。效果圖如下:
(2)、自動收縮、彈出優化
從上面的效果圖中,大家有沒有發現一個問題,滑動太死板了好嗎……想把它停哪就停哪,這也太隨意了點了吧。想想QQ,當用戶滑出一段距離後,它會自動接著往外全部滑出,而不是停那不動!!!反過來,當用戶往裡縮一定距離之後,它會繼續往裡縮,直到回到初始化狀態。就是下面的這個效果:
這裡的改進主要是在當用戶手指擡起的時候,判斷lin_root當前視角的位置,如果已經移動超過黃色塊的1/2,那麼就將黃色塊全部顯示出來,如果沒有超過黃色塊的1/2,那就不移動;看看這裡的改進程式碼:
public boolean onTouchEvent(MotionEvent event) {
int maxLength = dipToPx(this, MAX_WIDTH);
int scrollX = itemRoot.getScrollX();
int x = (int) event.getX();
int newScrollX = scrollX + mlastX - x;
if (event.getAction() == MotionEvent.ACTION_MOVE) {
if (newScrollX < 0) {
newScrollX = 0;
} else if (newScrollX > maxLength) {
newScrollX = maxLength;
}
itemRoot.scrollTo(newScrollX, 0);
}else if(event.getAction() == MotionEvent.ACTION_UP){
if (scrollX > maxLength/2){
newScrollX = maxLength;
}else {
newScrollX = 0;
}
itemRoot.scrollTo(newScrollX, 0);
}
mlastX = x;
return super.onTouchEvent(event);
}
比較以前,這裡新增的程式碼就是下面這幾行:}else if(event.getAction() == MotionEvent.ACTION_UP){
if (scrollX > maxLength/2){
newScrollX = maxLength;
}else {
newScrollX = 0;
}
itemRoot.scrollTo(newScrollX, 0);
}
即當用戶手指提起來的時候,判斷lin_root移動的當前位置,如果大於黃色塊長度的1/2,就直接將移動距離設定為最大移動距離:newScrollX = maxLength;
如果小於黃色塊長度的1/2,就將狀態還原:newScrollX = 0;
好了,到這裡有關移動的問題就結束了,下面就看看在listView中,如何滑動刪除ITEM。原始碼在文章底部給出
二、滑動刪除的ListView
先看看效果圖:
下圖顯示的是,可以把listview中每一個ITEM都可以滑動顯示出刪除框;然後在載入請的ITEM的時候,把新載入的ITEM全部還原到初始化狀態。
1、搭框架
我們先拋開滑動的問題不管,先把listview的顯示框架搭起來。(1)、重寫ListView:(MyListView)
由於我們後面會重寫ListView,所以我們在搭框架的時候就先重寫一個ListView,命名為:MyListViewpublic class MyListView extends ListView {
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
我們在這裡只是重寫,不做任何處理,至於為什麼要重寫,下面會講到 (2)、MainActivity的佈局:(activity_main.xml)
然後是MainActivity的佈局:<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.harvic.com.blog3_1.MyListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent"></com.harvic.com.blog3_1.MyListView>
</RelativeLayout>
這個依然沒什麼難度,就是把我們重寫的listView放進來顯示 (3)Item的佈局:(item_layout.xml)
這裡的佈局採用我們上面的藍色圖塊和黃色圖塊的佈局,由於我們使用的初始化顯示的,所以只會藍色塊會佔滿整個ITEM,黃色塊會被擠到右邊看不到的位置裡。當後面我們滑動的時候,lin_root視角移動,才能看得到黃色塊。這裡只搭框架,先不提視角移動。<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/lin_root"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minHeight="120dp">
<TextView
android:id="@+id/title"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#0000ff"
android:gravity="center"
android:textSize="25dp" />
<TextView
android:id="@+id/del"
android:layout_width="200dp"
android:layout_height="fill_parent"
android:background="#ffff00"
android:text="刪除"
android:textSize="25dp"
android:textColor="#ffffff"
android:gravity="center" />
</LinearLayout>
這裡的佈局採用第一部分:《一、如何讓VIEW跟隨手指反向移動》的佈局,便於大家理解。 (4)、ListView的Adapter:MergeListAdapter
下面就是對應Adapter的程式碼了;首先,定一個ViewHolder來承載ITEM中的各個VIEW,由於我們這裡只需要對第一個textview賦值,所以這裡的ViewHolder也只有一個成員變數TextView title;
private static class ViewHolder {
public TextView title;
}
有了ViewHolder來承載Item中的VIEW檢視,那就需要一個類來承載檢視中應該顯示的資料,所以還會有一個類DataHolder:public static class DataHolder {
public String title;
}
ViewHolder與DataHolder是完全對應的,一個儲存的是ITEM的檢視,一個儲存的是ITEM檢視中的值;然後就是MergeListAdapter的構造函數了:
private Context mContext;
private LayoutInflater mInflater;
private List<DataHolder> mDataList = new ArrayList<DataHolder>();
public MergeListAdapter(Context context, List<DataHolder> dataList){
mContext = context;
mInflater = LayoutInflater.from(context);
if (dataList!=null && dataList.size()>0){
mDataList.addAll(dataList);
}
}
可以看到,這裡有一個專門的陣列dataList來儲存傳過來的每一個ITEM對應的值然後是重寫BaseAdapter都要重寫的向個函式:
@Override
public int getCount() {
return mDataList.size();
}
@Override
public Object getItem(int position) {
return mDataList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
有關 getCount()和getItemId()沒什麼好講的,關鍵問題在於:getItem(int position)!當我們使用listview::getItemAtPosition(position),根據在listview中點選的位置來獲取當前點選的ITEM的時候,就會呼叫我們這裡重寫的 getItem(int position)!所以我們想通過listview::getItemAtPosition(position)得到什麼,我們這裡就可以通過重寫 getItem(int position)返回過去。這一點非常重要,因為,我們要移動某個ITEM的視角,所以必須得到這個ITEM的LinearLayout根佈局lin_root對應的變數,所以我們就可以在getItem(int position)中返回。看不懂也沒關係,下面會再次講一遍。最後,重頭戲來了,下面看看getView()的實現:
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null || convertView.getTag() == null) {
convertView = mInflater.inflate(R.layout.item_layout,parent,false);
holder = new ViewHolder();
holder.title = (TextView)convertView.findViewById(R.id.title);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
DataHolder item = mDataList.get(position);
holder.title.setText(item.title);
return convertView;
}
其實,對於重寫BaseAdapter而言,這裡應該沒什麼難度,下面簡單講一下:首先,獲取convertView檢視:
ViewHolder holder;
if (convertView == null || convertView.getTag() == null) {
convertView = mInflater.inflate(R.layout.item_layout,parent,false);
holder = new ViewHolder();
holder.title = (TextView)convertView.findViewById(R.id.title);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
由於listView的每個ITEM佈局都是完全一樣的,只是裡面的資料可能不同而已,所以為了充分利用資源,google將listview中每個item的view在不再使用時都會重新分配給下一個ITEM使用,有關convertView的回收利用機制參考以前的一篇文章:《BaseAdapter——convertView回收機制與動態控制元件響應》 ,相信大家看完這篇文章以後,應該就明白了為什麼會使用convertView.setTag()和convertView.getTag()了所以,這時候MergeListAdapter的完整程式碼應該是:
public class MergeListAdapter extends BaseAdapter {
private Context mContext;
private LayoutInflater mInflater;
private List<DataHolder> mDataList = new ArrayList<DataHolder>();
public MergeListAdapter(Context context, List<DataHolder> dataList) {
mContext = context;
mInflater = LayoutInflater.from(context);
if (dataList != null && dataList.size() > 0) {
mDataList.addAll(dataList);
}
}
@Override
public int getCount() {
return mDataList.size();
}
@Override
public Object getItem(int position) {
return mDataList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null || convertView.getTag() == null) {
convertView = mInflater.inflate(R.layout.item_layout, parent, false);
holder = new ViewHolder();
holder.title = (TextView) convertView.findViewById(R.id.title);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
DataHolder item = mDataList.get(position);
holder.title.setText(item.title);
return convertView;
}
private static class ViewHolder {
public TextView title;
}
public static class DataHolder {
public String title;
}
}
(5)、MainActivity中設定資料:
public class MainActivity extends Activity {
private MyListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (MyListView)findViewById(R.id.listview);
final List<DataHolder> items = new ArrayList<DataHolder>();
for(int i=0;i<20;i++){
DataHolder item = new DataHolder();
item.title = "第"+i+"項";
items.add(item);
}
MergeListAdapter adapter = new MergeListAdapter(this,items);
listView.setAdapter(adapter);
}
}
在這裡就是構造資料建立MergeListAdapter例項,然後設定到listview中顯示。到這裡的效果圖如下:
2、建立可滑動的ITEM
到這裡我們就需要滑動listView的Item了;先想一下方案:當用戶點選listView的時候,找到當前點選的ITEM,讓它像我們第一部分講的一樣,跟著使用者手指移動就好了。
但這裡涉及到兩個最重要的問題:
- 如何知道當前使用者點選的哪個項
- 即便知道了當前點選的哪一項,但在listview中怎麼移動指定的ITEM呢?
我們主要是在onTouchEvent(MotionEvent event)事件中,利用getItemAtPosition(position),根據當前的點選位置來獲取指定位置的值。基本程式碼如下:
int x = (int) event.getX();
int y = (int) event.getY();
int position = pointToPosition(x, y);
getItemAtPosition(position)
我們前面提到過getItemAtPosition(position)得到的結果就是我們在MergeListAdapter中,重寫getItem()函式時的返回值:public Object getItem(int position) {
return mDataList.get(position);
}
到這裡有沒有靈機一現?既然我們通過getItemAtPosition(position)能夠得到MergeListAdapter中getItem()函式的返回值,那我讓它直接返回指定ITEM的根佈局:lin_root的例項多好!!!!事實上,我們就是這樣獲得指定ITEM的lin_root例項的,只是將它加以變種:由於我們沒辦法直接在我們getItem()中返回指定position的例項,所以我們把lin_root的例項,放在DataHolder中:public static class DataHolder {
public String title;
public LinearLayout rootView;
}
這樣,我們在getItem()中,就可以根據position返回當前ITEM的DataHolder的物件了:public Object getItem(int position) {
return mDataList.get(position);
}
然後在listView中,當用戶下按時,根據位置找到當前的ITEM:private LinearLayout itemRoot;
public class MyListView extends ListView {
…………
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
//我們想知道當前點選了哪一行
int position = pointToPosition(x, y);
if (position != INVALID_POSITION) {
DataHolder data = (DataHolder) getItemAtPosition(position);
itemRoot = data.rootView;
}
}
break;
…………
}
}
這裡的關鍵點在於:根據getItemAtPosition(position)獲得指定位置的DataHolder物件,由於我們把這個ITEM的LinearLayout根結點存在了DataHolder的rootView中,所以在這裡就可以直接得到使用者要滑動的這個ITEM的lin_root的例項了。private LinearLayout itemRoot;
DataHolder data = (DataHolder) getItemAtPosition(position);
itemRoot = data.rootView;
然後就是在使用者移動和擡起手指時的判斷操作了,也就是把第一部分跟隨手指移動檢視的程式碼移過來:public boolean onTouchEvent(MotionEvent event) {
int maxLength = dipToPx(mContext, MAX_WIDTH);
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
//我們想知道當前點選了哪一行
int position = pointToPosition(x, y);
if (position != INVALID_POSITION) {
DataHolder data = (DataHolder) getItemAtPosition(position);
itemRoot = data.rootView;
}
}
case MotionEvent.ACTION_MOVE: {
int scrollX = itemRoot.getScrollX();
int newScrollX = scrollX + mlastX - x;
if (newScrollX < 0) {
newScrollX = 0;
} else if (newScrollX > maxLength) {
newScrollX = maxLength;
}
itemRoot.scrollTo(newScrollX, 0);
}
break;
case MotionEvent.ACTION_UP: {
int scrollX = itemRoot.getScrollX();
int newScrollX = scrollX + mlastX - x;
if (scrollX > maxLength / 2) {
newScrollX = maxLength;
} else {
newScrollX = 0;
}
itemRoot.scrollTo(newScrollX, 0);
}
break;
}
mlastX = x;
return super.onTouchEvent(event);
}
這段程式碼在第一部分已經講了,這裡就不再重複了,到這裡好像是結束了,但,但,但,我們忽略了一個重要的問題:DataHolder裡的rootView什麼時候賦值的呢?當然是在MergeListAdapter中的getView中,因為,MergeListAdapter中唯一一個能根據ID獲得對應VIEW的地方就只有getView()中了:修改後的getView()程式碼如下:public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null || convertView.getTag() == null) {
convertView = mInflater.inflate(R.layout.item_layout, parent, false);
holder = new ViewHolder();
holder.title = (TextView) convertView.findViewById(R.id.title);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
DataHolder item = mDataList.get(position);
holder.title.setText(item.title);
item.rootView = (LinearLayout)convertView.findViewById(R.id.lin_root);
item.rootView.scrollTo(0,0);
return convertView;
}
這裡相比較框架裡的不同在在於最後四句:DataHolder item = mDataList.get(position);
holder.title.setText(item.title);
item.rootView = (LinearLayout)convertView.findViewById(R.id.lin_root);
item.rootView.scrollTo(0,0);
因為我們的DataHolder最基本的任務就是初始化對應ITEM的資料,所以holder.title.setText(item.title);就句才是它的根本任務。而又由於,我們為了獲取lin_root的例項,因而我們不得以在DataHolder類中又新增一個他額外的活:儲存對應ITEM的lin_root例項,所以這裡在初始化完ITEM的值後,就利用下面這句獲取lin_root的例項儲存在DataHolder的rootView中:item.rootView = (LinearLayout)convertView.findViewById(R.id.lin_root);
而又由於,我們使用了convertView的回收機制,所以當用戶滑出來,而沒有還原的情況下,我們如果使用了這個回收的檢視,顯示出來的肯定也是滑出來的ITEM,這就不對了,所以我們要將lin_root的視角設為初始化的(0,0)位置。到這裡就實現了開頭檢視的效果。有關滑動的問題都講完了,大家要跟著工程看講解,不然可能會覺得很迷茫
原始碼在文章底部給出
3、響應ITEM刪除事件
這部分,我們就需要想辦法響應ITEM的刪除事件了,當用戶點選ITEM的刪除的時候,我們要把指定的ITEM刪除掉,就下面的這個效果:
方法一:
大家肯定很清楚的一點是,刪除的意義就是從MergeListAdapter的資料集mDataList中將它刪除即可,所以最簡單的辦法就是在getView()中直接響應點選事件:
public View getView(final int position, View convertView, ViewGroup parent) {
…………
TextView delTv = (TextView) convertView.findViewById(R.id.del);
delTv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mDataList.remove(position);
notifyDataSetChanged();
}
});
return convertView;
}
方法二:
而上面的處理方式是直接在MergeListAdapter中處理資料,這有點感覺不太好,因為在內部直接處理顯示資料,而外部使用Adapter的地方卻不知道,所以我們一般會將資料的處理工作全部放到外面去。而MergeListAdapter只提供處理資料的介面。所以我們在MergeListAdapter中新增一個刪除指定ITEM資料的介面:
public void removeItem(int position){
mDataList.remove(position);
notifyDataSetChanged();
}
然後在getView()中響應:public View getView(final int position, View convertView, ViewGroup parent) {
…………
TextView delTv = (TextView) convertView.findViewById(R.id.del);
delTv.setOnClickListener(mDelClickListener);
return convertView;
}
其中,mDelClickListener是在構造時傳過來的: public MergeListAdapter(Context context, List<DataHolder> dataList, View.OnClickListener delClickListener) {
…………
}
這樣,外部由於實現了delClickListener,所以就可以在外面處理del的點選事件了。public class MainActivity extends Activity implements View.OnClickListener {
private MergeListAdapter adapter;
…………
@Override
public void onClick(View v) {
if (v.getId() == R.id.del){
int position = listView.getPositionForView(v);
adapter.removeItem(position);
}
}
}
在構造MergeListAdapter傳進去OnClickListener,然後在使用者點選ITEM的時候,我們外部進行處理。我們這裡是刪除ITEM,所以我們能在內部處理mDataList,但如果我不是刪除ITEM呢,而是做其它工作呢?那這裡的實現方式無疑是最好的。4、存在問題——引出下文
好了,到這裡有關利用ScrollTo來做滑動刪除的問題已經解決了,但我們這裡有沒有發現一個問題:
大家還記不記得下面的程式碼:
public boolean onTouchEvent(MotionEvent event) {
…………
switch (event.:getAction()) {
…………
case MotionEvent.ACTION_UP: {
int scrollX = itemRoot.getScrollX();
int newScrollX = scrollX + mlastX - x;
if (scrollX > maxLength / 2) {
newScrollX = maxLength;
} else {
newScrollX = 0;
}
itemRoot.scrollTo(newScrollX, 0);
}
break;
}
…………
}
當我們手指擡起的時候,我們會根據當前所處位置直接將lin_root的視角移動到指定的位置——還原或完全展開。但scrollTo(x,y)是直接將lin_root的視角移到指定的位置,而沒有任何的緩衝過程。所以就會顯得非常生硬,試想一下,如果它能有一個移動時間,比如我們設定從當前位置移動目標位置使用10秒,那它在十秒內緩緩移動過去,這樣的感覺是不是很好。而scrollTo(x,y)是根本無法完成這個緩衝效果的,因為它只會一次性顯示在指定位置。所以為了解決這個視角緩緩移動的問題,google又補充了一個類:Scroller,它的出現就是為了解決視角緩慢移動的問題的!!這就是我們下節要講的內容。好了,這篇文章到這裡就結束了,內容比較長,涉及問題也比較多,大家可能要耐著性子看了。
原始碼內容包括:
1、《TryScrollToMotion》:對應第一部分:如何讓VIEW跟隨手指反向移動
2、《ScrollItem_1》:對應第二部分的PART2:建立可滑動的ITEM
3、《ScrollItem_2》:對應第二部分的PART3:響應ITEM刪除事件
如果本文有幫到你,記得加關注哦
參考文章: