实现每日优鲜中RecyclerView多条目背景效果

Posted 周文凯

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实现每日优鲜中RecyclerView多条目背景效果相关的知识,希望对你有一定的参考价值。

前段时间有朋友问我怎么使用DelegationAdapter实现每日优鲜的一种效果,安装每日优鲜之后发现一个比较炫酷的效果,如下图所示。

通过查看它的布局发现,整体是使用RecyclerView来实现的,头部的“平价好菜”是一个条目,下面的每种菜也是一个条目。那么“洋葱”和“油菜”后面的背景是怎么设置上去的呢?

思路

通过以上可以看到,背景不在任意一个条目上,是画在RecyclerView上面的。画在RecyclerView上的,大家想到了什么?没错就是ItemDecoration,可能有的朋友不太了解ItemDecoration,这里我简单的说下。

ItemDecoration

ItemDecoration通过字面意思就可以理解到,是条目装饰,即可以在条目底层、上层绘制一些装饰物,来看一下都提供了哪些方法。

public abstract static class ItemDecoration 

	// 在条目绘制之前绘制装饰,即在RecyclerView条目的底层展示。
    public void onDraw(Canvas c, RecyclerView parent, State state) 
    

    // 在条目绘制之后绘制装饰,即在RecyclerView条目的上层展示。
    public void onDrawOver(Canvas c, RecyclerView parent, State state) 
    

    // 重新设置条目的边距
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) 
    

是不是非常简单,仅仅提供了三个方法,重新设置条目边距,在条目底层绘制,在条目上层绘制。比如这里的,我们要重新设置蔬菜条目的边距,这样“洋葱”条目下面的内容才会被看到,否则我们在“洋葱”条目底层绘制了装饰也无法看到。

关于ItemDecoration的具体讲解,网上非常多,这里不展开来讲,不太了解的朋友可以先去查下资料。

实现

既然我们有了一种实现方案,那么就开始搞吧。俗话说,兵马未动,粮草先行。这里我们要现有接口数据以及实体Bean对象,这是根基,否则后面会寸步难行。

接口数据格式

定义接口数据,最简单的方式是什么呢?向每日优鲜致敬,我管抓别人接口、反编译别人的APP叫致敬,为什么是致敬呢?我理解是从别人那获取方案,是向别人学习,要怀有敬畏和感恩。那么,我们就抓它的接口吧。

去除标识响应状态,额外的条目数据之后,精简下接口大致这样:


  "cellList": [
    
      "bgImage": "https://j-image.missfresh.cn/mis_img_20190313192600772.png?mryxw=1125&mryxh=852",
      "cellType": 9,
      "secondBanner": 
        "path": "https://j-image.missfresh.cn/mis_img_20190313192555728.png?mryxw=1125&mryxh=270"
      
    ,
    
      "cellType": 7,
      "normalProduct": 
        "cartImage": "https://j-image.missfresh.cn/img_20170425134548759.png",
        "image": "https://fms-image.missfresh.cn/3bfd39e84df242d8a24e405be3f16705.jpg",
        "name": "樱桃小番茄500g*1盒",
        "subtitle": "点亮沙拉的红色甜心",
        "price": 490
      
    ,
    
      "cellType": 7,
      "normalProduct": 
        "cartImage": "https://j-image.missfresh.cn/img_20170425134548759.png",
        "image": "https://image.missfresh.cn/6ca7c143db584168860cf7bd0a758fed.jpg",
        "name": "平价胡萝卜500g",
        "subtitle": "兔仙女都爱胡萝北",
        "price": 250
      
    
  ]

通过观察接口我们发现,有个cellType字段用于标识条目类型,如果cellType=9就是上面的整条样式,如果cellType=7就是下面的蔬菜条目样式。既然有了数据,那么实体Bean就很简单了。

public class Products 

    public List<CellItem> cellList;

    public static class CellItem 
        public int cellType;
        public SecondBanner secondBanner;
        public String bgImage;
        public NormalProduct normalProduct;
    

    public static class SecondBanner 
        public String path;
    

    public static class NormalProduct 
        public String image;
        public String name;
        public String subtitle;
        public int price;
        public String cartImage;
    

RecyclerView架子

activity_main布局

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#f8f8f8"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

Adapter编写

通过上面的实体Bean定义,我们知道这里有两种类型的条目,并且是通过cellType进行区分的,我们可以通过getItemViewType返回不同的类型进行区分。

public class ProductsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> 

    private List<Products.CellItem> dataItems = new ArrayList<>();

    public void setProducts(List<Products.CellItem> dataItems) 
        this.dataItems = dataItems;
        notifyDataSetChanged();
    

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) 
        return null;
    

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) 

    

    @Override
    public int getItemViewType(int position) 
        return dataItems.get(position).cellType;
    

    @Override
    public int getItemCount() 
        return dataItems.size();
    

接下来就是onCreateViewHolder,在不同的ViewType的时候需要有不同的ViewHolder
再把这个图拿过来,顶部的“评价好菜”比较简单,就一张图片。

layout_second_banner_item布局

<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/iv_image"
    android:layout_width="match_parent"
    android:layout_height="95dp"
    android:scaleType="fitXY" />

SecondBannerViewHolder代码

public class SecondBannerViewHolder extends RecyclerView.ViewHolder 

    ImageView image;

    public SecondBannerViewHolder(View itemView) 
        super(itemView);
        image = itemView.findViewById(R.id.iv_image);
    

