首先声明,内化篇都是本人重写网络上的精品文章,是为了向学习他人进而内化为自己的知识。如果需要引用或者学习请参考原作者原著文章,地址为:http://www.jianshu.com/p/70788a7a5547。再次感谢作者OCNYang提供的精品文章。
主界面是两个按钮,点击会跳转到相应的不同的界面。
startActivity(new Intent(this,ListViewActivity.class));
很喜欢这种简洁的写法,看起来很清爽。
LIST VIEW跳转
点击LIST VIEW跳转过来时一个简单的RecyclerView,长按会有背景加深显示,还可以拖拽移动位置。省略一些常规的知识,这里用的较少的知识点归纳为一下几点:
1.在代码中添加分割线,而不是在xml文件中去操作(添加边距,或者单独布局一个view去显示分割线);
2.item长按可以拖拽移动位置;
3.item的长按的背景加深;
4.item水平滑动可实现删除。
重点知识点详解
1.代码添加分割线
mLinearRecyclerView.addItemDecoration(new DividerListItemDecoration(this,LinearLayoutManager.VERTICAL));
而这个DividerListItemDecoration类是系统没有的,我们需要自己去创建,代码如下:
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。这样就实现了分割线的添加。
需要在添加分割线后加入下列代码:
mLinearRecyclerView.setHasFixedSize(true);
因为当我们确定Item的改变不会影响RecyclerView的宽高的时候可以设置setHasFixedSize(true),并通过Adapter的增删改插方法去刷新RecyclerView,而不是通过notifyDataSetChanged()。(其实可以直接设置为true,当需要改变宽高的时候就用notifyDataSetChanged()去整体刷新一下),这是网上找到的答案,大概意思就是这样时固定item的宽高,可以避免重复的计算,提高性能,有空深入研究一下。
2.item长按可以拖拽移动位置
核心代码
// 方便来控制item的拖拽和滑动删除
RecycItemTouchHelperCallback itemTouchHelperCallback = new RecycItemTouchHelperCallback(mRecyAdapter);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemTouchHelperCallback);
// 具备了响应相关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) {
// 如果是网格就可以上下左右移动
// dragFlags是拖拽标志
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
// swipeFlags是滑动标志,当swipeFlags设置为0时不考虑滑动相关的操作
int swipeFlags = 0;
return makeMovementFlags(dragFlags, swipeFlags);
} else {
// 如果是线性只能上下滑动
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
return makeMovementFlags(dragFlags,swipeFlags);
}
}
// 拖拽的过程中不断调用的方法
@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++) {
// 数据一般都在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);
}
}
// 数据的更新也需要用到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;
}
}
实现长按拖拽移动需要重写getMovementFlags()和onMove(),这两个方法,其中getMovementFlags是为了确认可以移动的方向,需要做判断,如果是网格就可以上下左右移动,如果是线性只能上下滑动。onMove是在移动的过程中不断被调用的方法,其中主要是判断初始位置中终止位置,让后做相应的处理。
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水平滑动可实现删除
// 滑动移除的方法
@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()方法,既然可以添加触摸监听,那么我们完全可以获取触摸手势来识别点击事件,然后通过触摸坐标来判断点击的事哪个item。
直接传入OnItemTouchListener() 是行不通的,因为我们无法得到item的position,从而也就无法确认是哪一个item被点击,这里我们就自定义一个类去实现OnItemTouchListener() 这个接口,然后通过GestureDetectorCompat根据坐标来得到具体的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) {
}
// 抽象方法,实现类必须要实现
public abstract void onItemClick(RecyclerView.ViewHolder viewHolder);
public abstract void onLongClick(RecyclerView.ViewHolder viewHolder);
// SimpleOnGestureListener来识别手势事件的种类,调用我们相应的回调方法
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,让后调用抽象方法(抽象方法是为了规定实现类必须实现这个抽象方法)。
在addOnItemTouchListener()中添加自定义的OnRecyclerItemClickListener(通过GestureDetectorCompat 来获取到item对应的viewHolder从而来操作UI)。
mLinearRecyclerView.addOnItemTouchListener(new OnRecyclerItemClickListener(mLinearRecyclerView) {
// 定义抽象方法是为了让实现类必须实现这个方法
@Override
public void onItemClick(RecyclerView.ViewHolder viewHolder) {
// 要给相应的view设置属性就要先得到拥有该view的ViewHolder
RecyAdapter.ViewHolder viewHolder1 = (RecyAdapter.ViewHolder) viewHolder;
String tvString = viewHolder1.mTextView.getText().toString();
Toast.makeText(ListViewActivity.this,"内化后 *- -* 逗逗" + tvString, Toast.LENGTH_SHORT).show();
}
@Override
public void onLongClick(RecyclerView.ViewHolder viewHolder) {
Toast.makeText(ListViewActivity.this,"内化后 *- -* 讨厌,不要老是摸人家啦。。。", Toast.LENGTH_SHORT).show();
}
});
这时候不仅能够实现拖拽又可以实现点击弹出吐司(当然也可以根据具体情况操作UI),如下图:
但是item的拖拽,水平滑动删除,和响应点击事件是有区别的,如下图:
Grid View 跳转
GridViewActivity的操作和ListViewActivity大体一致,并可以复用ListViewActivity其中的方法,但是要主要添加分割线的那个类需要自己重新定义,因为网格的分割线是线性的分割线是不同的。我原以为只要有了下面的代码就可以实现长按拖拽了
RecycItemTouchHelperCallback itemTouchHelperCallback = new RecycItemTouchHelperCallback(mRecyAdapter1,false,true);
final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemTouchHelperCallback);
itemTouchHelper.attachToRecyclerView(mRecyclerView);
因为在ListViewActivity中是这样的,拖拽移动和点击是分开的,但是这里不是的,如果只是执行了上面的代码,是不能实现长按拖拽的。
必须还要执行下面的代码:
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,"内化后*--*"+tvString,Toast.LENGTH_SHORT).show();
}
@Override
public void onLongClick(RecyclerView.ViewHolder viewHolder) {
Toast.makeText(GridViewActivity.this,"内化后*--*,不要老是摸人家了。。。",Toast.LENGTH_SHORT).show();
if (viewHolder.getLayoutPosition() != 0) {
//实现长按拖拽
itemTouchHelper.startDrag(viewHolder);
}
}
});
这里必须要在长按的方法中 itemTouchHelper.startDrag(viewHolder)才能实现拖拽的效果。
如效果图可以实现长按的拖拽移动,还有相应的UI处理,但是第一个item是固定的不能改变位置的。
@Override
public void onLongClick(RecyclerView.ViewHolder viewHolder) {
Toast.makeText(GridViewActivity.this,"内化后*--*,不要老是摸人家了。。。",Toast.LENGTH_SHORT).show();
if (viewHolder.getLayoutPosition() != 0) {
itemTouchHelper.startDrag(viewHolder);
}
}
一般认为这样就可以了,其实是不行的。这是原作者的回答:虽然我们通过上面两步控制了首个 item 不能被长按拖曳,但是我们并没有处理,别的 item 被拖曳到首个 item 的情况。那么如何才能让首个 item 不被挤掉呢,这个也很简单,只需要在 Callback 的 onMove() 方法中处理首个 item 被当着目标 item 的情况就行了。
// 拖拽的过程中不断调用的方法
@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++) {
// 数据一般都在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);
}
}
// 数据的更新也需要用到adapter
mAdapter.notifyItemMoved(fromPosition,toPosition);
return true;
}
大功告成。
这里我还有疑问,为什么GridViewActivity不能像ListViewActivity那样不用添加点击事件就能拖动,必须要执行itemTouchHelper.startDrag(viewHolder);时间原因下次再自习研究。