如何在RecyclerView中制作粘性标题? (没有外部库)

我想修改我的标题视图在屏幕的顶部,如下图所示,而不使用外部库。

在这里输入图像说明

就我而言,我不想按字母顺序来做。 我有两种不同types的视图(标题和正常)。 我只想修复顶部,最后一个标题。

Solutions Collecting From Web of "如何在RecyclerView中制作粘性标题? (没有外部库)"

在这里我将解释如何在没有外部库的情况下做到这一点。 这将是一个非常长的职位,所以振作起来。

首先,让我感谢@ tim.paetz,他的post激励我启动了一个使用ItemDecoration实现自己的头文件的旅程。 我在我的实现中借用了他的代码的一些部分。

正如你可能已经经历的那样,如果你试图自己做,很难find一个很好的解释如何ItemDecoration技术来实现它。 我的意思是,步骤什么? 它背后的逻辑是什么? 如何将标题贴在列表顶部? 不知道这些问题的答案是什么让别人使用外部库,而使用ItemDecoration自己做这件事很容易。

初始条件

  1. 你的数据集应该是不同types的项目list (不是在“Javatypes”意义上,而是在“标题/项目”types意义上)。
  2. 您的列表应该已经sorting。
  3. 列表中的每个项目都应该是某种types – 应该有一个与之相关的标题项目。
  4. list第一个项目必须是标题项目。