layout_normal_product_item布局,也不是特别复杂。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="257dp"
    android:background="@drawable/shape_normal_product_bg">

    <ImageView
        android:id="@+id/iv_image"
        android:layout_width="138dp"
        android:layout_height="138dp"
        android:layout_marginTop="18dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_name"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="9dp"
        android:layout_marginRight="10dp"
        android:ellipsize="end"
        android:includeFontPadding="false"
        android:maxLines="1"
        android:textColor="#ff474245"
        android:textSize="14sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/iv_image" />

    <TextView
        android:id="@+id/tv_sub_title"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="3dp"
        android:layout_marginRight="10dp"
        android:ellipsize="end"
        android:includeFontPadding="false"
        android:maxLines="1"
        android:textColor="#ff969696"
        android:textSize="12.0sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tv_name" />

    <TextView
        android:id="@+id/tv_price"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="8dp"
        android:ellipsize="start"
        android:includeFontPadding="false"
        android:singleLine="true"
        android:textColor="#f44089"
        android:textSize="16sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv_sub_title" />

    <ImageView
        android:id="@+id/iv_cart"
        android:layout_width="49dp"
        android:layout_height="49dp"
        android:layout_marginTop="6dp"
        android:layout_marginRight="10dp"
        android:scaleType="fitXY"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv_sub_title" />

</android.support.constraint.ConstraintLayout>

NormalProductViewHolder代码

public class NormalProductViewHolder extends RecyclerView.ViewHolder 

    ImageView image;
    TextView name;
    TextView subTitle;
    TextView price;
    ImageView cart;

    public NormalProductViewHolder(View itemView) 
        super(itemView);
        image = itemView.findViewById(R.id.iv_image);
        name = itemView.findViewById(R.id.tv_name);
        subTitle = itemView.findViewById(R.id.tv_sub_title);
        price = itemView.findViewById(R.id.tv_price);
        cart = itemView.findViewById(R.id.iv_cart);
    

OK,这两种类型的ViewHolder就算完成了,剩下的只要在onCreateViewHolder中调用就行了。

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) 
    if (viewType == 9) 
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_second_banner_item, parent, false);
        return new SecondBannerViewHolder(view);
     else if (viewType == 7) 
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_normal_product_item, parent, false);
        return new NormalProductViewHolder(view);
     else 
        // 不可能到达
        return null;
     

剩下的就是绑定数据onBindViewHolder,两种对应的类型绑定对应的数据。

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) 
    Context context = holder.itemView.getContext();
    Products.CellItem item = dataItems.get(position);
    if (holder instanceof SecondBannerViewHolder) 
        Glide.with(context)
                .load(item.secondBanner.path)
                .into(((SecondBannerViewHolder) holder).image);
     else if (holder instanceof NormalProductViewHolder) 
        NormalProductViewHolder normalProductHolder = (NormalProductViewHolder) holder;
        Glide.with(context)
                .load(item.normalProduct.image)
                .into(normalProductHolder.image);
        normalProductHolder.name.setText(item.normalProduct.name);
        normalProductHolder.subTitle.setText(item.normalProduct.subtitle);
        normalProductHolder.price.setText("¥" + ((float) item.normalProduct.price / 100));
        Glide.with(context)
                .load(item.normalProduct.cartImage)
                .into(normalProductHolder.cart);
    

在MainActivity中初始化RecyclerView以及数据

public class MainActivity extends AppCompatActivity 

    private RecyclerView recyclerView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        recyclerView = findViewById(R.id.recycler_view);

        GridLayoutManager layoutManager = new GridLayoutManager(this, 2);
        recyclerView.setLayoutManager(layoutManager);
        ProductsAdapter adapter = new ProductsAdapter();
        recyclerView.setAdapter(adapter);

        String productListStr = LocalFileUtils.getStringFormAsset(this, "products.json");
        Products products = new Gson().fromJson(productListStr, Products.class);
        adapter.setProducts(products.cellList);
    

满怀激动的运行了一把,咦,这是什么玩意儿。是我们没有设置cellType=9是进行通栏显示。

设置cellType=9通栏显示

layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() 
    @Override
    public int getSpanSize(int position) 
        return adapter.getItemViewType(position) == 9 ? 2 : 1;
    
);


这样是不是有点那么回事了,这样我们的架子算是搭好了。

条目间距设置

通过我们上面搭的架子,发现是没有条目的分割线的,再把我们要实现的效果拿过来,通过肉眼查看,感觉蔬菜条目距离左右边距10dp,条目之间距离6dp。

BackgroundItemDecoration条目装饰,设置蔬菜条目距离左右边距10dp,条目之间距离6dp,下方6dp。

public class BackgroundItemDecoration extends RecyclerView.ItemDecoration 

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) 
        Context context = view.getContext();
        if (!isTargetItem(view, parent)) 
            super.getItemOffsets(outRect, view, parent, state);
         else if (isLeftItem(view, parent)) 
            outRect.set(dp2px(context, 10), 0, dp2px(context, 3), dp2px(context, 6));
         else 
            outRect.set(dp2px(context, 3), 以上是关于实现每日优鲜中RecyclerView多条目背景效果的主要内容,如果未能解决你的问题,请参考以下文章

每日优鲜:AI 技术驱动下的社区新零售

每日优鲜小程序订制开发

程序员面试每日优鲜被恶心到,网友:和阿里比?小公司戏真够多的

每日优鲜深陷“破产风波”,生鲜电商路在何方?

每日优鲜Q2财报:净收入同比强劲增长41%,单季度收入创新高

腾讯云腾讯优图携手每日优鲜便利购打造无人零售行业金三角