Android 如何在recyclerview中实现粘性页脚
我有RecyclerView,我需要下一个行为:Android 如何在recyclerview中实现粘性页脚,android,android-recyclerview,footer,Android,Android Recyclerview,Footer,我有RecyclerView,我需要下一个行为: 如果有很多项目(超过屏幕尺寸),则页脚是最后一项 如果少项/无项-页脚位于屏幕底部 请告知我如何实现此行为。如果您不能忘记RecyclerView并使用ListView,请查看此链接,它提供了您所需的一切。这是关于页眉的问题,但基本相同,只是页眉在列表的开头,页脚在列表的末尾。我知道,这是一个老问题,但我将为那些将来会搜索此类决策的人添加一个答案。可以将最后一个项目保留在屏幕底部,以防您只有很少或没有项目,并在您有很多项目时使用recycle
- 如果有很多项目(超过屏幕尺寸),则页脚是最后一项
- 如果少项/无项-页脚位于屏幕底部
请告知我如何实现此行为。如果您不能忘记RecyclerView并使用ListView,请查看此链接,它提供了您所需的一切。这是关于页眉的问题,但基本相同,只是页眉在列表的开头,页脚在列表的末尾。我知道,这是一个老问题,但我将为那些将来会搜索此类决策的人添加一个答案。可以将最后一个项目保留在屏幕底部,以防您只有很少或没有项目,并在您有很多项目时使用recyclerview滚动最后一个项目
public class StickyFooterItemDecoration extends RecyclerView.ItemDecoration {
/**
* Top offset to completely hide footer from the screen and therefore avoid noticeable blink during changing position of the footer.
*/
private static final int OFF_SCREEN_OFFSET = 5000;
@Override
public void getItemOffsets(Rect outRect, final View view, final RecyclerView parent, RecyclerView.State state) {
int adapterItemCount = parent.getAdapter().getItemCount();
if (isFooter(parent, view, adapterItemCount)) {
//For the first time, each view doesn't contain any parameters related to its size,
//hence we can't calculate the appropriate offset.
//In this case, set a big top offset and notify adapter to update footer one more time.
//Also, we shouldn't do it if footer became visible after scrolling.
if (view.getHeight() == 0 && state.didStructureChange()) {
hideFooterAndUpdate(outRect, view, parent);
} else {
outRect.set(0, calculateTopOffset(parent, view, adapterItemCount), 0, 0);
}
}
}
private void hideFooterAndUpdate(Rect outRect, final View footerView, final RecyclerView parent) {
outRect.set(0, OFF_SCREEN_OFFSET, 0, 0);
footerView.post(new Runnable() {
@Override
public void run() {
parent.getAdapter().notifyDataSetChanged();
}
});
}
private int calculateTopOffset(RecyclerView parent, View footerView, int itemCount) {
int topOffset = parent.getHeight() - visibleChildsHeightWithFooter(parent, footerView, itemCount);
return topOffset < 0 ? 0 : topOffset;
}
private int visibleChildsHeightWithFooter(RecyclerView parent, View footerView, int itemCount) {
int totalHeight = 0;
//In the case of dynamic content when adding or removing are possible itemCount from the adapter is reliable,
//but when the screen can fit fewer items than in adapter, getChildCount() from RecyclerView should be used.
int onScreenItemCount = Math.min(parent.getChildCount(), itemCount);
for (int i = 0; i < onScreenItemCount - 1; i++) {
totalHeight += parent.getChildAt(i).getHeight();
}
return totalHeight + footerView.getHeight();
}
private boolean isFooter(RecyclerView parent, View view, int itemCount) {
return parent.getChildAdapterPosition(view) == itemCount - 1;
}
}
如何实现。您的RecyclerView适配器应应用几种视图类型:视图,应显示为列表项;视图,应显示为页脚;空旷的景色。您可以在此处检查如何将具有不同视图的项目放入RecyclerView:
在主列表和页脚视图之间找到一个空视图。
然后在空视图的onBindViewHolder中检查主列表视图和页脚视图是否占据所有屏幕。如果是-将空视图高度设置为零,否则将其设置为项目和页脚似乎不采用的高度。这就是全部。
在删除/添加行时,还可以动态更新该高度。更新列表后,只需调用notifyItemChanged即可为您的空空间项更改
您还可以将RecyclerView高度设置为与父项或确切高度相匹配,而不是包装内容
希望这有帮助。您可以使用RecyclerView.ItemDecoration来实现此行为
public class StickyFooterItemDecoration extends RecyclerView.ItemDecoration {
/**
* Top offset to completely hide footer from the screen and therefore avoid noticeable blink during changing position of the footer.
*/
private static final int OFF_SCREEN_OFFSET = 5000;
@Override
public void getItemOffsets(Rect outRect, final View view, final RecyclerView parent, RecyclerView.State state) {
int adapterItemCount = parent.getAdapter().getItemCount();
if (isFooter(parent, view, adapterItemCount)) {
//For the first time, each view doesn't contain any parameters related to its size,
//hence we can't calculate the appropriate offset.
//In this case, set a big top offset and notify adapter to update footer one more time.
//Also, we shouldn't do it if footer became visible after scrolling.
if (view.getHeight() == 0 && state.didStructureChange()) {
hideFooterAndUpdate(outRect, view, parent);
} else {
outRect.set(0, calculateTopOffset(parent, view, adapterItemCount), 0, 0);
}
}
}
private void hideFooterAndUpdate(Rect outRect, final View footerView, final RecyclerView parent) {
outRect.set(0, OFF_SCREEN_OFFSET, 0, 0);
footerView.post(new Runnable() {
@Override
public void run() {
parent.getAdapter().notifyDataSetChanged();
}
});
}
private int calculateTopOffset(RecyclerView parent, View footerView, int itemCount) {
int topOffset = parent.getHeight() - visibleChildsHeightWithFooter(parent, footerView, itemCount);
return topOffset < 0 ? 0 : topOffset;
}
private int visibleChildsHeightWithFooter(RecyclerView parent, View footerView, int itemCount) {
int totalHeight = 0;
//In the case of dynamic content when adding or removing are possible itemCount from the adapter is reliable,
//but when the screen can fit fewer items than in adapter, getChildCount() from RecyclerView should be used.
int onScreenItemCount = Math.min(parent.getChildCount(), itemCount);
for (int i = 0; i < onScreenItemCount - 1; i++) {
totalHeight += parent.getChildAt(i).getHeight();
}
return totalHeight + footerView.getHeight();
}
private boolean isFooter(RecyclerView parent, View view, int itemCount) {
return parent.getChildAdapterPosition(view) == itemCount - 1;
}
}
公共类StickyFooterItemDecoration扩展了RecyclerView.ItemDecoration{
/**
*顶部偏移,以完全隐藏屏幕上的页脚,从而避免在更改页脚位置时出现明显的闪烁。
*/
专用静态最终int关闭屏幕偏移量=5000;
@凌驾
public void getItemOffset(Rect outRect、final View视图、final RecyclerView父级、RecyclerView.State){
int adapterItemCount=parent.getAdapter().getItemCount();
if(isFooter(父项、视图、adapterItemCount)){
//第一次,每个视图都不包含与其大小相关的任何参数,
//因此,我们无法计算适当的偏移量。
//在这种情况下,设置一个较大的顶部偏移量,并通知适配器再次更新页脚。
//此外,如果在滚动后页脚变得可见,我们也不应该这样做。
if(view.getHeight()==0&&state.didStructureChange()){
hideFooterAndUpdate(outRect、view、parent);
}否则{
outRect.set(0,calculateTopOffset(父项、视图、适配器项计数),0,0);
}
}
}
私有void hideFooterAndUpdate(Rect outRect、最终视图footerView、最终回收视图父级){
outRect.set(0,离屏偏移,0,0);
footerView.post(新的Runnable(){
@凌驾
公开募捐{
parent.getAdapter().notifyDataSetChanged();
}
});
}
私有int calculateTopOffset(RecyclerView父级、视图页脚视图、int itemCount){
int-topOffset=parent.getHeight()-visibleChildsHeightWithFooter(parent、footerView、itemCount);
返回topOffset<0?0:topOffset;
}
私有int visibleChildsHeightWithFooter(RecyclerView父级、View footerView、int itemCount){
int totalHeight=0;
//在添加或删除动态内容时,适配器的itemCount是可靠的,
//但是,当屏幕可以容纳的项目少于适配器中的项目时,应该使用RecyclerView中的getChildCount()。
int-onScreenItemCount=Math.min(parent.getChildCount(),itemCount);
对于(int i=0;i
确保为RecyclerView高度设置匹配父项
请看一下示例应用程序及其工作原理
此解决方案的一个巨大缺点是,只有在整个应用程序(而不是内部装饰)中notifyDataSetChanged()之后,它才能正常工作。对于更具体的通知,它将无法正常工作,为了支持它们,它需要一种更具逻辑性的方法。此外,您还可以通过eowise从库recyclerview stickyheaders中获得见解,并改进此解决方案。我正在使用带权重的线性布局。我为页脚权重创建了多个值,效果非常好
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:background="@color/white"
android:orientation="vertical"
<include layout="@layout/header" />
<android.support.v7.widget.RecyclerView
android:id="@+id/recycleView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.5"
tools:layout_height="0dp"
tools:listitem="@layout/row" />
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="@dimen/footer_weight"
android:padding="@dimen/extra_padding"
android:paddingEnd="@dimen/small_padding"
android:paddingLeft="@dimen/small_padding"
android:paddingRight="@dimen/small_padding"
android:paddingStart="@dimen/small_padding"
android:text="@string/contact"
android:textColor="@color/grey" />
</LinearLayout>
对Dmitry Kornikov进行了改进,解决了调用notify数据集的问题
public class StickyFooterItemDecoration extends RecyclerView.ItemDecoration {
@Override
public void getItemOffsets(Rect outRect, final View view, final RecyclerView parent,
RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view);
int adapterItemCount = parent.getAdapter().getItemCount();
if (adapterItemCount == RecyclerView.NO_POSITION || (adapterItemCount - 1) != position) {
return;
}
outRect.top = calculateTopOffset(parent, view, adapterItemCount);
}
private int calculateTopOffset(RecyclerView parent, View footerView, int itemCount) {
int topOffset =
parent.getHeight() - parent.getPaddingTop() - parent.getPaddingBottom()
- visibleChildHeightWithFooter(parent, footerView, itemCount);
return topOffset < 0 ? 0 : topOffset;
}
private int visibleChildHeightWithFooter(RecyclerView parent, View footerView, int itemCount) {
int totalHeight = 0;
int onScreenItemCount = Math.min(parent.getChildCount(), itemCount);
for (int i = 0; i < onScreenItemCount - 1; i++) {
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) parent.getChildAt(i)
.getLayoutParams();
int height =
parent.getChildAt(i).getHeight() + layoutParams.topMargin
+ layoutParams.bottomMargin;
totalHeight += height;
}
int footerHeight = footerView.getHeight();
if (footerHeight == 0) {
fixLayoutSize(footerView, parent);
footerHeight = footerView.getHeight();
}
footerHeight = footerHeight + footerView.getPaddingBottom() + footerView.getPaddingTop();
return totalHeight + footerHeight;
}
private void fixLayoutSize(View view, ViewGroup parent) {
// Check if the view has a layout parameter and if it does not create one for it
if (view.getLayoutParams() == null) {
view.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
}
// Create a width and height spec using the parent as an example:
// For width we make sure that the item matches exactly what it measures from the parent.
// IE if layout says to match_parent it will be exactly parent.getWidth()
int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY);
// For the height we are going to create a spec that says it doesn't really care what is calculated,
// even if its larger than the screen
int heightSpec = View.MeasureSpec
.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED);
// Get the child specs using the parent spec and the padding the parent has
int childWidth = ViewGroup.getChildMeasureSpec(widthSpec,
parent.getPaddingLeft() + parent.getPaddingRight(), view.getLayoutParams().width);
int childHeight = ViewGroup.getChildMeasureSpec(heightSpec,
parent.getPaddingTop() + parent.getPaddingBottom(), view.getLayoutParams().height);
// Finally we measure the sizes with the actual view which does margin and padding changes to the sizes calculated
view.measure(childWidth, childHeight);
// And now we setup the layout for the view to ensure it has the correct sizes.
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
}
}
公共类StickyFooterItemDecoration扩展了RecyclerView.ItemDecoration{
@凌驾
public void getItemOffset(Rect outRect、final View视图、final RecyclerView父级、,
RecyclerView.State){
int position=parent.getChildAdapterPosition(视图);
int adapterItemCount=parent.getAdapter().getItemCount();
如果(adapterItemCount==RecyclerView.NO_位置| |(adapterItemCount-1)!=位置){
返回;
}
outRect.top=calculateTopOffset(父项、视图、适配器项计数);
}
私有int calculateTopOffset(RecyclerView父级、视图页脚视图、int itemCount){
整数拓扑偏移=
parent.getHeight()-parent.getPaddingTop()-parent.getPaddingBottom()
-VisibleChightWithFooter(父项、页脚视图、项计数);
返回topOffset<0?0:topOffset;
}
私密儿童医院
recyclerView.isNestedScrollingEnabled = false
class FooterViewHolder(private val parent: ViewGroup) {
...
fun bind(item: Item) {
...
itemView.post(::adjustTop)
}
private fun adjustTop() {
val parent = parent as RecyclerView
var topOffset = parent.height
for (child in parent.children) topOffset -= child.height
(itemView.layoutParams as ViewGroup.MarginLayoutParams)
.setMargins(0, topOffset.coerceAtLeast(0), 0, 0)
}
}
<ConsraintLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<-- This is your footer and it can be anything you want -->
<TextView
android:id="@+id/yourFooter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</ConstraintLayout>
view.doOnPreDraw {
val footerheight = yourFooter.height
recyclerView.updatePadding(bottom = footerHeight)
...
}
view.doOnPreDraw {
val footerheight = yourFooter.height
recyclerView.updatePadding(bottom = footerHeight)
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
val range = recyclerView.computeVerticalScrollRange()
val extent = recyclerView.computeVerticalScrollExtent()
val offset = recyclerView.computeVerticalScrollOffset()
val threshHold = range - footerHeight
val currentScroll = extent + offset
val excess = currentScroll - threshHold
yourFooter.transalationX = if (excess > 0)
footerHeight * (excess.toFloat()/footerHeight.toFloat()) else 0F
}
})
}