首页 > 解决方案 > Kotlin - 与 Recyclerview 滚动同步移动视图

问题描述

我正在尝试实现一个设计,但我不确定它的一个基本方面。我已经设法通过使用 recyclerview 作为代码的核心来继续,但我坚持如何实现最后一个方面。
(请注意,这是对我之前提出的问题的修改。我为这个问题道歉,希望这会更好)
设计
我有一个屏幕,您在屏幕中央有一个“堆栈”文本视图,在屏幕底部有一个可见的按钮。这个想法是,当您尝试“向下滚动”以向上拉按钮时,向下滚动操作会导致最顶部的文本视图向上拖动并放置在上方,从而显示下一个文本视图。可以重复此操作,直到您到达倒数第二个文本视图。使用倒数第二个文本视图,当您向上拖动时,文本视图和下面的按钮都被向上拖动并存放(文本视图与其他文本视图,文本视图下方的按钮)。整个视图必须可滚动才能看到所有视图。 细红方块是Textview可以放下的区域, 在此处输入图像描述


实现
我正在使用 Recyclerview 来控制拖放系统。有多个视图类型来控制哪些可以拖动,哪些不能,包括额外的“限制器”视图类型来控制可以放置项目的位置(最大和最小位置)并在移动完成后调用 notifydatasetchanged 以更新堆栈和显示。
我创建了一个自定义的 Recyclerview,它允许我覆盖 onTouchEvent 方法,并且我能够在用户尝试滚动时进行拦截,因此不是滚动,而是拖动任何可拖动的项目。这对可拖动对象很有效。
但是,我还需要能够拖动 Button 视图,以与用户滚动相同的速度移动。我将触摸事件分派到视图的实验导致视图闪烁到触摸事件所在的位置,而不是保持其当前位置,但仅在用户上下移动相同程度时才移动(即用户向上移动50px,视图也会从原来的位置向上移动 50px)。我将不胜感激人们对此提出的任何见解。在触摸和移动编码方面,我有点新手。我提前感谢你,我已经把我的代码放在下面了。

编码
片段代码:

class DragDropFragment: BaseFragment()
{
    lateinit var mBinding: FragmentDragdropBinding
    lateinit var mGlideApp: GlideRequests

    lateinit var mAdapter: DragDropAdapter
    lateinit var mTouchHelperCallback: ItemTouchHelperCallback
    lateinit var mItemTouchHelper: ItemTouchHelper
    lateinit var mScrollListener: RecyclerView.OnScrollListener

    var mActivityReady = false
    var mViewRendered = false

    var mRecyclerViewHeight: Int = PMConsts.negNum
    var mBlankNo = 0

    override fun onCreate(savedInstanceState: Bundle?)
    {
        super.onCreate(savedInstanceState)
        mGlideApp =  GlideApp.with(this)
    }

    override fun onBindCreatedView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View
    {
        mBinding = FragmentDragdropBinding.inflate(inflater, container, false)
        return mBinding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?)
    {
        super.onViewCreated(view, savedInstanceState)

        val recycerView = mBinding.recyclerview
        recycerView.doOnLayout {
            mRecyclerViewHeight = it.height
            mViewRendered = true
            onCodeReady()
        }

        val layoutManager = LinearLayoutManager(requireContext())
        recycerView.layoutManager = layoutManager

        val overlapItemDecoration = OverlapItemDecoration()
        val top = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 156f, requireContext().resources.displayMetrics)
        overlapItemDecoration.mTopModifier = -top.toInt()
        recycerView.addItemDecoration(overlapItemDecoration)

        val adapter = createAdapter()

