ItemTouchHelper的實(shí)踐

頻道管理的功能,在新聞?lì)怉PP是很常見的.頻道管理功能效果圖如下


以前實(shí)現(xiàn)這種功能凛驮,網(wǎng)上有用GridView實(shí)現(xiàn)了這些功能冗美,但是很復(fù)雜溺蕉,而且實(shí)現(xiàn)的功能沒有這個(gè)這么好看该面。那么RecyclerView能不能更簡(jiǎn)單的實(shí)現(xiàn)這項(xiàng)功能呢骗奖?ItemTouchHelper就是一個(gè)很好的item移動(dòng)幫助類善延。這樣就能很好的去實(shí)現(xiàn)這項(xiàng)功能腮出。下面就先來了解下它怎么用。
ItemTouchHelper類
這里我們需要使用的是ItemTouchHelper.Callback這個(gè)抽象類蹦误,它需要用到下面幾個(gè)方法:

boolean isLongPressDragEnabled()

boolean isItemViewSwipeEnabled()

int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder)

boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target)

void onSwiped(RecyclerView.ViewHolder viewHolder, int i)

以上5個(gè)方法都是必須要重寫的劫拢,而下面2個(gè)方法是可選重寫的:

void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState)

void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder)

isLongPressDragEnabled返回的是一個(gè)boolean值肉津,當(dāng)boolean值為true時(shí),下面的makeMovementFlags方法的dragFlags值才會(huì)起效舱沧,它具有上下拖動(dòng)作用妹沙,返回false時(shí)則沒有任何效果。
isItemViewSwipeEnabled返回的也是一個(gè)boolean值熟吏,它和isLongPressDragEnabled類似距糖。不同的是它控制的是左右滑動(dòng)效果。
getMovementFlags方法返回的是一個(gè)int值牵寺,這個(gè)int值主要是makeMovementFlags(int dragFlags, int swipeFlags)方法返回的int值肾筐,其中makeMovementFlags需要傳遞兩個(gè)參數(shù)dragFlags和swipeFlags。dragFlags和swipeFlags是通過下面幾種方式結(jié)合

ItemTouchHelper.UP | ItemTouchHelper.DOWNItemTouchHelper.START |  
ItemTouchHelper.ENDItemTouchHelper.UP | ItemTouchHelper.DOWN|ItemTouchHelper.START | ItemTouchHelper.END

當(dāng)然缸剪,如果我們不需要其中一個(gè)方向的效果吗铐,那么參數(shù)直接傳0值就行了。
onMove方法杏节,主要是拖動(dòng)的時(shí)候唬渗,可以在這里監(jiān)聽進(jìn)行數(shù)據(jù)更新的操作
onSwiped方法,主要是相鄰的item進(jìn)行數(shù)據(jù)交換的數(shù)據(jù)更新奋渔。
onSelectedChanged和clearView主要是長(zhǎng)按操作對(duì)象可以進(jìn)行一些操作镊逝,比如放大縮小操作。

實(shí)現(xiàn)ItemTouchHelper.Callback
了解了ItemTouchHelper后嫉鲸,下面我們來實(shí)現(xiàn)下頻道管理移動(dòng)效果需要用到的類ItemDragHelperCallback
先設(shè)計(jì)兩個(gè)接口來對(duì)ItemTouchHelper.CallBack的事件監(jiān)聽
移動(dòng)交換的數(shù)據(jù)更新監(jiān)聽

 public interface ItemDragListener { 

 void onItemMove(int fromPosition, int toPosition);
 void onItemSwiped(int position);

 }

開始移動(dòng)和結(jié)束移動(dòng)的事件監(jiān)聽

public interface ItemDragVHListener { 

void onItemSelected(); 
void onItemFinished();

}

再結(jié)合上面的介紹來一步一步實(shí)現(xiàn)ItemTouchHelper.CallBack的方法

public class ItemDragHelperCallback extends ItemTouchHelper.Callback { 
 private ItemDragListener mDragMoveListener;
 public ItemDragHelperCallback(ItemDragListener listener) { mDragMoveListener = listener; } 

 @Override 
 public boolean isLongPressDragEnabled() { 
      return false;  
 }

 @Override 
 public boolean isItemViewSwipeEnabled() { 
 return false; 
  }

  @Override 
 public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
 int dragFlags;
 RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); 
 if(layoutManager instanceof GridLayoutManager || layoutManager instanceof StaggeredGridLayoutManager){ 
 dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN|ItemTouchHelper.START | ItemTouchHelper.END; 
 }else{ 
 dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
} 
int swipeFlags = 0; 
return makeMovementFlags(dragFlags, swipeFlags);
}

@Override 
 public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) {
    if(source.getItemViewType()!=target.getItemViewType())
   return false; 
   mDragMoveListener.onItemMove(source.getAdapterPosition(), target.getAdapterPosition());
   return true; } 
 @Override 
 public void onSwiped(RecyclerView.ViewHolder viewHolder, int i) { 
 mDragMoveListener.onItemSwiped(viewHolder.getAdapterPosition());
 }  
 @Override 
 public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { 
   if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) { 
   ItemDragVHListener itemViewHolder = (ItemDragVHListener) viewHolder;
   itemViewHolder.onItemSelected(); 
  } 
