原文地址
本文演示在你的 Android 应用程序中显示交错 GridView(Staggered GridView )。
交错 GridView 只是具有不等大小行、多个列的 GridView。你可能已经使用过 Pinterest,Expedia 或 Etsy Android app。
目前,已经有 2、3 个很不错的开源库。
交错 GridView 库
- Staggered GridView by Etsy
- Staggered GridView by Maurycy Wojtowicz
下面说明最流行使用最广泛的 Etsy’s staggered gridview。
自定义的交错 GridView 是根据 AbsListView 类扩展的,它当然也就支持 AbsListView.OnScrollListener。
Etsy’s staggered gridview doesn’t support Item long press event and selector drawables, where as Maurycy’s staggered gridview supports item long press event.
图 1 项目结构
图 2 演示交错 GridView
com.etsy.android.grid 库目前配置为只能通过 Gradle 生成,因此,如果您使用 Android Studio,那么你可以直接的方式包括这个库,作为 gradle 依赖来生成。如果你使用 Eclipse/ Ant,那么你必须执行额外的步骤。
对 Android Studio 环境:
class="alt">repositories {
mavenCentral()
}
dependencies {
compile 'com.etsy.android.grid:library:x.x.x' // read below comment
}
对 Eclipse/Ant build:下载 com.etsy.android.grid,并导入到项目。
<com.etsy.android.grid.StaggeredGridView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/grid_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:column_count="@integer/grid_column_count"
app:item_margin="8dp" />
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/panel_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="blocksDescendants"
android:orientation="horizontal" >
<com.etsy.android.grid.util.DynamicHeightImageView
android:id="@+id/imgView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />
</FrameLayout>自定义行应该包含 DynamicHeightImageView 或 DynamicHeightTextView。
public class SampleAdapter extends ArrayAdapter<String> {
private static final String TAG = "SampleAdapter";
private final LayoutInflater mLayoutInflater;
private final Random mRandom;
private static final SparseArray<Double> sPositionHeightRatios = new SparseArray<Double>();
public SampleAdapter(Context context, int textViewResourceId,
ArrayList<String> objects) {
super(context, textViewResourceId, objects);
this.mLayoutInflater = LayoutInflater.from(context);
this.mRandom = new Random();
}
@Override
public View getView(final int position, View convertView,
final ViewGroup parent) {
ViewHolder vh;
if (convertView == null) {
convertView = mLayoutInflater.inflate(R.layout.row_grid_item,
parent, false);
vh = new ViewHolder();
vh.imgView = (DynamicHeightImageView) convertView
.findViewById(R.id.imgView);
convertView.setTag(vh);
} else {
vh = (ViewHolder) convertView.getTag();
}
double positionHeight = getPositionRatio(position);
vh.imgView.setHeightRatio(positionHeight);
ImageLoader.getInstance().displayImage(getItem(position), vh.imgView);
return convertView;
}
static class ViewHolder {
DynamicHeightImageView imgView;
}
private double getPositionRatio(final int position) {
double ratio = sPositionHeightRatios.get(position, 0.0);
// if not yet done generate and stash the columns height
// in our real world scenario this will be determined by
// some match based on the known height and width of the image
// and maybe a helpful way to get the column height!
if (ratio == 0) {
ratio = getRandomHeightRatio();
sPositionHeightRatios.append(position, ratio);
Log.d(TAG, "getPositionRatio:" + position + " ratio:" + ratio);
}
return ratio;
}
private double getRandomHeightRatio() {
return (mRandom.nextDouble() / 2.0) + 1.0; // height will be 1.0 - 1.5
// the width
}
}自定义 adapter 类用来在交错 GridView 中显示动态高度的图片。另外,还使用了 Universal image loader 库用来异步加载图片。
public class StaggeredGridActivity extends Activity implements
AbsListView.OnScrollListener, AbsListView.OnItemClickListener {
private static final String TAG = "StaggeredGridActivity";
public static final String SAVED_DATA_KEY = "SAVED_DATA";
private StaggeredGridView mGridView;
private boolean mHasRequestedMore;
private SampleAdapter mAdapter;
private ArrayList<String> mData;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sgv);
setTitle("TechnoTalkative - SGV Demo");
mGridView = (StaggeredGridView) findViewById(R.id.grid_view);
mAdapter = new SampleAdapter(this, android.R.layout.simple_list_item_1,
generateData());
// do we have saved data?
if (savedInstanceState != null) {
mData = savedInstanceState.getStringArrayList(SAVED_DATA_KEY);
}
if (mData == null) {
mData = generateData();
}
for (String data : mData) {
mAdapter.add(data);
}
mGridView.setAdapter(mAdapter);
mGridView.setOnScrollListener(this);
mGridView.setOnItemClickListener(this);
}
@Override
protected void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState);
outState.putStringArrayList(SAVED_DATA_KEY, mData);
}
@Override
public void onScrollStateChanged(final AbsListView view,
final int scrollState) {
Log.d(TAG, "onScrollStateChanged:" + scrollState);
}
@Override
public void onScroll(final AbsListView view, final int firstVisibleItem,
final int visibleItemCount, final int totalItemCount) {
Log.d(TAG, "onScroll firstVisibleItem:" + firstVisibleItem
+ " visibleItemCount:" + visibleItemCount + " totalItemCount:"
+ totalItemCount);
// our handling
if (!mHasRequestedMore) {
int lastInScreen = firstVisibleItem + visibleItemCount;
if (lastInScreen >= totalItemCount) {
Log.d(TAG, "onScroll lastInScreen - so load more");
mHasRequestedMore = true;
onLoadMoreItems();
}
}
}
private void onLoadMoreItems() {
final ArrayList<String> sampleData = generateData();
for (String data : sampleData) {
mAdapter.add(data);
}
// stash all the data in our backing store
mData.addAll(sampleData);
// notify the adapter that we can update now
mAdapter.notifyDataSetChanged();
mHasRequestedMore = false;
}
private ArrayList<String> generateData() {
ArrayList<String> listData = new ArrayList<String>();
listData.add("http://images.cnblogs.com/cnblogs_com/liuning8023/610092/o_2iitkhx.jpg");
listData.add("http://images.cnblogs.com/cnblogs_com/liuning8023/610092/o_w0omeb.jpg");
listData.add("http://images.cnblogs.com/cnblogs_com/liuning8023/610092/o_w9iu1d.jpg");
listData.add("http://images.cnblogs.com/cnblogs_com/liuning8023/610092/o_iw6kh2.jpg");
listData.add("http://images.cnblogs.com/cnblogs_com/liuning8023/610092/o_ru08c8.jpg");
listData.add("http://images.cnblogs.com/cnblogs_com/liuning8023/610092/o_k12r10.jpg");
listData.add("http://images.cnblogs.com/cnblogs_com/liuning8023/610092/o_2e3daug.jpg");
listData.add("http://images.cnblogs.com/cnblogs_com/liuning8023/610092/o_2igznfr.jpg");
return listData;
}
@Override
public void onItemClick(AdapterView<?> adapterView, View view,
int position, long id) {
Toast.makeText(this, "Item Clicked: " + position, Toast.LENGTH_SHORT)
.show();
}
}
表 1 交错 GridView 可配置的属性
属性
说明
item_margin The margin around each grid item (default 0dp). column_count The number of columns displayed. Will override column_count_portrait and column_count_landscape if present (default 0). column_count_portrait The number of columns displayed when the grid is in portrait (default 2). column_count_landscape The number of columns displayed when the grid is in landscape (default 3). grid_paddingLeft Padding to the left of the grid. Does not apply to headers and footers (default 0). grid_paddingRight Padding to the right of the grid. Does not apply to headers and footers (default 0). grid_paddingTop Padding to the top of the grid. Does not apply to headers and footers (default 0). grid_paddingBottom Padding to the bottom of the grid. Does not apply to headers and footers (default 0).
下载 Demo