        recycerView.adapter = adapter
    }

    fun createAdapter(): DragDropAdapter
    {
        val combineListener = object : OnCombineViewsListener
        {
            override fun onCombineViews()
            {
                combineViewsTogether()
            }
        }
        val onItemDraggedListener = object : OnItemDraggedListener
        {
            override fun onItemDragged()
            {
                onItemContentChanged()
            }
        }
        val adapter = DragDropAdapter(requireContext(), mGlideApp, combineListener, onItemDraggedListener)
        val dataObserver = object : RecyclerView.AdapterDataObserver()
        {
            override fun onChanged()
            {
                onItemContentChanged()
            }
        }
        adapter.registerAdapterDataObserver(dataObserver)

        mTouchHelperCallback = ItemTouchHelperCallback(adapter)
        mItemTouchHelper = ItemTouchHelper(mTouchHelperCallback)
        mItemTouchHelper.attachToRecyclerView(mBinding.recyclerview)

        val dragDropListener = object : OnStartDragListener
        {
            override fun onStartDrag(viewHolder: RecyclerView.ViewHolder)
            {
                if (mItemTouchHelper != null)
                {
                    mItemTouchHelper.startDrag(viewHolder)
                }
            }
        }
        adapter.mDragDropListener = dragDropListener
        return adapter
    }

    fun combineViewsTogether()
    {
        val contentList = ArrayList<DataModel>()
        val okiModel = OkiModel()
        okiModel.mResourceId = R.drawable.oki_positive_orange
        contentList.add(okiModel)

        contentList.add(ButtonModel("Yes, I agree", true))
        contentList.add(ButtonModel("No, I don't agree", false))
        contentList.add(DataModel())//Adds empty space view (100dp height)

        val adapter = getAdapter()
        adapter!!.mContentList = contentList

        PMUIUtils.displayView(requireContext(), mBinding.hiddenbtn, View.GONE)
    }

    fun onItemContentChanged()
    {
        val adapter = getAdapter()
        if (adapter is DragDropAdapter)
        {
            val list = adapter.mDataList
            if (list != null && list.size > 0)
            {
                var cnt = 0
                for (item in list)
                {
                    if (item.mModel!!.contentType == PMEnums.ContentType.LIMIT)
                    {
                        break
                    }
                    cnt++
                }
                if (adapter.mStackList.size <= 0)
                {
                    cnt--
                }
                Log.w("AppCore", "cnt: $cnt & model: ${list[cnt].mModel}")
                val layoutManager = mBinding.recyclerview.layoutManager
                if (layoutManager is LinearLayoutManager)
                {
                    layoutManager.scrollToPosition(cnt)
                }
            }
        }
    }

    override fun onSetupContent(savedInstanceState: Bundle?)
    {
        mActivityReady = true
        onCodeReady()
    }

    fun onCodeReady()
    {
        if (mViewRendered && mActivityReady)
        {
            val halfscreen = (mRecyclerViewHeight / 2).toDouble()
            val blankheight = resources.getDimensionPixelSize(R.dimen.speechb_blank_height)
            //val blankNo = halfscreen / blankheight
            var blankNo = Math.ceil(halfscreen/ blankheight).toInt()
            if (blankNo > 1)
            {
                blankNo--
            }

            Log.e("AppCore", "mRecyclerViewHeight: $mRecyclerViewHeight % blankheight: $blankheight & blankNo: $blankNo")

            val stackArray = ArrayList<DragDropModel>()
            for (i in 0 until 4)
            //for (i in 3 downTo 0)
            {
                stackArray.add(DragDropModel(i, "This is Item: $i", PMEnums.ContentType.DRAGDROP))
            }
            val contentList = ArrayList<DataModel>()
            val okiModel = OkiModel()
            okiModel.mResourceId = R.drawable.oki_positive_orange
            contentList.add(okiModel)
            val adapter = getAdapter()
            if (adapter != null)
            {
                adapter.mStackList = stackArray
                adapter.mContentList = contentList
                adapter.mBlankCnt = blankNo

                adapter.sortContents()
                updateContent()
            }
        }
    }

    fun getAdapter(): DragDropAdapter?
    {
        val adapter = mBinding.recyclerview.adapter
        if (adapter is DragDropAdapter)
        {
            return adapter
        }
        return null
    }

    fun updateContent()
    {
        mUiHandler.post(object : Runnable
        {
            override fun run()
            {
                val adapter = mBinding.recyclerview.adapter
                if (adapter is DragDropAdapter)
                {
                    adapter.notifyDataSetChanged()
                }
            }
        })
    }
}

我用于拖放的自定义 ItemTouchHelper.Callback 类:

class ItemTouchHelperCallback(val mAdapter: ItemTouchHelperListener
): ItemTouchHelper.Callback()
{

    override fun isLongPressDragEnabled(): Boolean
    {
        return false
    }

    override fun isItemViewSwipeEnabled(): Boolean
    {
        return false
    }

    override fun getMovementFlags(
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder
    ): Int
    {
        val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
        if (viewHolder is DragDropVH)
        {
            return makeMovementFlags(dragFlags, ItemTouchHelper.ACTION_STATE_IDLE)
        }
        else
        {
            return makeFlag(ItemTouchHelper.ACTION_STATE_IDLE, ItemTouchHelper.ACTION_STATE_IDLE)
        }
        //return makeMovementFlags(dragFlags, ItemTouchHelper.ACTION_STATE_IDLE)
    }

    override fun onMove(
        recyclerView: RecyclerView,
        source: RecyclerView.ViewHolder,
        target: RecyclerView.ViewHolder
    ): Boolean
    {
        val fromPosition = source.adapterPosition
        val toPosition = target.adapterPosition
        mAdapter.onItemMove(fromPosition, toPosition)
        return true
    }

    override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int)
    {

    }

    override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder)
    {
        mAdapter.onClearView(recyclerView, viewHolder)
        //super.clearView(recyclerView, viewHolder)
    }

    override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
        super.onSelectedChanged(viewHolder, actionState)
    }
}