super.onSelectedChanged(viewHolder, actionState); 
}
@Override 
 public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {  
  super.clearView(recyclerView, viewHolder);
  ItemDragVHListener itemViewHolder = (ItemDragVHListener) viewHolder;
 itemViewHolder.onItemFinished(); 
 }
 }

這里通過下面幾點(diǎn)解釋下上面的代碼
1撑蒜,首先把監(jiān)聽事件的ItemDragListener 傳遞進(jìn)來, 2玄渗,把isLongPressDragEnabled的isItemViewSwipeEnabled返回改為false座菠,因?yàn)樵谖业姆诸愵l道里面可能前兩個(gè)Tab不能進(jìn)行操作,如果返回true藤树,那么我的分類里面的所有Tab就都可以移動(dòng)了浴滴。就無法實(shí)現(xiàn)這種效果了。返回false的話岁钓,我們后續(xù)可以通過調(diào)用ItemTouchHelper的startDrag方法進(jìn)行拖動(dòng)操作升略, 3、在getMovementFlags方法里面通過控制dragFlags的賦值來決定可移動(dòng)的方向屡限,如果RecyclerView的LayoutManager是GridLayoutManager或者StaggeredGridLayoutManager的話我們就可以上下左右進(jìn)行移動(dòng)品嚣,如果是LinearLayoutManager的話,就只能上下移動(dòng)了钧大。至于swipeFlags翰撑,暫時(shí)沒用到,所以這里直接賦值為0拓型,最后調(diào)用makeMovementFlags(dragFlags, swipeFlags)方法即可额嘿, 4,onMove里面有RecyclerView.ViewHolder source, RecyclerView.ViewHolder target這兩個(gè)參數(shù)劣挫,一個(gè)是操作對(duì)象的ViewHolder册养,一個(gè)是操作對(duì)象移動(dòng)到最終位置對(duì)于的item對(duì)應(yīng)的ViewHolder。這里如果是兩個(gè)不同的ViewHolder压固,我們直接返回false球拦,不對(duì)它進(jìn)行更新數(shù)據(jù)操作,相同的ViewHolder就可以調(diào)用DragMoveListener接口的onItemMove后續(xù)進(jìn)行更新數(shù)據(jù)帐我, 5坎炼,onSelectedChanged方法里面,我們首先通過actionState參數(shù)判斷RecyclerView是否在拖動(dòng)拦键,當(dāng)不在拖動(dòng)的情況下谣光,通過viewHolder參數(shù)獲取ItemDragVHListener接口對(duì)象,然后調(diào)用ItemDragVHListener接口的onItemSelected方法來監(jiān)聽Tab選中狀態(tài)芬为, 6萄金,clearView方法里面,通過viewHolder參數(shù)獲取ItemDragVHListener接口對(duì)象媚朦,然后調(diào)用ItemDragVHListener接口的onItemFinished方法來監(jiān)聽Tab取消選中狀態(tài)氧敢。
實(shí)現(xiàn)了ItemDragHelperCallback 之后,再通過下面幾個(gè)步驟
1询张,創(chuàng)建Callback對(duì)象

ItemDragHelperCallback itemDragHelperCallback = new ItemDragHelperCallback();

2孙乖,創(chuàng)建ItemTouchHelper對(duì)象

ItemTouchHelper touchHelper = new ItemTouchHelper(itemDragHelperCallback);

3,touchHelper綁定對(duì)應(yīng)的RecyclerView

touchHelper.attachToRecyclerView(mRecyclerView);

getItemViewType實(shí)戰(zhàn)
實(shí)現(xiàn)完了上面的ItemDragHelperCallback 對(duì)象之后份氧,接下來我們就應(yīng)該實(shí)現(xiàn)一下這個(gè)UI的基本布局唯袄, 首先它整體是一個(gè)RecyclerView,它可以規(guī)劃為4個(gè)Type:我的分類頭部蜗帜,我的分類越妈,推薦分類頭部,推薦分類钮糖。分析到這里梅掠,我們就可以定義一個(gè)接口,把不同Type模塊代碼分離出來實(shí)現(xiàn)不同的布局

public interface IChannelType {
   //我的頻道頭部部分
  int TYPE_MY_CHANNEL_HEADER = 0; 
   //我的頻道部分 
  int TYPE_MY_CHANNEL = 1; 
  //推薦頭部部分 
 int TYPE_REC_CHANNEL_HEADER = 2; 
  //推薦部分 
  int TYPE_REC_CHANNEL = 3 ;
   ChannelAdapter.ChannelViewHolder createViewHolder(LayoutInflater mInflater, ViewGroup parent);   
   void bindViewHolder(ChannelAdapter.ChannelViewHolder holder, int position, ChannelBean data);
 }

