学更好的别人,
做更好的自己。
——《微卡智享》
本文长度为3489字,预计阅读9分钟
前言
原来产品中有个功能要实现宫格中库位的移库效果,以前一直没做这块,也是为了先赶产品,所以没有做实现的拖拽效果,最近正好有时间,研究了一下DragAndDrop,做了一个Demo验证了一下,效果还是挺不错的,本篇就做一个分享。
今天这篇是传统的DragAndDrop效果实现,在JetPack库中新的dragandrop做的优化,要简单的很多,下一篇会说到,并做一下两个对比。
实现效果
DragAndDrop框架
微卡智享
在Android Level11后就增加了DragAndDrop拖拽框架,可以在界面中实现两个View的数据转换,具体的实现需要增加一个拖拽的事件,一个拖拽的监听器。
拖拽函数startDragAndDrop
拖拽功能通过View.startDragAndDrop来实现
public final boolean startDragAndDrop(ClipData data, View.DragShadowBuilder shadowBuilder, Object myLocalState, int flags) {
throw new RuntimeException("Stub!");
}
参数:
ClipData:用于存储传送的数据
DragShadowBuilder:拖动时展示的阴影
myLocalState:这个参数可以用作Activity内部一种轻量级的数据传输机制。监听方通过DragEvent#getLocalState()方法来获取数据。它不能跨Activity,如果在其他Activity调用getLocalState()方法会返回null
flags:设置为0表示不设置flag。
DRAG_FLAG_GLOBAL 表示可以跨window拖拽,典型的是分屏状态下的拖拽
DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION 通常跟DRAG_FLAG_GLOBAL_URI_READ和DRAG_FLAG_GLOBAL_URI_WRITE配合使用,用来在设备重启时保持权限。直到显示地调用Context.revokeUriPermission(这里我也没懂什么意思)
DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION 通常跟DRAG_FLAG_GLOBAL_URI_READ和DRAG_FLAG_GLOBAL_URI_WRITE配合使用,the URI permission grant applies to any URI that is a prefix match against the original granted URI。
DRAG_FLAG_GLOBAL_URI_READ 与DRAG_FLAG_GLOBAL一起使用,接收者将能够请求对包含在ClipData对象中的内容URI的读访问。
DRAG_FLAG_GLOBAL_URI_WRITE 与DRAG_FLAG_GLOBAL一起使用,接收者将能够请求对包含在ClipData对象中的内容URI的写访问。
DRAG_FLAG_OPAQUE 使拖动的阴影不透明。
监听事件OnDragListener
OnDragListener主要用于监听拖拽事件,当前可见的View都可以进行监听。
监听Drag的流程:
上面的流程通过Drag events事件来捕获,其中每个状态中还包含其他依赖于事件动作类型的数据,具体如下:
上面是简单的DragandDrop的介绍 ,接下来代码实现。
代码实现
微卡智享
01
build.gradle添加依赖项
implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4'
implementation "androidx.draganddrop:draganddrop:1.0.0-alpha03"
文章开头用的宫格列表,所以用的BaseRecyclerviewadapter了,然后draganddrop直接用的是jetpack库中alpha03的最新版,要注意这个要求minsdk最小是24。
02
布局文件及定义类
一个main中加入了一个按钮和recyclerview,另一个是每个宫格的布局。
定义CDrugs类
package pers.vaccae.draganddropdemo.bean
import android.os.Parcel
import android.os.Parcelable
/**
* 作者:Vaccae
* 邮箱:3657447@qq.com
* 创建时间:14:26
* 功能模块说明:
*/
class CDrugs() : Parcelable {
//货格号
var drugs_ckcode = 0
//药品编码
var drugs_code = ""
//药品名称
var drugs_name = ""
//药品规格
var drugs_specs = ""
//药品数量
var qty = 0;
constructor(parcel: Parcel) : this() {
drugs_ckcode = parcel.readInt()
drugs_code = parcel.readString()!!
drugs_name = parcel.readString()!!
drugs_specs = parcel.readString()!!
qty = parcel.readInt()
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(drugs_ckcode)
parcel.writeString(drugs_code)
parcel.writeString(drugs_name)
parcel.writeString(drugs_specs)
parcel.writeInt(qty)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<CDrugs> {
override fun createFromParcel(parcel: Parcel): CDrugs {
return CDrugs(parcel)
}
override fun newArray(size: Int): Array<CDrugs?> {
return arrayOfNulls(size)
}
}
}
03
编写适配器adapter
由于要每个宫格实现拽拖替换,所以在适配器中每个宫格都要加上拖拽的动作和监听。
在每个itemview中设置tag记录当前在adapter中的position,通过设置onDragListener来捕获监听事件,长按OnLongClick来实现拖拽。
在ClipData数据中,我们通过Intent传递,直接用ClipData.newIntent的方法实现,传入的是原来的位置,这样直接从列表中定位到对应序号就可以查到数据。
package pers.vaccae.draganddropdemo.adapter
import android.content.ClipData
import android.content.Intent
import android.view.DragEvent
import android.view.View
import androidx.core.view.DragStartHelper
import androidx.draganddrop.DropHelper
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.viewholder.BaseViewHolder
import pers.vaccae.draganddropdemo.R
import pers.vaccae.draganddropdemo.bean.CDrugs
/**
* 作者:Vaccae
* 邮箱:3657447@qq.com
* 创建时间:14:49
* 功能模块说明:
*/
class DrugsAdapter(layoutId: Int, drugslist: MutableList<CDrugs>? = null) :
BaseQuickAdapter<CDrugs, BaseViewHolder>(layoutId, drugslist) {
var mDragEventListener = DragEventListener(this)
override fun convert(holder: BaseViewHolder, item: CDrugs) {
holder.setText(R.id.rcl_drugs_ckcode, item.drugs_ckcode.toString())
holder.setText(R.id.rcl_drugs_code, item.drugs_code)
holder.setText(R.id.rcl_drugs_name, item.drugs_name)
holder.setText(R.id.rcl_drugs_specs, item.drugs_specs)
holder.setText(R.id.rcl_qty, item.qty.toString())
holder.itemView.setTag(holder.adapterPosition)
holder.itemView.setOnDragListener(mDragEventListener)
holder.itemView.setOnLongClickListener {
//定义Intent
val intent = Intent()
intent.putExtra("pos", holder.adapterPosition)
val dragdata = ClipData.newIntent("olditem", intent)
val shadow: View.DragShadowBuilder = View.DragShadowBuilder(it)
it.startDragAndDrop(dragdata, shadow, null, 0)
}
}
}
监听事件
写一个OnDragListener的事件,前面的流程图中可以看到,Droped的事件是用户手指在一个View的范围内松开拖拽影子的时候可以接受拖拽数据,所以事件中数据的替换直接在Droped中处理即可。
package pers.vaccae.draganddropdemo.adapter
import android.view.DragEvent
import android.view.View
import pers.vaccae.draganddropdemo.bean.CDrugs
/**
* 作者:Vaccae
* 邮箱:3657447@qq.com
* 创建时间:09:25
* 功能模块说明:
*/
class DragEventListener(adapter: DrugsAdapter) : View.OnDragListener {
var mAdapter = adapter
override fun onDrag(p0: View?, p1: DragEvent?): Boolean {
p1?.let {
when (it.action) {
DragEvent.ACTION_DROP -> {
val intent = it.clipData.getItemAt(0).intent
val oldpos = intent.getIntExtra("pos", -1)
//记录现在格的数据
val nowpos = p0?.getTag() as Int
//将现在格数据存到临时变量中
val nowitem = CDrugs()
nowitem.drugs_ckcode = mAdapter.data[nowpos].drugs_ckcode
nowitem.drugs_code = mAdapter.data[nowpos].drugs_code
nowitem.drugs_name = mAdapter.data[nowpos].drugs_name
nowitem.drugs_specs = mAdapter.data[nowpos].drugs_specs
nowitem.qty = mAdapter.data[nowpos].qty
//修改现在格的数据
mAdapter.data[nowpos].drugs_ckcode = nowitem.drugs_ckcode;
mAdapter.data[nowpos].drugs_code = mAdapter.data[oldpos].drugs_code
mAdapter.data[nowpos].drugs_name = mAdapter.data[oldpos].drugs_name
mAdapter.data[nowpos].drugs_specs = mAdapter.data[oldpos].drugs_specs
mAdapter.data[nowpos].qty = mAdapter.data[oldpos].qty
//修改原来的格数据
mAdapter.data[oldpos].drugs_code = nowitem.drugs_code
mAdapter.data[oldpos].drugs_name = nowitem.drugs_name
mAdapter.data[oldpos].drugs_specs = nowitem.drugs_specs
mAdapter.data[oldpos].qty = nowitem.qty
mAdapter.notifyItemChanged(nowpos)
mAdapter.notifyItemChanged(oldpos)
}
}
}
return true
}
}
事件最后的返回要都写true,返回false只能接受到ACTION_DRAG_STARTED事件,其他事件收不到。
04
主界面代码
主界面中加入一个初始化数据,生成对应的数据列表
package pers.vaccae.draganddropdemo
import android.content.ClipData
import android.content.ClipDescription
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ContentInfoCompat
import androidx.core.view.DragStartHelper
import androidx.core.view.OnReceiveContentListener
import androidx.draganddrop.DropHelper
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import pers.vaccae.draganddropdemo.adapter.DrugsAdapter
import pers.vaccae.draganddropdemo.adapter.DrugsAdapterNew
import pers.vaccae.draganddropdemo.bean.CDrugs
class MainActivity : AppCompatActivity() {
private val recyclerView: RecyclerView by lazy { findViewById(R.id.recycler_view) }
val drugslist = mutableListOf<CDrugs>()
lateinit var adapter: DrugsAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//初始化数据
initdata()
adapter = DrugsAdapter(R.layout.rcl_item, drugslist)
val gridLayoutManager = GridLayoutManager(this, 3)
recyclerView.layoutManager = gridLayoutManager
recyclerView.adapter = adapter
val btn = findViewById<Button>(R.id.btn)
btn.setOnClickListener {
for(item in drugslist){
Log.i("main", item.drugs_ckcode.toString() + " "+ item.drugs_code
+" "+item.drugs_name)
}
}
}
fun initdata() {
var ckcode = 1000
for (i in 1..9) {
val item = CDrugs()
if (i % 3 == 0) {
item.drugs_ckcode = ckcode + 3
ckcode += 1000
} else {
item.drugs_ckcode = ckcode + i % 3
}
item.drugs_code = "00000$i"
item.drugs_name = "测试药品$i"
item.drugs_specs = "50ml"
item.qty = i
drugslist.add(item)
}
}
}
这样拖拽的效果就可以实现了,打印按钮是输出生成的List数据,虽然拖拽的效果在adapter中编写的,但在外面List数据中也会同步的进行修改。
上图中的顺序,点击打印按钮可以看一下输出列表。
可以看到输入的和界面上的也完全一致。
源码地址
https://github.com/Vaccae/Android-DragAndDropDemo.git
点击原文链接可以跳转到“码云”的下载地址
完
往期精彩回顾
Android Studio 2021.1.1的getNdkVersion的Bug及解决办法