web-dev-qa-db-ja.com

Androidでスティッキーセクションヘッダー(iOSなど)を作成する方法は?

私の具体的な質問は、次のような効果をどのように実現できるかです。 http://youtu.be/EJm7subFbQI

バウンス効果は重要ではありませんが、ヘッダーに「スティッキー」効果が必要です。どこから始めますか? API 8以上で実装できるものが必要です。

ありがとう。

37

この問題には既にいくつかの解決策があります。説明しているのはセクションヘッダーであり、Androidではstickyセクションヘッダーと呼ばれるようになりました。

46
Kyle Clegg

編集:完全に動作する例のコードを追加するための空き時間がありました。それに応じて回答を編集しました。

サードパーティのコードを使用したくない(または、Xamarinなどで直接使用できない)場合、これは手作業でかなり簡単に実行できます。アイデアは、ヘッダーに別のListViewを使用することです。このリストビューには、ヘッダーアイテムのみが含まれます。ユーザーはスクロールできません(setEnabled(false))が、メインリストのスクロールに基づくコードからスクロールされます。したがって、2つのリスト-headerListviewとmainListview、および2つの対応するアダプターheaderAdapterとmainAdapterがあります。 headerAdapterはセクションビューのみを返しますが、mainAdapterは2つのビュータイプ(セクションとアイテム)をサポートします。メインリスト内の位置を取得し、セクションリスト内の対応する位置を返すメソッドが必要になります。

主な活動

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++;

これにより、メインリストは最初のセルを除くすべてのセルをレンダリングします。

もう1つは、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>
6
Dennis K

これをapp.gradleファイルに追加します

compile 'se.emilsjolander:StickyScrollViewItems:1.1.0'

次に、レイアウトを追加しましたAndroid:tag ="sticky" LinearLayoutではなくtextviewやedittextのような特定のビューに対しては、次のようになります。また、データバインディングも使用しますが、無視してください。

    <?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>

テキストビューのスタイルグループはこれに見えます

 <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>

テキストビューの背景は次のようになります:(@ 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>
1
Prakhar1001

SuperSLiM ライブラリを使用して、この効果に到達できます。ビューの交換可能な線形、グリッド、およびスタッガード表示を備えたRecyclerView用のLayoutManagerを提供します。

良いデモは github repository にあります

単にそのような結果を得るためです

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

enter image description here

0
yoAlex5

IPhoneのようなリストビューを実現するために、1つの特別なクラスを使用しました。ソースコードの例をこちらで見つけることができます。 https://demonuts.com/Android-recyclerview-sticky-header-like-iphone/

リストビューを更新したこのクラスは

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>
0
Parsania Hardik