处理类似于 iOS 的 UITableView 的行/部分逻辑在 Android 中并不像在 iOS 中那么简单,但是,当您使用 RecyclerView 时 - 您可以做的事情的灵活性要大得多。
最后,这完全取决于您如何确定您在适配器中显示的视图类型。一旦你弄清楚了,它应该很容易航行(不是真的,但至少你会排序)。
适配器公开了两个您应该覆盖的方法:
getItemViewType(int position)
此方法的默认实现将始终返回 0,表示只有 1 种视图。在您的情况下,情况并非如此,因此您需要找到一种方法来断言哪一行对应于哪种视图类型。与通过行和节为您管理的 iOS 不同,在这里您将只依赖一个索引,并且您需要使用您的开发人员技能来了解位置何时与节标题相关,以及何时与一个正常的行。
createViewHolder(ViewGroup parent, int viewType)
无论如何,您都需要重写此方法,但通常人们只是忽略 viewType 参数。根据视图类型,您需要扩充正确的布局资源并相应地创建您的视图持有者。RecyclerView 将以一种避免不同视图类型冲突的方式处理回收不同的视图类型。
如果您打算使用默认的 LayoutManager,例如LinearLayoutManager
,那么您应该很高兴。如果您打算制作自己的 LayoutManager 实现,则需要更加努力。您真正必须使用的唯一 API 是findViewByPosition(int position)
在特定位置提供给定视图。由于您可能希望根据视图的类型对其进行不同的布局,因此您有几个选择:
通常在使用 ViewHolder 模式时,您使用视图持有者设置视图的标签。您可以在运行时在布局管理器中使用它,通过在视图持有者中添加一个表达这一点的字段来找出视图的类型。
由于您需要一个函数来确定哪个位置与哪个视图类型相关,因此您不妨以某种方式使该方法全局可访问(可能是一个管理数据的单例类?),然后您可以根据查询简单地查询相同的方法位置。
这是一个代码示例:
// in this sample, I use an object array to simulate the data of the list.
// I assume that if the object is a String, it means I should display a header with a basic title.
// If not, I assume it's a custom model object I created which I will use to bind my normal rows.
private Object[] myData;
public static final int ITEM_TYPE_NORMAL = 0;
public static final int ITEM_TYPE_HEADER = 1;
public class MyAdapter extends Adapter<ViewHolder> {
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == ITEM_TYPE_NORMAL) {
View normalView = LayoutInflater.from(getContext()).inflate(R.layout.my_normal_row, null);
return new MyNormalViewHolder(normalView); // view holder for normal items
} else if (viewType == ITEM_TYPE_HEADER) {
View headerRow = LayoutInflater.from(getContext()).inflate(R.layout.my_header_row, null);
return new MyHeaderViewHolder(headerRow); // view holder for header items
}
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
final int itemType = getItemViewType(position);
if (itemType == ITEM_TYPE_NORMAL) {
((MyNormalViewHolder)holder).bindData((MyModel)myData[position]);
} else if (itemType == ITEM_TYPE_HEADER) {
((MyHeaderViewHolder)holder).setHeaderText((String)myData[position]);
}
}
@Override
public int getItemViewType(int position) {
if (myData[position] instanceof String) {
return ITEM_TYPE_HEADER;
} else {
return ITEM_TYPE_NORMAL;
}
}
@Override
public int getItemCount() {
return myData.length;
}
}
以下是这些视图持有者的外观示例:
public MyHeaderViewHolder extends ViewHolder {
private TextView headerLabel;
public MyHeaderViewHolder(View view) {
super(view);
headerLabel = (TextView)view.findViewById(R.id.headerLabel);
}
public void setHeaderText(String text) {
headerLabel.setText(text);
}
}
public MyNormalViewHolder extends ViewHolder {
private TextView titleLabel;
private TextView descriptionLabel;
public MyNormalViewHolder(View view) {
super(view);
titleLabel = (TextView)view.findViewById(R.id.titleLabel);
descriptionLabel = (TextView)view.findViewById(R.id.descriptionLabel);
}
public void bindData(MyModel model) {
titleLabel.setText(model.getTitle());
descriptionLabel.setText(model.getDescription());
}
}
当然,此示例假定您已以一种易于以这种方式实现适配器的方式构建数据源 (myData)。例如,我将向您展示如何构建一个数据源,该数据源显示姓名列表,以及每次姓名的第一个字母更改时的标题(假设列表按字母顺序排列) - 类似于联系人列表看起来像:
// Assume names & descriptions are non-null and have the same length.
// Assume names are alphabetized
private void processDataSource(String[] names, String[] descriptions) {
String nextFirstLetter = "";
String currentFirstLetter;
List<Object> data = new ArrayList<Object>();
for (int i = 0; i < names.length; i++) {
currentFirstLetter = names[i].substring(0, 1); // get the 1st letter of the name
// if the first letter of this name is different from the last one, add a header row
if (!currentFirstLetter.equals(nextFirstLetter)) {
nextFirstLetter = currentFirstLetter;
data.add(nextFirstLetter);
}
data.add(new MyModel(names[i], descriptions[i]));
}
myData = data.toArray();
}
这个例子用来解决一个相当具体的问题,但我希望这能让您很好地了解如何在回收器中处理不同的行类型,并允许您在自己的代码中进行必要的调整以满足您的需求。