GridView 是 Android 的一个常见组件,它能够在列表中显示多个数据项。同时,GridView 还支持数据排序,让用户能够按照自己的喜好对数据进行排序。本文将介绍如何通过 GridView 实现数据排序。
GridView 的排序模式
在 GridView 中,有两种排序模式:自然排序和自定义排序。自然排序是指按照某个字段(例如 ID、日期等)对数据进行排序,排序方式通常是升序或降序。自定义排序是指按照用户自己定义的顺序对数据进行排序,该顺序可以在代码中进行配置。
自然排序
在 GridView 中,自然排序通常是通过设置适配器中的排序方法来实现的。例如,我们可以通过以下的方法,将数据按照 ID 字段进行升序排列:
```
ArrayList
Collections.sort(dataList, new Comparator
@Override
public int compare(DataItem o1, DataItem o2) {
return o1.id - o2.id;
}
});
GridViewAdapter adapter = new GridViewAdapter(this, dataList);
mGridView.setAdapter(adapter);
```
在上面的代码中,getData() 方法返回了一个包含多个 DataItem 的 ArrayList 集合。通过 Collections.sort() 方法,我们可以将数据按照 ID 升序排列。compare() 方法则用于定义排序的规则。最后,我们将排序后的数据传入 GridViewAdapter 中,并将适配器与 GridView 关联。
自定义排序
自定义排序需要用户自己定义排序的规则。例如,可以通过拖拽、点击等操作,让用户自己决定数据的排序方式。为了实现自定义排序,需要在代码中对 GridView 进行配置。
以拖拽排序为例,以下是实现拖拽排序的主要步骤:
1. 给 GridView 的每个子项添加一个 OnTouchListener,当用户按下某个子项时,将该子项从 GridView 中移除。
2. 当用户移动手指时,将该子项在屏幕上绘制,并实时更新位置。
3. 当用户松开手指时,将该子项插入到目标位置。
以下是具体实现的示例代码:
```
public class CustomGridView extends GridView {
private boolean isDragged = false;
private boolean isMove = false;
private int dragPosition;
private int dropPosition;
private int startX;
private int startY;
private int rootX;
private int rootY;
private View dragView;
private ImageView dragImageView;
private List
private GridViewAdapter adapter;
public CustomGridView(Context context) {
super(context);
}
public CustomGridView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomGridView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setData(List
this.dataList = dataList;
adapter = new GridViewAdapter(getContext(), dataList);
setAdapter(adapter);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = (int) ev.getX();
startY = (int) ev.getY();
rootX = (int) ev.getRawX();
rootY = (int) ev.getRawY();
dragPosition = pointToPosition(startX, startY);
if (dragPosition == AdapterView.INVALID_POSITION) {
return super.onTouchEvent(ev);
}
isDragged = true;
View view = getChildAt(dragPosition - getFirstVisiblePosition());
view.setDrawingCacheEnabled(true);
view.destroyDrawingCache();
Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
dragView = view;
dragImageView = new ImageView(getContext());
dragImageView.setImageBitmap(bitmap);
dragImageView.setBackgroundColor(Color.parseColor("#f0f0f0"));
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.gravity = Gravity.TOP | Gravity.LEFT;
params.x = rootX - startX;
params.y = rootY - startY;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_BLUR_BEHIND;
params.format = PixelFormat.TRANSLUCENT;
params.windowAnimations = 0;
View parent = (View) getParent();
WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
windowManager.addView(dragImageView, params);
getChildAt(dragPosition - getFirstVisiblePosition()).setVisibility(View.INVISIBLE);
break;
case MotionEvent.ACTION_MOVE:
if (isDragged) {
int newX = (int) ev.getRawX();
int newY = (int) ev.getRawY();
int dx = newX - rootX;
int dy = newY - rootY;
int top = dragImageView.getTop() + dy;
int left = dragImageView.getLeft() + dx;
dragImageView.layout(left, top, left + dragImageView.getWidth(), top + dragImageView.getHeight());
int position = pointToPosition(newX - startX, newY - startY);
if (position != AdapterView.INVALID_POSITION) {
dropPosition = position;
}
int distance = Math.abs(newY - startY);
if (distance > 50) {
isMove = true;
}
}
break;
case MotionEvent.ACTION_UP:
if (isDragged && isMove) {
getChildAt(dragPosition - getFirstVisiblePosition()).setVisibility(View.VISIBLE);
View view1 = getChildAt(dropPosition - getFirstVisiblePosition());
adapter.swap(dragPosition, dropPosition);
}
ViewGroup parent = (ViewGroup) dragImageView.getParent();
parent.removeView(dragImageView);
dragImageView = null;
isDragged = false;
isMove = false;
break;
}
return super.onTouchEvent(ev);
}
public void refresh() {
if (adapter != null) {
adapter.notifyDataSetChanged();
}
}
}
```
在以上代码中,首先定义了几个用于拖拽排序的属性,例如 dragPosition 用于保存被拖动的子项的位置等。当用户按下某个子项时,我们通过 Bitmap 创建了一个对应的 ImageView,显示在屏幕上。在用户移动手指时,我们将该 ImageView 位置实时更新,并更新 dragPosition 和 dropPosition 的值。最后,在用户松开手指时,我们将该子项插入到 dropPosition 的位置,实现拖拽排序。
实现自定义排序的方式有很多种,以上代码仅为示例。在实际开发中,需要根据具体情况选择最适合的方案。
GridView 的刷新
当数据发生变化时(例如添加、删除等),需要刷新 GridView。在 GridView 中,刷新数据有两种方式:调用 GridView 的 invalidateViews() 方法或 notifyDataSetChanged() 方法。
invalidateViews() 方法会重新绘制所有可见的子项,可以在某些情况下提高刷新的效率。例如,当数据量比较大时,调用 notifyDataSetChanged() 方法需要重新绘制全部子项,性能较差,而 invalidateViews() 方法则只会重新绘制可见子项,性能相对较好。
当数据量比较小时,两种方法的性能差别不大。以下是两种方法的示例代码:
```
// 使用 invalidateViews() 方法刷新数据
CustomGridView gridView = findViewById(R.id.gridView);
List
gridView.setData(dataList);
gridView.invalidateViews();
// 使用 notifyDataSetChanged() 方法刷新数据
CustomGridView gridView = findViewById(R.id.gridView);
List
gridView.setData(dataList);
gridView.refresh();
```
GridView 的特性
在 GridView 中,有一些常见的特性,如多选、单选等。这些特性都能够在代码中进行配置。
多选模式
GridView 的多选模式可以通过设置 GridView 的选择模式来实现。具体实现步骤如下:
1. 配置 GridView 的选择模式为多选:
gridView.setChoiceMode(GridView.CHOICE_MODE_MULTIPLE);
2. 在 GridViewAdapter 中,为每个子项添加一个 CheckBox,用于显示当前子项是否被选中:
public View getView(final int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
if (convertView == null) {
convertView = LayoutInflater.from(mContext).inflate(R.layout.grid_item, parent, false);
holder = new ViewHolder();
holder.iconView = convertView.findViewById(R.id.iconView);
holder.nameView = convertView.findViewById(R.id.nameView);
holder.checkBox = convertView.findViewById(R.id.checkBox);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
DataItem item = mDataList.get(position);
holder.iconView.setImageResource(item.icon);
holder.nameView.setText(item.name);
holder.checkBox.setOnCheckedChangeListener(null);
holder.checkBox.setChecked(mCheckedList.get(position));
holder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
mCheckedList.set(position, isChecked);
}
});
return convertView;
}
在以上代码中,mCheckedList 保存了每个子项的选中状态。当用户勾选某个子项时,我们更新 mCheckedList 中对应项的值,并调用 notifyDataSetChanged() 方法刷新 GridView。
单选模式
GridView 的单选模式可以通过配置 GridView 的选择模式,并在 GridViewAdapter 中添加一个当前选中的子项来实现。
具体实现步骤如下:
1. 配置 GridView 的选择模式为单选:
gridView.setChoiceMode(GridView.CHOICE_MODE_SINGLE);
2. 在 GridViewAdapter 中,添加一个 currentSelection 属性,用于保存当前选中的子项。
3. 在 GridViewAdapter 中,为 GridView 的每个子项添加一个 checkable 属性。当用户点击某个子项时,将该子项的 checkable 设置为 true,并将之前的选中项的 checkable 设置为 false。
public View getView(final int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
if (convertView == null) {
convertView = LayoutInflater.from(mContext).inflate(R.layout.grid_item, parent, false);
holder = new ViewHolder();
holder.iconView = convertView.findViewById(R.id.iconView);
holder.nameView = convertView.findViewById(R.id.nameView);
holder.checkBox = convertView.findViewById(R.id.checkBox);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
DataItem item = mDataList.get(position);
holder.iconView.setImageResource(item.icon);
holder.nameView.setText(item.name);
if (position == currentSelection) { // 当前项被选中
holder.checkBox.setChecked(true);
} else {
holder.checkBox.setChecked(false);
}
holder.checkBox.setClickable(false);
holder.checkBox.setFocusable(false);
convertView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
currentSelection = position;
notifyDataSetChanged();
}
});
return convertView;
}
在以上代码中,currentSelection 保存了当前选中的子项的位置。当用户点击某个子项时,我们将该子项设置为选中状态,并将之前的选中项设置为非选中状态。最后,调用 notifyDataSetChanged() 方法刷新 GridView。
GridView 的附加功能
除了上述基本特性外,GridView 还有很多附加功能。以下是一些常见的功能介绍。
分页加载
分页加载是指当数据量比较大时,每次只显示部分数据,并在用户滚动到底部时,再加载下一页数据。在 GridView 中,分页加载可以通过 OnScrollListener 来实现。
具体实现步骤如下:
1. 给 GridView 添加 OnScrollListener 监听器:
gridView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE
&& view.getLastVisiblePosition() == adapter.getCount() - 1) {
// 加载下一页数据
...
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
});
2. 在 OnScrollListener 监听器的 onScrollStateChanged() 方法中,判断当前 GridView 是否滑动到底部,如果是,则加载下一页数据即可。
以上是通过 OnScrollListener 实现分页加载的方式。在实际开发中,还可以使用一些第三方库,例如 PagedList 和 PagingLibrary 等来简化分页加载的实现。
图片缓存
在 GridView 中,如果需要加载大量图片,很容易就会造成内存溢出。为了解决这个问题,可以考虑使用图片缓存。
图片缓存通常会将图片加载到本地,并使用 LruCache 等缓存机制来管理。当需要加载图片时,首先检查是否有已下载的图片,如果没有,则从网络上下载。
以下是 Picasso 图片缓存库使用的示例代码:
Picasso.with(context).load(url).into(imageView);
在以上代码中,Picasso.with(context) 创建了一个 Picasso 实例,调用 load(url) 方法来加载图片,并将其显示在 imageView 中。
需要注意的是,使用图片缓存库时,需要注意图片的大小,因为图片缓存会占用一定的内存。
总结
GridView 是 Android 中常见的一个列表控件,它能够在界面上显示多个数据项,并提供了多种附加功能。在使用 GridView 时,需要注意数据的排序、刷新和特性等方面,同时也要注意内存管理和性能优化等问题。