and*_*per 13 android android-recyclerview
我有2个RecyclerView实例.一个是水平的,第二个是垂直的.
它们都显示相同的数据并且具有相同数量的项目,但是以不同的方式,并且单元格的大小不必相等.
我希望滚动到一个将与另一个同步,以便在一个上显示的第一个项目将始终显示在另一个上(作为第一个).
即使我成功地使它们同步(我只选择哪一个是"主",控制另一个的滚动),滚动的方向似乎影响它的工作方式.
假设片刻具有相同的高度:
如果我向上/向左滚动,它或多或少地按照我的意图工作:
但是,如果我向下/向右滚动,它会让其他RecyclerView显示另一个的第一项,但通常不会作为第一项:
注意:在上面的截图中,我已经在底部的RecyclerView中滚动,但是类似的结果将与顶部的一起.
如果像我写的那样,细胞有不同的大小,情况会变得更糟:
我尝试使用其他滚动方式并转到其他位置,但所有尝试都失败了.
使用smoothScrollToPosition使事情变得更糟(虽然它看起来确实更好),因为如果我扔了,在某些时候,其他RecyclerView控制我与之交互的那个.
我想我应该使用滚动的方向,以及其他RecyclerView上当前显示的项目.
这是当前(示例)代码.请注意,在实际代码中,单元格可能没有相同的大小(有些是高,有些是短等等).代码中的一行使单元格具有不同的高度.
activity_main.xml中
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" tools:context=".MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/topReccyclerView" android:layout_width="0dp" android:layout_height="100dp"
android:layout_marginEnd="8dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp"
android:orientation="horizontal" app:layoutManager="android.support.v7.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" tools:listitem="@layout/horizontal_cell"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/bottomRecyclerView" android:layout_width="0dp" android:layout_height="0dp"
android:layout_marginBottom="8dp" android:layout_marginEnd="8dp" android:layout_marginStart="8dp"
android:layout_marginTop="8dp" app:layoutManager="android.support.v7.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/topReccyclerView"
tools:listitem="@layout/horizontal_cell"/>
</android.support.constraint.ConstraintLayout>
Run Code Online (Sandbox Code Playgroud)
horizontal_cell.xml
<TextView
android:id="@+id/textView" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="100dp" android:layout_height="100dp"
android:gravity="center" tools:text="@tools:sample/lorem"/>
Run Code Online (Sandbox Code Playgroud)
vertical_cell.xml
<TextView
android:id="@+id/textView" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="50dp"
android:gravity="center" tools:text="@tools:sample/lorem"/>
Run Code Online (Sandbox Code Playgroud)
主要活动
class MainActivity : AppCompatActivity() {
var masterView: View? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val inflater = LayoutInflater.from(this)
topReccyclerView.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder.itemView as TextView).text = position.toString()
holder.itemView.setBackgroundColor(if(position%2==0) 0xffff0000.toInt() else 0xff00ff00.toInt())
}
override fun getItemCount(): Int {
return 100
}
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {
return object : RecyclerView.ViewHolder(inflater.inflate(R.layout.horizontal_cell, parent, false)) {}
}
}
bottomRecyclerView.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
val baseHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50f, resources.displayMetrics).toInt()
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder.itemView as TextView).text = position.toString()
holder.itemView.setBackgroundColor(if(position%2==0) 0xffff0000.toInt() else 0xff00ff00.toInt())
// this makes the heights of the cells different from one another:
holder.itemView.layoutParams.height = baseHeight + (if (position % 3 == 0) 0 else baseHeight / (position % 3))
}
override fun getItemCount(): Int {
return 100
}
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {
return object : RecyclerView.ViewHolder(inflater.inflate(R.layout.vertical_cell, parent, false)) {}
}
}
LinearSnapHelper().attachToRecyclerView(topReccyclerView)
LinearSnapHelper().attachToRecyclerView(bottomRecyclerView)
topReccyclerView.addOnScrollListener(OnScrollListener(topReccyclerView, bottomRecyclerView))
bottomRecyclerView.addOnScrollListener(OnScrollListener(bottomRecyclerView, topReccyclerView))
}
inner class OnScrollListener(private val thisRecyclerView: RecyclerView, private val otherRecyclerView: RecyclerView) : RecyclerView.OnScrollListener() {
var lastItemPos: Int = Int.MIN_VALUE
val thisRecyclerViewId = resources.getResourceEntryName(thisRecyclerView.id)
override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
Log.d("AppLog", "onScrollStateChanged:$thisRecyclerViewId $newState")
when (newState) {
RecyclerView.SCROLL_STATE_DRAGGING -> if (masterView == null) {
Log.d("AppLog", "setting $thisRecyclerViewId to be master")
masterView = thisRecyclerView
}
RecyclerView.SCROLL_STATE_IDLE -> if (masterView == thisRecyclerView) {
Log.d("AppLog", "resetting $thisRecyclerViewId from being master")
masterView = null
lastItemPos = Int.MIN_VALUE
}
}
}
override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if ((dx == 0 && dy == 0) || (masterView != null && masterView != thisRecyclerView))
return
// Log.d("AppLog", "onScrolled:$thisRecyclerView $dx-$dy")
val currentItem = (thisRecyclerView.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition()
if (lastItemPos == currentItem)
return
lastItemPos = currentItem
otherRecyclerView.scrollToPosition(currentItem)
// otherRecyclerView.smoothScrollToPosition(currentItem)
Log.d("AppLog", "currentItem:" + currentItem)
}
}
}
Run Code Online (Sandbox Code Playgroud)
如何让其他RecycerView始终将第一个项目与当前控制的项目相同?
如何修改代码以支持平滑滚动,而不会导致突然让其他RecyclerView成为控件的问题?
编辑:在更新这里的示例代码后,具有不同大小的单元格(因为最初更接近我所拥有的问题,如前所述),我注意到捕捉不起作用.
这就是我选择使用此库正确捕捉它的原因:
https://github.com/DevExchanges/SnappingRecyclerview
因此,我使用'GravitySnapHelper'代替LinearSnapHelper.似乎工作得更好,但仍然有同步问题,并在滚动时触摸.
编辑:我终于修复了所有同步问题,即使单元格大小不同,它也可以正常工作.
还有一些问题:
如果您使用一个RecyclerView,然后触摸另一个,它会有非常奇怪的滚动行为.可能滚动的方式比它应该更多.
滚动不平滑(同步和投掷时),因此看起来不太好.
遗憾的是,由于捕捉(我实际上可能只需要顶级的RecyclerView),它会导致另一个问题:底部的RecyclerView可能部分显示最后一个项目(截图有100个项目),我无法滚动更多以显示它完全:
我甚至不认为底部的RecyclerView应该是贴紧的,除非触顶了.可悲的是,这是我到目前为止所做的全部,没有同步问题.
在找到所有修复后,这是新代码:
class MainActivity : AppCompatActivity() {
var masterView: View? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val inflater = LayoutInflater.from(this)
topReccyclerView.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder.itemView as TextView).text = position.toString()
holder.itemView.setBackgroundColor(if (position % 2 == 0) 0xffff0000.toInt() else 0xff00ff00.toInt())
}
override fun getItemCount(): Int = 1000
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {
return object : RecyclerView.ViewHolder(inflater.inflate(R.layout.horizontal_cell, parent, false)) {}
}
}
bottomRecyclerView.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
val baseHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50f, resources.displayMetrics).toInt()
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder.itemView as TextView).text = position.toString()
holder.itemView.setBackgroundColor(if (position % 2 == 0) 0xffff0000.toInt() else 0xff00ff00.toInt())
holder.itemView.layoutParams.height = baseHeight + (if (position % 3 == 0) 0 else baseHeight / (position % 3))
}
override fun getItemCount(): Int = 1000
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {
return object : RecyclerView.ViewHolder(inflater.inflate(R.layout.vertical_cell, parent, false)) {}
}
}
// GravitySnapHelper is available from : https://github.com/DevExchanges/SnappingRecyclerview
GravitySnapHelper(Gravity.START).attachToRecyclerView(topReccyclerView)
GravitySnapHelper(Gravity.TOP).attachToRecyclerView(bottomRecyclerView)
topReccyclerView.addOnScrollListener(OnScrollListener(topReccyclerView, bottomRecyclerView))
bottomRecyclerView.addOnScrollListener(OnScrollListener(bottomRecyclerView, topReccyclerView))
}
inner class OnScrollListener(private val thisRecyclerView: RecyclerView, private val otherRecyclerView: RecyclerView) : RecyclerView.OnScrollListener() {
var lastItemPos: Int = Int.MIN_VALUE
val thisRecyclerViewId = resources.getResourceEntryName(thisRecyclerView.id)
override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
when (newState) {
RecyclerView.SCROLL_STATE_DRAGGING -> if (masterView == null) {
masterView = thisRecyclerView
}
RecyclerView.SCROLL_STATE_IDLE -> if (masterView == thisRecyclerView) {
masterView = null
lastItemPos = Int.MIN_VALUE
}
}
}
override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (dx == 0 && dy == 0 || masterView !== null && masterView !== thisRecyclerView) {
return
}
val otherLayoutManager = otherRecyclerView.layoutManager as LinearLayoutManager
val thisLayoutManager = thisRecyclerView.layoutManager as LinearLayoutManager
val currentItem = thisLayoutManager.findFirstCompletelyVisibleItemPosition()
if (lastItemPos == currentItem) {
return
}
lastItemPos = currentItem
otherLayoutManager.scrollToPositionWithOffset(currentItem, 0)
}
}
}
Run Code Online (Sandbox Code Playgroud)
结合这两个RecyclerView,有四种运动情况:
A。将水平回收器向左滚动
b. 向右滚动
C。将垂直回收器滚动到顶部
d. 滚动到底部
情况 a 和 c 不需要处理,因为它们开箱即用。对于情况 b 和 d,您需要做两件事:
otherRecyclerView(如果屏幕更大,则偏移量也需要更大)。弄清楚这一点有点复杂,但结果非常简单。
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
if (masterView == otherRecyclerView) {
thisRecyclerView.stopScroll();
otherRecyclerView.stopScroll();
syncScroll(1, 1);
}
masterView = thisRecyclerView;
} else if (newState == RecyclerView.SCROLL_STATE_IDLE && masterView == thisRecyclerView) {
masterView = null;
}
}
@Override
public void onScrolled(RecyclerView recyclerview, int dx, int dy) {
super.onScrolled(recyclerview, dx, dy);
if ((dx == 0 && dy == 0) || (masterView != null && masterView != thisRecyclerView)) {
return;
}
syncScroll(dx, dy);
}
void syncScroll(int dx, int dy) {
LinearLayoutManager otherLayoutManager = (LinearLayoutManager) otherRecyclerView.getLayoutManager();
LinearLayoutManager thisLayoutManager = (LinearLayoutManager) thisRecyclerView.getLayoutManager();
int offset = 0;
if ((thisLayoutManager.getOrientation() == HORIZONTAL && dx > 0) || (thisLayoutManager.getOrientation() == VERTICAL && dy > 0)) {
// scrolling horizontal recycler to left or vertical recycler to bottom
offset = otherLayoutManager.findLastCompletelyVisibleItemPosition() - otherLayoutManager.findFirstCompletelyVisibleItemPosition();
}
int currentItem = thisLayoutManager.findFirstCompletelyVisibleItemPosition();
otherLayoutManager.scrollToPositionWithOffset(currentItem, offset);
}
Run Code Online (Sandbox Code Playgroud)
当然,您可以组合两个 if 子句,因为主体是相同的。为了可读性,我认为将它们分开是很好的。
第二个问题是当“第一个”回收器仍在滚动时触摸相应的“其他”回收器时的同步。以下代码(包含在上面)是相关的:
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
if (masterView == otherRecyclerView) {
thisRecyclerView.stopScroll();
otherRecyclerView.stopScroll();
syncScroll(1, 1);
}
masterView = thisRecyclerView;
}
Run Code Online (Sandbox Code Playgroud)
newState等于SCROLL_STATE_DRAGGING当回收器被触摸并拖动一点时。因此,如果这是在触摸相应“其他”回收器之后的触摸(和拖动),则第二个条件(masterView == otherRecyclerview)为真。然后两个回收器都停止,并且“另一个”回收器与“这个”回收器同步。
| 归档时间: |
|
| 查看次数: |
1303 次 |
| 最近记录: |