Android使用DragAndDrop拖拽效果实现宫格位置变换

学更好的别人,

做更好的自己。

——《微卡智享》

cc84c51943c6085ffa7c05c9010423fb.png

本文长度为3489,预计阅读9分钟


前言

原来产品中有个功能要实现宫格中库位的移库效果,以前一直没做这块,也是为了先赶产品,所以没有做实现的拖拽效果,最近正好有时间,研究了一下DragAndDrop,做了一个Demo验证了一下,效果还是挺不错的,本篇就做一个分享。

今天这篇是传统的DragAndDrop效果实现,在JetPack库中新的dragandrop做的优化,要简单的很多,下一篇会说到,并做一下两个对比。

b13772c591e20af57578e0dc6da65197.png

实现效果

0ee301d82bfa50eb062a9d3db1ef6a40.gif

DragAndDrop框架

b711a42303dddae6d9f2276a7742b7ab.png

微卡智享

在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的流程:

588483b6fe8623f5d5416f1bf10ea1f8.png

上面的流程通过Drag events事件来捕获,其中每个状态中还包含其他依赖于事件动作类型的数据,具体如下:

7781d91d2e42f1b47292feb83c80585d.png

上面是简单的DragandDrop的介绍 ,接下来代码实现。

代码实现

630dcf69d99cae674f54850b0cd415ee.png

微卡智享

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。

b5bd451f3fcd416356739791fd16c17b.png

02

布局文件及定义类

0c054616d5ead90adeb0634c1eb73b77.png

d784d22b91763a7589cb7f209a409617.png

一个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

由于要每个宫格实现拽拖替换,所以在适配器中每个宫格都要加上拖拽的动作和监听。

7471a27c8d6d68eece2d64b848aeede1.png

在每个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数据中也会同步的进行修改。

dbc34d87df71961131a5a6e2f9a64e2c.png

上图中的顺序,点击打印按钮可以看一下输出列表。

6906c41dd3edbf66b47aee707982bf95.png

可以看到输入的和界面上的也完全一致。

源码地址

https://github.com/Vaccae/Android-DragAndDropDemo.git

点击原文链接可以跳转到“码云”的下载地址

620153ac38b5c8bdc2ee6b3d6efd7788.png

bf1aac4b917b2c53a90ecca0da1257e4.png

往期精彩回顾

979e79b08d28ad5dc2ba8f4f1e943c08.png

OpenCV实现图片批号效期提取


70a271bda26b67f35ea236e72b2263bc.png

OpenCV图像锐化---USM锐化和Laplace锐化


5c7950a540bc47ea9791777ef890a77d.png

Android Studio 2021.1.1的getNdkVersion的Bug及解决办法


  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vaccae

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值