Kotlin - 移动视图与 Recyclerview 滚动同步

问题描述

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

enter image description here


细红色方块是可以放置 Textview 的区域,而细蓝色方块是放置按钮的区域(可能超过 1 个按钮)。

实施
我正在使用 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),false))
            }
            ViewType.DRAGDROP.value ->
            {
                return DragDropVH(ItemDragdropBinding.inflate(LayoutInflater.from(parent.context),false),mDragDropListener)
            }
            ViewType.OKI.value ->
            {
                return OkiBubbleVH(ItemOkiBubbleBinding.inflate(LayoutInflater.from(parent.context),false))
            }
            ViewType.BUTTON.value ->
            {
                return DragButtonVH(ItemButtonBinding.inflate(LayoutInflater.from(parent.context),false))
            }
        }
        return EmptyViewVH(ItemEmptyBinding.inflate(LayoutInflater.from(parent.context),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 - 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)
    }

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)