自定义View学习(三)自定义可拖动的RecyclerView

由于项目中需要实现列表可长按条目拖动改变位置的效果,网上有很多成型的demo,写的都非常好,自己下载研究后,自己模仿着写了一个,以下作为整理:

前言
  • 预期效果
    列表形式、长按条目放大动画,拖动修改列表中item位置,松开时候显示缩小动画
  • 思路
    列表使用RecycleView,动画使用ScaleAnimation(缩放动画),拖动功能使用ItemTouchHelper
知识点讲解
  • ScaleAnimation缩放动画的使用
ScaleAnimation mScaleAnimation=new ScaleAnimation(float fromX, 
         float toX, float fromY, float toY,int pivotXType, float pivotXValue, 
         int pivotYType, float pivotYValue)

fromX:动画开始前在X坐标的大小。
toX:动画结束后在X坐标的大小。
fromY:动画开始前在Y坐标的大小。
toY:动画结束后在Y坐标的大小。
pivotXType:缩放中心点的X坐标类型。取值范围为ABSOLUTE、RELATIVE_TO_SELF、 RELATIVE_TO_PARENT。
pivotXValue:缩放中心点的X坐标值。当pivotXType==ABSOLUTE时,表示绝对位置;否则表示相对位置,1.0表示100%。
pivotYType:缩放中心点的Y坐标类型。
pivotYValue:缩放中心点的Y坐标。

Java代码使用:

//放大效果:
 private var startAnimation: ScaleAnimation? = ScaleAnimation(
        1.0f,
        1.1f,
        1.0f,
        1.1f,
        Animation.RELATIVE_TO_SELF,
        0.5f,
        Animation.RELATIVE_TO_SELF,
        0.5f
    )
//从放大恢复至原来的效果
 private var endAnimation: ScaleAnimation? = ScaleAnimation(
        1.1f,
        1.0f,
        1.1f,
        1.0f,
        Animation.RELATIVE_TO_SELF,
        0.5f,
        Animation.RELATIVE_TO_SELF,
        0.5f
    )
  • ItemTouchHelper
    作为RecyclerView的辅助类ItemTouchHelper,在实现侧滑删除和拖动交换位置的时候便展示出举足轻重的作用。
    ItemTouchHelper继承了ItemDecoration
public class ItemTouchHelper extends RecyclerView.ItemDecoration
        implements RecyclerView.OnChildAttachStateChangeListener {
}

ItemTouchHelper的使用:

var mDragItemTouchHelper =ItemTouchHelper(object : ItemTouchHelper.Callback() {

       /**
        * 在此方法里面我们需要构建两个flag,一个是dragFlags,表示拖动效果支持的方向,
        * 另一个是swipeFlags,表示侧滑效果支持的方向。在我们的Demo中,拖动执行上下两个方向,
        * 侧滑执行左右两个方向,这些操作我们都可以在此方法里面定义。
        */
       override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: ViewHolder): Int {
           //上下滑动
           val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
           //向左滑动
           val swipeFlags = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
           //return makeMovementFlags(dragFlags, swipeFlags)
              return makeMovementFlags(dragFlags, 0)//此处禁止侧滑删除
       }

       /**
        * 当拖动效果已经产生了,会回调此方法。在此方法里面,我们通常会更新数据源,
        * 比如说,一个ItemView从0拖到了1位置,那么对应的数据源也需要更改位置。
        */
       override fun onMove(
           recyclerView: RecyclerView,
           viewHolder: ViewHolder,
           target: ViewHolder
       ): Boolean {

           oldPosition=viewHolder.adapterPosition
           toPosition=target.adapterPosition

           return  mItemTouchStatus!!.onItemMove(viewHolder.adapterPosition, target.adapterPosition)
       }

       /**
        * 当侧滑效果以上产生了,会回调此方法。在此方法里面,我们也会更新数据源。
        * 与onMove方法不同到的是,我们在这个方法里面从数据源里面移除相应的数据,
        * 然后调用notifyXXX方法就行了。
        */
       override fun onSwiped(viewHolder: ViewHolder, direction: Int) {
       }

       override fun onSelectedChanged(viewHolder: ViewHolder?, actionState: Int) {
           /**
            * actionState:
            * 1.ACTION_STATE_IDLE表示没有任何手势,此时selected对应的应当是null;
            * 2. ACTION_STATE_SWIPE表示当前ItemView处于侧滑状态;
            * 3. ACTION_STATE_DRAG表示当前ItemView处于拖动状态。
            */
           if (showDragAnimation && actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
               startAnimation(viewHolder!!.itemView)
           }
           super.onSelectedChanged(viewHolder, actionState)
       }

       override fun clearView(recyclerView: RecyclerView, viewHolder: ViewHolder) {
           super.clearView(recyclerView, viewHolder)
           if (showDragAnimation) {
               //操作结束后会走这个方法
               removeView(viewHolder.itemView)
           }
       }

   })
