仿盒马生鲜首页Banner效果方案

最近产品看到盒马生鲜首页Banner效果被惊艳了,于是突发奇想,想把APP首页也搞成这样,但是回头瞄了一下代码.....emu,RecyleView 的各种子控件逻辑太多了,总不能把几个item组合起来,然后加载背景吧。于是就调研了一下,以下是解决方案:

1.假如你的首页没有数据驱动,只是展示, adapter.notifyDataSetChanged();也没啥影响的话,可以考虑采用增加一个ItemDecoration ,然后onDraw 里面,绘制下载好的一个背景下来,添加到recyview里。代码如下,其中有部分代码是借鉴了:

package com.jimdear.shopcartmodule;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.jimdear.shopcart_module.R;

/**
 * Created by huihui on 2023/2/22
 *
 * Comment:
 */
public class ScrollBgRvItemDecoration extends RecyclerView.ItemDecoration {
    private static final String TAG = "ScrollBgRvItemDecoration";
    private Bitmap mBmp;
    private final Paint mBmpPaint;
    private final Paint mColorPaint;
    private final Rect srcRect;
    private final Rect desRect;
    private int bmpHeight;
    private int bmWidth;
    SparseArray<Integer> sparseArray;
    private final int mDefaultBmpRes; //默认背景图片的resId
    private boolean mBmpRepeat;  //是否重复画背景图
    private int mShowBmpHeight;  //图片渲染的高度, 当bmpRepeat=false,才生效
//    private final boolean isDefaultBm = false;
    protected Context mContext;
    private Integer lastScrollY;
//    private Integer nowScrollY;


    /**
     * RV的滚动背景
     * 支持2种样式:
     * 1、图片平铺(defaultBmpRes, bmpRepeat为true)
     * 2、图片(defaultBmpRes)渲染一次,其他区域用颜色(defaultColorRes)渲染 ==> bmpRepeat为false, 若showBmpHeight < BmpHeight,则裁剪底部图片; 若showBmpHeight >= BmpHeight,则只展示BmpHeight高度
     *
     * @param defaultBmpRes   默认背景图片的resId
     * @param defaultColorRes 默认背景色的resId
     * @param bmpRepeat       是否重复画背景图
     * @param showBmpHeight   图片渲染的高度, 当bmpRepeat=false,才生效
     */
    public ScrollBgRvItemDecoration(Context context, int defaultBmpRes, int defaultColorRes, boolean bmpRepeat, int showBmpHeight) {
        this.mContext = context;
        this.mDefaultBmpRes = defaultBmpRes;
        //默认背景色的resId
        int mDefaultColorRes = defaultColorRes == 0 ? R.color.white : defaultColorRes;
        this.mBmpRepeat = bmpRepeat;
        this.mShowBmpHeight = showBmpHeight;
        restoreDefaultBm();
        mBmpPaint = new Paint();
        mBmpPaint.setAntiAlias(true);
        srcRect = new Rect();
        desRect = new Rect();
        sparseArray = new SparseArray<>();
        mColorPaint = new Paint();
        mColorPaint.setColor(context.getResources().getColor(mDefaultColorRes));
        mColorPaint.setAntiAlias(true);
        mColorPaint.setStyle(Paint.Style.FILL);
    }

