編輯:關於Android編程
先上效果圖:
本篇文章我們來學習一個開源項目Android-ItemTouchHelper-Demo
這個項目使用了RecyclerView的ItemTouchHelper類實現了Item的拖動和刪除功能,ItemTouchHelper是v7包下的一個類,我們看一下他的介紹
This is a utility class to add swipe to dismiss and drag & drZ喎?/kf/ware/vc/" target="_blank" class="keylink">vcCBzdXBwb3J0IHRvIFJlY3ljbGVyVmlldy48L3A+DQo8L2Jsb2NrcXVvdGU+DQo8cD7V4srH0ru49rmkvt/A4KOs16jDxdPDwLTF5LrPUmVjeWNsZXJWaWV3yrXP1rustq/JvrP9us3Nz9enuabE3LXEwOA8L3A+DQo8aDIgaWQ9"先搭起一個小框架">先搭起一個小框架
我們從頭開始,一點一點實現最終的功能,首先我們先搭起一個小框架,我們的首頁顯示兩個Item,一個點擊進入ListView形式的RecyclerView;一個點擊進入GridView形式的RecyclerView。
我們先在values/strings.xml中定義一個數組
- List - Basic Drag and Swipe
- Grid - Basic Drag
再創建一個MainFragment繼承自ListFragment
public class MainFragment extends ListFragment { private onListItemClickListener mListItemClickListener; //定義一個回調接口,用來將點擊事件傳回他的宿主Activity去做,Fragment中不做具體的邏輯操作 public interface onListItemClickListener{ void onListItemClick(int position); } public MainFragment(){ } @Override public void onAttach(Context context) { super.onAttach(context); //他的宿主Activity將實現onListItemClickListener接口 //使用getActivity()獲得的宿主Activity,將他強轉成onListItemClickListener接口 mListItemClickListener = (onListItemClickListener)getActivity(); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); //獲得我們在strings.xml中定義個數組 final String[] items = getResources().getStringArray(R.array.main_items); //創建適配器 final ArrayAdapter
adapter = new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1, items); //設置適配器 setListAdapter(adapter); } @Override public void onListItemClick(ListView l, View v, int position, long id) { if (mListItemClickListener!=null){ //由於宿主Activity實現了onListItemClickListener接口 //因此調用的是宿主Activity的onListItemClick方法 //並且將點擊的item的position傳給Activity mListItemClickListener.onListItemClick(position); } } } 我們再創建一個RecyclerListFragment,我們先不做具體的實現,只是先把架子搭起來
public class RecyclerListFragment extends Fragment { public RecyclerListFragment(){} @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return new RecyclerView(container.getContext()); } }
再來一個RecyclerGridFragment
public class RecyclerGridFragment extends Fragment { public RecyclerGridFragment(){} @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return new RecyclerView(container.getContext()); } }
好了,Fragment我們已經准備好了,就差一個宿主Activity了,現在我們就來創建MainActivity,並且實現MainFragment.OnListItemClickListener接口,重寫onListItemClick方法
public class MainActivity extends AppCompatActivity implements MainFragment.onListItemClickListener{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //當savedInstanceState為null時才new一個MainFragment出來 //否則每次旋轉屏幕都會new出來一個 if (savedInstanceState == null){ MainFragment fragment = new MainFragment(); //用add將MainFragment添加到framelayout上 getSupportFragmentManager().beginTransaction() .add(R.id.content,fragment) .commit(); } } @Override public void onListItemClick(int position) { //當MainFragment的Item被點擊後,就會回調此方法 //在此方法中寫真正的邏輯,這樣Activity和Fragment //之間就是松耦合關系,MainFragment可以復用 Fragment fragment = null; switch (position){ case 0: //當點擊第一個item時候,new一個RecyclerListFragment fragment = new RecyclerListFragment(); break; case 1: //當點擊第二個item時候,new一個RecyclerGridFragment fragment = new RecyclerGridFragment(); break; } //這次用replace,替換framelayout的布局,也就是MainFragment getSupportFragmentManager().beginTransaction() .replace(R.id.content,fragment) .addToBackStack(null) .commit(); } }
activity_main.xml
<framelayout android:id="@+id/content" android:layout_height="match_parent" android:layout_width="match_parent" /> 好了,現在我們可以運行一下,運行的結果就是一開始那個截圖的效果,我們點擊item會進入相應的Fragment中,但是現在是空白的,因為我們還沒寫完呢。
為RecyclerView寫Adapter
我們之前使用ListView的時候,數據是靠Adapter適配到ListView上的吧,RecyclerView也是靠Adapter,所以我們先來寫個Adapter吧
public class RecyclerViewAdapter extends RecyclerView.Adapter
{ /**在這裡反射出我們的item的布局*/ @Override public RecyclerViewAdapter.ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return null; } /**在這裡為布局中的控件設置數據*/ @Override public void onBindViewHolder(ItemViewHolder holder, int position) { } /**返回數據個數*/ @Override public int getItemCount() { return 0; } /**相當於ListView中的ViewHolder*/ public static class ItemViewHolder extends RecyclerView.ViewHolder{ public ItemViewHolder(View itemView) { super(itemView); } } } 這就是一個標准的Adapter的結構,接下來我們要逐一完善其中的方法,首先我們先在values/strings.xml中增加我們item的數組
- One
- Two
- Three
- Four
- Five
- Six
- Seven
- Eight
- Nine
- Ten
接著在構造方法中將數據添加到ArrayList中
public RecyclerViewAdapter(Context context){ //初始化數據 mItems.addAll(Arrays.asList(context.getResources().getStringArray(R.array.dummy_items))); }
然後我們再寫我們item的布局文件
接下來是在ItemViewHolder中進行findViewById操作
/**相當於ListView中的ViewHolder*/ public static class ItemViewHolder extends RecyclerView.ViewHolder{ private TextView text; private ImageView handle; public ItemViewHolder(View itemView) { super(itemView); text = (TextView) itemView.findViewById(R.id.text); handle = (ImageView) itemView.findViewById(R.id.handle); } }
然後在onCreateViewHolder中加載出布局,並且完成控件的初始化
/**在這裡反射出我們的item的布局*/ @Override public RecyclerViewAdapter.ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { //利用反射將item的布局加載出來 View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_view,null); //new一個我們的ViewHolder,findViewById操作都在ItemViewHolder的構造方法中進行了 return new ItemViewHolder(view); }
然後在onBindViewHolder中給控件綁定數據
/**在這裡為布局中的控件設置數據*/ @Override public void onBindViewHolder(ItemViewHolder holder, int position) { holder.text.setText(mItems.get(position)); //handle是我們拖動item時候要用的,目前先空著 holder.handle.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return false; } }); }
還有這個方法別忘了
/**返回數據個數*/ @Override public int getItemCount() { return mItems.size(); }
好了我們一個Adapter已經寫完了,然後我們來到RecyclerListFragment中給我們的RecyclerView進行配置
public class RecyclerListFragment extends Fragment { public RecyclerListFragment(){} @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return new RecyclerView(container.getContext()); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); RecyclerViewAdapter adapter = new RecyclerViewAdapter(getActivity()); //參數view即為我們在onCreateView中return的view RecyclerView recyclerView = (RecyclerView)view; //固定recyclerview大小 recyclerView.setHasFixedSize(true); //設置adapter recyclerView.setAdapter(adapter); //設置布局類型為LinearLayoutManager,相當於ListView的樣式 recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); } }
同樣的,我們再來配置RecyclerGridFragment
public class RecyclerGridFragment extends Fragment { public RecyclerGridFragment(){} @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return new RecyclerView(container.getContext()); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); RecyclerViewAdapter adapter = new RecyclerViewAdapter(getActivity()); RecyclerView recyclerView = (RecyclerView)view; recyclerView.setHasFixedSize(true); recyclerView.setAdapter(adapter); //只有這裡和RecyclerListFragment不一樣,這裡我們指定布局為GridView樣式,2列 recyclerView.setLayoutManager(new GridLayoutManager(getActivity(),2)); } }
好了,現在我們可以運行了,這就是recyclerView的使用方法,接下來我們就要為recyclerView添加拖拽和側滑刪除的功能了
實現拖拽和側滑刪除功能
拖拽和側滑刪除的功能我們要借助ItemTouchHelper這個類,我們只需要創建出一個ItemTouchHelper對象,然後調用
mItemTouchHelper.attachToRecyclerView(recyclerView);
就可以了。
我們看一下ItemTouchHelper的構造方法,他需要一個Callbackpublic ItemTouchHelper(Callback callback) { mCallback = callback; }
這個Callback是ItemTouchHelper的內部類,所以我們需要寫一個類繼承自ItemTouchHelper.Callback ,然後重寫裡面的方法
public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback { /**這個方法是用來設置我們拖動的方向以及側滑的方向的*/ @Override public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { return 0; } /**當我們拖動item時會回調此方法*/ @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { return false; } /**當我們側滑item時會回調此方法*/ @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { } }
首先先來完成getMovementFlags方法
/**這個方法是用來設置我們拖動的方向以及側滑的方向的*/ @Override public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { //如果是ListView樣式的RecyclerView if (recyclerView.getLayoutManager() instanceof LinearLayoutManager){ //設置拖拽方向為上下 final int dragFlags = ItemTouchHelper.UP|ItemTouchHelper.DOWN; //設置側滑方向為從左到右和從右到左都可以 final int swipeFlags = ItemTouchHelper.START|ItemTouchHelper.END; //將方向參數設置進去 return makeMovementFlags(dragFlags,swipeFlags); }else{//如果是GridView樣式的RecyclerView //設置拖拽方向為上下左右 final int dragFlags = ItemTouchHelper.UP|ItemTouchHelper.DOWN| ItemTouchHelper.LEFT|ItemTouchHelper.RIGHT; //不支持側滑 final int swipeFlags = 0; return makeMovementFlags(dragFlags,swipeFlags); } }
當item被拖拽或者側滑的時候會回調onMove和onSwiped方法,所以我們需要同時Adapter做出相應的改變,對mItems數據做出交換或者刪除的操作,因此我們需要一個回調接口來繼續回調Adapter中的方法
public interface onMoveAndSwipedListener { boolean onItemMove(int fromPosition , int toPosition); void onItemDismiss(int position); }
我們讓RecyclerViewAdapter實現此接口,並且重寫裡面的兩個方法
public class RecyclerViewAdapter extends RecyclerView.Adapter
implements onMoveAndSwipedListener 重寫兩個方法
@Override public boolean onItemMove(int fromPosition, int toPosition) { //交換mItems數據的位置 Collections.swap(mItems,fromPosition,toPosition); //交換RecyclerView列表中item的位置 notifyItemMoved(fromPosition,toPosition); return true; } @Override public void onItemDismiss(int position) { //刪除mItems數據 mItems.remove(position); //刪除RecyclerView列表對應item notifyItemRemoved(position); }
好了,現在我們再回到我們的SimpleItemTouchHelperCallback,在構造方法中將實現了onMoveAndSwipedListener接口的RecyclerViewAdapter 傳進來
private onMoveAndSwipedListener mAdapter; public SimpleItemTouchHelperCallback(onMoveAndSwipedListener listener){ mAdapter = listener; }
現在我們在onMove和onSwipe方法中調用mAdapter的onItemMove和onItemDismiss方法,就相當於通知adapter去做相應的改變了
/**當我們拖動item時會回調此方法*/ @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { //如果兩個item不是一個類型的,我們讓他不可以拖拽 if (viewHolder.getItemViewType() != target.getItemViewType()){ return false; } //回調adapter中的onItemMove方法 mAdapter.onItemMove(viewHolder.getAdapterPosition(),target.getAdapterPosition()); return true; } /**當我們側滑item時會回調此方法*/ @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { //回調adapter中的onItemDismiss方法 mAdapter.onItemDismiss(viewHolder.getAdapterPosition()); }
好了,現在我們回到RecyclerListFragment中,在onViewCreated方法中添加如下幾行代碼,將ItemTouchHelper和recyclerView關聯起來
@Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); RecyclerViewAdapter adapter = new RecyclerViewAdapter(getActivity()); //參數view即為我們在onCreateView中return的view RecyclerView recyclerView = (RecyclerView)view; //固定recyclerview大小 recyclerView.setHasFixedSize(true); //設置adapter recyclerView.setAdapter(adapter); //設置布局類型為LinearLayoutManager,相當於ListView的樣式 recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); //關聯ItemTouchHelper和RecyclerView ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(adapter); mItemTouchHelper = new ItemTouchHelper(callback); mItemTouchHelper.attachToRecyclerView(recyclerView); }
現在運行一下程序,我們已經可以實現拖拽和側滑刪除的功能了
現在我們為RecyclerGridFragment同樣添加一下關聯代碼
@Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); RecyclerViewAdapter adapter = new RecyclerViewAdapter(getActivity()); RecyclerView recyclerView = (RecyclerView)view; recyclerView.setHasFixedSize(true); recyclerView.setAdapter(adapter); //只有這裡和RecyclerListFragment不一樣,這裡我們指定布局為GridView樣式,2列 recyclerView.setLayoutManager(new GridLayoutManager(getActivity(),2)); ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(adapter); mItemTouchHelper = new ItemTouchHelper(callback); mItemTouchHelper.attachToRecyclerView(recyclerView); }
看一下效果
處理細節
1.拖動圖標即可拖拽整個item
OK,目前我們的功能已經實現了,但是還有一些細節我們需要處理,我們還記得當時我們的item中有一個ImageView對吧,我們想通過點擊ImageView就可以拖拽item,而目前只能通過長按才能夠拖動。
我們回到RecyclerListFragment中,找到剛才我們還空著的ImageView的onTouch方法holder.handle.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return false; } });
在onTouch方法中,我們應該回調RecyclerListFragment類中的mItemTouchHelper,調用mItemTouchHelper的onStartDrag方法,因此我們又需要一個回調接口
public interface onStartDragListener { void startDrag(RecyclerView.Adapter adapter); }
我們讓RecyclerListFragment實現此接口並且重寫startDrag方法
@Override public void startDrag(RecyclerView.ViewHolder viewHolder) { mItemTouchHelper.startDrag(viewHolder); }
我們應該將實現了onStartDragListener接口的RecyclerListFragment對象傳給RecyclerViewAdapter,那麼我們就要在RecyclerViewAdapter的構造方法中添加一個參數
public RecyclerViewAdapter(Context context , onStartDragListener startDragListener){ //初始化數據 mItems.addAll(Arrays.asList(context.getResources().getStringArray(R.array.dummy_items))); mStartDragListener = startDragListener; }
接著在ImageView的onTouch方法中做如下操作
holder.handle.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { //如果按下 if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN){ //回調RecyclerListFragment中的startDrag方法 //讓mItemTouchHelper執行拖拽操作 mStartDragListener.startDrag(holder); } return false; } });
好了,現在我們可以通過拖動item右側的ImageView來拖拽整個item了
2.拖拽item時改變item的背景顏色
我們來到SimpleItemTouchHelperCallback中,重寫onSelectedChanged這個回調方法
/**當狀態改變時回調此方法*/ @Override public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { //當前狀態不是idel(空閒)狀態時,說明當前正在拖拽或者側滑 if (actionState != ItemTouchHelper.ACTION_STATE_IDLE){ //TODO 改變item的背景顏色 } super.onSelectedChanged(viewHolder, actionState); }
改變item的背景顏色我們仍然需要在adapter中去做實際的修改,因此我們還需要一個回調接口,我們已經寫了3個回調接口了
public interface onStateChangedListener { void onItemSelected(); }
我們應該讓誰來實現這個接口並且重寫onItemSelected方法呢?我們看到onSelectedChanged方法中第一個參數是RecyclerView.ViewHolder。 其實在RecyclerView.ViewHolder中有個成員參數itemView,他就是我們item的布局,我們修改item的背景顏色直接修改itemView的背景顏色就可以了,所以我們讓我們的ViewHolder實現這個接口
public static class ItemViewHolder extends RecyclerView.ViewHolder implements onStateChangedListener{ private TextView text; private ImageView handle; public ItemViewHolder(View itemView) { super(itemView); text = (TextView) itemView.findViewById(R.id.text); handle = (ImageView) itemView.findViewById(R.id.handle); } @Override public void onItemSelected() { //設置item的背景顏色為淺灰色 itemView.setBackgroundColor(Color.LTGRAY); } }
我們來完善onSelectedChanged方法
/**當狀態改變時回調此方法*/ @Override public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { //當前狀態不是idel(空閒)狀態時,說明當前正在拖拽或者側滑 if (actionState != ItemTouchHelper.ACTION_STATE_IDLE){ //看看這個viewHolder是否實現了onStateChangedListener接口 if (viewHolder instanceof onStateChangedListener){ onStateChangedListener listener = (onStateChangedListener)viewHolder; //回調ItemViewHolder中的onItemSelected方法來改變item的背景顏色 listener.onItemSelected(); } } super.onSelectedChanged(viewHolder, actionState); }
運行一下看看效果
有點問題,我們發現每個item的背景顏色不會自動變回原來的顏色,所以我們還得再手動改回他的背景顏色,所以我們再在onStateChangedListener接口中添加一個方法,用於當拖拽結束後回調修改item背景顏色public interface onStateChangedListener { void onItemSelected(); void onItemClear(); }
然後在ItemViewHolder中重寫onItemClear方法
@Override public void onItemClear() { //恢復item的背景顏色 itemView.setBackgroundColor(0); }
同時,我們還得在SimpleItemTouchHelperCallback中再重寫一個clearView方法
/**當用戶拖拽完或者側滑完一個item時回調此方法,用來清除施加在item上的一些狀態*/ @Override public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { super.clearView(recyclerView, viewHolder); if (viewHolder instanceof onStateChangedListener){ onStateChangedListener listener = (onStateChangedListener)viewHolder; listener.onItemClear(); } }
我們再來看一下效果
3.側滑刪除時item的顏色逐漸變淺
我們希望在側滑刪除一個item的時候有一種顏色逐漸變淺的效果,這個效果我們要借助SimpleItemTouchHelperCallback的onChildDraw方法
/**這個方法可以判斷當前是拖拽還是側滑*/ @Override public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE){ //根據側滑的位移來修改item的透明度 final float alpha = ALPHA_FULL - Math.abs(dX) / (float) viewHolder.itemView.getWidth(); viewHolder.itemView.setAlpha(alpha); viewHolder.itemView.setTranslationX(dX); } super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); }
我們來看一下效果
結束語
這個項目我們學習完了,通過這個項目我們真的可以學到很多東西,比如Fragment的使用,RecyclerView的使用,ItemTouchHelper的使用,回調接口的使用等等。一個好的項目值得我們去仔細推敲。
效果項目地址DraggableTableView所有Cell都可以拖拽。固定第一個Cell限制長按區域實現原理對UITableView添加LongPress手勢 在lon
(1)文件的目錄 (2)各文件的代碼: FileService.java也就是操作sdcard的工具類: pa
BinaryTree線索化二叉樹>二叉樹是一種非線性結構,在之前實現的二叉樹遍歷中不管是遞歸還是非遞歸用二叉樹作為存儲結構時只能取到該結點的左孩子和右孩子,不能得到
在眾多的社交類軟件中,朋友圈是必不可少的,可以與好友、同學等分享自己的日常和有意思的事情,在開發社交類App時,朋友圈發表的內容你不可能讓他全部顯示,全部顯示的話用戶體驗