接下來就是4個(gè)模塊都實(shí)現(xiàn)這個(gè)接口店归,然后進(jìn)行布局和數(shù)據(jù)的綁定阎抒,主要部分如下:
我的分類頭部模塊

 public class MyChannelHeaderWidget implements IChannelType { 

 private RecyclerView mRecyclerView;
 private EditModeHandler editModeHandler;
 public MyChannelHeaderWidget(EditModeHandler handler){ 
 this.editModeHandler = handler; 
 } 
 @Override 
 public ChannelAdapter.ChannelViewHolder createViewHolder(LayoutInflater mInflater,           ViewGroup parent) {
  mRecyclerView = (RecyclerView) parent; 

return new  MyChannelHeaderViewHolder(mInflater.inflate(R.layout.activity_channel_my_header,parent,false));
  } 
 @Override 
 public void bindViewHolder(final ChannelAdapter.ChannelViewHolder holder, int position, ChannelBean data) {
  final MyChannelHeaderViewHolder viewHolder = (MyChannelHeaderViewHolder) holder;   

 viewHolder.mEditModeTv.setOnClickListener(new View.OnClickListener() {
  @Override 
 public void onClick(View view) { 
 if(!viewHolder.mEditModeTv.isSelected()){
  if(editModeHandler!=null) editModeHandler.startEditMode(mRecyclerView);  
     viewHolder.mEditModeTv.setText("完成"); }else{ if(editModeHandler!=null)           
 editModeHandler.cancelEditMode(mRecyclerView); 
 viewHolder.mEditModeTv.setText("編輯");
  }
  viewHolder.mEditModeTv.setSelected(!viewHolder.mEditModeTv.isSelected());      }
  }); 
 } 
 public class MyChannelHeaderViewHolder extends ChannelAdapter.ChannelViewHolder{ 
 private TextView mEditModeTv; 
 public MyChannelHeaderViewHolder(View itemView) { 
 super(itemView); 
 mEditModeTv = (TextView) itemView.findViewById(R.id.id_edit_mode);      } 
 }
 }

我的分類模塊

 public class MyChannelWidget implements IChannelType { 
 private RecyclerView mRecyclerView; 
 private EditModeHandler editModeHandler; 
 public MyChannelWidget(EditModeHandler editModeHandler){  
     
 this.editModeHandler = editModeHandler; 
 } 
  @Override 
 public ChannelAdapter.ChannelViewHolder createViewHolder(LayoutInflater mInflater, ViewGroup parent) { mRecyclerView = (RecyclerView) parent; 
 return new MyChannelHeaderViewHolder(mInflater.inflate(R.layout.activity_channel_my,parent,false));
  } 
 @Override 
 public void bindViewHolder(final ChannelAdapter.ChannelViewHolder holder,final int position,final ChannelBean data) { 
 final MyChannelHeaderViewHolder myHolder = (MyChannelHeaderViewHolder) holder;       
myHolder.mChannelTitleTv.setText(data.getTabName()); 
 int textSize = data.getTabName().length()>=4?14:16;  
  myHolder.mChannelTitleTv.setTextSize(TypedValue.COMPLEX_UNIT_SP,textSize);      

 myHolder.mChannelTitleTv.setBackgroundResource(data.getTabType()==0||data.getTabType()==1? R.drawable.channel_fixed_bg_shape:R.drawable.channel_my_bg_shape);
  myHolder.mChannelTitleTv.setTextColor(data.getTabType()==0?Color.RED: data.getTabType()==1?Color.parseColor("#666666"):Color.parseColor("#333333")); 

 myHolder.mDeleteIv.setVisibility(data.getEditStatus()==1? View.VISIBLE:View.INVISIBLE);    
  myHolder.mChannelTitleTv.setOnClickListener(new View.OnClickListener() {
  @Override 
 public void onClick(View view) {   
    if(editModeHandler!=null&&data.getTabType()==2){ 
 editModeHandler.clickMyChannel(mRecyclerView,holder); 
 } 
 } 
 });
  myHolder.mChannelTitleTv.setOnTouchListener(new View.OnTouchListener() { 
 @Override 
 public boolean onTouch(View view, MotionEvent motionEvent) { 
 if(editModeHandler!=null&&data.getTabType()==2){ 
 editModeHandler.touchMyChannel(motionEvent,holder); 
 } 
 return false; }
  }); 
 myHolder.mChannelTitleTv.setOnLongClickListener(new View.OnLongClickListener() { 
 @Override 
 public boolean onLongClick(View view) {      
 if(editModeHandler!=null&&data.getTabType()==2){ 
 editModeHandler.clickLongMyChannel(mRecyclerView,holder); }      
 return true; 
 }
  });
  } 
 public class MyChannelHeaderViewHolder extends ChannelAdapter.ChannelViewHolder{ 
 private TextView mChannelTitleTv; 
 private ImageView mDeleteIv; 
 private MyChannelHeaderViewHolder(View itemView) { 
 super(itemView); 
 mChannelTitleTv = (TextView)itemView.findViewById(R.id.id_channel_title); 
 mDeleteIv = (ImageView) itemView.findViewById(R.id.id_delete_icon);      
 } 
 }
 }

