先上效果圖
淘寶首頁是從上到下是各種不同的樣式,最上面是搜索,其次是一個輪播圖,再下來是10個圓角的菜單,等等,我們可以採用一個recyclerView實現,但是實現起來的複雜程度是比較高的,如果使用阿里開源的VLayout控件,實現起來則簡單多了,Vlayout就適用於這種多種item的佈局。
官方文檔
詳細的介紹可以參考官網文檔,中文版:https://github.com/alibaba/vlayout/blob/master/README-ch.md
代碼實現
先來看主頁面的xml佈局,爲了簡單把搜索欄用圖片代替,下面是一個RecyclerView
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<ImageView
android:layout_width="match_parent"
android:layout_height="48dp"
android:scaleType="centerCrop"
android:src="@mipmap/img_title_bar" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
完了我們對recyclerView先進行初始化,設置LayoutManager,平時我們使用的系統自帶的,這裏我們使用VirtualLayoutManager,並設置回收複用池大小,(如果一屏內相同類型的 View 個數比較多,需要設置一個合適的大小,防止來回滾動時重新創建 View)。
recyclerView = findViewById(R.id.rv);
VirtualLayoutManager virtualLayoutManager = new VirtualLayoutManager(this);
recyclerView.setLayoutManager(virtualLayoutManager);
RecyclerView.RecycledViewPool viewPool = new RecyclerView.RecycledViewPool();
viewPool.setMaxRecycledViews(0, 10);
recyclerView.setRecycledViewPool(viewPool);
完了就到了加載數據的地方,正常的adaper是繼承recyclerView的adapter,使用VLayout的話就要繼承BaseDelegeteAdapter,可以像平常一樣寫繼承自DelegateAdapter.Adapter
的Adapter, 只比之前的Adapter需要多重載onCreateLayoutHelper
方法。 其他的和默認Adapter一樣。
public class BaseDelegeteAdapter extends DelegateAdapter.Adapter<BaseViewHolder> {
private LayoutHelper mLayoutHelper;
private int mCount = -1;
private int mLayoutId = -1;
private int mViewTypeItem = -1;
public BaseDelegeteAdapter(LayoutHelper layoutHelper, int layoutId, int count) {
this.mLayoutHelper = layoutHelper;
this.mCount = count;
this.mLayoutId = layoutId;
}
@Override
public LayoutHelper onCreateLayoutHelper() {
return mLayoutHelper;
}
@NonNull
@Override
public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new BaseViewHolder(LayoutInflater.from(parent.getContext()).inflate(mLayoutId, parent, false));
}
@Override
public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) {
}
@Override
public int getItemCount() {
return mCount;
}
}
ViewHolder的寫法還和之前的一樣
public class BaseViewHolder extends RecyclerView.ViewHolder {
private SparseArray<View> views;
public BaseViewHolder(@NonNull View itemView) {
super(itemView);
this.views = new SparseArray<>();
}
public BaseViewHolder setTextColor(@IdRes int viewId, @ColorInt int textColor) {
TextView view = getView(viewId);
view.setTextColor(textColor);
return this;
}
public BaseViewHolder setImageResource(@IdRes int viewId, @DrawableRes int imageResId) {
ImageView view = getView(viewId);
view.setImageResource(imageResId);
return this;
}
public BaseViewHolder setText(@IdRes int viewId, CharSequence value) {
TextView view = getView(viewId);
view.setText(value);
return this;
}
public <T extends View> T getView(@IdRes int viewId) {
View view = views.get(viewId);
if (view == null) {
view = itemView.findViewById(viewId);
views.put(viewId, view);
}
return (T) view;
}
}
到這裏跟之前其實都差不多,都是一些初始化的工作,接下來就加載數據了。先是第一個是輪播圖,這裏使用開源框架 implementation 'com.youth.banner:banner:1.4.10',我們先來創建Banner輪播圖的adapter,因爲其只有一個元素,所以我們使用LinearLayoutHelper: 線性佈局就可以
private BaseDelegeteAdapter getBannerAdapter() {
return new BaseDelegeteAdapter(new LinearLayoutHelper(), R.layout.vlayout_banner, 1) {
@Override
public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("https://img.alicdn.com/tps/TB1EMhjIpXXXXaPXVXXXXXXXXXX.jpg");
arrayList.add("https://img.alicdn.com/simba/img/TB17OFBloD1gK0jSZFGSuvd3FXa.jpg");
arrayList.add("https://img.alicdn.com/tfs/TB1HSHMlKH2gK0jSZJnXXaT1FXa-520-280.jpg");
arrayList.add("https://img.alicdn.com/simba/img/TB181dplkL0gK0jSZFxSutWHVXa.jpg");
arrayList.add("https://img.alicdn.com/simba/img/TB1rWhyleH2gK0jSZFESuwqMpXa.jpg");
arrayList.add("https://img.alicdn.com/tfs/TB15xIZk7T2gK0jSZFkXXcIQFXa-520-280.png");
// 綁定數據
Banner mBanner = holder.getView(R.id.banner);
//設置banner樣式
mBanner.setBannerStyle(BannerConfig.CIRCLE_INDICATOR);
//設置圖片加載器
mBanner.setImageLoader(new ImageLoader() {
@Override
public void displayImage(Context context, Object path, ImageView imageView) {
Glide.with(context)
.load(path)
.into(imageView);
}
});
//設置圖片集合
mBanner.setImages(arrayList);
//設置banner動畫效果
mBanner.setBannerAnimation(Transformer.DepthPage);
//設置標題集合(當banner樣式有顯示title時)
// mBanner.setBannerTitles(titles);
//設置自動輪播,默認爲true
mBanner.isAutoPlay(true);
//設置輪播時間
mBanner.setDelayTime(3000);
//設置指示器位置(當banner模式中有指示器時)
mBanner.setIndicatorGravity(BannerConfig.CENTER);
//banner設置方法全部調用完畢時最後調用
mBanner.start();
}
};
}
接下來是10個按鈕菜單,這裏是上面5個,下面5個,我們採用GridLayoutHelper: Grid佈局, 支持橫向的colspan
private BaseDelegeteAdapter getMenuAdapter() {
GridLayoutHelper gridLayoutHelper = new GridLayoutHelper(5);
gridLayoutHelper.setPadding(0, 16, 0, 0);
gridLayoutHelper.setVGap(10);
gridLayoutHelper.setHGap(0);//// 控制子元素之間的水平間距
return new BaseDelegeteAdapter(gridLayoutHelper, R.layout.vlayout_menu, 10) {
@Override
public void onBindViewHolder(@NonNull BaseViewHolder holder, final int position) {
holder.setText(R.id.tv_menu_title_home, ITEM_NAMES[position] + "");
holder.setImageResource(R.id.iv_menu_home, IMG_URLS[position]);
holder.getView(R.id.ll_menu_home).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(getApplicationContext(), ITEM_NAMES[position], Toast.LENGTH_SHORT).show();
}
});
}
};
}
在接下來是兩個豎直拍的新聞推送,滾動的文字我們也使用開源框架MarqueeView,我們使用LinearLayoutHelper: 線性佈局
private BaseDelegeteAdapter getNewsAdapter() {
return new BaseDelegeteAdapter(new LinearLayoutHelper(), R.layout.vlayout_news, 1) {
@Override
public void onBindViewHolder(@NonNull BaseViewHolder holder, int i) {
MarqueeView marqueeView1 = holder.getView(R.id.marqueeView1);
MarqueeView marqueeView2 = holder.getView(R.id.marqueeView2);
List<String> info1 = new ArrayList<>();
info1.add("天貓超市最近發大活動啦,快來搶");
info1.add("沒有最便宜,只有更便宜!");
List<String> info2 = new ArrayList<>();
info2.add("這個是用來搞笑的,不要在意這寫小細節!");
info2.add("啦啦啦啦,我就是來搞笑的!");
marqueeView1.startWithList(info1);
marqueeView2.startWithList(info2);
// 在代碼裏設置自己的動畫
marqueeView1.startWithList(info1, R.anim.anim_bottom_in, R.anim.anim_top_out);
marqueeView2.startWithList(info2, R.anim.anim_bottom_in, R.anim.anim_top_out);
marqueeView1.setOnItemClickListener(new MarqueeView.OnItemClickListener() {
@Override
public void onItemClick(int position, TextView textView) {
Toast.makeText(getApplicationContext(), textView.getText().toString(), Toast.LENGTH_SHORT).show();
}
});
marqueeView2.setOnItemClickListener(new MarqueeView.OnItemClickListener() {
@Override
public void onItemClick(int position, TextView textView) {
Toast.makeText(getApplicationContext(), textView.getText().toString(), Toast.LENGTH_SHORT).show();
}
});
}
};
}
以此類推,後面的Adapter就不寫了,得到各種adapter之後我們需要創建一個DelegateAdapter,並將各種adapter添加到DelegateAdapter,最後給recycler設置adapter設置DelegateAdapter就ok了
BaseDelegeteAdapter bannerAdapter = getBannerAdapter();
BaseDelegeteAdapter menuAdapter = getMenuAdapter();
BaseDelegeteAdapter newsAdapter = getNewsAdapter();
DelegateAdapter delegateAdapter = new DelegateAdapter(virtualLayoutManager, true);
delegateAdapter.addAdapter(bannerAdapter);
delegateAdapter.addAdapter(menuAdapter);
delegateAdapter.addAdapter(newsAdapter);
for (int i = 0; i < ITEM_URL.length; i++) {
final int finalI = i;
BaseDelegeteAdapter titleAdapter = getTitleAdapter(ITEM_URL[finalI]);
GridLayoutHelper gridHelper = new GridLayoutHelper(2);
BaseDelegeteAdapter gridAdapter = getGridAdapter(gridHelper);
delegateAdapter.addAdapter(titleAdapter);
delegateAdapter.addAdapter(gridAdapter);
}
recyclerView.setAdapter(delegateAdapter);
VLayout的使用就是這麼簡單,實現複雜的recyclerview佈局,下面有MainActivity完整代碼,文末附上Demo地址
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
//應用
String[] ITEM_NAMES = {"天貓", "聚划算", "天貓國際", "外賣", "天貓超市", "充值中心", "飛豬旅行", "領金幣", "拍賣", "分類"};
int[] IMG_URLS = {R.mipmap.ic_tian_mao, R.mipmap.ic_ju_hua_suan, R.mipmap.ic_tian_mao_guoji, R.mipmap.ic_waimai, R.mipmap.ic_chaoshi, R.mipmap.ic_voucher_center, R.mipmap.ic_travel, R.mipmap.ic_tao_gold, R.mipmap.ic_auction, R.mipmap.ic_classify};
// 高顏值商品位
int[] ITEM_URL = {R.mipmap.item1, R.mipmap.item2, R.mipmap.item3, R.mipmap.item4, R.mipmap.item5};
int[] GRID_URL = {R.mipmap.flashsale1, R.mipmap.flashsale2, R.mipmap.flashsale3, R.mipmap.flashsale4};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
recyclerView = findViewById(R.id.rv);
VirtualLayoutManager virtualLayoutManager = new VirtualLayoutManager(this);
recyclerView.setLayoutManager(virtualLayoutManager);
RecyclerView.RecycledViewPool viewPool = new RecyclerView.RecycledViewPool();
viewPool.setMaxRecycledViews(0, 10);
recyclerView.setRecycledViewPool(viewPool);
BaseDelegeteAdapter bannerAdapter = getBannerAdapter();
BaseDelegeteAdapter menuAdapter = getMenuAdapter();
BaseDelegeteAdapter newsAdapter = getNewsAdapter();
DelegateAdapter delegateAdapter = new DelegateAdapter(virtualLayoutManager, true);
delegateAdapter.addAdapter(bannerAdapter);
delegateAdapter.addAdapter(menuAdapter);
delegateAdapter.addAdapter(newsAdapter);
for (int i = 0; i < ITEM_URL.length; i++) {
final int finalI = i;
BaseDelegeteAdapter titleAdapter = getTitleAdapter(ITEM_URL[finalI]);
GridLayoutHelper gridHelper = new GridLayoutHelper(2);
BaseDelegeteAdapter gridAdapter = getGridAdapter(gridHelper);
delegateAdapter.addAdapter(titleAdapter);
delegateAdapter.addAdapter(gridAdapter);
}
recyclerView.setAdapter(delegateAdapter);
}
private BaseDelegeteAdapter getGridAdapter(final GridLayoutHelper gridHelper) {
return new BaseDelegeteAdapter(gridHelper, R.layout.vlayout_grid, 4) {
@Override
public void onBindViewHolder(@NonNull BaseViewHolder holder, final int position) {
int item = GRID_URL[position];
ImageView iv = holder.getView(R.id.iv);
Glide.with(getApplicationContext()).load(item).into(iv);
iv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(getApplicationContext(), "item" + position, Toast.LENGTH_SHORT).show();
}
});
}
};
}
private BaseDelegeteAdapter getTitleAdapter(final int imageResId) {
return new BaseDelegeteAdapter(new LinearLayoutHelper(), R.layout.vlayout_title, 1) {
@Override
public void onBindViewHolder(BaseViewHolder holder, int position) {
super.onBindViewHolder(holder, position);
holder.setImageResource(R.id.iv, imageResId);
}
};
}
private BaseDelegeteAdapter getBannerAdapter() {
return new BaseDelegeteAdapter(new LinearLayoutHelper(), R.layout.vlayout_banner, 1) {
@Override
public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("https://img.alicdn.com/tps/TB1EMhjIpXXXXaPXVXXXXXXXXXX.jpg");
arrayList.add("https://img.alicdn.com/simba/img/TB17OFBloD1gK0jSZFGSuvd3FXa.jpg");
arrayList.add("https://img.alicdn.com/tfs/TB1HSHMlKH2gK0jSZJnXXaT1FXa-520-280.jpg");
arrayList.add("https://img.alicdn.com/simba/img/TB181dplkL0gK0jSZFxSutWHVXa.jpg");
arrayList.add("https://img.alicdn.com/simba/img/TB1rWhyleH2gK0jSZFESuwqMpXa.jpg");
arrayList.add("https://img.alicdn.com/tfs/TB15xIZk7T2gK0jSZFkXXcIQFXa-520-280.png");
// 綁定數據
Banner mBanner = holder.getView(R.id.banner);
//設置banner樣式
mBanner.setBannerStyle(BannerConfig.CIRCLE_INDICATOR);
//設置圖片加載器
mBanner.setImageLoader(new ImageLoader() {
@Override
public void displayImage(Context context, Object path, ImageView imageView) {
Glide.with(context)
.load(path)
.into(imageView);
}
});
//設置圖片集合
mBanner.setImages(arrayList);
//設置banner動畫效果
mBanner.setBannerAnimation(Transformer.DepthPage);
//設置標題集合(當banner樣式有顯示title時)
// mBanner.setBannerTitles(titles);
//設置自動輪播,默認爲true
mBanner.isAutoPlay(true);
//設置輪播時間
mBanner.setDelayTime(3000);
//設置指示器位置(當banner模式中有指示器時)
mBanner.setIndicatorGravity(BannerConfig.CENTER);
//banner設置方法全部調用完畢時最後調用
mBanner.start();
}
};
}
private BaseDelegeteAdapter getMenuAdapter() {
GridLayoutHelper gridLayoutHelper = new GridLayoutHelper(5);
gridLayoutHelper.setPadding(0, 16, 0, 0);
gridLayoutHelper.setVGap(10);
gridLayoutHelper.setHGap(0);//// 控制子元素之間的水平間距
return new BaseDelegeteAdapter(gridLayoutHelper, R.layout.vlayout_menu, 10) {
@Override
public void onBindViewHolder(@NonNull BaseViewHolder holder, final int position) {
holder.setText(R.id.tv_menu_title_home, ITEM_NAMES[position] + "");
holder.setImageResource(R.id.iv_menu_home, IMG_URLS[position]);
holder.getView(R.id.ll_menu_home).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(getApplicationContext(), ITEM_NAMES[position], Toast.LENGTH_SHORT).show();
}
});
}
};
}
private BaseDelegeteAdapter getNewsAdapter() {
return new BaseDelegeteAdapter(new LinearLayoutHelper(), R.layout.vlayout_news, 1) {
@Override
public void onBindViewHolder(@NonNull BaseViewHolder holder, int i) {
MarqueeView marqueeView1 = holder.getView(R.id.marqueeView1);
MarqueeView marqueeView2 = holder.getView(R.id.marqueeView2);
List<String> info1 = new ArrayList<>();
info1.add("天貓超市最近發大活動啦,快來搶");
info1.add("沒有最便宜,只有更便宜!");
List<String> info2 = new ArrayList<>();
info2.add("這個是用來搞笑的,不要在意這寫小細節!");
info2.add("啦啦啦啦,我就是來搞笑的!");
marqueeView1.startWithList(info1);
marqueeView2.startWithList(info2);
// 在代碼裏設置自己的動畫
marqueeView1.startWithList(info1, R.anim.anim_bottom_in, R.anim.anim_top_out);
marqueeView2.startWithList(info2, R.anim.anim_bottom_in, R.anim.anim_top_out);
marqueeView1.setOnItemClickListener(new MarqueeView.OnItemClickListener() {
@Override
public void onItemClick(int position, TextView textView) {
Toast.makeText(getApplicationContext(), textView.getText().toString(), Toast.LENGTH_SHORT).show();
}
});
marqueeView2.setOnItemClickListener(new MarqueeView.OnItemClickListener() {
@Override
public void onItemClick(int position, TextView textView) {
Toast.makeText(getApplicationContext(), textView.getText().toString(), Toast.LENGTH_SHORT).show();
}
});
}
};
}
}