完整代码:

DragRecycleView.java

internal class DragRecycleView : RecyclerView {
    //是否允许拖动
    var dragEnable = true

    //是否显示动画
    var showDragAnimation = true

    //事件监听
    var mItemTouchStatus: ItemTouchStatues? = null

    //记录move位置
    var oldPosition=0
    var toPosition=0


    constructor(context: Context) : super(context) {
        init(context, null)
    }

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        init(context, attrs)
    }

    fun init(context: Context?, attrs: AttributeSet?) {
        if (attrs != null) {//xml方式调用
            var ta = context!!.obtainStyledAttributes(attrs, R.styleable.DragRecyclerView)
            dragEnable = ta.getBoolean(R.styleable.DragRecyclerView_drag_enable, true)
            showDragAnimation = ta.getBoolean(R.styleable.DragRecyclerView_show_decoration, true)
        } else {
            dragEnable = true
            showDragAnimation = true
        }
        mDragItemTouchHelper.attachToRecyclerView(this)
    }

    //设置是否允许拖动
    @JvmName("setDragEnable1")
    fun setDragEnable(b: Boolean) {
        dragEnable = b
    }

    //设置是否显示动画
    @JvmName("setShowDragAnimation1")
    fun setShowDragAnimation(b: Boolean) {
        showDragAnimation = b
    }

    //动画
    private var startAnimation: ScaleAnimation? = ScaleAnimation(
        1.0f,
        1.1f,
        1.0f,
        1.1f,
        Animation.RELATIVE_TO_SELF,
        0.5f,
        Animation.RELATIVE_TO_SELF,
        0.5f
    )
    private var endAnimation: ScaleAnimation? = ScaleAnimation(
        1.1f,
        1.0f,
        1.1f,
        1.0f,
        Animation.RELATIVE_TO_SELF,
        0.5f,
        Animation.RELATIVE_TO_SELF,
        0.5f
    )

    fun startAnimation(v: View) {
        v.animation = startAnimation
        startAnimation!!.fillAfter = true
        startAnimation!!.duration = 200
        startAnimation!!.start()
    }

    fun endAnimation(v: View) {
        v.animation = endAnimation
        endAnimation!!.fillAfter = true
        endAnimation!!.duration = 200
        endAnimation!!.start()
    }


    fun bindEvent(onItemTouchEvent: HoldTouchHelper.OnItemTouchEvent?): DragRecycleView? {
        HoldTouchHelper.bind(this, onItemTouchEvent)
        return this
    }


   var mDragItemTouchHelper =ItemTouchHelper(object : ItemTouchHelper.Callback() {

       /**
        * 在此方法里面我们需要构建两个flag,一个是dragFlags,表示拖动效果支持的方向,
        * 另一个是swipeFlags,表示侧滑效果支持的方向。在我们的Demo中,拖动执行上下两个方向,
        * 侧滑执行左右两个方向,这些操作我们都可以在此方法里面定义。
        */
       override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: ViewHolder): Int {
           //上下滑动
           val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
           //向左滑动
           val swipeFlags = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
           return makeMovementFlags(dragFlags, swipeFlags)
       }

       /**
        * 当拖动效果已经产生了,会回调此方法。在此方法里面,我们通常会更新数据源,
        * 比如说,一个ItemView从0拖到了1位置,那么对应的数据源也需要更改位置。
        */
       override fun onMove(
           recyclerView: RecyclerView,
           viewHolder: ViewHolder,
           target: ViewHolder
       ): Boolean {

           oldPosition=viewHolder.adapterPosition
           toPosition=target.adapterPosition
//           mItemTouchStatus!!.onItemMove(viewHolder.adapterPosition, target.adapterPosition)
//           Log.i("DragRecycleView==",mItemTouchStatus!!.onItemMove(viewHolder.adapterPosition, target.adapterPosition).toString())
           return  mItemTouchStatus!!.onItemMove(viewHolder.adapterPosition, target.adapterPosition)
//           return  mItemTouchStatus!!.onItemMove(viewHolder.adapterPosition, target.adapterPosition)
       }

       /**
        * 当侧滑效果以上产生了,会回调此方法。在此方法里面,我们也会更新数据源。
        * 与onMove方法不同到的是,我们在这个方法里面从数据源里面移除相应的数据,
        * 然后调用notifyXXX方法就行了。
        */
       override fun onSwiped(viewHolder: ViewHolder, direction: Int) {
       }

       override fun onSelectedChanged(viewHolder: ViewHolder?, actionState: Int) {
           /**
            * actionState:
            * 1.ACTION_STATE_IDLE表示没有任何手势,此时selected对应的应当是null;
            * 2. ACTION_STATE_SWIPE表示当前ItemView处于侧滑状态;
            * 3. ACTION_STATE_DRAG表示当前ItemView处于拖动状态。
            */
           if (showDragAnimation && actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
               startAnimation(viewHolder!!.itemView)
           }
           super.onSelectedChanged(viewHolder, actionState)
       }

       override fun clearView(recyclerView: RecyclerView, viewHolder: ViewHolder) {
           super.clearView(recyclerView, viewHolder)
           if (showDragAnimation) {
               //操作结束后会走这个方法
               removeView(viewHolder.itemView)

           }
       }

   })

    fun setDragAdapter(dragBaseAdapter: ItemTouchStatues?): DragRecycleView? {
        if (dragBaseAdapter is Adapter<*>) {
            this.mItemTouchStatus = dragBaseAdapter
            mDragItemTouchHelper.attachToRecyclerView(this)
            super.setAdapter(mItemTouchStatus as Adapter<*>?)
        } else {
            throw IllegalArgumentException()
        }
        return this
    }
}

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <declare-styleable name="DragRecyclerView">
        <attr name="show_decoration" format="boolean"/>
        <attr name="drag_enable" format="boolean"/>
        <attr name="show_drag_animation" format="boolean"/>
    </declare-styleable>