推薦分類頭部模塊和推薦分類代碼和上面的類似,這里就不一一貼出來了消痛,以防代碼過多且叁。
通過查看代碼我們會(huì)發(fā)現(xiàn),我們傳遞了一個(gè)EditModeHandler抽象類出來秩伞,這個(gè)抽象類主要是抽象了各個(gè)模塊的點(diǎn)擊事件逞带,然后在RecyclerView.Adapter里面統(tǒng)一處理欺矫,主要的點(diǎn)擊事件有如下:

 public abstract class EditModeHandler { 
 //開始編輯處理的事件
  public void startEditMode(RecyclerView mRecyclerView){} 
 //取消編輯完成狀態(tài)的事件 
 public void cancelEditMode(RecyclerView mRecyclerView){} 
 //點(diǎn)擊我的分類里面item事件 
 public void clickMyChannel(RecyclerView mRecyclerView,ChannelAdapter.ChannelViewHolder holder){}
  //長(zhǎng)按我的分類里面item事件 
 public void clickLongMyChannel(RecyclerView mRecyclerView,ChannelAdapter.ChannelViewHolder holder){} 
 //手機(jī)觸摸我的分類里面item事件 
 public void touchMyChannel(MotionEvent motionEvent, ChannelAdapter.ChannelViewHolder holder){} 
 //點(diǎn)擊推薦分類里面的item事件 
 public void clickRecChannel(RecyclerView mRecyclerView,ChannelAdapter.ChannelViewHolder holder){}
 }

實(shí)現(xiàn)了各個(gè)模塊布局和數(shù)據(jù)綁定之后,接下來我們要在RecyclerView.Adapter里面把這些模塊通過getItemViewType進(jìn)行綁定展氓。 首先定義一個(gè)SparseArray穆趴,存儲(chǔ)各個(gè)模塊,

 private SparseArray<IChannelType> mTypeMap = new SparseArray();

 mTypeMap.put(IChannelType.TYPE_MY_CHANNEL_HEADER,new MyChannelHeaderWidget(new EditHandler()));
 mTypeMap.put(IChannelType.TYPE_MY_CHANNEL,new MyChannelWidget(new EditHandler()));
 mTypeMap.put(IChannelType.TYPE_REC_CHANNEL_HEADER,new RecChannelHeaderWidget());
 mTypeMap.put(IChannelType.TYPE_REC_CHANNEL,new RecChannelWidget(new EditHandler()));

然后getItemViewType返回不同的類型

 @Override
 public int getItemViewType(int position) { 
 if(position<mMyHeaderCount)
  return IChannelType.TYPE_MY_CHANNEL_HEADER; 
 if(position>=mMyHeaderCount&&position<mMyChannelItems.size()+mMyHeaderCount) 
 return IChannelType.TYPE_MY_CHANNEL;      
 if(position>=mMyChannelItems.size()+mMyHeaderCount&&position<mMyChannelItems.size()+mMyHeaderCount+mRecHeaderCount) 
 return IChannelType.TYPE_REC_CHANNEL_HEADER; return IChannelType.TYPE_REC_CHANNEL;
 }

其中mMyHeaderCount我的分類頭部總量遇汞,這里為1未妹,mMyChannelItems為我的分類里面的Tab數(shù)據(jù),mRecHeaderCount為推薦分類頭部總量空入,這里也為1, 最后我們?cè)僬{(diào)用對(duì)應(yīng)的實(shí)現(xiàn)IChannelType接口模塊的createViewHolder方法和bindViewHolder方法络它。

 @Override 
 public ChannelViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 
 return mTypeMap.get(viewType).createViewHolder(mInflater,parent); 
 }
  @Override 
 public void onBindViewHolder(ChannelViewHolder holder, int position) { 
 if(getItemViewType(position)==IChannelType.TYPE_MY_CHANNEL){ 
 int myPosition = position-mMyHeaderCount;
 myPosition = myPosition<0||myPosition>=mMyChannelItems.size()?0:myPosition;   
 mTypeMap.get(getItemViewType(position)).bindViewHolder(holder,position,mMyChannelItems.get(myPosition)); 
 return;   
    } 
  if(getItemViewType(position)==IChannelType.TYPE_REC_CHANNEL){ 
 int otherPosition = position-mMyChannelItems.size()-mMyHeaderCount-mRecHeaderCount;  
 otherPosition = otherPosition<0||otherPosition>=mOtherChannelItems.size()?0:otherPosition;   
  mTypeMap.get(getItemViewType(position)).bindViewHolder(holder,position,mOtherChannelItems.get(otherPosition));      
 return; 
 } 
 mTypeMap.get(getItemViewType(position)).bindViewHolder(holder,position,null);
  }

