【问题标题】:How to make sticky section headers (like iOS) in Android?如何在 Android 中制作粘性部分标题(如 iOS)?
【发布时间】:2014-01-09 20:55:42
【问题描述】:

我的具体问题是:我怎样才能达到这样的效果:http://youtu.be/EJm7subFbQI

反弹效果并不重要,但我需要标题的“粘性”效果。我从哪里开始?,我能以什么为基础?我需要一些可以在 API 8 上实现的东西。

谢谢。

【问题讨论】:

    标签: android ios listview scrollview


    【解决方案1】:

    对于这个问题,已有一些解决方案。您所描述的是节标题,在 Android 中被称为 sticky 节标题。

    【讨论】:

    • 在尝试了我在这里得到的选项之后,HeaderListView 似乎最适合我的需求,并且比其他的更兼容。谢谢
    • 我现在无法访问 github.io 的 HeaderListView。这是 github.com 的直接链接:github.com/applidium/HeaderListView
    • 哪些支持快速滚动?
    • 似乎HeaderListView不支持快速滚动。
    • 滚动速度不慢,第 2 节一开始就崩溃:(
    【解决方案2】:

    编辑:有一些空闲时间来添加完整工作示例的代码。相应地编辑了答案。

    对于那些不想使用 3rd 方代码(或不能直接使用它,例如在 Xamarin 中)的人来说,这可以很容易地手动完成。 这个想法是为标题使用另一个 ListView。此列表视图仅包含标题项。用户无法滚动它(setEnabled(false)),但会根据主列表的滚动从代码中滚动。因此,您将有两个列表 - headerListview 和 mainListview,以及两个相应的适配器 headerAdapter 和 mainAdapter。 headerAdapter 只返回截面视图,而 mainAdapter 支持两种视图类型(截面和项目)。您将需要一个方法,该方法在主列表中占据一个位置并返回部分列表中的相应位置。

    主要活动

    public class MainActivity extends AppCompatActivity {
    
        public static final int TYPE_SECTION = 0;
        public static final int TYPE_ITEM = 1;
    
        ListView mainListView;
        ListView headerListView;
        MainAdapter mainAdapter;
        HeaderAdapter headerAdapter;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            mainListView = (ListView)findViewById(R.id.list);
            headerListView = (ListView)findViewById(R.id.header);
            mainAdapter = new MainAdapter();
            headerAdapter = new HeaderAdapter();
    
            headerListView.setEnabled(false);
            headerListView.setAdapter(headerAdapter);
            mainListView.setAdapter(mainAdapter);
    
            mainListView.setOnScrollListener(new AbsListView.OnScrollListener(){
    
                @Override
                public void onScrollStateChanged(AbsListView view, int scrollState){
    
                }
    
                @Override
                public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                    // this should return an index in the headers list, based one the index in the main list. The logic for this is highly dependent on your data.
                    int pos = mainAdapter.getSectionIndexForPosition(firstVisibleItem);
                    // this makes sure our headerListview shows the proper section (the one on the top of the mainListview)
                    headerListView.setSelection(pos);
    
                    // this makes sure that headerListview is scrolled exactly the same amount as the mainListview
                    if(mainAdapter.getItemViewType(firstVisibleItem + 1) == TYPE_SECTION){
                        headerListView.setSelectionFromTop(pos, mainListView.getChildAt(0).getTop());
                    }
                }
            });
        }
    
        public class MainAdapter extends BaseAdapter{
            int count = 30;
    
            @Override
            public int getItemViewType(int position){
                if((float)position / 10 == (int)((float)position/10)){
                    return TYPE_SECTION;
                }else{
                    return TYPE_ITEM;
                }
            }
    
            @Override
            public int getViewTypeCount(){ return 2; }
    
            @Override
            public int getCount() { return count - 1; }
    
            @Override
            public Object getItem(int position) { return null; }
    
            @Override
            public long getItemId(int position) { return position; }
    
            public int getSectionIndexForPosition(int position){ return position / 10; }
    
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                View v =  getLayoutInflater().inflate(R.layout.item, parent, false);
                position++;
                if(getItemViewType(position) == TYPE_SECTION){
                    ((TextView)v.findViewById(R.id.text)).setText("SECTION "+position);
    
                }else{
                    ((TextView)v.findViewById(R.id.text)).setText("Item "+position);
                }
                return v;
            }
        }
    
        public class HeaderAdapter extends BaseAdapter{
            int count = 5;
    
            @Override
            public int getCount() { return count; }
    
            @Override
            public Object getItem(int position) { return null; }
    
            @Override
            public long getItemId(int position) { return position; }
    
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                View v =  getLayoutInflater().inflate(R.layout.item, parent, false);
                ((TextView)v.findViewById(R.id.text)).setText("SECTION "+position*10);
                return v;
            }
        }
    
    }
    

    这里有几点需要注意。我们不想在主视图列表中显示第一部分,因为它会产生重复(它已经显示在标题中)。为避免这种情况,请在您的 mainAdapter.getCount() 中:

    return actualCount - 1;
    

    并确保 getView() 方法的第一行是

    position++;
    

    这样,您的主列表将呈现除第一个以外的所有单元格。

    另一件事是您要确保 headerListview 的高度与列表项的高度匹配。在此示例中,高度是固定的,但如果您的项目高度未设置为 dp 中的精确值,则可能会很棘手。有关如何解决此问题,请参阅此答案:https://stackoverflow.com/a/41577017/291688

    主布局

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin">
        <ListView
            android:id="@+id/header"
            android:layout_width="match_parent"
            android:layout_height="48dp"/>
    
        <ListView
            android:id="@+id/list"
            android:layout_below="@+id/header"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    </RelativeLayout>
    

    项目/标题布局

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical" android:layout_width="match_parent"
        android:layout_height="48dp">
        <TextView
            android:id="@+id/text"
            android:gravity="center_vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            />
    
    </LinearLayout>
    

    【讨论】:

    • @DennisK。你对我来说就像上帝。非常感谢这个精彩的代码。这真的让我很开心。我花了 4 到 5 天时间寻找解决方案,而您的答案在 5 分钟内就奏效了 :)
    • 这很有帮助,谢谢!我在收到大量此类警告时遇到了问题:requestLayout() improperly called by android.widget.ListView。原来他们来自 onScroll 监听器。对于其他遇到问题的人,将 onScroll UI 更新移动到 UI 线程解决了我的问题。更多信息:stackoverflow.com/questions/5065339/…
    • @DennisK 感谢您的回答,但是当我尝试设置 headerListView.addHeaderView(view); 时,它会替换 headerListView 中的第一项。如何在sectionHeader之上实现headerView?
    【解决方案3】:

    将此添加到您的 app.gradle 文件中

    compile 'se.emilsjolander:StickyScrollViewItems:1.1.0'
    

    然后我的布局(我已将 android:tag ="sticky" 添加到特定视图(如 textview 或 edittext 而不是 LinearLayout)中)看起来像这样。它还使用数据绑定,忽略它。

        <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
    
        <data>
    
            <variable
                name="temp"
                type="com.lendingkart.prakhar.lendingkartdemo.databindingmodel.BusinessDetailFragmentModel" />
    
            <variable
                name="presenter"
                type="com.lendingkart.prakhar.lendingkartdemo.presenters.BusinessDetailsPresenter" />
        </data>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
    
    
            <com.lendingkart.prakhar.lendingkartdemo.customview.StickyScrollView
                android:id="@+id/sticky_scroll"
                android:layout_width="match_parent"
                android:layout_height="match_parent">
                <!-- scroll view child goes here -->
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:orientation="vertical">
    
    
                    <android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center"
                        card_view:cardCornerRadius="5dp"
                        card_view:cardUseCompatPadding="true">
    
                        <LinearLayout
                            android:layout_width="match_parent"
                            android:layout_height="match_parent"
                            android:orientation="vertical">
    
    
                            <TextView
                                style="@style/group_view_text"
                                android:layout_width="match_parent"
                                android:layout_height="wrap_content"
                                android:background="@drawable/businessdetailtitletextviewbackground"
                                android:padding="@dimen/activity_horizontal_margin"
                                android:tag="sticky"
                                android:text="@string/business_contact_detail" />
    
                            <android.support.design.widget.TextInputLayout
                                android:layout_width="match_parent"
                                android:layout_height="wrap_content"
                                android:layout_margin="7dp">
    
                                <android.support.design.widget.TextInputEditText
                                    android:layout_width="match_parent"
                                    android:layout_height="wrap_content"
                                    android:hint="@string/comapnyLabel"
                                    android:textSize="16sp" />
    
                            </android.support.design.widget.TextInputLayout>
    
                            <android.support.design.widget.TextInputLayout
                                android:layout_width="match_parent"
                                android:layout_height="wrap_content"
                                android:layout_margin="5dp">
    
                                <android.support.design.widget.TextInputEditText
                                    android:layout_width="match_parent"
                                    android:layout_height="wrap_content"
                                    android:hint="@string/contactLabel"
                                    android:textSize="16sp" />
    
                            </android.support.design.widget.TextInputLayout>
    
                            <android.support.design.widget.TextInputLayout
                                android:layout_width="match_parent"
                                android:layout_height="wrap_content"
                                android:layout_margin="5dp">
    
                                <android.support.design.widget.TextInputEditText
                                    android:layout_width="match_parent"
                                    android:layout_height="wrap_content"
                                    android:hint="@string/emailLabel"
                                    android:textSize="16sp" />
    
                            </android.support.design.widget.TextInputLayout>
    
                            <android.support.design.widget.TextInputLayout
                                android:layout_width="match_parent"
                                android:layout_height="wrap_content"
                                android:layout_margin="5dp">
    
                                <android.support.design.widget.TextInputEditText
                                    android:layout_width="match_parent"
                                    android:layout_height="wrap_content"
                                    android:hint="@string/NumberOfEmployee"
                                    android:textSize="16sp" />
    
                            </android.support.design.widget.TextInputLayout>
    
    
                        </LinearLayout>
                    </android.support.v7.widget.CardView>
    
                    <android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center"
                        card_view:cardCornerRadius="5dp"
                        card_view:cardUseCompatPadding="true">
    
                        <TextView
                            style="@style/group_view_text"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:background="@drawable/businessdetailtitletextviewbackground"
                            android:padding="@dimen/activity_horizontal_margin"
                            android:tag="sticky"
                            android:text="@string/nature_of_business" />
    
    
                    </android.support.v7.widget.CardView>
    
                    <android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center"
                        card_view:cardCornerRadius="5dp"
                        card_view:cardUseCompatPadding="true">
    
                        <TextView
                            style="@style/group_view_text"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:background="@drawable/businessdetailtitletextviewbackground"
                            android:padding="@dimen/activity_horizontal_margin"
                            android:tag="sticky"
                            android:text="@string/taxation" />
    
    
                    </android.support.v7.widget.CardView>
    
    
    
                </LinearLayout>
            </com.lendingkart.prakhar.lendingkartdemo.customview.StickyScrollView>
    
    
        </LinearLayout>
    </layout>
    

    textview 的样式组看起来像这样

     <style name="group_view_text" parent="@android:style/TextAppearance.Medium">
            <item name="android:layout_width">wrap_content</item>
            <item name="android:layout_height">wrap_content</item>
            <item name="android:textColor">@color/edit_text_color</item>
            <item name="android:textSize">16dp</item>
            <item name="android:layout_centerVertical">true</item>
            <item name="android:textStyle">bold</item>
        </style>
    

    textview 的背景是这样的:(@drawable/businessdetailtitletextviewbackground)

    <?xml version="1.0" encoding="utf-8"?>
    <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
        <item>
            <shape android:shape="rectangle">
                <solid android:color="@color/edit_text_color" />
            </shape>
        </item>
        <item android:bottom="2dp">
            <shape android:shape="rectangle">
                <solid android:color="@color/White" />
            </shape>
        </item>
    </layer-list>
    

    【讨论】:

      【解决方案4】:

      您可以使用SuperSLiM 库来达到此效果。它为 RecyclerView 提供了一个 LayoutManager,具有可互换的线性、网格和交错显示的视图。

      一个不错的demo位于github repository

      只是为了得到这样的结果

      app:slm_headerDisplay="inline|sticky"
      or
      app:slm_headerDisplay="sticky"
      

      【讨论】:

        【解决方案5】:

        我使用了一个特殊的类来实现像 iPhone 这样的列表视图。 您可以在此处找到带有源代码的示例。 https://demonuts.com/android-recyclerview-sticky-header-like-iphone/

        这个类更新了listview是as

        import android.content.Context;
        import android.graphics.drawable.Drawable;
        import android.util.AttributeSet;
        import android.util.TypedValue;
        import android.view.Gravity;
        import android.view.View;
        import android.view.animation.AlphaAnimation;
        import android.widget.AbsListView;
        import android.widget.AdapterView;
        import android.widget.FrameLayout;
        import android.widget.ImageView;
        import android.widget.ImageView.ScaleType;
        import android.widget.ListView;
        import android.widget.RelativeLayout;
        
        public class HeaderListView extends RelativeLayout {
        
            // TODO: Handle listViews with fast scroll
            // TODO: See if there are methods to dispatch to mListView
        
            private static final int FADE_DELAY    = 1000;
            private static final int FADE_DURATION = 2000;
        
            private InternalListView mListView;
            private SectionAdapter   mAdapter;
            private RelativeLayout   mHeader;
            private View             mHeaderConvertView;
            private FrameLayout      mScrollView;
            private AbsListView.OnScrollListener mExternalOnScrollListener;
        
            public HeaderListView(Context context) {
                super(context);
                init(context, null);
            }
        
            public HeaderListView(Context context, AttributeSet attrs) {
                super(context, attrs);
                init(context, attrs);
            }
        
            private void init(Context context, AttributeSet attrs) {
                mListView = new InternalListView(getContext(), attrs);
                LayoutParams listParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
                listParams.addRule(ALIGN_PARENT_TOP);
                mListView.setLayoutParams(listParams);
                mListView.setOnScrollListener(new HeaderListViewOnScrollListener());
                mListView.setVerticalScrollBarEnabled(false);
                mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                    @Override
                    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                        if (mAdapter != null) {
                            mAdapter.onItemClick(parent, view, position, id);
                        }
                    }
                });
                addView(mListView);
        
                mHeader = new RelativeLayout(getContext());
                LayoutParams headerParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
                headerParams.addRule(ALIGN_PARENT_TOP);
                mHeader.setLayoutParams(headerParams);
                mHeader.setGravity(Gravity.BOTTOM);
                addView(mHeader);
        
                // The list view's scroll bar can be hidden by the header, so we display our own scroll bar instead
                Drawable scrollBarDrawable = getResources().getDrawable(R.drawable.scrollbar_handle_holo_light);
                mScrollView = new FrameLayout(getContext());
                LayoutParams scrollParams = new LayoutParams(scrollBarDrawable.getIntrinsicWidth(), LayoutParams.MATCH_PARENT);
                scrollParams.addRule(ALIGN_PARENT_RIGHT);
                scrollParams.rightMargin = (int) dpToPx(2);
                mScrollView.setLayoutParams(scrollParams);
        
                ImageView scrollIndicator = new ImageView(context);
                scrollIndicator.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
                scrollIndicator.setImageDrawable(scrollBarDrawable);
                scrollIndicator.setScaleType(ScaleType.FIT_XY);
                mScrollView.addView(scrollIndicator);
                mScrollView.setVisibility(INVISIBLE);
        
                addView(mScrollView);
            }
        
            public void setAdapter(SectionAdapter adapter) {
                mAdapter = adapter;
                mListView.setAdapter(adapter);
            }
        
            public void setOnScrollListener(AbsListView.OnScrollListener l) {
                mExternalOnScrollListener = l;
            }
        
            private class HeaderListViewOnScrollListener implements AbsListView.OnScrollListener {
        
                private int            previousFirstVisibleItem = -1;
                private int            direction                = 0;
                private int            actualSection            = 0;
                private boolean        scrollingStart           = false;
                private boolean        doneMeasuring            = false;
                private int            lastResetSection         = -1;
                private int            nextH;
                private int            prevH;
                private View           previous;
                private View           next;
                private AlphaAnimation fadeOut                  = new AlphaAnimation(1f, 0f);
                private boolean        noHeaderUpToHeader       = false;
                private boolean        didScroll = false;
        
                @Override
                public void onScrollStateChanged(AbsListView view, int scrollState) {
                    if (mExternalOnScrollListener != null) {
                        mExternalOnScrollListener.onScrollStateChanged(view, scrollState);
                    }
                    didScroll = true;
                }
        
                @Override
                public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                    if (mExternalOnScrollListener != null) {
                        mExternalOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
                    }
        
                    if (!didScroll) {
                        return;
                    }
        
                    firstVisibleItem -= mListView.getHeaderViewsCount();
                    if (firstVisibleItem < 0) {
                        mHeader.removeAllViews();
                        return;
                    }
        
                    updateScrollBar();
                    if (visibleItemCount > 0 && firstVisibleItem == 0 && mHeader.getChildAt(0) == null) {
                        addSectionHeader(0);
                        lastResetSection = 0;
                    }
        
                    int realFirstVisibleItem = getRealFirstVisibleItem(firstVisibleItem, visibleItemCount);
                    if (totalItemCount > 0 && previousFirstVisibleItem != realFirstVisibleItem) {
                        direction = realFirstVisibleItem - previousFirstVisibleItem;
        
                        actualSection = mAdapter.getSection(realFirstVisibleItem);
        
                        boolean currIsHeader = mAdapter.isSectionHeader(realFirstVisibleItem);
                        boolean prevHasHeader = mAdapter.hasSectionHeaderView(actualSection - 1);
                        boolean nextHasHeader = mAdapter.hasSectionHeaderView(actualSection + 1);
                        boolean currHasHeader = mAdapter.hasSectionHeaderView(actualSection);
                        boolean currIsLast = mAdapter.getRowInSection(realFirstVisibleItem) == mAdapter.numberOfRows(actualSection) - 1;
                        boolean prevHasRows = mAdapter.numberOfRows(actualSection - 1) > 0;
                        boolean currIsFirst = mAdapter.getRowInSection(realFirstVisibleItem) == 0;
        
                        boolean needScrolling = currIsFirst && !currHasHeader && prevHasHeader && realFirstVisibleItem != firstVisibleItem;
                        boolean needNoHeaderUpToHeader = currIsLast && currHasHeader && !nextHasHeader && realFirstVisibleItem == firstVisibleItem && Math.abs(mListView.getChildAt(0).getTop()) >= mListView.getChildAt(0).getHeight() / 2;
        
                        noHeaderUpToHeader = false;
                        if (currIsHeader && !prevHasHeader && firstVisibleItem >= 0) {
                            resetHeader(direction < 0 ? actualSection - 1 : actualSection);
                        } else if ((currIsHeader && firstVisibleItem > 0) || needScrolling) {
                            if (!prevHasRows) {
                                resetHeader(actualSection-1);
                            }
                            startScrolling();
                        } else if (needNoHeaderUpToHeader) {
                            noHeaderUpToHeader = true;
                        } else if (lastResetSection != actualSection) {
                            resetHeader(actualSection);
                        }
        
                        previousFirstVisibleItem = realFirstVisibleItem;
                    }
        
                    if (scrollingStart) {
                        int scrolled = realFirstVisibleItem >= firstVisibleItem ? mListView.getChildAt(realFirstVisibleItem - firstVisibleItem).getTop() : 0;
        
                        if (!doneMeasuring) {
                            setMeasurements(realFirstVisibleItem, firstVisibleItem);
                        }
        
                        int headerH = doneMeasuring ? (prevH - nextH) * direction * Math.abs(scrolled) / (direction < 0 ? nextH : prevH) + (direction > 0 ? nextH : prevH) : 0;
        
                        mHeader.scrollTo(0, -Math.min(0, scrolled - headerH));
                        if (doneMeasuring && headerH != mHeader.getLayoutParams().height) {
                            LayoutParams p = (LayoutParams) (direction < 0 ? next.getLayoutParams() : previous.getLayoutParams());
                            p.topMargin = headerH - p.height;
                            mHeader.getLayoutParams().height = headerH;
                            mHeader.requestLayout();
                        }
                    }
        
                    if (noHeaderUpToHeader) {
                        if (lastResetSection != actualSection) {
                            addSectionHeader(actualSection);
                            lastResetSection = actualSection + 1;
                        }
                        mHeader.scrollTo(0, mHeader.getLayoutParams().height - (mListView.getChildAt(0).getHeight() + mListView.getChildAt(0).getTop()));
                    }
                }
        
                private void startScrolling() {
                    scrollingStart = true;
                    doneMeasuring = false;
                    lastResetSection = -1;
                }
        
                private void resetHeader(int section) {
                    scrollingStart = false;
                    addSectionHeader(section);
                    mHeader.requestLayout();
                    lastResetSection = section;
                }
        
                private void setMeasurements(int realFirstVisibleItem, int firstVisibleItem) {
        
                    if (direction > 0) {
                        nextH = realFirstVisibleItem >= firstVisibleItem ? mListView.getChildAt(realFirstVisibleItem - firstVisibleItem).getMeasuredHeight() : 0;
                    }
        
                    previous = mHeader.getChildAt(0);
                    prevH = previous != null ? previous.getMeasuredHeight() : mHeader.getHeight();
        
                    if (direction < 0) {
                        if (lastResetSection != actualSection - 1) {
                            addSectionHeader(Math.max(0, actualSection - 1));
                            next = mHeader.getChildAt(0);
                        }
                        nextH = mHeader.getChildCount() > 0 ? mHeader.getChildAt(0).getMeasuredHeight() : 0;
                        mHeader.scrollTo(0, prevH);
                    }
                    doneMeasuring = previous != null && prevH > 0 && nextH > 0;
                }
        
                private void updateScrollBar() {
                    if (mHeader != null && mListView != null && mScrollView != null) {
                        int offset = mListView.computeVerticalScrollOffset();
                        int range = mListView.computeVerticalScrollRange();
                        int extent = mListView.computeVerticalScrollExtent();
                        mScrollView.setVisibility(extent >= range ? View.INVISIBLE : View.VISIBLE);
                        if (extent >= range) {
                            return;
                        }
                        int top = range == 0 ? mListView.getHeight() : mListView.getHeight() * offset / range;
                        int bottom = range == 0 ? 0 : mListView.getHeight() - mListView.getHeight() * (offset + extent) / range;
                        mScrollView.setPadding(0, top, 0, bottom);
                        fadeOut.reset();
                        fadeOut.setFillBefore(true);
                        fadeOut.setFillAfter(true);
                        fadeOut.setStartOffset(FADE_DELAY);
                        fadeOut.setDuration(FADE_DURATION);
                        mScrollView.clearAnimation();
                        mScrollView.startAnimation(fadeOut);
                    }
                }
        
                private void addSectionHeader(int actualSection) {
                    View previousHeader = mHeader.getChildAt(0);
                    if (previousHeader != null) {
                        mHeader.removeViewAt(0);
                    }
        
                    if (mAdapter.hasSectionHeaderView(actualSection)) {
                        mHeaderConvertView = mAdapter.getSectionHeaderView(actualSection, mHeaderConvertView, mHeader);
                        mHeaderConvertView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
        
                        mHeaderConvertView.measure(MeasureSpec.makeMeasureSpec(mHeader.getWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
        
                        mHeader.getLayoutParams().height = mHeaderConvertView.getMeasuredHeight();
                        mHeaderConvertView.scrollTo(0, 0);
                        mHeader.scrollTo(0, 0);
                        mHeader.addView(mHeaderConvertView, 0);
                    } else {
                        mHeader.getLayoutParams().height = 0;
                        mHeader.scrollTo(0, 0);
                    }
        
                    mScrollView.bringToFront();
                }
        
                private int getRealFirstVisibleItem(int firstVisibleItem, int visibleItemCount) {
                    if (visibleItemCount == 0) {
                        return -1;
                    }
                    int relativeIndex = 0, totalHeight = mListView.getChildAt(0).getTop();
                    for (relativeIndex = 0; relativeIndex < visibleItemCount && totalHeight < mHeader.getHeight(); relativeIndex++) {
                        totalHeight += mListView.getChildAt(relativeIndex).getHeight();
                    }
                    int realFVI = Math.max(firstVisibleItem, firstVisibleItem + relativeIndex - 1);
                    return realFVI;
                }
            }
        
            public ListView getListView() {
                return mListView;
            }
        
            public void addHeaderView(View v) {
                mListView.addHeaderView(v);
            }
        
            private float dpToPx(float dp) {
                return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getContext().getResources().getDisplayMetrics());
            }
        
            protected class InternalListView extends ListView {
        
                public InternalListView(Context context, AttributeSet attrs) {
                    super(context, attrs);
                }
        
                @Override
                protected int computeVerticalScrollExtent() {
                    return super.computeVerticalScrollExtent();
                }
        
                @Override
                protected int computeVerticalScrollOffset() {
                    return super.computeVerticalScrollOffset();
                }
        
                @Override
                protected int computeVerticalScrollRange() {
                    return super.computeVerticalScrollRange();
                }
            }
        }
        

        XML 用法

        <?xml version="1.0" encoding="utf-8"?>
        <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">
        
            <com.example.parsaniahardik.listview_stickyheader_ios.HeaderListView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:id="@+id/lv">
        
            </com.example.parsaniahardik.listview_stickyheader_ios.HeaderListView>
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-04-03
          • 1970-01-01
          • 2011-12-13
          • 2016-01-14
          • 2020-02-27
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多