实现每日优鲜中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), 0, dp2px(context, 10), dp2px(context, 6));
        }
    }

    /**
     * 判断是否是蔬菜条目
     *
     * @param view
     * @param parent
     * @return
     */
    private boolean isTargetItem(View view, RecyclerView parent) {
        boolean target = false;
        RecyclerView.Adapter adapter = parent.getAdapter();
        if (adapter != null && adapter.getItemViewType(parent.getChildAdapterPosition(view)) == 7) {
            target = true;
        }
        return target;
    }

    private boolean isLeftItem(View view, RecyclerView parent) {
        boolean isLeft;
        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {
            int spanIndex = ((GridLayoutManager) layoutManager).getSpanSizeLookup()
                    .getSpanIndex(parent.getChildAdapterPosition(view),
                            ((GridLayoutManager) layoutManager).getSpanCount());
            isLeft = spanIndex == 0;
        } else {
            isLeft = true;
        }
        return isLeft;
    }

    private int dp2px(Context context, float dipValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }
}

来看下效果,间距有啦,剩下的就是悲剧的绘制了。
在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值