到這里,基本的布局就完成了歪赢。
實(shí)現(xiàn)頻道管理效果
首先化戳,我們需要實(shí)現(xiàn)點(diǎn)擊我的分類頭部的“完成/編輯”按鈕,然后切換不同的編輯狀態(tài)埋凯,這里的變化主要是可編輯狀態(tài)時(shí)迂烁,我的分類頭部提示文案修改,以及我的分類Tab增加刪除Icon递鹉,對(duì)應(yīng)的抽象點(diǎn)擊事件為startEditMode和cancelEditMode盟步,所以定義一個(gè)繼承EditModeHandler的類EditHandler,重寫這兩個(gè)事件躏结。代碼如下

 private class EditHandler extends EditModeHandler{ 
 @Override 
 public void startEditMode(RecyclerView mRecyclerView) {   
    doStartEditMode(mRecyclerView);
  } 
 @Override 
 public void cancelEditMode(RecyclerView mRecyclerView) { 
 doCancelEditMode(mRecyclerView); 
 } 
 }
 private void doStartEditMode(RecyclerView parent) {

  isEditMode = true; 
 int visibleChildCount = parent.getChildCount(); 
 for (int i = 0; i < visibleChildCount; i++) { 
 View view = parent.getChildAt(i);
  ImageView imgEdit = (ImageView) view.findViewById(R.id.id_delete_icon); 
 if (imgEdit != null) { ChannelBean item = mMyChannelItems.get(i - mMyHeaderCount); 
 if(item.getTabType() == 2 ){ 
 imgEdit.setVisibility(View.VISIBLE); 
 }else{ 
 imgEdit.setVisibility(View.INVISIBLE); 
 }
 } 
 }
 }


 private void doCancelEditMode(RecyclerView parent) { 
 isEditMode = false;
  int visibleChildCount = parent.getChildCount(); 
 for (int i = 0; i < visibleChildCount; i++) { 
 View view = parent.getChildAt(i); 
 ImageView imgEdit = (ImageView) view.findViewById(R.id.id_delete_icon); 
 if (imgEdit != null) { 
 imgEdit.setVisibility(View.INVISIBLE); 
 } 
 } 
 }

主要是通過RecyclerView獲取RecyclerView的所有子View却盘,然后通過子View查找布局里面id為id_delete_icon的View,如果查找到了媳拴,可編輯狀態(tài)且Tab類型為2(通過定義Tab類型控制我的分類前兩個(gè)Tab永遠(yuǎn)不可編輯)的情況下VISIBLE黄橘,不可編輯狀態(tài)則INVISIBLE
實(shí)現(xiàn)了狀態(tài)切換之后,我們繼續(xù)實(shí)現(xiàn)移除分類的功能屈溉,這項(xiàng)功能對(duì)應(yīng)的抽象點(diǎn)擊事件為clickMyChannel塞关。所以繼續(xù)在EditModeHandler類里面從寫這個(gè)方法

 @Override 
 public void clickMyChannel(RecyclerView mRecyclerView,ChannelAdapter.ChannelViewHolder holder) { 
 RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager(); 
 int position = holder.getAdapterPosition();
  if(isEditMode){ 
 View targetView = layoutManager.findViewByPosition(mMyChannelItems.size() +mMyHeaderCount+mRecHeaderCount); 
 View currentView = mRecyclerView.getLayoutManager().findViewByPosition(position); 
 int targetX ; 
 int targetY; 
 if(mRecyclerView.indexOfChild(targetView)>=0){
  int spanCount = ((GridLayoutManager)layoutManager).getSpanCount();
  targetX = targetView.getLeft();
  targetY = targetView.getTop(); 
 if ((mMyChannelItems.size()) % spanCount == 1) { 
 View preTargetView = layoutManager.findViewByPosition(mMyChannelItems.size() + mMyHeaderCount+mRecHeaderCount - 1); 
 targetX = preTargetView.getLeft(); 
 targetY = preTargetView.getTop(); 
 } }else{ 
 View preTargetView = layoutManager.findViewByPosition(mMyChannelItems.size() + mMyHeaderCount+ mRecHeaderCount- 1);      
 targetX = preTargetView.getLeft();
 targetY = preTargetView.getTop()+preTargetView.getHeight()+APPConst.ITEM_SPACE; 
 } 
 moveMyToOther(position); 
 startAnimation(mRecyclerView, currentView, targetX, targetY); 
 }else{
  if(channelItemClickListener!=null)
 {channelItemClickListener.onChannelItemClick(mMyChannelItems,position-mMyHeaderCount); 
 } 
 }
 }

這個(gè)方法里面通過isEditMode獲取當(dāng)前編輯狀態(tài),如果為不可編輯狀態(tài)子巾,那么點(diǎn)擊我的分類Tab帆赢,我們直接結(jié)束當(dāng)前的DialogFragment,然后切換到首頁(yè)相應(yīng)的Tab對(duì)應(yīng)的頁(yè)面就行了线梗。如果為可編輯狀態(tài)椰于,點(diǎn)擊的話,那就是移除當(dāng)前點(diǎn)擊的Tab仪搔,同時(shí)把移除的Tab添加到推薦分類的第一位瘾婿。直接操作mMyChannelItems和mOtherChannelItems進(jìn)行數(shù)據(jù)源更新然后通過RecyclerView的notifyItemMoved(int fromPosition, int toPosition)是沒有從fromPosition到toPosition移動(dòng)的動(dòng)畫,所以這里再給它添加一個(gè)移動(dòng)的動(dòng)畫,這樣我們就要進(jìn)行動(dòng)畫初始位置和結(jié)束位置的計(jì)算偏陪。計(jì)算過程為 首先獲取當(dāng)前操作的View和移動(dòng)到最終位置也就是推薦分類第一個(gè)Tab的View

 View currentView = mRecyclerView.getLayoutManager().findViewByPosition(position);
 View targetView = layoutManager.findViewByPosition(mMyChannelItems.size() +mMyHeaderCount+mRecHeaderCount);

