由于项目中需要实现列表可长按条目拖动改变位置的效果,网上有很多成型的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
}