快速實現RecycleView的網格和瀑布流佈局新增頭部(尾部原理一樣,故略之)
RecycleView高度的解耦,非常靈活,通過佈局管理器LayoutManager控制其顯示的佈局;通過ItemDecoration控制Item間的間隔;通過ItemAnimator控制Item增刪的動畫;不過需要自己寫介面實現點選、長按事件。它只管回收與複用View。
然後最坑爹的是沒了addHeader和addFooter,需要自己實現(怨念中…),自己找開源的程式碼發現稍微有點繁瑣,於是自己動手研究利用Manager的特性還有LayoutParams實
現了新增頭尾等操作,接下來介紹GridLayoutManager和StaggeredGridLayoutManager怎麼新增頭部,LinearLayout太簡單了就不介紹了。
①GridLayoutManager新增頭部操作
網格佈局配置:
關鍵在於繼承GridLayoutManager.SpanSizeLookup實現HeaderSpanSizeLookup來處理指定位置佔多少列的操作來實現新增頭部
GridLayoutManager glm = new GridLayoutManager(this, 2);
glm.setSpanSizeLookup(new HeaderSpanSizeLookup(glm));
recyclerView.setLayoutManager(glm);
recyclerView.addItemDecoration(new SpacesItemDecoration(5));
recyclerView.setAdapter(new LayoutAdapter());
HeaderSpanSizeLookup 在getSpanSize方法這裡通過position=0來設定該位置的view佔滿getSpanCount即我們設定的兩列
class HeaderSpanSizeLookup extends GridLayoutManager.SpanSizeLookup {
private final GridLayoutManager layoutManager;
public HeaderSpanSizeLookup(GridLayoutManager layoutManager) {
this.layoutManager = layoutManager;
}
@Override
public int getSpanSize(int position) {
position = position == 0 ? layoutManager.getSpanCount() : 1;
return position;
}
}
LayoutAdapter 的實現跟瀑布流的大同小異,細節註釋去看下面的瀑布流即可
class LayoutAdapter extends RecyclerView.Adapter<LayoutAdapter.SimpleViewHolder> {
private static final int TYPE_HEADER = 1, TYPE_ITEM = 2;
class SimpleViewHolder extends RecyclerView.ViewHolder {
ImageView header;
ImageView item;
public SimpleViewHolder(View view) {
super(view);
header = (ImageView) view.findViewById(R.id.iv_header);
item = (ImageView) view.findViewById(R.id.iv_item);
}
}
@Override
public SimpleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = null;
switch (viewType) {
case TYPE_HEADER:
view = LayoutInflater.from(GridLayoutManagerActivity.this).inflate(R.layout.header, parent, false);
break;
case TYPE_ITEM:
view = LayoutInflater.from(GridLayoutManagerActivity.this).inflate(R.layout.item, parent, false);
break;
}
return new SimpleViewHolder(view);
}
int size[] = new int[2];
@Override
public void onBindViewHolder(final SimpleViewHolder holder, int position) {
switch (holder.getItemViewType()) {
case TYPE_HEADER:
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) holder.header.getLayoutParams();
size = getImageRealSize(R.drawable.ic_girls_banner);
lp.width = metrics.widthPixels;
lp.height = lp.width * size[1] / size[0];
holder.header.setLayoutParams(lp);
Glide.with(GridLayoutManagerActivity.this).
load(R.drawable.ic_girls_banner).
asBitmap().
diskCacheStrategy(DiskCacheStrategy.ALL).
into(new BitmapImageViewTarget(holder.header) {
@Override
public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
super.onResourceReady(resource, glideAnimation);
Palette.from(resource).generate(new Palette.PaletteAsyncListener() {
@Override
public void onGenerated(Palette palette) {
((CardView)holder.header.getParent()).setCardBackgroundColor(palette.getMutedColor(Color.parseColor("#ffffff")));
}
});
}
});
break;
case TYPE_ITEM:
final ViewGroup.LayoutParams lp1 = (ViewGroup.LayoutParams) holder.item.getLayoutParams();
size = getImageRealSize(mItems.get(position - 1));
lp1.width = metrics.widthPixels / 2;
lp1.height = metrics.widthPixels / 2 * size[1] / size[0];
holder.item.setLayoutParams(lp1);
Glide.with(GridLayoutManagerActivity.this).
load(mItems.get(position - 1)).
asBitmap().
diskCacheStrategy(DiskCacheStrategy.ALL).
into(new BitmapImageViewTarget(holder.item) {
@Override
public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
super.onResourceReady(resource, glideAnimation);
Palette.from(resource).generate(new Palette.PaletteAsyncListener() {
@Override
public void onGenerated(Palette palette) {
((CardView) holder.item.getParent()).setCardBackgroundColor(palette.getMutedColor(Color.parseColor("#ffffff")));
}
});
}
});
break;
}
}
@Override
public int getItemViewType(int position) {
return position = position == 0 ? TYPE_HEADER : TYPE_ITEM;
}
@Override
public int getItemCount() {
return mItems.size() + 1;
}
}
②StaggeredGridLayoutManager新增頭部操作
首先按正常的配置配,這裡我配的是兩列,垂直
StaggeredGridLayoutManager sglm = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(sglm);
recyclerView.addItemDecoration(new SpacesItemDecoration(5));
recyclerView.setAdapter(new LayoutAdapter());
SpacesItemDecoration是繼承ItemDecoration用來設間隔的
class SpacesItemDecoration extends RecyclerView.ItemDecoration {
private int space;
private StaggeredGridLayoutManager.LayoutParams lp;
public SpacesItemDecoration(int space) {
this.space = space;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view);
outRect.left = space;
outRect.right = space;
outRect.bottom = space;
if (position == 0) {
// 用於設第一個位置跟頂部的距離
outRect.top = space;
} else {
lp = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
if (lp.getSpanIndex() == 0) {
// 用於設同行兩個間隔間跟其距離左右螢幕間隔相同
outRect.right = 0;
}
}
}
}
最關鍵的是Adapter的實現了,
頭部是利用StaggeredGridLayoutManager.LayoutParams.setFullSpan(true)來實現佔滿兩列的
class LayoutAdapter extends RecyclerView.Adapter<LayoutAdapter.SimpleViewHolder> {
// view的type:頭部,內容
private static final int TYPE_HEADER = 1, TYPE_ITEM = 2;
class SimpleViewHolder extends RecyclerView.ViewHolder {
// 頭部的控制元件
ImageView header;
CardView cardview;
// 內容的控制元件
ImageView item;
public SimpleViewHolder(View view) {
super(view);
header = (ImageView) view.findViewById(R.id.iv_header);
item = (ImageView) view.findViewById(R.id.iv_item);
cardview = (CardView) view.findViewById(R.id.cardview);
}
}
@Override
public int getItemViewType(int position) {
// 位置為0的話型別為頭部,其餘為內容
return position = position == 0 ? TYPE_HEADER : TYPE_ITEM;
}
@Override
public int getItemCount() {
// 加一是因為添加了頭部
return mItems.size() + 1;
}
@Override
public SimpleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = null;
switch (viewType) {
// 根據型別填充不同佈局
case TYPE_HEADER:
view = LayoutInflater.from(MainActivity.this).inflate(R.layout.header, parent, false);
break;
case TYPE_ITEM:
view = LayoutInflater.from(MainActivity.this).inflate(R.layout.item, parent, false);
break;
}
return new SimpleViewHolder(view);
}
int size[] = new int[2];
@Override
public void onBindViewHolder(final SimpleViewHolder holder, int position) {
switch (holder.getItemViewType()) {
case TYPE_HEADER:
// 根據view型別處理不同的操作
// 獲取cardview的佈局屬性,記住這裡要是佈局的最外層的控制元件的佈局屬性,如果是裡層的會報cast錯誤
StaggeredGridLayoutManager.LayoutParams clp = (StaggeredGridLayoutManager.LayoutParams) holder.cardview.getLayoutParams();
// 最最關鍵一步,設定當前view佔滿列數,這樣就可以佔據兩列實現頭部了
clp.setFullSpan(true);
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) holder.header.getLayoutParams();
// 獲取資源圖片寬高,用於等比例載入圖片
size = getImageRealSize(R.drawable.ic_girls_banner);
// 寬度為螢幕寬度
lp.width = metrics.widthPixels;
// 等比例得到高度
lp.height = lp.width * size[1] / size[0];
holder.header.setLayoutParams(lp);
Glide.with(MainActivity.this).
load(R.drawable.ic_girls_banner).
asBitmap().
diskCacheStrategy(DiskCacheStrategy.ALL).
into(new BitmapImageViewTarget(holder.header) {
@Override
public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
super.onResourceReady(resource, glideAnimation);
Palette.from(resource).generate(new Palette.PaletteAsyncListener() {
@Override
public void onGenerated(Palette palette) {
/*Palette這個類中提取以下突出的顏色:
Vibrant(有活力)
Vibrant dark(有活力 暗色)
Vibrant light(有活力 亮色)
Muted (柔和)
Muted dark(柔和 暗色)
Muted light(柔和 亮色)*/
holder.cardview.setCardBackgroundColor(palette.getMutedColor(Color.parseColor("#ffffff")));
}
});
}
});
break;
case TYPE_ITEM:
final ViewGroup.LayoutParams lp1 = (ViewGroup.LayoutParams) holder.item.getLayoutParams();
size = getImageRealSize(mItems.get(position - 1));
lp1.width = metrics.widthPixels / 2;
lp1.height = metrics.widthPixels / 2 * size[1] / size[0];
holder.item.setLayoutParams(lp1);
Glide.with(MainActivity.this).
load(mItems.get(position - 1)).
asBitmap().
diskCacheStrategy(DiskCacheStrategy.ALL).
into(new BitmapImageViewTarget(holder.item) {
@Override
public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
super.onResourceReady(resource, glideAnimation);
Palette.from(resource).generate(new Palette.PaletteAsyncListener() {
@Override
public void onGenerated(Palette palette) {
((CardView) holder.item.getParent()).setCardBackgroundColor(palette.getMutedColor(Color.parseColor("#ffffff")));
}
});
}
});
break;
}
}
}
頭部佈局
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/cardview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardCornerRadius="8dp"
app:cardElevation="5dp"
app:contentPadding="1dp">
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/iv_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#44000000">
</ImageView>
</android.support.v7.widget.CardView>
內容佈局
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/cardview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardCornerRadius="8dp"
app:cardElevation="5dp"
app:contentPadding="1dp">
<ImageView
android:id="@+id/iv_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#44000000">
</ImageView>
</android.support.v7.widget.CardView>
由上可以看到,GridLayoutManager是通過GridLayoutManager.SpanSizeLookup來控制佔的列數來實現頭部新增的;
StaggeredGridLayoutManager是通過在Adapter的oncreateView那裡判斷ViewType為頭部型別的時候,獲取佈局最外層控制元件的StaggeredGridLayoutManager.LayoutParams,呼叫StaggeredGridLayoutManager.LayoutParams.setFullSpan(true)來控制佔的列數來實現頭部新增的。
看完了是不是覺得so easy呢?只要掌握了這個,任意新增不同佈局,控制佔的不同列數,我們可以實現各種各樣的需求,我想這就是RecycleView取代listview和gridview的必殺技。講解完畢,謝謝!
附圖片: