仿盒马生鲜首页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>