這樣當(dāng)前位置通過currentView.getLeft()和currentView.getTop()就獲取到了抢呆,而最終位置如果移除后有換行或者targetView不存在的話位置是可變的,所以這里要判斷下targetView是否存在笛谦,如果不存在抱虐,則要通過targetView的前一個(gè)item來計(jì)算最終的位置,計(jì)算代碼如上揪罕。 如果targetView存在的話,那么就要判斷移除后是否會(huì)換行宝泵,如果不換行直接取targetView .getLeft()和targetView .getTop()好啰,如果換行就要取targetView的前一個(gè)item位置。 最后我們需要更新數(shù)據(jù)源

 private void moveMyToOther(int position) { 
 int myPosition = position - mMyHeaderCount;
  ChannelBean item = mMyChannelItems.get(myPosition); 
 mMyChannelItems.remove(myPosition);
  mOtherChannelItems.add(0, item); 
 notifyItemMoved(position, mMyChannelItems.size() + mMyHeaderCount+mRecHeaderCount);
 }

在實(shí)現(xiàn)移動(dòng)的動(dòng)畫之前儿奶,還需要對(duì)當(dāng)前操作的currentView生成鏡像

/** * 我們要獲取cache首先要通過setDrawingCacheEnable方法開啟cache框往,然后再調(diào)用getDrawingCache方法就可以獲得view的cache圖片了。 buildDrawingCache方法可以不用調(diào)用闯捎,因?yàn)檎{(diào)用getDrawingCache方法時(shí)椰弊,若果cache沒有建立,系統(tǒng)會(huì)自動(dòng)調(diào)用buildDrawingCache方法生成cache瓤鼻。 若想更新cache, 必須要調(diào)用destoryDrawingCache方法把舊的cache銷毀秉版,才能建立新的。 當(dāng)調(diào)用setDrawingCacheEnabled方法設(shè)置為false, 系統(tǒng)也會(huì)自動(dòng)把原來的cache銷毀茬祷。
*/

 private ImageView addMirrorView(ViewGroup parent, RecyclerView recyclerView, View view) { 
 view.destroyDrawingCache(); 
 view.setDrawingCacheEnabled(true); 
 final ImageView mirrorView = new ImageView(recyclerView.getContext()); 
 Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache()); 
 mirrorView.setImageBitmap(bitmap); 
 view.setDrawingCacheEnabled(false);
  int[] locations = new int[2]; 
 view.getLocationOnScreen(locations); 
 int[] parenLocations = new int[2]; 
 parent.getLocationOnScreen(parenLocations); 
 RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(bitmap.getWidth(),bitmap.getHeight()); 
 params.setMargins(locations[0], locations[1] - parenLocations[1], 0, 0); 
 parent.addView(mirrorView, params); 
 return mirrorView;
 }

接下來就是實(shí)現(xiàn)動(dòng)畫

 private void startAnimation(RecyclerView recyclerView, final View currentView, float targetX, float targetY) {
  final ViewGroup viewGroup = (ViewGroup) recyclerView.getParent(); 
 final ImageView mirrorView = addMirrorView(viewGroup, recyclerView, currentView); 

 Animation animation = getTranslateAnimator(targetX - currentView.getLeft(), targetY -  currentView.getTop());
  currentView.setVisibility(View.INVISIBLE);

  mirrorView.startAnimation(animation); 
 animation.setAnimationListener(new Animation.AnimationListener() { 

 @Override 
 public void onAnimationStart(Animation animation) { 

 } 
 @Override 
 public void onAnimationEnd(Animation animation) {
  viewGroup.removeView(mirrorView); 
 if (currentView.getVisibility() == View.INVISIBLE) { 
 currentView.setVisibility(View.VISIBLE); 
 } 
 } 
 @Override 
 public void onAnimationRepeat(Animation animation) { 
 } 
 }
 );
 }

 private TranslateAnimation getTranslateAnimator(float targetX, float targetY) { 
 TranslateAnimation translateAnimation = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0f, 
 Animation.ABSOLUTE, targetX, Animation.RELATIVE_TO_SELF, 0f, Animation.ABSOLUTE, targetY);
  // RecyclerView默認(rèn)移動(dòng)動(dòng)畫250ms 這里設(shè)置360ms 
 //是為了防止在位移動(dòng)畫結(jié)束后 remove(view)過早導(dǎo)致閃爍
  translateAnimation.setDuration(360); 
 translateAnimation.setFillAfter(true); 
 return translateAnimation; 
 }

