如何在Android中制作粘性节标题(如iOS)?

Dan*_*les 37 android listview scrollview ios

我的具体问题是:如何实现这样的效果:http://youtu.be/EJm7subFbQI

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

谢谢.

Kyl*_*egg 48

这个问题已经存在一些解决方案.您所描述的是节标题,并且在Android中被称为粘性节标题.

  • 在尝试了我到这里的选项后,HeaderListView似乎最适合我的需求,并且比其他更兼容.谢谢 (4认同)

Den*_*s K 7

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

对于那些不想使用第三方代码(或者不能直接使用它,例如在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;
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

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

return actualCount - 1;
Run Code Online (Sandbox Code Playgroud)

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

position++;
Run Code Online (Sandbox Code Playgroud)

这样,您的主列表将呈现所有单元格,但第一个单元格.

另一件事是你要确保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>
Run Code Online (Sandbox Code Playgroud)

项目/标题布局

<?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>
Run Code Online (Sandbox Code Playgroud)