    private Bitmap getBitmap(int vectorDrawableId) {
        Log.i("zql1", "getBitmap");
        try {
            Bitmap bitmap;
            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
                Drawable vectorDrawable = mContext.getDrawable(vectorDrawableId);
                bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(),
                        vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
                Log.i("zql1", "bitmap-width:" + vectorDrawable.getIntrinsicWidth() + "; height:" + vectorDrawable.getIntrinsicHeight() + ";size:" + bitmap.getByteCount());
                Canvas canvas = new Canvas(bitmap);
                vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
                Log.i("zql1", "canvas-width:" + canvas.getWidth() + "; height:" + canvas.getHeight());
                vectorDrawable.draw(canvas);
            } else {
                bitmap = BitmapFactory.decodeResource(mContext.getResources(), vectorDrawableId);
            }
            return bitmap;
        } catch (Exception ex) {
            return null;
        }
    }

    public void setBackground(Bitmap bmp, int colorResId, boolean isBmpRepeat, int showBmpHeight) {
        if (this.mBmp != null) {
            this.mBmp.recycle();
            this.mBmp = null;
        }

        this.mBmp = bmp;
        this.mBmpRepeat = isBmpRepeat;
        this.mShowBmpHeight = showBmpHeight;
        if (mBmp != null) {
            bmpHeight = bmp.getHeight();
            bmWidth = bmp.getWidth();
        }
        if (colorResId != 0) {
            mColorPaint.setColor(mContext.getResources().getColor(colorResId));
        }
        clearMap();
    }


    private synchronized void clearMap() {
        sparseArray.clear();
    }

    private void restoreDefaultBm() {
        if (this.mBmp != null) {
            this.mBmp.recycle();
            this.mBmp = null;
        }

        if (mDefaultBmpRes == 0) {
            return;
        }
        this.mBmp = getBitmap(mDefaultBmpRes);

        if (this.mBmp == null) {
            return;
        }

        bmpHeight = mBmp.getHeight();
        bmWidth = mBmp.getWidth();
    }

    @Override
    public void onDraw(@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.onDraw(canvas, parent, state);
        if (this.mBmp == null || mShowBmpHeight == 0) {
            drawOnlyColor(canvas, parent, state);
            return;
        }

        int childCount = parent.getChildCount();
        Log.i("zql", "onDraw---childCount:" + childCount);
        Integer firstTop;
        if (childCount == 0) {
            firstTop = 0;
        } else {
            View firstView = parent.getChildAt(0);
            int position = parent.getChildAdapterPosition(firstView);
            if (sparseArray.size() == 0) {
                firstTop = 0;
                sparseArray.put(position, 0);
            } else {
                firstTop = sparseArray.get(position);
            }
            if (firstTop != null) {
                Integer preScrollY = firstTop;
                for (int index = 1, nowPos = position + 1; index < childCount; index++, nowPos++) {
                    Integer nowScroll = sparseArray.get(nowPos);
                    if (nowScroll == null) {
                        View preView = parent.getChildAt(index - 1);
                        if (preView == null) {
                            break;
                        }
                        nowScroll = preScrollY + preView.getHeight();
                        sparseArray.put(nowPos, nowScroll);
                    }
                    preScrollY = nowScroll;
                }
            } else {
                int lastIndex = childCount - 1;
                int lastPos = position + lastIndex;
                lastScrollY = sparseArray.get(lastPos);
                for (int index = lastIndex - 1, nowPos = lastPos - 1; index >= 0; index--, nowPos--) {
                    Integer nowScrollY = sparseArray.get(nowPos);
                    if (nowScrollY == null) {
                        if (lastScrollY != null) {
                            Log.i("index", "----" + index);
                            View nowView = parent.getChildAt(index);
                            if (nowView == null) {
                                break;
                            }
                            nowScrollY = lastScrollY - nowView.getHeight();
                            sparseArray.put(nowPos, nowScrollY);
                        }
                    }
                    lastScrollY = nowScrollY;
                }
                firstTop = sparseArray.get(position);
            }


            if (firstTop == null) {
                firstTop = 0;
            } else {
                firstTop -= firstView.getTop();
            }
        }
        Log.i("zql", "firstTop:" + firstTop + "lastScrollY: " + lastScrollY);


        int totalHeight = parent.getHeight();
        int totalWidth = parent.getWidth();

        float screenRate = (float) totalHeight / totalWidth;
        float widthRate = (float) totalWidth / bmWidth;

        int bmShowHeightNoRepeat = Math.round(bmWidth * mShowBmpHeight / totalWidth);
        int bmShowTotalHeight = Math.round(bmWidth * screenRate);
        int bmStart = Math.round(firstTop / widthRate);
        int bmTotalEnd = bmStart + bmShowTotalHeight;

        int nowStart = bmStart;
        int nowPage = floorDiv(nowStart, bmpHeight);
        int lastPage = floorDiv(bmTotalEnd, bmpHeight);

        int srcStart;
        int srcEnd;
        int desStart = 0;
        int desEnd;
        Log.d("zql", "totalHeight/totalWidth:" + totalHeight + "/" + totalWidth + ";screenRate/widthRate:" + screenRate + "/" + widthRate + ";bmWidth:" + bmWidth + "; bmHeight:" + bmpHeight);
        Log.d("zql", "bmShowTotalHeight:" + bmShowTotalHeight + ";bmStart:" + bmStart + ";bmTotalEnd:" + bmTotalEnd + "; nowStart:" + nowStart + "; nowPage:" + nowPage + "; lastPage:" + lastPage);
        Log.d("zql", "nowPage:" + nowPage + ";lastPage:" + lastPage);
        while (nowPage <= lastPage) {
            int pageEndHeight = (nowPage + 1) * bmpHeight;
            Log.d("zql", "nowPage:" + nowPage + "; bmTotalEnd:" + bmTotalEnd + ";pageEndHeight:" + pageEndHeight);
            if (bmTotalEnd < pageEndHeight) {//图片未超出屏幕
                srcStart = floorMod(nowStart, bmpHeight);
                srcEnd = floorMod(bmTotalEnd, bmpHeight);
                desEnd = totalHeight;
                nowStart = bmTotalEnd;
            } else if (bmTotalEnd == pageEndHeight) {
                srcStart = floorMod(nowStart, bmpHeight);
                srcEnd = bmpHeight;
                desEnd = totalHeight;
                nowStart = bmTotalEnd;
            } else {
                srcStart = floorMod(nowStart, bmpHeight);
                srcEnd = bmpHeight;
                desEnd = desStart + (int) ((srcEnd - srcStart) * widthRate);
                nowStart = pageEndHeight;
            }

            srcRect.left = 0;
            srcRect.top = srcStart;
            srcRect.right = bmWidth;
            srcRect.bottom = srcEnd;

            desRect.left = 0;
            desRect.top = desStart;
            desRect.right = totalWidth;
            desRect.bottom = desEnd;
            Log.d("zql", "nowPage:" + nowPage + ";srcRect:[0," + srcStart + "," + bmWidth + "," + srcEnd + "], desRect:[0," + desStart + "," + totalWidth + "," + desEnd + "]");

            if (mBmpRepeat) {
                //图片循环渲染
                canvas.drawBitmap(mBmp, srcRect, desRect, mBmpPaint);
            } else {
                //图片只渲染一次
                if (bmpHeight <= bmShowHeightNoRepeat) {
                    if (nowPage == 0) {
                        canvas.drawBitmap(mBmp, srcRect, desRect, mBmpPaint);
                    } else {
                        canvas.drawRect(0, desStart, totalWidth, totalHeight, mColorPaint);
                    }
                } else {
                    if (nowPage == 0) {
                        canvas.drawBitmap(mBmp, srcRect, desRect, mBmpPaint);
                    } else {
                        canvas.drawRect(0, desStart, totalWidth, totalHeight, mColorPaint);
                    }
                }
            }

            desStart = desEnd;
            nowPage++;
        }

    }

    private void drawOnlyColor(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
        canvas.drawRect(0, 0, parent.getWidth(), parent.getHeight(), mColorPaint);
    }

    private int floorDiv(int nowStart, int bmpHeight) {
        return (int) Math.floor((double) nowStart / bmpHeight);
    }

    public int floorMod(int x, int y) {
        int r = x - floorDiv(x, y) * y;
        return r;
    }

}


2.假如,你首页逻辑特别多,各种控件相互交互,又不想影响,那只能牺牲一下了,考虑用Scrollview嵌套Recyleview的方法,布局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/root_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <androidx.core.widget.NestedScrollView
        android:id="@+id/scrollView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <ImageView
                android:id="@+id/iv"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:adjustViewBounds="true"
                android:scaleType="fitXY" />

            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/recyclerview"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hapticFeedbackEnabled="true" />
        </FrameLayout>
    </androidx.core.widget.NestedScrollView>
</LinearLayout>

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,192评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,858评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,517评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,148评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,162评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,905评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,537评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,439评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,956评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,083评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,218评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,899评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,565评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,093评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,201评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,539评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,215评论 2 358

推荐阅读更多精彩内容