在这里,我提供了完整的代码为我的RecyclerView.ItemDecoration称为HeaderItemDecoration 。 然后,我详细解释所采取的步骤。

 public class HeaderItemDecoration extends RecyclerView.ItemDecoration { private StickyHeaderInterface mListener; private int mStickyHeaderHeight; public HeaderItemDecoration(RecyclerView recyclerView, @NonNull StickyHeaderInterface listener) { mListener = listener; // On Sticky Header Click recyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() { public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent motionEvent) { if (motionEvent.getY() <= mStickyHeaderHeight) { // Handle the clicks on the header here ... return true; } return false; } public void onTouchEvent(RecyclerView recyclerView, MotionEvent motionEvent) { } public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { } }); } @Override public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDrawOver(c, parent, state); View topChild = parent.getChildAt(0); if (Util.isNull(topChild)) { return; } int topChildPosition = parent.getChildAdapterPosition(topChild); if (topChildPosition == RecyclerView.NO_POSITION) { return; } View currentHeader = getHeaderViewForItem(topChildPosition, parent); fixLayoutSize(parent, currentHeader); int contactPoint = currentHeader.getBottom(); View childInContact = getChildInContact(parent, contactPoint); if (Util.isNull(childInContact)) { return; } if (mListener.isHeader(parent.getChildAdapterPosition(childInContact))) { moveHeader(c, currentHeader, childInContact); return; } drawHeader(c, currentHeader); } private View getHeaderViewForItem(int itemPosition, RecyclerView parent) { int headerPosition = mListener.getHeaderPositionForItem(itemPosition); int layoutResId = mListener.getHeaderLayout(headerPosition); View header = LayoutInflater.from(parent.getContext()).inflate(layoutResId, parent, false); mListener.bindHeaderData(header, headerPosition); return header; } private void drawHeader(Canvas c, View header) { c.save(); c.translate(0, 0); header.draw(c); c.restore(); } private void moveHeader(Canvas c, View currentHeader, View nextHeader) { c.save(); c.translate(0, nextHeader.getTop() - currentHeader.getHeight()); currentHeader.draw(c); c.restore(); } private View getChildInContact(RecyclerView parent, int contactPoint) { View childInContact = null; for (int i = 0; i < parent.getChildCount(); i++) { View child = parent.getChildAt(i); if (child.getBottom() > contactPoint) { if (child.getTop() <= contactPoint) { // This child overlaps the contactPoint childInContact = child; break; } } } return childInContact; } /** * Properly measures and layouts the top sticky header. * @param parent ViewGroup: RecyclerView in this case. */ private void fixLayoutSize(ViewGroup parent, View view) { // Specs for parent (RecyclerView) int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY); int heightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED); // Specs for children (headers) int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec, parent.getPaddingLeft() + parent.getPaddingRight(), view.getLayoutParams().width); int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec, parent.getPaddingTop() + parent.getPaddingBottom(), view.getLayoutParams().height); view.measure(childWidthSpec, childHeightSpec); view.layout(0, 0, view.getMeasuredWidth(), mStickyHeaderHeight = view.getMeasuredHeight()); } public interface StickyHeaderInterface { /** * This method gets called by {@link HeaderItemDecoration} to fetch the position of the header item in the adapter * that is used for (represents) item at specified position. * @param itemPosition int. Adapter's position of the item for which to do the search of the position of the header item. * @return int. Position of the header item in the adapter. */ int getHeaderPositionForItem(int itemPosition); /** * This method gets called by {@link HeaderItemDecoration} to get layout resource id for the header item at specified adapter's position. * @param headerPosition int. Position of the header item in the adapter. * @return int. Layout resource id. */ int getHeaderLayout(int headerPosition); /** * This method gets called by {@link HeaderItemDecoration} to setup the header View. * @param header View. Header to set the data on. * @param headerPosition int. Position of the header item in the adapter. */ void bindHeaderData(View header, int headerPosition); /** * This method gets called by {@link HeaderItemDecoration} to verify whether the item represents a header. * @param itemPosition int. * @return true, if item at the specified adapter's position represents a header. */ boolean isHeader(int itemPosition); } } 

商业逻辑

那么,如何让它坚持下去呢?

你没有。 除非您是自定义布局的专家,并且您知道RecyclerView的代码行数超过12,000行,否则您无法将RecyclerView的所选项目停留在顶部。 所以,因为它总是与用户界面devise,如果你不能做的东西,假的。 您只需使用Canvas 在标题上绘制标题 。 您还应该知道用户目前可以看到哪些项目。 只是发生, ItemDecoration可以为您提供Canvas和有关可见项目的信息。 有了这个,这里是基本的步骤:

  1. RecyclerView.ItemDecoration onDrawOver方法中,获取用户可见的第一个(顶部)项目。

      View topChild = parent.getChildAt(0); 
  2. 确定哪个头代表它。

      int topChildPosition = parent.getChildAdapterPosition(topChild); View currentHeader = getHeaderViewForItem(topChildPosition, parent); 
  3. 通过使用drawHeader()方法在RecyclerView的顶部绘制适当的标题。

我也想要在新的即将到来的头球达到顶级时执行这种行为:看起来即将到来的头球轻轻地将当前头球头顶出视线并最终取代他的位置。

同样的“在所有事物上绘图”的技巧在这里也适用。

  1. 确定顶部“卡住”标题何时符合新即将出现的标题。

      View childInContact = getChildInContact(parent, contactPoint); 
  2. 得到这个联系点(这是你画的粘性头部的底部和即将到来的头部的顶部)。

      int contactPoint = currentHeader.getBottom(); 
  3. 如果列表中的项目侵入此“接触点”,请重新绘制粘性标题,以使其底部位于侵入物品的顶部。 你可以通过Canvas translate()方法来实现。 结果,顶部标题的出发点将在可见区域外,并且看起来像是“被即将到来的标题推出”。 当它完全消失时,将新的标题画在最上面。

      if (childInContact != null) { if (mListener.isHeader(parent.getChildAdapterPosition(childInContact))) { moveHeader(c, currentHeader, childInContact); } else { drawHeader(c, currentHeader); } } 

其余的解释是通过我提供的代码片段中的注释和详尽的注释来解释的。

用法很简单:

 mRecyclerView.addItemDecoration(new HeaderItemDecoration((HeaderItemDecoration.StickyHeaderInterface) mAdapter)); 

您的mAdapter必须实施StickyHeaderInterface才能正常工作。 实施取决于你所拥有的数据。

最后,在这里,我提供了一个带有半透明标题的gif,这样你就可以把握这个想法,并真正看到底下发生了什么。

这里是“只是在一切上”的概念的插图。 您可以看到有两个项目“标题1” – 我们在一个卡住的位置上绘制并保留在顶部,另一个来自数据集并与所有其余项目一起移动。 用户不会看到它的内部工作,因为你将不会有半透明的标题。

在这里输入图像说明

在这里,“推出”阶段会发生什么:

在这里输入图像说明

希望它有帮助。

编辑

这是我在RecyclerView的适配器中的getHeaderPositionForItem()方法的实际实现:

 @Override public int getHeaderPositionForItem(int itemPosition) { int headerPosition = 0; do { if (this.isHeader(itemPosition)) { headerPosition = itemPosition; break; } itemPosition -= 1; } while (itemPosition >= 0); return headerPosition; } 

虽然您不必使用外部库,但您始终可以查看现有的开源解决scheme,并使用对您来说重要的类来提出您的问题所需的自定义解决scheme:

头,装饰

https://github.com/edubarr/header-decor

超薄

https://github.com/TonicArtos/SuperSLiM

[已过时]

Recyclerview-stickyheaders

https://github.com/eowise/recyclerview-stickyheaders

答案已经在这里了。 如果您不想使用任何库,可以按照下列步骤操作:

  1. 按名称对数据进行sorting
  2. 迭代通过数据列表,并在当前的项目的第一个字母!=下一个项目的第一个字母时,插入“特殊”种类的对象。
  3. 项目是“特殊”时,在适配器内部放置特殊视图。

说明:

onCreateViewHolder方法中,我们可以检查viewType并根据值(我们的“特殊”类)膨胀一个特殊的布局。

例如:

 public static final int TITLE = 0; public static final int ITEM = 1; @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (context == null) { context = parent.getContext(); } if (viewType == TITLE) { view = LayoutInflater.from(context).inflate(R.layout.recycler_adapter_title, parent,false); return new TitleElement(view); } else if (viewType == ITEM) { view = LayoutInflater.from(context).inflate(R.layout.recycler_adapter_item, parent,false); return new ItemElement(view); } return null; } 

class ItemElementclass TitleElement可以看起来像普通的ViewHolder

 public class ItemElement extends RecyclerView.ViewHolder { //TextView text; public ItemElement(View view) { super(view); //text = (TextView) view.findViewById(R.id.text); } 

所有这一切的想法都很有趣。 但是我感兴趣,如果这是有效的,因为我们需要sorting数据列表。 我认为这会降低速度。 如果有任何想法,请写信给我:)

还有一个开放的问题,就是如何把“特殊”的布局放在最上面,而物品是可回收的。 也许将所有这些与CoordinatorLayout结合起来。

您可以在我的FlexibleAdapter项目中检查并实施类StickyHeaderHelper ,并将其调整为您的用例。

但是,我build议使用这个库,因为它简化和重新组织了您通常为RecyclerView实现适配器的方式:不要重新发明轮子。

我也会说,不要使用装饰器或不赞成使用的库,也不要使用只做1或3件事情的库,你必须自己合并其他库的实现。

最简单的方法就是为您的RecyclerView创build一个物品装饰。

 import android.graphics.Canvas; import android.graphics.Rect; import android.support.annotation.NonNull; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; public class RecyclerSectionItemDecoration extends RecyclerView.ItemDecoration { private final int headerOffset; private final boolean sticky; private final SectionCallback sectionCallback; private View headerView; private TextView header; public RecyclerSectionItemDecoration(int headerHeight, boolean sticky, @NonNull SectionCallback sectionCallback) { headerOffset = headerHeight; this.sticky = sticky; this.sectionCallback = sectionCallback; } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); int pos = parent.getChildAdapterPosition(view); if (sectionCallback.isSection(pos)) { outRect.top = headerOffset; } } @Override public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDrawOver(c, parent, state); if (headerView == null) { headerView = inflateHeaderView(parent); header = (TextView) headerView.findViewById(R.id.list_item_section_text); fixLayoutSize(headerView, parent); } CharSequence previousHeader = ""; for (int i = 0; i < parent.getChildCount(); i++) { View child = parent.getChildAt(i); final int position = parent.getChildAdapterPosition(child); CharSequence title = sectionCallback.getSectionHeader(position); header.setText(title); if (!previousHeader.equals(title) || sectionCallback.isSection(position)) { drawHeader(c, child, headerView); previousHeader = title; } } } private void drawHeader(Canvas c, View child, View headerView) { c.save(); if (sticky) { c.translate(0, Math.max(0, child.getTop() - headerView.getHeight())); } else { c.translate(0, child.getTop() - headerView.getHeight()); } headerView.draw(c); c.restore(); } private View inflateHeaderView(RecyclerView parent) { return LayoutInflater.from(parent.getContext()) .inflate(R.layout.recycler_section_header, parent, false); } /** * Measures the header view to make sure its size is greater than 0 and will be drawn * https://yoda.entelect.co.za/view/9627/how-to-android-recyclerview-item-decorations */ private void fixLayoutSize(View view, ViewGroup parent) { int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY); int heightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED); int childWidth = ViewGroup.getChildMeasureSpec(widthSpec, parent.getPaddingLeft() + parent.getPaddingRight(), view.getLayoutParams().width); int childHeight = ViewGroup.getChildMeasureSpec(heightSpec, parent.getPaddingTop() + parent.getPaddingBottom(), view.getLayoutParams().height); view.measure(childWidth, childHeight); view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); } public interface SectionCallback { boolean isSection(int position); CharSequence getSectionHeader(int position); } 

}

