先上效果图
淘宝首页是从上到下是各种不同的样式,最上面是搜索,其次是一个轮播图,再下来是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();
}
});
}
};
}
}