實(shí)現(xiàn)了移除頻道之后清焕,繼續(xù)實(shí)現(xiàn)增加分類的功能,這項(xiàng)功能對(duì)應(yīng)的抽象點(diǎn)擊事件為clickRecChannel祭犯。所以繼續(xù)在EditModeHandler類里面從寫這個(gè)方法

 @Override 
 public void clickRecChannel(RecyclerView mRecyclerView, ChannelViewHolder holder){ 

 GridLayoutManager layoutManager = (GridLayoutManager) mRecyclerView.getLayoutManager(); 
 int position = holder.getAdapterPosition(); 
 View targetView = layoutManager.findViewByPosition(mMyChannelItems.size() +mMyHeaderCount-1);
  View currentView = mRecyclerView.getLayoutManager().findViewByPosition(position);   
  if(mRecyclerView.indexOfChild(targetView)>=0){ int targetX = targetView.getLeft();
  int targetY = targetView.getTop(); int spanCount = layoutManager.getSpanCount();
  View nextTargetView = layoutManager.findViewByPosition(mMyChannelItems.size()           +mMyHeaderCount);
  if (mMyChannelItems.size() % spanCount == 0) { 
 targetX = nextTargetView.getLeft(); 
 targetY = nextTargetView.getTop(); 
 }else{
  targetX += targetView.getWidth() + 2* APPConst.ITEM_SPACE; 
 } moveOtherToMy(position); 
 startAnimation(mRecyclerView, currentView, targetX, targetY);
  }else{ 
 moveOtherToMy(position); 
 } 
 }

  private void moveMyToOther(int position) {
  int myPosition = position - mMyHeaderCount; 
 ChannelBean item = mMyChannelItems.get(myPosition);
  mMyChannelItems.remove(myPosition); mOtherChannelItems.add(0, item); 
 notifyItemMoved(position, mMyChannelItems.size() + mMyHeaderCount+mRecHeaderCount);
  } 
 private void moveOtherToMy(int position) { 
 int recPosition = processItemRemoveAdd(position);
  if (recPosition == -1) { 
 return; 
 } 
 notifyItemMoved(position, mMyChannelItems.size() + mMyHeaderCount-1);
  } 
 private int processItemRemoveAdd(int position) { 
 int startPosition = position - mMyChannelItems.size() - mRecHeaderCount-mMyHeaderCount;
  if (startPosition > mOtherChannelItems.size() - 1) { 
 return -1; 
 } 
 ChannelBean item = mOtherChannelItems.get(startPosition); 
 item.setEditStatus(isEditMode?1:0); 
 mOtherChannelItems.remove(startPosition); 
 mMyChannelItems.add(item);
  return position; 
 }

這個(gè)方法和上面移除頻道類似秸妥,主要不同的就是更新數(shù)據(jù)源不同以及計(jì)算動(dòng)畫起始位置和終點(diǎn)位置計(jì)算不同,更新數(shù)據(jù)源不同的是沃粗,當(dāng)我的分類為可編輯狀態(tài)時(shí)粥惧,我們要改變添加Item的編輯狀態(tài),不可編輯則不用最盅,這里通過改變數(shù)據(jù)源里面的EditStatus來改變編輯狀態(tài)突雪。主要也是終點(diǎn)位置,也就是我的分類最后一個(gè)item的位置的計(jì)算涡贱,如果不換行的話挂签,那就是當(dāng)前targetView的getLeft加上targetView的寬度加上Item之間的間距,就可以計(jì)算出來了盼产,如果換行的話饵婆,那就計(jì)算下一個(gè)item的位置
實(shí)現(xiàn)了增加頻道之后,繼續(xù)實(shí)現(xiàn)改變我的分類Item的順序的功能,這項(xiàng)功能對(duì)應(yīng)的抽象點(diǎn)擊事件不可編輯狀態(tài)的clickLongMyChannel和可編輯狀態(tài)時(shí)的touchMyChannel侨核。所以繼續(xù)在EditModeHandler類里面從寫這兩個(gè)個(gè)方法首先是clickLongMyChannel

 @Override 
 public void clickLongMyChannel(RecyclerView mRecyclerView, ChannelViewHolder holder) { 
 if(!isEditMode){ 
 doStartEditMode(mRecyclerView); 
 View view = mRecyclerView.getChildAt(0); 
 if(view == mRecyclerView.getLayoutManager().findViewByPosition(0)){ 
 TextView dragTip = (TextView) view.findViewById(R.id.id_my_header_tip_tv); 
 dragTip.setText("拖拽可以排序"); 
 TextView tvBtnEdit = (TextView) view.findViewById(R.id.id_edit_mode); 
 tvBtnEdit.setText("完成"); 
 tvBtnEdit.setSelected(true);
  } 
 mItemTouchHelper.startDrag(holder);
  } 
 }