在recycler_section_header.xml中为您的标题XML:

 <?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/list_item_section_text" android:layout_width="match_parent" android:layout_height="@dimen/recycler_section_header_height" android:background="@android:color/black" android:paddingLeft="10dp" android:paddingRight="10dp" android:textColor="@android:color/white" android:textSize="14sp" /> 

最后,将项目装饰添加到您的RecyclerView:

 RecyclerSectionItemDecoration sectionItemDecoration = new RecyclerSectionItemDecoration(getResources().getDimensionPixelSize(R.dimen.recycler_section_header_height), true, // true for sticky, false for not new RecyclerSectionItemDecoration.SectionCallback() { @Override public boolean isSection(int position) { return position == 0 || people.get(position) .getLastName() .charAt(0) != people.get(position - 1) .getLastName() .charAt(0); } @Override public CharSequence getSectionHeader(int position) { return people.get(position) .getLastName() .subSequence(0, 1); } }); recyclerView.addItemDecoration(sectionItemDecoration); 

使用此项目装饰,您可以在创build项目装饰时使标题固定/粘贴或不使用布尔值。

你可以在github上find一个完整的工作示例: https : //github.com/paetztm/recycler_view_headers

上面我已经做出了自己的Sevastyan解决scheme的变体

 class HeaderItemDecoration(recyclerView: RecyclerView, private val listener: StickyHeaderInterface) : RecyclerView.ItemDecoration() { private val headerContainer = FrameLayout(recyclerView.context) private var stickyHeaderHeight: Int = 0 private var currentHeader: View? = null private var currentHeaderPosition = 0 init { val layout = RelativeLayout(recyclerView.context) val params = recyclerView.layoutParams val parent = recyclerView.parent as ViewGroup val index = parent.indexOfChild(recyclerView) parent.addView(layout, index, params) parent.removeView(recyclerView) layout.addView(recyclerView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) layout.addView(headerContainer, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) } override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { super.onDrawOver(c, parent, state) val topChild = parent.getChildAt(0) ?: return val topChildPosition = parent.getChildAdapterPosition(topChild) if (topChildPosition == RecyclerView.NO_POSITION) { return } val currentHeader = getHeaderViewForItem(topChildPosition, parent) fixLayoutSize(parent, currentHeader) val contactPoint = currentHeader.bottom val childInContact = getChildInContact(parent, contactPoint) ?: return val nextPosition = parent.getChildAdapterPosition(childInContact) if (listener.isHeader(nextPosition)) { moveHeader(currentHeader, childInContact, topChildPosition, nextPosition) return } drawHeader(currentHeader, topChildPosition) } private fun getHeaderViewForItem(itemPosition: Int, parent: RecyclerView): View { val headerPosition = listener.getHeaderPositionForItem(itemPosition) val layoutResId = listener.getHeaderLayout(headerPosition) val header = LayoutInflater.from(parent.context).inflate(layoutResId, parent, false) listener.bindHeaderData(header, headerPosition) return header } private fun drawHeader(header: View, position: Int) { headerContainer.layoutParams.height = stickyHeaderHeight setCurrentHeader(header, position) } private fun moveHeader(currentHead: View, nextHead: View, currentPos: Int, nextPos: Int) { val marginTop = nextHead.top - currentHead.height if (currentHeaderPosition == nextPos && currentPos != nextPos) setCurrentHeader(currentHead, currentPos) val params = currentHeader?.layoutParams as? MarginLayoutParams ?: return params.setMargins(0, marginTop, 0, 0) currentHeader?.layoutParams = params headerContainer.layoutParams.height = stickyHeaderHeight + marginTop } private fun setCurrentHeader(header: View, position: Int) { currentHeader = header currentHeaderPosition = position headerContainer.removeAllViews() headerContainer.addView(currentHeader) } private fun getChildInContact(parent: RecyclerView, contactPoint: Int): View? = (0 until parent.childCount) .map { parent.getChildAt(it) } .firstOrNull { it.bottom > contactPoint && it.top <= contactPoint } private fun fixLayoutSize(parent: ViewGroup, view: View) { val widthSpec = View.MeasureSpec.makeMeasureSpec(parent.width, View.MeasureSpec.EXACTLY) val heightSpec = View.MeasureSpec.makeMeasureSpec(parent.height, View.MeasureSpec.UNSPECIFIED) val childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec, parent.paddingLeft + parent.paddingRight, view.layoutParams.width) val childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec, parent.paddingTop + parent.paddingBottom, view.layoutParams.height) view.measure(childWidthSpec, childHeightSpec) stickyHeaderHeight = view.measuredHeight view.layout(0, 0, view.measuredWidth, stickyHeaderHeight) } interface StickyHeaderInterface { fun getHeaderPositionForItem(itemPosition: Int): Int fun getHeaderLayout(headerPosition: Int): Int fun bindHeaderData(header: View, headerPosition: Int) fun isHeader(itemPosition: Int): Boolean } } 

