前言
GridView常用于以网格形式展示数据,九宫格布局就是一种典型的应用。本文将详细讲解GridView的使用方法和常用技巧。
基本使用
GridView的使用和ListView相似,重点在于数据由Adapter(适配器)提供,GridView并不直接访问数据源。因此,可以将GridView的使用分为3步:
- 获得数据源(如数组,List等)
- 通过数据源建立适配器(如ArrayAdapter等)
- 为GridView设置适配器
使用系统提供的布局
针对一些简单的场景(如只需要展示字符串),使用系统提供的ArrayAdapter即可。ArrayAdapter使用数组或List作为数据源,常用的两个构造方法如下:
//resource:列表项的布局文件
//objects:数据源
public ArrayAdapter(Context context,@LayoutRes int resource,T[] objects);
public ArrayAdapter(Context context,@LayoutRes int resource,List<T> objects)
使用GridView的示例代码如下:
<GridView
android:id="@+id/grid_view_normal"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:numColumns="3"/>
//初始化普通布局的GridView
String[] normalArray=new String[]{"coding","ending","Java","Github","coder","Android"};
ArrayAdapter<String> normalAdapter=new ArrayAdapter<>(this,
android.R.layout.simple_list_item_1,normalArray);
normalGridView.setAdapter(normalAdapter);
ArrayAdapter中使用的android.R.layout.simple_list_item_1
是系统提供的布局文件,其实就是一个TextView。
效果截图:
监听点击事件
//监听单击事件
normalGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String itemMsg= (String) parent.getAdapter().getItem(position);//获取选中对象
Toast.makeText(GridViewActivity.this,itemMsg,Toast.LENGTH_SHORT).show();
}
});
//监听长按事件
normalGridView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(GridViewActivity.this,"发生长按事件",Toast.LENGTH_SHORT).show();
return true;
}
});
可以看到,在监听器中通过parent.getAdapter().getItem(position)
获取选中对象。注意,这个方法的返回值是Object对象,因此需要进行强制转换。
相关属性
android:columnWidth:设置列的宽度。
android:numColumns:设置需要展示的列数。 可选值[数字|auto_fit]
android:stretchMode:设置拉伸模式。 可选值[none|columnWidth|spacingWidth|spacingWidthUniform]
android:horizontalSpacing:设置列与列的水平间距。
android:verticalSpacing:设置行与行的垂直间距。
android:listSelector:设置列表项被选中时的效果。 [color或drawable资源]
android:fastScrollEnabled:是否在快速滑动的是否显示右侧的滑动块。
android:stackFromBottom:是否在初始状态时显示GridView的最底部。 [默认为false]
numColumns属性既可以使用具体的数字,也可以使用auto_fit
,GridView会尽可能展示更多的列去填充可视区域。
stackFromBottom这个属性需要简单解释一下:如果设置为true,那么打开GridView首先看到的就是最底部的内容,看起来就像是GridView已经滚动到了最后一行;如果设置为false,就和默认状态一样,首先看到第一行的内容。
stretchMode是拉伸模式,即指定GridView的每一列应该怎样填充可用的空白区域,4种可选值的含义如下:
- none:不使用拉伸模式。[默认值]
- columnWidth:每一列会被平均拉伸。
- spacingWidth:每列之间的间隔会被拉伸。
- spacingWidthUniform:每列之间的间隔会被统一拉伸。
注意:如果要使用stretchMode,就需要设置columnWidth
属性,否则GridView的内容可能不会显示。
下面给出不同stretchMode属性的具体表现:
none:
columnWidth:
spacingWidth:
spacingWidthUniform:
自定义网格布局
如果需要展示的内容比较复杂(比如图片加文字),我们就应该自定义适配器,使用自己的布局去展示列表项。
首先,建立一个实体类Book:
public class Book {
private String name;
private int imageRes;//图片资源
public Book(String name, int imageRes) {
this.name = name;
this.imageRes = imageRes;
}
@Override
public String toString() {
return name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getImageRes() {
return imageRes;
}
public void setImageRes(int imageRes) {
this.imageRes = imageRes;
}
}
接着,自定义一个布局文件(上边图片,下边文字),本例中命名为gridview_custom_item.xml
,代码如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<ImageView
android:id="@+id/book_image"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="6dp"/>
<TextView
android:id="@+id/book_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="6dp"
android:textAllCaps="false"
android:textSize="16sp" />
</LinearLayout>
然后,通过继承BaseAdapter
实现我们自己的适配器,本例中命名为StyleGridViewAdapter
:
public class StyleGridViewAdapter extends BaseAdapter{
private Context context;
private List<Book> dataList;//数据源
public StyleGridViewAdapter(Context context,List<Book> dataList) {
this.dataList = dataList;
this.context = context;
}
@Override
public int getCount() {
return dataList.size();
}
@Override
public Object getItem(int position) {
return dataList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Book book=dataList.get(position);
ViewHolder viewHolder;
if(convertView==null){//只有在无法复用View时再重新实例化列表项布局
LayoutInflater inflater=LayoutInflater.from(context);
convertView=inflater.inflate(R.layout.gridview_custom_item,parent,false);
viewHolder=new ViewHolder();
viewHolder.bookImageView=convertView.findViewById(R.id.book_image);
viewHolder.bookNameView=convertView.findViewById(R.id.book_name);
convertView.setTag(viewHolder);//存储ViewHolder
}else{//复用已有的View
viewHolder= (ViewHolder) convertView.getTag();
}
viewHolder.bookImageView.setImageResource(book.getImageRes());
viewHolder.bookNameView.setText(book.getName());
return convertView;
}
//用于在GridView中复用View
static class ViewHolder{
ImageView bookImageView;
TextView bookNameView;
}
}
可以看到,需要重写getCount、getItem、getItemId、getView
这四个方法。此外,还要提供一个构造方法用于外界传入Context和数据源(本例中为List<Book>
)。
注意:在自定义的适配器中通常会使用ViewHolder
提升GridView的运行效率,这一方式将充分利用GridView中View的复用机制。
随后,在XML文件中定义GridView并设置相应的属性:
<GridView
android:id="@+id/grid_view_custom"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:numColumns="3"
android:columnWidth="100dp"
android:stretchMode="columnWidth"/>
最后,在代码中为GridView设置自定义的适配器即可,代码如下:
//初始化自定义布局的GridView
List<Book> dataList=new ArrayList<>();
dataList.add(new Book("《小王子》",R.mipmap.ic_launcher));
dataList.add(new Book("《资本论》",R.mipmap.ic_launcher));
dataList.add(new Book("《三体》",R.mipmap.ic_launcher));
dataList.add(new Book("《必然》",R.mipmap.ic_launcher));
dataList.add(new Book("《创客》",R.mipmap.ic_launcher));
dataList.add(new Book("《平凡世界》",R.mipmap.ic_launcher));
StyleGridViewAdapter styleAdapter=new StyleGridViewAdapter(this,dataList);
customGridView.setAdapter(styleAdapter);
效果截图:
常用技巧
下面只列出了一些常见的技巧,其实很多ListView能够使用的技巧GridView也可以使用,更多的知识点可以参考这篇博客:
去掉默认的选中颜色
只需将android:listSelector
属性设置为#00000000
就可以去掉默认的选中颜色(其实是设置为透明色)。
设置分割线
GridView和ListView不同,并没有专门的属性用于设置列表项间的分割线。如果想要在GridView为Item设置分割线,就需要在Item的布局文件中进行设置。
首先,在drawable
文件夹下创建一个shape
资源,本例中命名为gridview_item_divider.xml
,示例代码如下:
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke android:color="#aaa" android:width="1dp"/>
</shape>
然后,为Item对应的根布局设置这个shape资源为background
,示例代码如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/gridview_item_divider"
android:gravity="center">
<ImageView
android:id="@+id/book_image"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_marginTop="6dp"/>
<TextView
android:id="@+id/book_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:layout_marginBottom="6dp"
android:textAllCaps="false"
android:textSize="16sp" />
</LinearLayout>
效果截图:
注意:这种方法有一个不足之处,那就是内部的网格线会比外部的网格线要粗(因为这是两个Item间的网格线叠加形成的)。
监听滚动状态
只需要为GridView设置OnScrollListener
即可,示例代码如下:
//监听ListView的滑动状态
normalGridView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
//滑动状态发生变化时触发
//scrollState的可能值:[SCROLL_STATE_IDLE|SCROLL_STATE_TOUCH_SCROLL|SCROLL_STATE_FLING]
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
//滑动完成时触发
//firstVisibleItem:第一个可见项的索引值
//visibleItemCount:可见项的个数
//totalItemCount:列表项的总数
}
});
onScrollStateChanged中的scrollState
可能有三种取值,含义如下:
- SCROLL_STATE_IDLE:静止状态
- SCROLL_STATE_TOUCH_SCROLL:滑动状态(用户此时触碰着屏幕且在滑动)
- SCROLL_STATE_FLING:惯性滑动状态(用户此时未触碰屏幕,GridView借助上一次滑动的惯性滑动)
跳转到指定位置
//跳转到指定位置(让这个Item所在的行成为GridView当前的第一行)
public void setSelection(int position);
平滑滚动到指定位置
//平滑滚动到指定位置
public void smoothScrollToPosition(int position);
//平滑滚动n行高度的距离
//offset:需要滚动的行数(offset为正数时GridView向上滚动,为负数时向下滚动)
public void smoothScrollByOffset(int offset);
注意: smoothScrollToPosition只保证这个列表项在可视范围内,并不保证让这个Item所在的行成为GridView当前的第一行。
常见问题
- 子控件抢夺焦点
- 异步加载时图片显示错位
请参考这篇博客:《Android UI ListView讲解》
更多博客
《Android UI 常用控件讲解》:包括CheckBox、RadioButton、ToggleButton、Switch、ProgressBar、SeekBar、RatingBar、Spinner、ImageButton。
《Android UI 与文本相关的控件》:包括TextView、EditText、AutoCompleteTextView和MultiAutoCompleteTextView。
《Android UI ListView讲解》:详细讲解ListView的使用和常用技巧。