首先聲明烈拒,內(nèi)化篇都是本人重寫網(wǎng)絡上的精品文章圆裕,是為了向?qū)W習他人進而內(nèi)化為自己的知識。如果需要引用或者學習請參考原作者原著文章荆几,地址為:http://www.reibang.com/p/70788a7a5547吓妆。再次感謝作者OCNYang提供的精品文章。
主界面是兩個按鈕吨铸,點擊會跳轉(zhuǎn)到相應的不同的界面行拢。
startActivity(new Intent(this,ListViewActivity.class));
很喜歡這種簡潔的寫法,看起來很清爽诞吱。
LIST VIEW跳轉(zhuǎn)
點擊LIST VIEW跳轉(zhuǎn)過來時一個簡單的RecyclerView舟奠,長按會有背景加深顯示,還可以拖拽移動位置房维。省略一些常規(guī)的知識沼瘫,這里用的較少的知識點歸納為一下幾點:
1.在代碼中添加分割線,而不是在xml文件中去操作(添加邊距握巢,或者單獨布局一個view去顯示分割線);
2.item長按可以拖拽移動位置松却;
3.item的長按的背景加深暴浦;
4.item水平滑動可實現(xiàn)刪除溅话。
重點知識點詳解
1.代碼添加分割線
mLinearRecyclerView.addItemDecoration(new DividerListItemDecoration(this,LinearLayoutManager.VERTICAL));
而這個DividerListItemDecoration類是系統(tǒng)沒有的,我們需要自己去創(chuàng)建歌焦,代碼如下:
class DividerListItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
private Drawable mDivider;
private int mOrientation;
public DividerListItemDecoration(Context context, int orientation) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
setOrientation(orientation);
}
public DividerListItemDecoration(Context context, int orientation, int drawableId) {
mDivider = ContextCompat.getDrawable(context, drawableId);
setOrientation(orientation);
}
public void setOrientation(int orientation) {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}
@Override
public void onDraw(Canvas c, RecyclerView parent) {
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
public void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView(parent.getContext());
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
public void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int left = child.getRight() + params.rightMargin;
final int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
}
這個類需要繼承RecyclerView.ItemDecoration飞几。這樣就實現(xiàn)了分割線的添加。
需要在添加分割線后加入下列代碼:
mLinearRecyclerView.setHasFixedSize(true);
因為當我們確定Item的改變不會影響RecyclerView的寬高的時候可以設(shè)置setHasFixedSize(true)独撇,并通過Adapter的增刪改插方法去刷新RecyclerView屑墨,而不是通過notifyDataSetChanged()。(其實可以直接設(shè)置為true纷铣,當需要改變寬高的時候就用notifyDataSetChanged()去整體刷新一下)卵史,這是網(wǎng)上找到的答案,大概意思就是這樣時固定item的寬高搜立,可以避免重復的計算以躯,提高性能,有空深入研究一下啄踊。
2.item長按可以拖拽移動位置
核心代碼
// 方便來控制item的拖拽和滑動刪除
RecycItemTouchHelperCallback itemTouchHelperCallback = new RecycItemTouchHelperCallback(mRecyAdapter);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemTouchHelperCallback);
// 具備了響應相關(guān)touchEvent的能力
itemTouchHelper.attachToRecyclerView(mLinearRecyclerView);
先完整的貼出重要的自定義的類
public class RecycItemTouchHelperCallback extends ItemTouchHelper.Callback {
RecyclerView.Adapter mAdapter;
boolean isSwipeEnable;
boolean isFirstDragUnable;
public RecycItemTouchHelperCallback(RecyAdapter recyAdapter) {
mAdapter = recyAdapter;
isSwipeEnable = true;
isFirstDragUnable = false;
}
public RecycItemTouchHelperCallback(RecyAdapter recyAdapter, boolean isSwipeEnable, boolean isFirstDragUnable) {
mAdapter = recyAdapter;
this.isSwipeEnable = isSwipeEnable;
this.isFirstDragUnable = isFirstDragUnable;
}
// 確定可以移動的方向
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
// 如果是網(wǎng)格就可以上下左右移動
// dragFlags是拖拽標志
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
// swipeFlags是滑動標志忧设,當swipeFlags設(shè)置為0時不考慮滑動相關(guān)的操作
int swipeFlags = 0;
return makeMovementFlags(dragFlags, swipeFlags);
} else {
// 如果是線性只能上下滑動
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
return makeMovementFlags(dragFlags,swipeFlags);
}
}
// 拖拽的過程中不斷調(diào)用的方法
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
int fromPosition = viewHolder.getAdapterPosition();
int toPosition = target.getAdapterPosition();
if (isFirstDragUnable && toPosition == 0) {
return false;
}
if (fromPosition < toPosition) {
for (int i = fromPosition; i < toPosition; i++) {
// 數(shù)據(jù)一般都在adapter中,所以要傳入adapter
Collections.swap(((RecyAdapter) mAdapter).getDataList(),i, i+1); //TODO
}
}else {
for (int i = fromPosition; i > toPosition; i--) {
Collections.swap(((RecyAdapter) mAdapter).getDataList(),i, i-1);
}
}
// 數(shù)據(jù)的更新也需要用到adapter
mAdapter.notifyItemMoved(fromPosition,toPosition);
return true;
}
// 滑動移除的方法
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int adapterPosition = viewHolder.getAdapterPosition();
mAdapter.notifyItemRemoved(adapterPosition);
((RecyAdapter) mAdapter).getDataList().remove(adapterPosition);
}
// 給選中的item添加背景顏色
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
// IDLE閑置的
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
viewHolder.itemView.setBackgroundColor(Color.LTGRAY);
}
super.onSelectedChanged(viewHolder, actionState);
}
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
viewHolder.itemView.setBackgroundColor(Color.WHITE);
}
@Override
public boolean isLongPressDragEnabled() {
return !isFirstDragUnable;
}
@Override
public boolean isItemViewSwipeEnabled() {
return isSwipeEnable;
}
}
實現(xiàn)長按拖拽移動需要重寫getMovementFlags()和onMove()颠通,這兩個方法址晕,其中g(shù)etMovementFlags是為了確認可以移動的方向,需要做判斷顿锰,如果是網(wǎng)格就可以上下左右移動谨垃,如果是線性只能上下滑動。onMove是在移動的過程中不斷被調(diào)用的方法撵儿,其中主要是判斷初始位置中終止位置乘客,讓后做相應的處理。
3.item的長按的背景加深
// 給選中的item添加背景顏色
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
// IDLE閑置的
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
viewHolder.itemView.setBackgroundColor(Color.LTGRAY);
}
super.onSelectedChanged(viewHolder, actionState);
}
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
viewHolder.itemView.setBackgroundColor(Color.WHITE);
}
4.item水平滑動可實現(xiàn)刪除
// 滑動移除的方法
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int adapterPosition = viewHolder.getAdapterPosition();
mAdapter.notifyItemRemoved(adapterPosition);
((RecyAdapter) mAdapter).getDataList().remove(adapterPosition);
}
刪除一個item最重要的就是要先得到這個item的position淀歇,通過getAdapterPosition()得到item的position易核,然后在相應的adapter和集合中都刪除對應position的item即可。
點擊事件
既然是item當然少不了有點擊事件浪默,RecyclerView的api雖然沒有提供onItemClickListener但是提供了addOnItemTouchListener()方法牡直,既然可以添加觸摸監(jiān)聽,那么我們完全可以獲取觸摸手勢來識別點擊事件纳决,然后通過觸摸坐標來判斷點擊的事哪個item碰逸。
直接傳入OnItemTouchListener() 是行不通的,因為我們無法得到item的position阔加,從而也就無法確認是哪一個item被點擊饵史,這里我們就自定義一個類去實現(xiàn)OnItemTouchListener() 這個接口,然后通過GestureDetectorCompat根據(jù)坐標來得到具體的item,從而對相應的UI做出處理胳喷。
//行不通湃番,因為無法得到item的position,無法確定是哪個item
mLinearRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
return false;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
});
下面是自定義的OnRecyclerItemClickListener 類:
public abstract class OnRecyclerItemClickListener implements RecyclerView.OnItemTouchListener {
// 手勢探測器來探測屏幕事件
private GestureDetectorCompat mGestureDetectorCompat;
private RecyclerView mRecyclerView;
public OnRecyclerItemClickListener(RecyclerView recyclerView) {
mRecyclerView = recyclerView;
mGestureDetectorCompat = new GestureDetectorCompat(mRecyclerView.getContext(),new ItemTouchHelperGestureListener());
}
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
mGestureDetectorCompat.onTouchEvent(e);
return false;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
mGestureDetectorCompat.onTouchEvent(e);
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
// 抽象方法吭露,實現(xiàn)類必須要實現(xiàn)
public abstract void onItemClick(RecyclerView.ViewHolder viewHolder);
public abstract void onLongClick(RecyclerView.ViewHolder viewHolder);
// SimpleOnGestureListener來識別手勢事件的種類吠撮,調(diào)用我們相應的回調(diào)方法
private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
// 簡單的點擊
@Override
public boolean onSingleTapUp(MotionEvent e) {
View childViewUnder = mRecyclerView.findChildViewUnder(e.getX(), e.getY());
if (childViewUnder != null) {
RecyclerView.ViewHolder childViewHolder = mRecyclerView.getChildViewHolder(childViewUnder);
onItemClick(childViewHolder);
}
return true;
}
// 長按
@Override
public void onLongPress(MotionEvent e) {
View childViewUnder = mRecyclerView.findChildViewUnder(e.getX(), e.getY());
if (childViewUnder != null ) {
RecyclerView.ViewHolder childViewHolder = mRecyclerView.getChildViewHolder(childViewUnder);
onLongClick(childViewHolder);
}
}
}
}
其中主要是要自定義一個類去繼承GestureDetector.SimpleOnGestureListener(很奇怪,明明起名字是一個Listener這要需要繼承讲竿,很容易引起誤解)泥兰,這里主要是通過MotionEvent來獲取到點擊的坐標,通過坐標得到相應的view题禀,讓后通過這個view找到相應的ViewHolder鞋诗,讓后調(diào)用抽象方法(抽象方法是為了規(guī)定實現(xiàn)類必須實現(xiàn)這個抽象方法)。
在addOnItemTouchListener()中添加自定義的OnRecyclerItemClickListener(通過GestureDetectorCompat 來獲取到item對應的viewHolder從而來操作UI)投剥。
mLinearRecyclerView.addOnItemTouchListener(new OnRecyclerItemClickListener(mLinearRecyclerView) {
// 定義抽象方法是為了讓實現(xiàn)類必須實現(xiàn)這個方法
@Override
public void onItemClick(RecyclerView.ViewHolder viewHolder) {
// 要給相應的view設(shè)置屬性就要先得到擁有該view的ViewHolder
RecyAdapter.ViewHolder viewHolder1 = (RecyAdapter.ViewHolder) viewHolder;
String tvString = viewHolder1.mTextView.getText().toString();
Toast.makeText(ListViewActivity.this,"內(nèi)化后 *- -* 逗逗" + tvString, Toast.LENGTH_SHORT).show();
}
@Override
public void onLongClick(RecyclerView.ViewHolder viewHolder) {
Toast.makeText(ListViewActivity.this,"內(nèi)化后 *- -* 討厭师脂,不要老是摸人家啦。江锨。吃警。", Toast.LENGTH_SHORT).show();
}
});
這時候不僅能夠?qū)崿F(xiàn)拖拽又可以實現(xiàn)點擊彈出吐司(當然也可以根據(jù)具體情況操作UI),如下圖:
但是item的拖拽啄育,水平滑動刪除酌心,和響應點擊事件是有區(qū)別的,如下圖:
Grid View 跳轉(zhuǎn)
GridViewActivity的操作和ListViewActivity大體一致挑豌,并可以復用ListViewActivity其中的方法安券,但是要主要添加分割線的那個類需要自己重新定義,因為網(wǎng)格的分割線是線性的分割線是不同的氓英。我原以為只要有了下面的代碼就可以實現(xiàn)長按拖拽了
RecycItemTouchHelperCallback itemTouchHelperCallback = new RecycItemTouchHelperCallback(mRecyAdapter1,false,true);
final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemTouchHelperCallback);
itemTouchHelper.attachToRecyclerView(mRecyclerView);
因為在ListViewActivity中是這樣的侯勉,拖拽移動和點擊是分開的,但是這里不是的铝阐,如果只是執(zhí)行了上面的代碼址貌,是不能實現(xiàn)長按拖拽的。
必須還要執(zhí)行下面的代碼:
mRecyclerView.addOnItemTouchListener(new OnRecyclerItemClickListener(mRecyclerView) {
@Override
public void onItemClick(RecyclerView.ViewHolder viewHolder) {
RecyAdapter.ViewHolder viewHolder1 = ((RecyAdapter.ViewHolder) viewHolder);
String tvString = viewHolder1.mTextView.getText().toString();
Toast.makeText(GridViewActivity.this,"內(nèi)化后*--*"+tvString,Toast.LENGTH_SHORT).show();
}
@Override
public void onLongClick(RecyclerView.ViewHolder viewHolder) {
Toast.makeText(GridViewActivity.this,"內(nèi)化后*--*,不要老是摸人家了徘键。练对。。",Toast.LENGTH_SHORT).show();
if (viewHolder.getLayoutPosition() != 0) {
//實現(xiàn)長按拖拽
itemTouchHelper.startDrag(viewHolder);
}
}
});
這里必須要在長按的方法中 itemTouchHelper.startDrag(viewHolder)才能實現(xiàn)拖拽的效果吹害。
如效果圖可以實現(xiàn)長按的拖拽移動螟凭,還有相應的UI處理,但是第一個item是固定的不能改變位置的它呀。
@Override
public void onLongClick(RecyclerView.ViewHolder viewHolder) {
Toast.makeText(GridViewActivity.this,"內(nèi)化后*--*,不要老是摸人家了螺男。棒厘。。",Toast.LENGTH_SHORT).show();
if (viewHolder.getLayoutPosition() != 0) {
itemTouchHelper.startDrag(viewHolder);
}
}
一般認為這樣就可以了下隧,其實是不行的绊谭。這是原作者的回答:雖然我們通過上面兩步控制了首個 item 不能被長按拖曳,但是我們并沒有處理汪拥,別的 item 被拖曳到首個 item 的情況。那么如何才能讓首個 item 不被擠掉呢篙耗,這個也很簡單迫筑,只需要在 Callback 的 onMove() 方法中處理首個 item 被當著目標 item 的情況就行了。
// 拖拽的過程中不斷調(diào)用的方法
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
int fromPosition = viewHolder.getAdapterPosition();
int toPosition = target.getAdapterPosition();
// 第一個item不能拖動和改變位置
if (isFirstDragUnable && toPosition == 0) {
return false;
}
if (fromPosition < toPosition) {
for (int i = fromPosition; i < toPosition; i++) {
// 數(shù)據(jù)一般都在adapter中宗弯,所以要傳入adapter
Collections.swap(((RecyAdapter) mAdapter).getDataList(),i, i+1); //TODO
}
}else {
for (int i = fromPosition; i > toPosition; i--) {
Collections.swap(((RecyAdapter) mAdapter).getDataList(),i, i-1);
}
}
// 數(shù)據(jù)的更新也需要用到adapter
mAdapter.notifyItemMoved(fromPosition,toPosition);
return true;
}
大功告成脯燃。
這里我還有疑問,為什么GridViewActivity不能像ListViewActivity那樣不用添加點擊事件就能拖動蒙保,必須要執(zhí)行itemTouchHelper.startDrag(viewHolder);時間原因下次再自習研究辕棚。