…这里是StickyHeaderInterface的实现(我直接在回收站适配器中):

 override fun getHeaderPositionForItem(itemPosition: Int): Int = (itemPosition downTo 0) .map { Pair(isHeader(it), it) } .firstOrNull { it.first }?.second ?: RecyclerView.NO_POSITION override fun getHeaderLayout(headerPosition: Int): Int { /* ... */ } override fun bindHeaderData(header: View, headerPosition: Int) { if (headerPosition == RecyclerView.NO_POSITION) header.layoutParams.height = 0 else /* ... */ } override fun isHeader(itemPosition: Int): Boolean { /* ... */ } 

所以,在这种情况下,标题不仅仅是画在canvas上,而是用select器或纹波,clicklistener等来查看。

| * | 经过一整天的奋斗之后,我开发了Session Adopter或Manager Class
这只需复制粘贴并指定您的列表和值即可使用。

这是为了帮助所有我不想和我一样挣扎的人。


| * | 在SsnHdrAryVar中指定标题名称:

 String SsnHdrAryVar[] = {"NamHdr1", "NamHdr2", "NamHdr3"}; 

| * | 在SsnItmCwtAryVar中分别指定每个会话下要显示的子项数目:

 int SsnItmCwtAryVar[] = {2, 3, 5}; 

