頻道管理的功能,在新聞?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)了所有的效果,最后看下效果