在该Demo中,有一个分段标头(section header)随列表滚动,当前分段标头一直显示在屏幕顶端。在下图中,突出显示的字母就是分段标头,其下方的列表项显示首字母与分段标头相同的国家。
Android开发者通常需要创建两种类型的列表项来达到上述效果:一个常规列表项用于显示数据,一个特殊列表项用于显示分段标头。这种方式需要重写Adapter的getViewTypeCount()方法,让其返回2;然后修改getView()方法,在该方法中创建并返回对应类型的列表项。实践中,这种方法会导致代码逻辑混乱。如果原始列表包含20个列表项,使用上述方法,适配器就需要包含21到40个列表项,列表项的数目依赖于分段数目。这样就会导致复杂的代码逻辑:ListView中显示的是15个可视列表项,但是该列表项在原始列表中可能是第9个列表项。
更简单的方法是在列表项中嵌入分段标头,然后根据需要显示或者隐藏分段标头。这样便大幅度简化了创建列表以及选择列表项的逻辑。我们可以创建一个特殊的TextView,让其叠加在列表的顶部,当列表滚动到一个新的分段时,就更新其内容。
创建列表布局
我们在单独的文件中为分段标头创建布局,这样就可以在随列表滚动的分段标头和列表顶部的固定分段标头中复用这个布局文件。源码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/header"
style="@android:style/TextAppearance.Small"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#0000ff" />
然后,我们创建一个包含顶部固定分段标头的XML布局文件,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<!--使用Android标准的列表ID,因此可以在ListActivity的子类中使用它。-->
<ListView
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
<include layout="@layout/header" />
</FrameLayout>
将分段标头包含帧布局中,这样该标头就可以与列表重叠在一起,以显示当前所在分段。
最后要创建列表项对应的XML布局文件。该布局文件既包含数据项也包含分段标头,源码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<include layout="@layout/header" />
<TextView
android:id="@+id/label"
style="@android:style/TextAppearance.Large"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</LinearLayout>
列表项中的分段标头会在新的分段开始时显示,否则就会被隐藏。ID为label的TextView用于显示列表项中的数据项。
创建可视分段标头
与其他创建分段列表的方式的不同之处在于:开发者只需要重写getView()方法。我们不需要返回多种类型的视图,也不需要在分段列表与原始列表间转换数据项的位置(position)。源码如下:
package com.example.huangfei.myapplication;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
/**
* Created by huangfeihong on 2015/12/19.
*/
public class SectionAdapter extends ArrayAdapter<String> {
private Context mContext;
private LayoutInflater mInflater;
public SectionAdapter(Context context, String[] objects) {
super(context, R.layout.list_item, R.id.label, objects);
mContext = context;
mInflater = LayoutInflater.from(context);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null)
convertView = mInflater.inflate(R.layout.list_item, parent, false);
TextView header = (TextView) convertView.findViewById(R.id.header);
String label = getItem(position);
//检查列表项起始字母是否发生改变
if(position == 0 || getItem(position - 1).charAt(0) != label.charAt(0)){
header.setVisibility(View.VISIBLE);
header.setText(label.substring(0,1));
}else{
header.setVisibility(View.GONE);
}
return super.getView(position, convertView, parent);
}
}
接下来,我们编写一个辅助方法用于配置屏幕顶部悬浮的分段标头,源码如下:
private void setTopHeader(int position){
String text = Countries.COUNTRIES[position].substring(0, 1);
mTopHeader.setText(text);
}
当开始创建或者滚动列表时,会调用这个辅助方法,通过该方法找到该分段标头对应的字母,并以此更新其文本内容。
最后一步
配置列表并为列表设置监听器,当列表滚动时,更新分段标头的内容。源码如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.list);
setContentView(R.layout.list);
mTopHeader = (TextView) findViewById(R.id.header);
setListAdapter(new SectionAdapter(this, Countries.COUNTRIES));
//设置滚动监听器
getListView().setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if(firstVisibleItem != mTopVisiblePosition){
mTopVisiblePosition = firstVisibleItem;
setTopHeader(firstVisibleItem);
}
}
});
setTopHeader(0);//初始化第一个列表项的分段标头
}
概要
即便ListView并非原生支持分段标头,但是,通过将分段标头嵌入到列表项中,并在合适的时候令其可视或不可视,这样,开发者仍然可以很容易的添加分段标头。尽管这个demo适用于以字母排序的列表,但是,方法本身可以应用于任何分段类型。