适配器代码:

class DragDropAdapter constructor(
    context: Context,
    glideApp: GlideRequests?,
    val mOnCombineViewsListener: OnCombineViewsListener,
    val mOnItemDraggedListener: OnItemDraggedListener
): BaseRecVAdapter(context, glideApp), ItemTouchHelperListener
{
    var mDraggedList = ArrayList<DragDropModel>()
    var mStackList = ArrayList<DragDropModel>()
    var mContentList = ArrayList<DataModel>()

    var mDragDropListener: OnStartDragListener? = null

    var mDragUpLimit: Int = PMConsts.negNum
    var mDragDownLimit: Int = PMConsts.negNum
    var mDragMinimum: Int = PMConsts.negNum

    var mBlankCnt = 0
    var mTopPadding: Int = PMConsts.negNum

    init
    {
        mTopPadding = context.resources.getDimensionPixelSize(R.dimen.speechb_blank_height)
    }

    fun sortContents()
    {
        if (mContentList.size > 0 || mStackList.size > 0 || mDraggedList.size > 0)
        {
            mDataList = ArrayList()
            var blankCnt = 0
            if (mDraggedList.size > 0)
            {
                for (item in mDraggedList)
                {
                    val viewHolderModel = ViewHolderModel(item)
                    mDataList!!.add(viewHolderModel)
                }
                mDragUpLimit = mDataList!!.size
            }
            else
            {
                mDataList!!.add(createBlankItem(mTopPadding, ContentType.BLANK))
                mDragUpLimit = 0
            }

            //Insert invisible bar row to act as minimum required to allow for change...
            mDataList!!.add(createBlankItem(PMConsts.negNum, ContentType.LIMIT))
            mDragMinimum = mDataList!!.size

            if (mStackList.size > 0)
            {
                //mDataList!!.add(createBlankItem(mTopPadding, ContentType.BLANK))

                val listL1 = mStackList.size
                val tempArray = ArrayList<ViewHolderModel>()
                for (i in listL1 - 1 downTo listL1 - 2)
                {
                    val item = mStackList[i]
                    if (i == (listL1 - 1))
                    {
                        item.mDragCondition = DragCondition.DRAGGABLE
                    }
                    else
                    {
                        item.mDragCondition = DragCondition.NEXTINLINE
                    }
                    tempArray.add(ViewHolderModel(item))
                }
                tempArray.reverse()
                for (item in tempArray)
                {
                    //Log.w("AppCore", "mStackList: ${item.mModel}")
                    mDataList!!.add(item)
                }
                blankCnt = mBlankCnt
            }
            mDragDownLimit = mDataList!!.size
            if (mContentList.size > 0)
            {
                for (item in mContentList)
                {
                    if (item is BarObjectModel)
                    {
                        mDataList!!.add(createBlankItem((mTopPadding / 2), ContentType.BLANK))
                    }
                    else
                    {
                        mDataList!!.add(ViewHolderModel(item))
                    }
                }
            }
            if (blankCnt > 0)
            {
                for (i in 0 until blankCnt)
                {
                    mDataList!!.add(createBlankItem(mTopPadding, ContentType.BLANK))
                }
            }
        }
    }

    override fun getItemViewType(position: Int): Int
    {
        if (mDataList != null)
        {
            val item = getDataModel(position)
            if (item is DragDropModel)
            {
                return ViewType.DRAGDROP.value
            }
            else if (item is BarObjectModel)
            {
                return ViewType.BAR.value
            }
            else if (item is OkiModel)
            {
                return ViewType.OKI.value
            }
            else if (item is ButtonModel)
            {
                return ViewType.BUTTON.value
            }
            else
            {
                return ViewType.SIMPLE.value
            }
        }
        return super.getItemViewType(position)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder
    {
        when (viewType)
        {
            ViewType.SIMPLE.value ->
            {
                return SimpleVH(ItemSimplecontentBinding.inflate(LayoutInflater.from(parent.context), parent, false))
            }
            ViewType.BAR.value ->
            {
                return BarObjectVH(ItemBarBinding.inflate(LayoutInflater.from(parent.context), parent, false))
            }
            ViewType.DRAGDROP.value ->
            {
                return DragDropVH(ItemDragdropBinding.inflate(LayoutInflater.from(parent.context), parent, false), mDragDropListener)
            }
            ViewType.OKI.value ->
            {
                return OkiBubbleVH(ItemOkiBubbleBinding.inflate(LayoutInflater.from(parent.context), parent, false))
            }
            ViewType.BUTTON.value ->
            {
                return DragButtonVH(ItemButtonBinding.inflate(LayoutInflater.from(parent.context), parent, false))
            }
        }
        return EmptyViewVH(ItemEmptyBinding.inflate(LayoutInflater.from(parent.context), parent, false))
    }


    /**
     * Drag Drop Code *
     */

    override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean
    {
        Log.d("AppCore", "onItemMove: $fromPosition & toPosition: $toPosition")
        if (toPosition >= 0 && fromPosition >= 0)
        {
            var newToPosition = toPosition
            if (toPosition <= mDragUpLimit)
            {//Prevent items from being dragged above maximum movement.
                newToPosition = mDragUpLimit + 1
            }
            else if (toPosition >= mDragDownLimit)
            {//Cannot drag below stacked List...
                newToPosition = mDragDownLimit - 1
            }

            Log.i("AppCore", "swapDraggedItem - fromPosition: $fromPosition & toPosition: $toPosition " +
                "& mDragUpLimit: $mDragUpLimit & mDragMinimum: $mDragMinimum")
            if (newToPosition <= mDragMinimum)
            {
                if (fromPosition < newToPosition)
                {
                    for (i in fromPosition until newToPosition)
                    {
                        swap(mDataList, i, i + 1)
                    }
                }
                else
                {
                    for (i in fromPosition downTo newToPosition + 1)
                    {
                        swap(mDataList, i, i - 1)
                    }
                }
                notifyItemMoved(fromPosition, newToPosition)
            }
        }
        return true
    }

    override fun onClearView(recyclerView: RecyclerView?, viewHolder: RecyclerView.ViewHolder?)
    {
        if (viewHolder is DragDropVH)
        {
            val position = viewHolder.adapterPosition
            val model = getDataModel(position)

            if (model is DragDropModel && position <= (mDragMinimum))
            {
                var cnt = 0
                for (item in mStackList)
                {
                    if (item.mStackedPos == model.mStackedPos)
                    {
                        break
                    }
                    cnt++
                }

                model.hasBeenDragged()
                mStackList.removeAt(cnt)
                mDraggedList.add(model)

                if (mStackList.size == 1)
                {
                    val item = mStackList[0]
                    item.hasBeenDragged()
                    mDraggedList.add(item)
                    mStackList.removeAt(0)

                    mOnCombineViewsListener.onCombineViews()
                }
                sortContents()
                notifyDataSetChanged()
            }
        }
    }

    /**
     * Misc Methods *
     */
    fun createBlankItem(emptySize: Int, contentType: ContentType): ViewHolderModel
    {
        val blankModel = BarObjectModel(false, contentType)
        val viewHolderModel = ViewHolderModel(blankModel)
        if (emptySize != PMConsts.negNum)
        {
            viewHolderModel.height = emptySize
        }
        return viewHolderModel
    }
}

我的自定义回收站视图:

override fun onTouchEvent(e: MotionEvent?): Boolean
    {
        val requiredAdapter = adapter
        if (requiredAdapter is DragDropAdapter)
        {
            val itemList = requiredAdapter.mDataList
            if (itemList != null)
            {
                val listL1 = itemList.size
                var cnt = -1
                for (i in 0 until listL1)
                {
                    val model = itemList[i].mModel
                    if (model is DragDropModel && model.mDragCondition == DragCondition.DRAGGABLE)
                    {
                        cnt = i
                        break
                    }
                }
                if (cnt != -1)
                {
                    val childView = getChildAt(cnt)
                    val viewHolder = getChildViewHolder(childView)
                    if (viewHolder is DragDropVH)
                    {
                        viewHolder.itemView.dispatchTouchEvent(e)
                        //return true
                    }
                }
            }
        }
        return super.onTouchEvent(e)
    }

标签: androidkotlinandroid-recyclerviewdrag-and-dropdraggable

解决方案


推荐阅读