RecycleView是个很常用的控件,很多APP中都可以看到它的身影,同时它也是个很难用的控件,主要就难在多种布局的实现。
在《第一行代码—Android》这本书里边有个RecycleView实现的聊天界面布局,左右两种布局写在了同一个文件中,如果是发送来的消息,就隐藏右侧布局,反之隐藏左侧布局,这种方式对于比较简单的、只有两种Item的界面是可行的,假如我们的Item有多种布局,那么这种方式就显得很笨重。对于多种布局,我们可以使用工厂模式来实现。
Github:https://github.com/imcloudfloating/DesignApp
1.首先看看效果(GIF一直上传失败,只好传JPG了):
这里的LayoutManager使用GridLayoutManager,设置为6列,然后在Adapter类中根据不同的类型来设置所占列数,具体见Adapter类的setSpanCount方法。
2.然后是类图:
3.Adapter类:
适配器的代码很短,设置数据和绑定View的代码都写在了ItemHolder的子类里面;
List<Item>储存三种类型的Item数据,如果需要增加新的类型,只要实现Item接口就可以了;
在onBindViewHolder方法中调用ItemHolder的setData()方法来设置数据;
1 public class MultiListAdapter extends RecyclerView.Adapter<ItemHolder> { 2 3 private List<Item> mDataList; 4 5 public MultiListAdapter(List<Item> dataList) { 6 mDataList = dataList; 7 } 8 9 @NonNull 10 @Override 11 public ItemHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int type) { 12 return ItemHolderFactory.getItemHolder(viewGroup, type); 13 } 14 15 @Override 16 public void onBindViewHolder(@NonNull ItemHolder viewHolder, int i) { 17 //设置 Holder 数据 18 viewHolder.setData(mDataList.get(i)); 19 } 20 21 @Override 22 public int getItemViewType(int position) { 23 return mDataList.get(position).getType(); 24 } 25 26 @Override 27 public int getItemCount() { 28 return mDataList.size(); 29 } 30 31 public void setSpanCount(GridLayoutManager layoutManager) { 32 layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { 33 @Override 34 public int getSpanSize(int i) { 35 int type = getItemViewType(i); 36 switch (type) { 37 default: 38 case ItemHolderFactory.ITEM_LARGE: 39 return 3; 40 case ItemHolderFactory.ITEM_SMALL: 41 return 2; 42 case ItemHolderFactory.ITEM_TITLE_BAR: 43 return 6; 44 } 45 } 46 }); 47 } 48 }
4.ItemHolder抽象类:
setData方法用来设置Item布局的数据
1 public abstract class ItemHolder extends RecyclerView.ViewHolder { 2 public ItemHolder(View item) { 3 super(item); 4 } 5 6 public abstract void setData(Item itemData); 7 }
5.LargeItemHolder类:
另外两个类似
1 public class LargeItemHolder extends ItemHolder { 2 3 private ImageView mItemImage; 4 private TextView mTitle; 5 private TextView mSubTitle; 6 7 public LargeItemHolder(View item) { 8 super(item); 9 mItemImage = item.findViewById(R.id.item_image); 10 mTitle = item.findViewById(R.id.item_title); 11 mSubTitle = item.findViewById(R.id.item_sub_title); 12 } 13 14 @Override 15 public void setData(Item itemData) { 16 ItemLarge item = (ItemLarge) itemData; 17 mItemImage.setImageBitmap(item.getImage()); 18 mTitle.setText(item.getTitle()); 19 mSubTitle.setText(item.getSubTitle()); 20 } 21 }
6.Item接口:
1 public interface Item { 2 int getType(); 3 }
7.ItemLarge类(一个图片、一个标题和一个副标题):
另外两个类似
1 public class ItemLarge implements Item { 2 3 private Bitmap mImage; 4 private String mTitle; 5 private String mSubTitle; 6 7 public ItemLarge(Bitmap bitmap, String title, String subTitle) { 8 mImage = bitmap; 9 mTitle = title; 10 mSubTitle = subTitle; 11 } 12 13 public Bitmap getImage() { 14 return mImage; 15 } 16 17 public String getTitle() { 18 return mTitle; 19 } 20 21 public String getSubTitle() { 22 return mSubTitle; 23 } 24 25 @Override 26 public int getType() { 27 return ItemHolderFactory.ITEM_LARGE; 28 } 29 }
8.工厂类ItemHolderFactory:
三个常量表示不同的布局类型,通过getItemHolder来创建ViewHolder。
1 public class ItemHolderFactory { 2 3 public static final int ITEM_LARGE = 0; 4 public static final int ITEM_SMALL = 1; 5 public static final int ITEM_TITLE_BAR = 2; 6 7 @IntDef({ 8 ITEM_LARGE, 9 ITEM_SMALL, 10 ITEM_TITLE_BAR 11 }) 12 @interface ItemType {} 13 14 static ItemHolder getItemHolder(ViewGroup parent, @ItemType int type) { 15 switch (type) { 16 default: 17 case ITEM_LARGE: 18 return new LargeItemHolder(LayoutInflater 19 .from(parent.getContext()).inflate(R.layout.item_large, parent, false)); 20 case ITEM_SMALL: 21 return new SmallItemHolder(LayoutInflater 22 .from(parent.getContext()).inflate(R.layout.item_small, parent, false)); 23 case ITEM_TITLE_BAR: 24 return new TitleBarItemHolder(LayoutInflater 25 .from(parent.getContext()).inflate(R.layout.item_title_bar, parent, false)); 26 } 27 } 28 }
9.ListActivity类:
1 public class ListActivity extends AppCompatActivity { 2 3 List<Item> itemList = new ArrayList<>(); 4 5 @Override 6 protected void onCreate(Bundle savedInstanceState) { 7 super.onCreate(savedInstanceState); 8 setContentView(R.layout.activity_list); 9 10 initData(); 11 12 GridLayoutManager layoutManager = new GridLayoutManager(this, 6); 13 MultiListAdapter adapter = new MultiListAdapter(itemList); 14 adapter.setSpanCount(layoutManager); 15 16 RecyclerView recyclerView = findViewById(R.id.recycle_view); 17 recyclerView.setLayoutManager(layoutManager); 18 recyclerView.setAdapter(adapter); 19 } 20 21 private void initData() { 22 //添加数据 23 itemList.add(new ItemTitleBar("Hot New", null)); 24 itemList.add(new ItemLarge( 25 BitmapFactory.decodeResource(getResources(), R.drawable.img_1), 26 "One More Light", 27 "Linkin Park")); 28 itemList.add(new ItemLarge( 29 BitmapFactory.decodeResource(getResources(), R.drawable.img_2), 30 "Let Go ", 31 "Avril Lavigne")); 32 itemList.add(new ItemTitleBar("Recommended", null)); 33 itemList.add(new ItemSmall( 34 BitmapFactory.decodeResource(getResources(), R.drawable.img_3), 35 "Bridge to Terabithia")); 36 itemList.add(new ItemSmall( 37 BitmapFactory.decodeResource(getResources(), R.drawable.img_4), 38 "Life Is Beautiful")); 39 itemList.add(new ItemSmall( 40 BitmapFactory.decodeResource(getResources(), R.drawable.img_5), 41 "A Violent Flame")); 42 itemList.add(new ItemTitleBar("Top Rated", null)); 43 itemList.add(new ItemLarge( 44 BitmapFactory.decodeResource(getResources(), R.drawable.img_6), 45 "Furious 7: Original Motion Picture Soundtrack", 46 "Various Artists")); 47 itemList.add(new ItemLarge( 48 BitmapFactory.decodeResource(getResources(), R.drawable.img_7), 49 "Halo 5: Guardians (Original Soundtrack)", 50 "Kazuma Jinnouchi")); 51 } 52 }
10.布局文件(item_large.xml):
layout_width用match_parent是为了Item在网格中居中,此处match_parent相当于宽度为Item所占的列数。
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:tools="http://schemas.android.com/tools" 4 android:orientation="vertical" 5 android:layout_width="match_parent" 6 android:layout_height="wrap_content" 7 android:layout_margin="4dp" 8 android:background="#ffffff" 9 android:elevation="2dp"> 10 11 <ImageView 12 android:contentDescription="@id/item_title" 13 android:id="@+id/item_image" 14 android:layout_width="match_parent" 15 android:layout_height="170dp" 16 android:scaleType="centerCrop" 17 tools:src="@drawable/img_7" /> 18 19 <TextView 20 android:id="@+id/item_title" 21 android:layout_width="match_parent" 22 android:layout_height="wrap_content" 23 android:layout_marginTop="8dp" 24 android:paddingStart="8dp" 25 android:paddingEnd="8dp" 26 android:lines="1" 27 android:ellipsize="end" 28 android:textColor="#000000" 29 android:textSize="18sp" 30 tools:text="Item Title" /> 31 32 <TextView 33 android:id="@+id/item_sub_title" 34 android:layout_width="match_parent" 35 android:layout_height="wrap_content" 36 android:layout_marginBottom="8dp" 37 android:paddingStart="8dp" 38 android:paddingEnd="8dp" 39 android:lines="1" 40 android:ellipsize="end" 41 android:textSize="14sp" 42 tools:text="Sub Title" /> 43 44 </LinearLayout>