本文介绍如何使用 SpanSizeLookup
和 RecyclerView
来实现居中效果的 GridView。
居中的Grid
使用RecyclerView
,很容易实现如下Grid效果:

但视觉师对此不满意,他们认为更理想的效果应该是这样:

注意看红色区域,它是 居中 而非靠左
怎么办?很简单,我们耍点”小聪明”就能实现上述布局。”聪明”的布局如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| <LinearLayout 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="wrap_content" android:gravity="center_horizontal" android:orientation="vertical">
<android.support.v7.widget.RecyclerView android:id="@+id/rv_roles" android:layout_width="match_parent" android:layout_height="wrap_content" android:clipToPadding="false" android:padding="2dp" android:scrollbarStyle="outsideOverlay" android:scrollbars="vertical" android:transitionGroup="false" app:layoutManager="android.support.v7.widget.GridLayoutManager" app:spanCount="2" tools:ignore="UnusedAttribute"/>
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:orientation="horizontal">
<include android:id="@+id/rl_role" layout="@layout/tc_dialog_confirm_select_role_item" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="gone"/> </LinearLayout> </LinearLayout>
|
<RecyclerView>
后跟着一个 <LinearLayout>
,其中包含着一个跟 RecyclerView item 完全一样的布局。它其实是个”假” item,不妨称之为 tail
。
假设 RecyclerView 上待显示的数据为 itemList,itemList 的大小为 n,对 tail
进行如下处理:
- 当 n 为奇数时,RecyclerView 仅加载 itemList 前
n - 1
个数据;显示 tail
, 并加载 itemList 最后一条数据
- 当n为偶数时,RecyclerView 加载 itemList 全部数据;隐藏
tail
支持选中态
需求发生变化了。视觉师要求 Grid 最后一个 item 居中的基础上,交互师又要求 Grid 的 item 支持选中态,如下图:

问题来了:原来耍小聪明的方案变得不很适用了,因为在 RecyclerView
和 tail
之间保持选中态的不好同步,写代码比较麻烦。
这里提供一个优雅的解决方案:利用SpanSizeLookup()
接口调整 item 占用的列数以实现居中效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| GridLayoutManager layoutManager = new GridLayoutManager(mContext, 2); layoutManager.setOrientation(LinearLayoutManager.VERTICAL); layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { if (position % 2 == 0 && position == mSelectRoleAdapter.getItemCount() - 1) { return 2; } else { return 1; } } }); rv.setLayoutManager(layoutManager);
|
这个方案只是调整 GridLayoutManager
,并不需要额外的”假” item,自然也就不存在选中状态同步的问题,代码写起来方便不易出错。截图如下:

上图跟我们期望的效果还有点差距,但已经解决了居中的关键问题。
我们微调一下 item 的布局代码即可实现最终想要的效果。几个关键的调整点:
- 将 item 背景图设置在
rl_real_root
上,而非 rl_fake_root
- 将
rl_real_root
的 width
设置为 wrap_content
- 将
rl_fake_root
的 width
设置为 match_parent
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/rl_fake_root" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/transparent" tools:ignore="UnusedAttribute">
<RelativeLayout android:id="@+id/rl_real_root" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:background="@color/b_G7">
<ImageView android:layout_marginLeft="5dp" android:layout_marginStart="5dp" android:layout_marginTop="5dp" android:layout_marginBottom="5dp" android:id="@+id/iv_role_head" android:layout_width="36dp" android:layout_height="36dp" android:layout_centerVertical="true" android:src="@drawable/tc_user_icon_circle_default" />
<LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginEnd="10dp" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:layout_marginStart="10dp" android:layout_toEndOf="@id/iv_role_head" android:layout_toRightOf="@id/iv_role_head" android:orientation="vertical">
<TextView android:id="@+id/tv_role_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="肉盾" android:textColor="@color/t_G1" android:textSize="@dimen/campus_textsize_m" />
<TextView android:id="@+id/tv_role_desc" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="3dp" android:text="建议边路" android:textColor="@color/t_G2" android:textSize="@dimen/campus_textsize_s" />
</LinearLayout> </RelativeLayout> </RelativeLayout>
|
我对简单且优雅的代码比较满意。视觉和交互对最后呈现的效果也很满意:
