如何使用 CoordinatorLayout 使视图作为 RecyclerView 的一部分移动
How to make a view move as part of RecyclerView using CoordinatorLayout
我正在学习 Android CoordinatorLayout/RecyclerView/Behavior/Nested 滚动内容。我发现 API 不是很直接。因此,我做了一个小实验,但发现 CoordinatorLayout 内部视图的行为并没有按预期出现。
这是我想要做的:我想要一个 CoordinatoryLayout 作为 parent 容器。在它里面,有一个RecyclerView作为它的child。同样作为它的 child,还有另一个 View 是红色的。我希望红色视图随着 RecyclerView 的滚动而移动。我希望两个视图的移动如此同步,以至于红色视图似乎是 RecyclerView 的一部分。
注意:我知道要实现我刚才所说的,还有更简单的方法。但我只想用 CoordinatorLayout 和 Behavior class 来做,这样我就可以了解它们是如何工作的。
这是我的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context="io.github.seemuch.coordinatorlayoutexperiment.MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="40dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:dividerHeight="10dp"
/>
<View
android:id="@+id/red_view"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginBottom="20dp"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:background="#ff0000"
app:layout_scrollFlags="scroll|enterAlways"
/>
</android.support.design.widget.CoordinatorLayout>
这是我的行为 class:
package io.github.seemuch.coordinatorlayoutexperiment;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.view.View;
public class MyBehavior extends CoordinatorLayout.Behavior<View> {
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View toolbar, View dependency) {
return dependency instanceof RecyclerView;
}
@Override
public boolean onStartNestedScroll (CoordinatorLayout coordinatorLayout,
View child,
View directTargetChild,
View target,
int nestedScrollAxes) {
int vertical = (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL);
int horizontal = (nestedScrollAxes & ViewCompat.SCROLL_AXIS_HORIZONTAL);
return (vertical != 0 && horizontal == 0);
}
@Override
public void onNestedPreScroll (CoordinatorLayout coordinatorLayout,
View child,
View target,
int dx,
int dy,
int[] consumed) {
float currY = child.getY();
if (currY <= 0 && dy >= 0) {
return;
}
child.setY(currY - dy);
}
}
正如您在 onNestedPreScroll 方法中看到的那样,我想要 child,即 red_view,移动它所依赖的视图,即 RecyclerView。
实际发生的事情是这样的:如果你以非常大的移动滚动 RecyclerView,红色视图确实会像预期的那样移动;但是如果你移动得很少,red_view 移动得比 RecyclerView 快得多。
这里是link到屏幕录制的视频:https://youtu.be/zK4g61F2aa0
这里是项目的 Github 仓库,如果你想下载它并 运行 自己:https://github.com/seemuch/CoordinatorLayoutExperiment
那么,有人知道发生了什么事吗?
谢谢!
找到解决方案:不要覆盖 onNestedPreScroll,而是覆盖 onNestedScroll()。就那么简单。
但是,它不处理 fling。 Flings 需要单独处理。
我用非常简单的方法解决了这个问题。首先,我定义了 RecyclerView
不可滚动(每一行仍然有触摸),然后我在布局中放置了一个 ScrollView
。
这里是一些代码:
layout.xml
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<androidx.constraintlayout.widget.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">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="409dp"
android:layout_height="729dp"
android:layout_marginTop="200dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.505" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.089" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val adapter = PersonAdapter { /*handle touch*/ }
val manager = CustomGridLayoutManager(this)
manager.setScrollEnabled(false)
recycler.layoutManager = manager
recycler.adapter = adapter
}
}
CustomGridLayoutManager.kt
class CustomGridLayoutManager(context: Context?) :
LinearLayoutManager(context) {
private var isScrollEnabled = true
fun setScrollEnabled(flag: Boolean) {
isScrollEnabled = flag
}
override fun canScrollVertically(): Boolean {
return isScrollEnabled && super.canScrollVertically()
}
}
PersonAdapter.kt
class PersonAdapter(val onItemClick : (Int) -> (Unit))
: RecyclerView.Adapter<PersonAdapter.ViewHolder>() {
private val personList = arrayOf("marco", "anna", "gianna", "giulia", "alice", "sini", "marco", "anna", "gianna",
"giulia", "alice", "sini", "marco", "anna", "gianna", "giulia", "alice", "sini", "marco", "anna", "gianna", "giulia", "alice", "sini")
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.row_layout, parent, false))
}
override fun getItemCount(): Int {
return personList.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val element = personList[position]
holder.text.text = element
holder.text.setOnClickListener {
onItemClick(position)
}
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val text = itemView.findViewById<TextView>(R.id.textRow)
}
}
我正在学习 Android CoordinatorLayout/RecyclerView/Behavior/Nested 滚动内容。我发现 API 不是很直接。因此,我做了一个小实验,但发现 CoordinatorLayout 内部视图的行为并没有按预期出现。
这是我想要做的:我想要一个 CoordinatoryLayout 作为 parent 容器。在它里面,有一个RecyclerView作为它的child。同样作为它的 child,还有另一个 View 是红色的。我希望红色视图随着 RecyclerView 的滚动而移动。我希望两个视图的移动如此同步,以至于红色视图似乎是 RecyclerView 的一部分。
注意:我知道要实现我刚才所说的,还有更简单的方法。但我只想用 CoordinatorLayout 和 Behavior class 来做,这样我就可以了解它们是如何工作的。
这是我的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context="io.github.seemuch.coordinatorlayoutexperiment.MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="40dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:dividerHeight="10dp"
/>
<View
android:id="@+id/red_view"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginBottom="20dp"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:background="#ff0000"
app:layout_scrollFlags="scroll|enterAlways"
/>
</android.support.design.widget.CoordinatorLayout>
这是我的行为 class:
package io.github.seemuch.coordinatorlayoutexperiment;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.view.View;
public class MyBehavior extends CoordinatorLayout.Behavior<View> {
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View toolbar, View dependency) {
return dependency instanceof RecyclerView;
}
@Override
public boolean onStartNestedScroll (CoordinatorLayout coordinatorLayout,
View child,
View directTargetChild,
View target,
int nestedScrollAxes) {
int vertical = (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL);
int horizontal = (nestedScrollAxes & ViewCompat.SCROLL_AXIS_HORIZONTAL);
return (vertical != 0 && horizontal == 0);
}
@Override
public void onNestedPreScroll (CoordinatorLayout coordinatorLayout,
View child,
View target,
int dx,
int dy,
int[] consumed) {
float currY = child.getY();
if (currY <= 0 && dy >= 0) {
return;
}
child.setY(currY - dy);
}
}
正如您在 onNestedPreScroll 方法中看到的那样,我想要 child,即 red_view,移动它所依赖的视图,即 RecyclerView。
实际发生的事情是这样的:如果你以非常大的移动滚动 RecyclerView,红色视图确实会像预期的那样移动;但是如果你移动得很少,red_view 移动得比 RecyclerView 快得多。
这里是link到屏幕录制的视频:https://youtu.be/zK4g61F2aa0 这里是项目的 Github 仓库,如果你想下载它并 运行 自己:https://github.com/seemuch/CoordinatorLayoutExperiment
那么,有人知道发生了什么事吗? 谢谢!
找到解决方案:不要覆盖 onNestedPreScroll,而是覆盖 onNestedScroll()。就那么简单。 但是,它不处理 fling。 Flings 需要单独处理。
我用非常简单的方法解决了这个问题。首先,我定义了 RecyclerView
不可滚动(每一行仍然有触摸),然后我在布局中放置了一个 ScrollView
。
这里是一些代码:
layout.xml
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<androidx.constraintlayout.widget.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">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="409dp"
android:layout_height="729dp"
android:layout_marginTop="200dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.505" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.089" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val adapter = PersonAdapter { /*handle touch*/ }
val manager = CustomGridLayoutManager(this)
manager.setScrollEnabled(false)
recycler.layoutManager = manager
recycler.adapter = adapter
}
}
CustomGridLayoutManager.kt
class CustomGridLayoutManager(context: Context?) :
LinearLayoutManager(context) {
private var isScrollEnabled = true
fun setScrollEnabled(flag: Boolean) {
isScrollEnabled = flag
}
override fun canScrollVertically(): Boolean {
return isScrollEnabled && super.canScrollVertically()
}
}
PersonAdapter.kt
class PersonAdapter(val onItemClick : (Int) -> (Unit))
: RecyclerView.Adapter<PersonAdapter.ViewHolder>() {
private val personList = arrayOf("marco", "anna", "gianna", "giulia", "alice", "sini", "marco", "anna", "gianna",
"giulia", "alice", "sini", "marco", "anna", "gianna", "giulia", "alice", "sini", "marco", "anna", "gianna", "giulia", "alice", "sini")
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.row_layout, parent, false))
}
override fun getItemCount(): Int {
return personList.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val element = personList[position]
holder.text.text = element
holder.text.setOnClickListener {
onItemClick(position)
}
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val text = itemView.findViewById<TextView>(R.id.textRow)
}
}