效果图
长按进入编辑模式,编辑模式下,我的频道可以拖拽排序,同时,可以移除我的频道栏目,并添加到其他频道中,也可以从其他频道添加进我的频道,同时移除其他频道的该模块。
这个效果,相信在不少app中看到过,这个拖拽排序的实现方式有好几种,如WindowManager,利用他的updateViewLayout,不断更新view的位置。
这里,我们使用RecyclerView的ItemTouchHelper辅助类,来实现
github地址:
https://github.com/livesun/DragSortRecyclerView
ItemTouchHelper
先来写一个简单的Demo来了解ItemTouchHelper类。
效果图
代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/sw"
android:text="切换"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<android.support.v7.widget.RecyclerView
android:id="@+id/slide_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
xml没得说,一个RecyclerView一个Button
activity类
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.slide_activity);
findViewById(R.id.sw).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(!sw){
sw=!sw;
mSlidRecycler.setLayoutManager(new GridLayoutManager(SlideActivity.this,2));
}else{
sw=!sw;
mSlidRecycler.setLayoutManager(new LinearLayoutManager(SlideActivity.this));
}
mAdapter.notifyDataSetChanged();
}
});
mSlidRecycler = (RecyclerView) findViewById(R.id.slide_recycler);
mSlidRecycler.setLayoutManager(new LinearLayoutManager(this));
mAdapter = new MoveAndSwipedAdapter(this);
mSlidRecycler.setAdapter(mAdapter);
mAdapter.addDatas(getDatas());
//创建ItemTouchHelper
ItemTouchHelper touchHelper=new ItemTouchHelper(new ToucheCallBcak(mAdapter));
//attach到RecyclerView中
touchHelper.attachToRecyclerView(mSlidRecycler);
}
private List<String> getDatas(){
List<String> datas=new ArrayList<>();
for(int i=0;i<10;i++){
datas.add(""+i);
}
return datas;
}
这里面,创建ItemTouchHelper的实例,需用ItemTouchHelper.CallBack的实例走位参数,而他是抽象类,需要实现其中默认的三个方法,这里另外多写一个类,来专门实现它,最后不要忘记attchToRecyclerView,把ItemTouchHelper与RecycleView管理起来。
TouchCallBack类
public class ToucheCallBcak extends ItemTouchHelper.Callback {
private IItemHelper itemHelper;
public ToucheCallBcak(IItemHelper itemHelper){
this.itemHelper = itemHelper;
}
//声明不同转台下的移动方向
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
int dragFlags = ItemTouchHelper.UP|ItemTouchHelper.DOWN|ItemTouchHelper.LEFT|ItemTouchHelper.RIGHT;
int swipedFlags = ItemTouchHelper.START|ItemTouchHelper.END;
return makeMovementFlags(dragFlags,swipedFlags);
}
// 拖动的条目从旧位置--到新位置时调用
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
itemHelper.itemMoved(viewHolder.getLayoutPosition(),target.getLayoutPosition());
return false;
}
// 滑动到消失调用
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
itemHelper.itemDismiss(viewHolder.getLayoutPosition());
}
/**
* true --开启长按
* false --关闭长按拖动 默认开启
* @return
*/
@Override
public boolean isLongPressDragEnabled() {
return true;
}
//状态改变时调用 比如正在滑动,正在拖动
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
//不是空闲状态--背景加深
if(actionState!= ItemTouchHelper.ACTION_STATE_IDLE){
viewHolder.itemView.setBackgroundColor(Color.GRAY);
}
}
//滑动完,拖动完调用
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
viewHolder.itemView.setBackgroundColor(0);
}
}
IItemHelper
/**
*
*传递接口
*/
public interface IItemHelper {
void itemMoved(int oldPosition,int newPosition);
void itemDismiss(int position);
}
Adapter类
public class MoveAndSwipedAdapter extends RecyclerView.Adapter<MoveAndSwipedAdapter.MyViewHolder>
implements IItemHelper
{
private Context context;
private final LayoutInflater mInflater;
private List<String> mDatas=new ArrayList<>();
public MoveAndSwipedAdapter(Context context){
this.context = context;
mInflater = LayoutInflater.from(context);
}
public void addDatas(List<String> datas){
mDatas.addAll(datas);
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new MyViewHolder(mInflater.inflate(R.layout.simple_lsit,parent,false));
}
@Override
public void onBindViewHolder(final MyViewHolder holder, int position) {
holder.src.setText(mDatas.get(position));
}
@Override
public int getItemCount() {
return mDatas.size();
}
/**
* 移动
* @param oldPosition
* @param newPosition
*/
@Override
public void itemMoved(int oldPosition, int newPosition) {
Collections.swap(mDatas,oldPosition,newPosition);
notifyItemMoved(oldPosition,newPosition);
}
/**
* 销毁
* @param position
*/
@Override
public void itemDismiss(int position) {
mDatas.remove(position);
notifyItemRemoved(position);
}
public class MyViewHolder extends RecyclerView.ViewHolder{
TextView src;
ImageView imaHandle;
public MyViewHolder(View itemView) {
super(itemView);
src = (TextView) itemView.findViewById(R.id.text);
imaHandle = (ImageView) itemView.findViewById(R.id.handle);
}
}
}
频道切换
文章开始的gif,它是由两种布局组成,一个是title,一个是我们先实现条目展示。
实体类ChannelBean
public class ChannelBean {
//描述
String src;
//分类
int typeView;
public ChannelBean(String src, int typeView) {
this.src = src;
this.typeView = typeView;
}
public String getSrc() {
return src;
}
public void setSrc(String src) {
this.src = src;
}
public int getTypeView() {
return typeView;
}
public void setTypeView(int typeView) {
this.typeView = typeView;
}
}
ChannelActivity
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.channel_activity);
channelRecyler = (RecyclerView) findViewById(R.id.channl_recyler);
mAdapter = new ChannelAdapter(this,getDatas());
GridLayoutManager manager=new GridLayoutManager(this,4);
manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
int itemViewType = mAdapter.getItemViewType(position);
if(itemViewType==mAdapter.CHANNEL_TITLE){
return 4;
}
return 1;
}
});
channelRecyler.setLayoutManager(manager);
channelRecyler.setAdapter(mAdapter);
}
private List<ChannelBean> getDatas(){
List<ChannelBean> beanList=new ArrayList<>();
beanList.add(new ChannelBean("我的频道",1));
for(int i=0;i<20;i++){
beanList.add(new ChannelBean("频道"+i,2));
}
beanList.add(new ChannelBean("其他频道",1));
for(int i=0;i<19;i++){
beanList.add(new ChannelBean("其他"+i,3));
}
return beanList;
}
这里面需要注意的是,通过manager.setSpanSizeLookup的回调方法就可以动态控制条目的多少,从而达到目的。
因为是多条目,我们这里对ViewHolder进行特殊处理,代码如下。
public class ViewHolder extends RecyclerView.ViewHolder {
private SparseArray<View> mViews;
public ViewHolder(View itemView) {
super(itemView);
mViews=new SparseArray();
}
/**
* 获取View方法
* @param id
* @param <T>
* @return
*/
public <T extends View> T getView(@IdRes int id){
View view= mViews.get(id);
if(view==null){
view =itemView.findViewById(id);
mViews.put(id,view);
}
return (T) view;
}
}
ChannelAdapter
public class ChannelAdapter extends RecyclerView.Adapter<ViewHolder> {
//标题
public static final int CHANNEL_TITLE=1;
//我的频道
public static final int MY_CHANNEL=2;
//其他频道
public static final int OTHER_CHANNEL=3;
private final Context mContext;
private List<ChannelBean> mDatas=new ArrayList<>();
private final LayoutInflater mInflater;
public ChannelAdapter(Context context, List<ChannelBean> datas){
this.mContext = context;
this.mDatas = datas;
mInflater = LayoutInflater.from(context);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view=null;
if(viewType==CHANNEL_TITLE){
view = mInflater.inflate(R.layout.channel_title, parent, false);
}else{
view = mInflater.inflate(R.layout.channel_item, parent, false);
}
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
TextView channel = holder.getView(R.id.src);
if(channel!=null){
channel.setText(mDatas.get(position).getSrc());
}
TextView title = holder.getView(R.id.title);
if(title!=null){
title.setText(mDatas.get(position).getSrc());
}
}
@Override
public int getItemCount() {
return mDatas.size();
}
@Override
public int getItemViewType(int position) {
if(mDatas.get(position).getTypeView()==1){
return CHANNEL_TITLE;
}else if(mDatas.get(position).getTypeView()==2){
return MY_CHANNEL;
}else{
return OTHER_CHANNEL;
}
}
}
此刻效果如下
绑定ItemTouchHelper
根据需求,拖拽我们自己处理,所以需要注意的是。
/**
* true --开启长按
* false --关闭长按拖动
* @return
*/
@Override
public boolean isLongPressDragEnabled() {
return false;
}
/**
* 关闭侧滑--默认开启
* @return
*/
@Override
public boolean isItemViewSwipeEnabled() {
return false;
}
在adapter中 设置一个接口OnStartDragListener,通过它来控制,条目的拖拽
private OnStartDragListener onStartDragListener;
public interface OnStartDragListener{
void startDrag(RecyclerView.ViewHolder holder);
}
public void setOnStartDragListener(OnStartDragListener onStartDragListener){
this.onStartDragListener = onStartDragListener;
}
activity中,经行绑定。
//创建ItemTouchHelper
mTouchHelper = new ItemTouchHelper(new ToucheCallBcak(mAdapter));
//attach到RecyclerView中
mTouchHelper.attachToRecyclerView(channelRecyler);
mAdapter.setOnStartDragListener(this);
最终ChannelAdapter的代码如下
public class ChannelAdapter extends RecyclerView.Adapter<ViewHolder> implements IItemHelper {
//标题
public static final int CHANNEL_TITLE=1;
//我的频道
public static final int MY_CHANNEL=2;
//其他频道
public static final int OTHER_CHANNEL=3;
private final Context mContext;
private List<ChannelBean> mDatas=new ArrayList<>();
//我的频道集合。
List<ChannelBean> mMyChannel=new ArrayList<>();
//其他频道集合
List<ChannelBean> mOtherChannel=new ArrayList<>();
private final LayoutInflater mInflater;
private RecyclerView mRecyclerView;
//是否编辑模式
private boolean isEdit;
public ChannelAdapter(Context context, List<ChannelBean> datas,RecyclerView recyclerView){
this.mContext = context;
this.mDatas = datas;
mInflater = LayoutInflater.from(context);
this.mRecyclerView = recyclerView;
for(int i=0;i<mDatas.size();i++){
if(mDatas.get(i).getTypeView()==2){
mMyChannel.add(mDatas.get(i));
}else if(mDatas.get(i).getTypeView()==3){
mOtherChannel.add(mDatas.get(i));
}
}
}
@Override
public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
View view=null;
if(viewType==CHANNEL_TITLE){
view = mInflater.inflate(R.layout.channel_title, parent, false);
}else {
view = mInflater.inflate(R.layout.channel_item, parent, false);
}
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
//是否展示编辑图标
if(isEdit&&position<=mMyChannel.size()){
ImageView imageEdit=holder.getView(R.id.img_edit);
if(imageEdit!=null){
imageEdit.setVisibility(View.VISIBLE);
}
}else {
ImageView imageEdit=holder.getView(R.id.img_edit);
if(imageEdit!=null){
imageEdit.setVisibility(View.GONE);
}
}
TextView channel = holder.getView(R.id.src);
if(channel!=null){
channel.setText(mDatas.get(position).getSrc());
}
TextView title = holder.getView(R.id.title);
if(title!=null){
title.setText(mDatas.get(position).getSrc());
}
//点击事件
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(mDatas.get(holder.getAdapterPosition()).getTypeView()==MY_CHANNEL){
//我的频道执行方法
if(isEdit){
move(holder.getAdapterPosition());
}else{
ToastUtil.showShort(mContext,mDatas.get(holder.getAdapterPosition()).getSrc());
}
}else if(mDatas.get(holder.getAdapterPosition()).getTypeView()==OTHER_CHANNEL){
//其他频道执行方法
if(isEdit){
move(holder.getAdapterPosition());
}else{
ToastUtil.showShort(mContext,mDatas.get(holder.getAdapterPosition()).getSrc());
}
}
}
});
if(mDatas.get(holder.getAdapterPosition()).getTypeView()==MY_CHANNEL){
//长按事件
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
int childCount = mRecyclerView.getChildCount();
childCount=childCount-1-mOtherChannel.size();
if(!isEdit){
//可看的
for (int i = 0; i < childCount; i++) {
View view = mRecyclerView.getChildAt(i);
ImageView imgEdit = (ImageView) view.findViewById(R.id.img_edit);
if (imgEdit != null) {
imgEdit.setVisibility(View.VISIBLE);
}
}
isEdit=true;
// 开启拖动
if(onStartDragListener!=null){
onStartDragListener.startDrag(holder);
}
}else{
for (int i = 0; i < childCount; i++) {
View view = mRecyclerView.getChildAt(i);
ImageView imgEdit = (ImageView) view.findViewById(R.id.img_edit);
if (imgEdit != null) {
imgEdit.setVisibility(View.GONE);
}
}
isEdit=false;
}
return true;
}
});
//编辑状态下 排序
holder.itemView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if(isEdit){
switch (event.getAction()){
case MotionEvent.ACTION_MOVE:
if(onStartDragListener!=null){
onStartDragListener.startDrag(holder);
}
break;
}
}
return false;
}
});
}
}
@Override
public int getItemCount() {
return mDatas.size();
}
@Override
public int getItemViewType(int position) {
if(mDatas.get(position).getTypeView()==1){
return CHANNEL_TITLE;
}else if(mDatas.get(position).getTypeView()==2){
return MY_CHANNEL;
}else{
return OTHER_CHANNEL;
}
}
/**
* 条目移动
* @param position
*/
private void move(int position) {
if(position>mMyChannel.size()+1){
int i = position - 2 - mMyChannel.size();
//其他
ChannelBean item = mOtherChannel.get(position-2-mMyChannel.size());
mOtherChannel.remove(item);
item.setTypeView(2);
mMyChannel.add(item);
//这里实现的不择手段了,,之后再改改
notifyData();
notifyItemMoved(position, mMyChannel.size());
}else if(position>0&&position<=mMyChannel.size()){
//我的
ChannelBean item = mMyChannel.get(position-1);
mMyChannel.remove(item);
item.setTypeView(3);
mOtherChannel.add(0, item);
notifyData();
notifyItemMoved(position, mMyChannel.size() + 2);
}
}
@Override
public void itemMoved(int oldPosition, int newPosition) {
ChannelBean channelBean = mMyChannel.get(oldPosition - 1);
mMyChannel.remove(oldPosition - 1);
mMyChannel.add(newPosition-1,channelBean);
notifyData();
notifyItemMoved(oldPosition, newPosition);
}
private void notifyData(){
//这里实现的不择手段了,,之后再改改
mDatas.clear();
mDatas.add(new ChannelBean("我的频道",1));
mDatas.addAll(mMyChannel);
mDatas.add(new ChannelBean("其他频道",1));
mDatas.addAll(mOtherChannel);
}
@Override
public void itemDismiss(int position) {
}
private OnStartDragListener onStartDragListener;
public interface OnStartDragListener{
void startDrag(RecyclerView.ViewHolder holder);
}
public void setOnStartDragListener(OnStartDragListener onStartDragListener){
this.onStartDragListener = onStartDragListener;
}
}
这里需要注意的是:获取位置,不要直接拿position,通过holder.getAdapterPosition来获取条目的位置,以确保条目位置的准确性。移动动画则是通过notifyItemMoved来实现,没什么好说的。