Android 组件化

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

2023-02-23  本文已影响0人  jimdear

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

上一篇下一篇

猜你喜欢

热点阅读