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