</resources>

ListActivity .kt

class ListActivity : AppCompatActivity() {
    var mDataList: List<Bean>? = null
    private val mLinearLayoutManager: LinearLayoutManager =
        LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_list)
        init()
    }

    fun init() {
        mDataList = getData()
        var mAdapter = RecyclerViewAdapter(this,mDataList)
        recycle.setDragAdapter(mAdapter)
        recycle.layoutManager = mLinearLayoutManager
    }


    fun getData(): List<Bean>? {
        val dataList: ArrayList<Bean> = ArrayList()
        for (i in 0 until 20) {
            var bean = Bean()
            bean.num = "第" + i + "个"
            bean.color = 1
            dataList.add(bean)
        }
        return dataList
    }
}

xml:

    <com.light.mytext.mycustomview.list.DragRecycleView
        android:id="@+id/recycle"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

适配器:

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.RecyclerViewHolder> implements ItemTouchStatues {

    private List<Bean> mDataList;
    private Context mContext;

    public RecyclerViewAdapter(Context context,List<Bean> dataList) {
        mDataList = dataList;
        mContext=context;
    }


    @NonNull
    @Override
    public RecyclerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list, parent, false);
        return new RecyclerViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull final RecyclerViewHolder holder, final int position) {
        holder.mTextView.setText(mDataList.get(position).getNum());
//        holder.itemView.setBackgroundColor(mDataList.get(position).getColor());
    }

    @Override
    public int getItemCount() {
        return mDataList.size();
    }

    @Override
    public boolean onItemMove(int fromPosition, int toPosition) {
//        Collections.swap(mDataList, fromPosition, toPosition);
//        notifyItemMoved(fromPosition, toPosition);
        if (fromPosition < toPosition) {
            // after
            for (int i = fromPosition; i < toPosition; i++) {
                Collections.swap(mDataList, i, i + 1);//数组中更换两个item的位置
            }
        } else {
            // before
            for (int i = fromPosition; i > toPosition; i--) {
                Collections.swap(mDataList, i, i - 1);
            }
        }
        notifyItemMoved(fromPosition, toPosition);
        ToastUtils.show(mContext,"from"+fromPosition+"to"+toPosition);
        return true;
    }

    @Override
    public boolean onItemRemove(int position) {
        mDataList.remove(position);
        notifyItemRemoved(position);
        return true;
    }

    public class RecyclerViewHolder extends RecyclerView.ViewHolder {

        TextView mTextView;

        public RecyclerViewHolder(View itemView) {
            super(itemView);
            mTextView=(TextView) itemView.findViewById(R.id.tv_text);
        }
    }
}


xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/tv_text"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout_marginBottom="10dp"
        android:background="#44ff0000"
        android:gravity="center"
        android:text=""
        android:textColor="#ffffff"
        android:textSize="17sp" />

</LinearLayout>

ItemTouchStatues.java

interface ItemTouchStatues {
    fun onItemMove(fromPosition: Int, toPosition: Int): Boolean
    fun onItemRemove(position: Int): Boolean
}
参考文献

RecyclerView 扩展(二) - 手把手教你认识ItemTouchHelper

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值