美文网首页Android 组件化
仿盒马生鲜首页Banner效果方案

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

作者: jimdear | 来源:发表于2023-02-23 11:48 被阅读0次

    最近产品看到盒马生鲜首页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>
    
    

    相关文章

      网友评论

        本文标题:仿盒马生鲜首页Banner效果方案

        本文链接:https://www.haomeiwen.com/subject/kugzkdtx.html