| * | 在SsnHdrHytVar中指定会话头的高度:

  int SsnHdrHytVar = 100; 

| * | 指定标题背景颜色和文本颜色:

  int SsnHdrBgdClr = Color.GREEN; int SsnHdrTxtClr = Color.MAGENTA; 

| * | 如果您需要粘性会话标头,请指定true:

  boolean SsnStkVab = true; 

| * | 完整列表活动分类代码(不包括列表采用者和获取ArrayList)

 public class NamLysSrnCls extends Activity { ArrayList<ItmLysCls> ItmAryLysVar = new ArrayList<>(); // CodTdo :=> Specify all your requirement here : String SsnHdrAryVar[] = {"NamHdr1", "NamHdr2", "NamHdr3"}; int SsnItmCwtAryVar[] = {2, 3, 5}; int LysItmHytVal = 200; int SsnHdrHytVar = 100; int SsnHdrBgdClr = Color.GREEN; int SsnHdrTxtClr = Color.MAGENTA; boolean SsnStkVab = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); RecyclerView NamLysLyoVav = new RecyclerView(this); NamLysAdrCls NamLysAdrVar = new NamLysAdrCls(GetItmAryLysFnc()); NamLysLyoVav.setAdapter(NamLysAdrVar); NamLysLyoVav.setLayoutManager(new LinearLayoutManager(this)); NamLysLyoVav.addItemDecoration(new LysSsnMgrCls()); // CodTdo :=> If you need Horizontal Lines : NamLysLyoVav.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL)); setContentView(NamLysLyoVav); } // TskTdo :=> Add Below List Seesion Manager or Adoptor Class to ur List activity class class LysSsnMgrCls extends RecyclerView.ItemDecoration { private RelativeLayout SsnHdrVav; private TextView SsnHdrTxtVav; private int SsnHdrRefIdxAryVar[]; public LysSsnMgrCls() { SsnHdrRefIdxAryVar = new int[ItmAryLysVar.size()]; int TmpSsnHdrIdxVar = 0; int TmpItmCwtAdnVar = SsnItmCwtAryVar[0]; for(int IdxVat = 1; IdxVat < ItmAryLysVar.size(); IdxVat++) { if(IdxVat < TmpItmCwtAdnVar) SsnHdrRefIdxAryVar[IdxVat] = TmpSsnHdrIdxVar; else { TmpSsnHdrIdxVar++; TmpItmCwtAdnVar += SsnItmCwtAryVar[TmpSsnHdrIdxVar]; SsnHdrRefIdxAryVar[IdxVat] = TmpSsnHdrIdxVar; } Log.d("TAG", "onCreate: " + SsnHdrRefIdxAryVar[IdxVat]); } } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); int LysItmIdxVal = parent.getChildAdapterPosition(view); if (ChkSsnHasHdrFnc(LysItmIdxVal)) { outRect.top = SsnHdrHytVar; } } @Override public void onDrawOver(Canvas SsnCanvasPsgVal, RecyclerView SupLysLyoPsgVav, RecyclerView.State LysSttPsgVal) { super.onDrawOver(SsnCanvasPsgVal, SupLysLyoPsgVav, LysSttPsgVal); if (SsnHdrVav == null) { // TskTdo :=> Design Session Header : SsnHdrVav = new RelativeLayout(NamLysSrnCls.this); SsnHdrVav.setBackgroundColor(SsnHdrBgdClr); SsnHdrVav.setLayoutParams(new LinearLayoutCompat.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)); SsnHdrTxtVav = new TextView(NamLysSrnCls.this); SsnHdrTxtVav.setPadding(20,10,20,10); SsnHdrTxtVav.setLayoutParams(new LinearLayoutCompat.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)); SsnHdrTxtVav.setTextColor(SsnHdrTxtClr); SsnHdrTxtVav.setTextSize(20); SsnHdrTxtVav.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); SsnHdrVav.addView(SsnHdrTxtVav); LyoSyzFixFnc(SsnHdrVav, SupLysLyoPsgVav); } for (int i = 0; i < SupLysLyoPsgVav.getChildCount(); i++) { View DspSubVyuVav = SupLysLyoPsgVav.getChildAt(i); final int LysItmIdxVal = SupLysLyoPsgVav.getChildAdapterPosition(DspSubVyuVav); if (ChkSsnHasHdrFnc(LysItmIdxVal)) { String title = GetSsnHdrTxtFnc(LysItmIdxVal); SsnHdrTxtVav.setText(title); DevSsnHdrFnc(SsnCanvasPsgVal, DspSubVyuVav, SsnHdrVav); } } } boolean ChkSsnHasHdrFnc(int LysItmIdxPsgVal) { return LysItmIdxPsgVal == 0 ? true : SsnHdrRefIdxAryVar[LysItmIdxPsgVal] != SsnHdrRefIdxAryVar[LysItmIdxPsgVal - 1]; } String GetSsnHdrTxtFnc(int LysItmIdxPsgVal) { return SsnHdrAryVar[SsnHdrRefIdxAryVar[LysItmIdxPsgVal]]; } private void DevSsnHdrFnc(Canvas HdrCanvasPsgVal, View DspSubItmPsgVav, View SsnHdrPsgVav) { HdrCanvasPsgVal.save(); if (SsnStkVab) HdrCanvasPsgVal.translate(0, Math.max(0, DspSubItmPsgVav.getTop() - SsnHdrPsgVav.getHeight())); else HdrCanvasPsgVal.translate(0, DspSubItmPsgVav.getTop() - SsnHdrPsgVav.getHeight()); SsnHdrPsgVav.draw(HdrCanvasPsgVal); HdrCanvasPsgVal.restore(); } private void LyoSyzFixFnc(View SsnHdrVyuPsgVal, ViewGroup SupLysLyoPsgVav) { int LysLyoWytVal = View.MeasureSpec.makeMeasureSpec(SupLysLyoPsgVav.getWidth(), View.MeasureSpec.EXACTLY); int LysLyoHytVal = View.MeasureSpec.makeMeasureSpec(SupLysLyoPsgVav.getHeight(), View.MeasureSpec.UNSPECIFIED); int SsnHdrWytVal = ViewGroup.getChildMeasureSpec(LysLyoWytVal, SupLysLyoPsgVav.getPaddingLeft() + SupLysLyoPsgVav.getPaddingRight(), SsnHdrVyuPsgVal.getLayoutParams().width); int SsnHdrHytVal = ViewGroup.getChildMeasureSpec(LysLyoHytVal, SupLysLyoPsgVav.getPaddingTop() + SupLysLyoPsgVav.getPaddingBottom(), SsnHdrVyuPsgVal.getLayoutParams().height); SsnHdrVyuPsgVal.measure(SsnHdrWytVal, SsnHdrHytVal); SsnHdrVyuPsgVal.layout(0, 0, SsnHdrVyuPsgVal.getMeasuredWidth(), SsnHdrVyuPsgVal.getMeasuredHeight()); } } } 

我有和你一样的问题,但在一个更复杂的情况下。 我想添加多种types的头,根据他的viewType和不同高度的不同分隔符保持粘性。

我在github上发布了一个可以做到这一点的android库。

Github项目: https : //github.com/sokarcreative/BasicStuffRecyclerview

在你的应用程序build.gradle:

 implementation 'com.github.sokarcreative:BasicStuffRecyclerview:0.1.0' 

在你的项目build.gradle:

 allprojects { repositories { maven { url "https://jitpack.io" } } } 

Android示例