Android RecyclerView 頂部懸浮實(shí)現(xiàn)

上圖: 本文代碼的Github地址
sticky.gif
思路:
  1. 每一個(gè)RecyclerView的item的布局(下文叫itemUI)里面都包含“吸頂文本”這個(gè)布局(下文叫StickyLayout),根據(jù)當(dāng)前itemA和上一個(gè)itemB的吸頂信息是否相同,決定是否展示itemA的StickyLayout.
  2. 包含RecyclerView的布局(下文叫wrapperUI)最上部分,有一個(gè)假的StickyLayout(下文叫FakeStickyLayout).
  3. 監(jiān)聽RecyclerView的滾動(dòng),根據(jù)RecyclerView的滾動(dòng)距離,決定FakeStickyLayout向上或者向下滾動(dòng)的距離.
代碼解析
  • 先說布局:
wrapperUI.xml

<FrameLayout    
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      app:layout_behavior="@string/appbar_scrolling_view_behavior

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_sticky_example"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical" />  

    <include layout="@layout/layout_sticky_header_view" />
</FrameLayout>
itemUI.xml

<?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="wrap_content" 
    android:layout_marginTop="1dp"      
    android:orientation="vertical">  

  <include layout="@layout/layout_sticky_header_view" />  

  <RelativeLayout      
    android:id="@+id/rl_content_wrapper"           ]
    android:layout_width="match_parent"      
    android:layout_height="wrap_content"      
    android:layout_marginTop="1dp"     
    android:padding="10dp">    

  ~~~~~~~

  </RelativeLayout>
</LinearLayout>

這兩個(gè)布局沒什么特別,重點(diǎn)是

<include layout="@layout/layout_sticky_header_view" /> 

這個(gè)include標(biāo)簽引用的布局,就是吸頂?shù)哪莻€(gè)布局,保證了itemUI中的StickyLayout和wrapperUI中的FakeStickyLayout的布局一致.

  • 再說RecyclerView 的 Adapter:
// RecyclerView 的第一個(gè)item,肯定是展示StickyLayout的.
public static final int FIRST_STICKY_VIEW = 1;
// RecyclerView 除了第一個(gè)item以外,要展示StickyLayout的.
public static final int HAS_STICKY_VIEW = 2;
// RecyclerView 的不展示StickyLayout的item.
public static final int NONE_STICKY_VIEW = 3;
if (position == 0) {     
    recyclerViewHolder.tvStickyHeader.setVisibility(View.VISIBLE);        
    recyclerViewHolder.tvStickyHeader.setText(stickyExampleModel.sticky);  

    // 第一個(gè)item的吸頂信息肯定是展示的,并且標(biāo)記tag為FIRST_STICKY_VIEW
    recyclerViewHolder.itemView.setTag(FIRST_STICKY_VIEW);

  } else {  
    // 之后的item都會(huì)和前一個(gè)item要展示的吸頂信息進(jìn)行比較,不相同就展示,并且標(biāo)記tag為HAS_STICKY_VIEW
    if (!TextUtils.equals(stickyExampleModel.sticky, stickyExampleModels.get(position - 1).sticky)) {  
      recyclerViewHolder.tvStickyHeader.setVisibility(View.VISIBLE);    
      recyclerViewHolder.tvStickyHeader.setText(stickyExampleModel.sticky);    
      recyclerViewHolder.itemView.setTag(HAS_STICKY_VIEW); 

    } else {       
      // 相同就不展示,并且標(biāo)記tag為NONE_STICKY_VIEW
      recyclerViewHolder.tvStickyHeader.setVisibility(View.GONE);       
      recyclerViewHolder.itemView.setTag(NONE_STICKY_VIEW);  

    }
  }
// ContentDescription 用來記錄并獲取要吸頂展示的信息
recyclerViewHolder.itemView.setContentDescription(stickyExampleModel.sticky);
  • 說了半天,重點(diǎn)終于來了
rvStickyExample.addOnScrollListener(new RecyclerView.OnScrollListener() {  
    @Override  
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {    
      super.onScrolled(recyclerView, dx, dy);    

      // 找到RecyclerView的item中,和RecyclerView的getTop 向下相距5個(gè)像素的那個(gè)item
      // (嘗試2、3個(gè)像素位置都找不到,所以干脆用了5個(gè)像素),
      // 我們根據(jù)這個(gè)item,來更新吸頂布局的內(nèi)容,
      // 因?yàn)槲覀兊腟tickyLayout展示的信息肯定是最上面的那個(gè)item的信息. 
      View stickyInfoView = recyclerView.findChildViewUnder(tvStickyHeaderView.getMeasuredWidth() / 2, 5);    
      if (stickyInfoView != null && stickyInfoView.getContentDescription() != null) {      
          tvStickyHeaderView.setText(String.valueOf(stickyInfoView.getContentDescription()));    
      }    

      // 找到固定在屏幕上方那個(gè)FakeStickyLayout下面一個(gè)像素位置的RecyclerView的item,
      // 我們根據(jù)這個(gè)item來更新假的StickyLayout要translate多少距離. 
      // 并且只處理HAS_STICKY_VIEW和NONE_STICKY_VIEW這兩種tag,
      // 因?yàn)榈谝粋€(gè)item的StickyLayout雖然展示,但是一定不會(huì)引起FakeStickyLayout的滾動(dòng). 
      View transInfoView = recyclerView.findChildViewUnder(
                         tvStickyHeaderView.getMeasuredWidth() / 2, tvStickyHeaderView.getMeasuredHeight() + 1);    

      if (transInfoView != null && transInfoView.getTag() != null) {      
        int transViewStatus = (int) transInfoView.getTag();      
        int dealtY = transInfoView.getTop() - tvStickyHeaderView.getMeasuredHeight();      

        // 如果當(dāng)前item需要展示StickyLayout,
        // 那么根據(jù)這個(gè)item的getTop和FakeStickyLayout的高度相差的距離來滾動(dòng)FakeStickyLayout. 
        // 這里有一處需要注意,如果這個(gè)item的getTop已經(jīng)小于0,也就是滾動(dòng)出了屏幕,
        // 那么我們就要把假的StickyLayout恢復(fù)原位,來覆蓋住這個(gè)item對應(yīng)的吸頂信息. 
        if (transViewStatus == StickyExampleAdapter.HAS_STICKY_VIEW) {              
          if (transInfoView.getTop() > 0) {          
            tvStickyHeaderView.setTranslationY(dealtY);        
          } else {          
            tvStickyHeaderView.setTranslationY(0);       
          }      
        } else if (transViewStatus == StickyExampleAdapter.NONE_STICKY_VIEW) {        
          // 如果當(dāng)前item不需要展示StickyLayout,那么就不會(huì)引起FakeStickyLayout的滾動(dòng). 
          tvStickyHeaderView.setTranslationY(0);      
        }    
      }  
    }
});
相關(guān)文章

一個(gè)屬性實(shí)現(xiàn)帶進(jìn)度的圓形進(jìn)度條

寫在末尾:

本人第一次寫簡書,如果有不正確的地方歡迎批評(píng)指正.

個(gè)人博客:

https://christmasjason.github.io

https://github.com/christmasjason/StickyHeaderView

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容