這里方法首先要改變的是我的分類改為可編輯狀態(tài)草穆,以及修改我的分類頭部提示文案,然后就是調(diào)用mItemTouchHelper.startDrag來進(jìn)行拖動(dòng)搓译。
然后就是touchMyChannel

 @Override 
 public void touchMyChannel(MotionEvent motionEvent, ChannelViewHolder holder) {
  if (!isEditMode) {
  return;
  } switch (MotionEventCompat.getActionMasked(motionEvent)) { 
 case MotionEvent.ACTION_DOWN: startTime = System.currentTimeMillis(); 
 break; 
 case MotionEvent.ACTION_MOVE:
  if (System.currentTimeMillis() - startTime > SPACE_TIME) {
  mItemTouchHelper.startDrag(holder);
  } 
 break; 
 case MotionEvent.ACTION_CANCEL: 

 case MotionEvent.ACTION_UP: startTime = 0;
  break; 
 } 
 }

這個(gè)方法主要是當(dāng)手指按下拖拽時(shí)間達(dá)到100ms悲柱,就調(diào)用mItemTouchHelper.startDrag(holder)進(jìn)行拖拽item。
以上兩個(gè)方法的前提是些己,需要再RecyclerView.Adapter里面初始化mItemTouchHelper以及實(shí)現(xiàn)ItemDragListener接口豌鸡。 初始化代碼主要是

 this.mItemTouchHelper = new ItemTouchHelper(new ItemDragHelperCallback(this));
 mItemTouchHelper.attachToRecyclerView(recyclerView);

上文也有提到過,接下來就是在ItemDragListener實(shí)現(xiàn)的兩個(gè)接口方法里面進(jìn)行頻道順序數(shù)據(jù)的更新段标,代碼如下

 @Override 
 public void onItemMove(int fromPosition, int toPosition) {

  if(toPosition > 2){

  ChannelBean item = mMyChannelItems.get(fromPosition - mMyHeaderCount); 
 mMyChannelItems.remove(fromPosition - mMyHeaderCount);
  mMyChannelItems.add(toPosition - mMyHeaderCount, item); 
 notifyItemMoved(fromPosition, toPosition); }

  } 

 @Override 
 public void onItemSwiped(int position) { 

 }

當(dāng)它調(diào)用onItemMove方法的時(shí)候涯冠,我的分類后面2個(gè)item的都進(jìn)行更新。onItemSwiped暫時(shí)沒用到逼庞。
接下來為了item選中狀態(tài)更明顯蛇更,當(dāng)選中的時(shí)候進(jìn)行放大效果,如果取消選中之后則還原赛糟,這個(gè)就要在RecyclerView.ViewHolder實(shí)現(xiàn)ItemDragVHListener派任,在ItemDragVHListener的兩個(gè)方法里面實(shí)現(xiàn)

@Override 
 public void onItemSelected() {
scaleItem(1.0f , 1.2f , 0.5f);
} 

@Override 
 public void onItemFinished() { 
scaleItem(1.2f , 1.0f , 1.0f); 
}

scaleItem的動(dòng)畫為

public void scaleItem(float start , float end , float alpha) { 

ObjectAnimator anim1 = ObjectAnimator.ofFloat(itemView, "scaleX", start, end); 
ObjectAnimator anim2 = ObjectAnimator.ofFloat(itemView, "scaleY", start, end); 
ObjectAnimator anim3 = ObjectAnimator.ofFloat(itemView, "alpha", alpha); 
AnimatorSet animSet = new AnimatorSet();
animSet.setDuration(200); 
animSet.setInterpolator(new LinearInterpolator()); 
animSet.playTogether(anim1, anim2 ,anim3);
animSet.start();

}

這樣我們就實(shí)現(xiàn)了所有的效果,最后看下效果


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末璧南,一起剝皮案震驚了整個(gè)濱河市掌逛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌司倚,老刑警劉巖颤诀,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異对湃,居然都是意外死亡崖叫,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門拍柒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來心傀,“玉大人,你說我怎么就攤上這事拆讯≈校” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵种呐,是天一觀的道長(zhǎng)宰翅。 經(jīng)常有香客問我,道長(zhǎng)爽室,這世上最難降的妖魔是什么汁讼? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上嘿架,老公的妹妹穿的比我還像新娘瓶珊。我一直安慰自己,他們只是感情好耸彪,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布伞芹。 她就那樣靜靜地躺著,像睡著了一般蝉娜。 火紅的嫁衣襯著肌膚如雪唱较。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天召川,我揣著相機(jī)與錄音南缓,去河邊找鬼。 笑死扮宠,一個(gè)胖子當(dāng)著我的面吹牛西乖,可吹牛的內(nèi)容都是我干的狐榔。 我是一名探鬼主播坛增,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼薄腻!你這毒婦竟也來了收捣?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤庵楷,失蹤者是張志新(化名)和其女友劉穎罢艾,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體尽纽,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咐蚯,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了弄贿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片春锋。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖差凹,靈堂內(nèi)的尸體忽然破棺而出期奔,到底是詐尸還是另有隱情,我是刑警寧澤危尿,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布呐萌,位于F島的核電站,受9級(jí)特大地震影響谊娇,放射性物質(zhì)發(fā)生泄漏肺孤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望渠旁。 院中可真熱鬧攀例,春花似錦、人聲如沸顾腊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)杂靶。三九已至梆惯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間吗垮,已是汗流浹背垛吗。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留烁登,地道東北人怯屉。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像饵沧,于是被迫代替她去往敵國(guó)和親锨络。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容