Android 如何在RecyclerView项目装饰中在画布上绘制CardView阴影?
我最近在使用具有多种视图类型的RecyclerView和PagedListAdapter时遇到了各种问题。不同视图类型的原因本质上是为了添加节标题。切换到使用物品装饰似乎更稳定和更好的模式 所以我想我应该对另一个RecyclerView做同样的事情,尝试消除多个视图类型,这样RecyclerView中的每一行都对应于基础PagedList中的一个项目。问题是,这次它不是一个简单的节头文本视图。这是一张卡片 我在获得正确的宽度时遇到了一点困难(这个CardView是用来匹配父母的)。我想我知道了,但我还有一个问题。CardView正在绘制,但没有背景阴影。我从StackOverflow的问题中看到,其他人也有同样的问题。似乎没有使用常规布局/测量/绘制功能绘制立面阴影 如何在项目装饰中获得我的CardView阴影?有办法吗 这就是我目前拥有的:Android 如何在RecyclerView项目装饰中在画布上绘制CardView阴影?,android,android-recyclerview,Android,Android Recyclerview,我最近在使用具有多种视图类型的RecyclerView和PagedListAdapter时遇到了各种问题。不同视图类型的原因本质上是为了添加节标题。切换到使用物品装饰似乎更稳定和更好的模式 所以我想我应该对另一个RecyclerView做同样的事情,尝试消除多个视图类型,这样RecyclerView中的每一行都对应于基础PagedList中的一个项目。问题是,这次它不是一个简单的节头文本视图。这是一张卡片 我在获得正确的宽度时遇到了一点困难(这个CardView是用来匹配父母的)。我想我知道了,
class CardItemDecoration(val adapter: ReservationAdapter) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
super.getItemOffsets(outRect, view, parent, state)
val position = parent.getChildAdapterPosition(view)
if (adapter.hasCard && position == 0) {
outRect.top = getcardView(parent.context, parent).measuredHeight
}
}
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDraw(c, parent, state)
val left = parent.paddingLeft
val right = parent.width - parent.paddingRight
if (adapter.hascard && parent.childCount > 0) {
val child = parent.getChildAt(0)
val layout = getCardView(parent.context, parent)
// Draw in the space made by getItemOffsets()
layout.layout(left, 0, right, layout.measuredHeight)
c.save()
// Adjust Y coordinates, as they'll be different for each row
val top = child.top - layout.measuredHeight
c.translate(0f, top.toFloat())
layout.draw(c)
c.restore()
}
}
private lateinit var cardView: ViewGroup
private fun getCardView(context: Context, parent: RecyclerView): View {
if (!::cardView.isInitialized) {
cardView = LinearLayout(context)
LayoutInflater.from(context).inflate(R.layout.call_out_bis_profile, cardView, true)
cardView.apply {
findViewById<TextView>(R.id.infoTextView).text = context.getString(R.string.card_description)
}
val width = parent.width - parent.paddingLeft - parent.paddingRight
cardView.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED))
}
return cardView
}
}
类CardItemDecoration(val适配器:ReservationAdapter):RecyclerView.ItemDecoration(){
覆盖有趣的GetItemOffset(outRect:Rect,view:view,父对象:RecyclerView,状态:RecyclerView.state){
super.getItemOffset(outRect、view、parent、state)
val position=parent.getChildAdapterPosition(视图)
if(adapter.hasCard&&position==0){
outRect.top=getcardView(parent.context,parent).measuredHeight
}
}
重写onDraw(c:Canvas,父对象:RecyclerView,状态:RecyclerView.state){
super.onDraw(c、父级、状态)
val left=parent.paddingLeft
val right=parent.width-parent.paddingRight
if(adapter.hascard&&parent.childCount>0){
val child=parent.getChildAt(0)
val layout=getCardView(parent.context,parent)
//在getItemOffsets()生成的空间中绘制
布局。布局(左、0、右、布局。测量高度)
c、 保存()
//调整Y坐标,因为每行的坐标都不同
val top=child.top-layout.measuredHeight
c、 转换(0f,top.toFloat())
布局图(c)
c、 还原()
}
}
私有lateinit变量cardView:ViewGroup
private fun getCardView(上下文:上下文,父:RecyclerView):视图{
如果(!::cardView.i初始化){
cardView=LinearLayout(上下文)
LayoutFlater.from(上下文)。充气(R.layout.call_out_bis_profile,cardView,true)
cardwiew.apply{
findviewbyd(R.id.infoTextView).text=context.getString(R.string.card\u说明)
}
val width=parent.width-parent.paddingLeft-parent.paddingRight
cardView.measure(视图.MeasureSpec.makeMeasureSpec(宽度,视图.MeasureSpec.精确),
View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED))
}
返回卡视图
}
}
这是一个卡视图装饰示例。这是我的参考:
Paint;
静态RoundRectHelper sRoundRectHelper;
油漆mCornerShadowPaint;
油漆,油漆,油漆;
最终RectF mPreShadowBounds;
漂浮麦考内拉迪乌斯;
路径mCornerShadowPath;
浮动mShadowSize;
私有布尔mDirty=true;
私人最终int mShadowStartColor;
私有最终int mShadowEndColor;
私人浮动支付;
公共CardViewDecoration(资源、int背景色、浮动半径){
mShadowStartColor=resources.getColor(R.color.cardwiew\u shadow\u start\u color);
mShadowEndColor=resources.getColor(R.color.cardwiew\u shadow\u end\u color);
mShadowSize=resources.getDimension(R.dimen.cardview\u shadow\u size)*shadow\u乘数;
mPaint=新油漆(油漆.防伪别名标志|油漆.抖动标志);
mPaint.setColor(背景色);
mCornerShadowPaint=新油漆(油漆.防伪别名标志|油漆.抖动标志);
mCornerShadowPaint.setStyle(Paint.Style.FILL);
mCornerShadowPaint.setDither(真);
mCornerRadius=半径;
mPreShadowBounds=new RectF();
mEdgeShadowPaint=新油漆(mCornerShadowPaint);
构建阴影角();
}
@凌驾
公共void onDrawOver(画布c、RecyclerView父级、RecyclerView.State){
Rect bounds=new Rect();
浮动edgeShadowTop=-mCornerRadius-mShadowSize;
RecycleView.LayoutManager lm=parent.getLayoutManager();
浮子尺寸16DP=16f;
int padding16dp=(int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,size16dp,parent.getContext().getResources().getDisplayMetrics());
对于(int i=0;iPaint mPaint;
static RoundRectHelper sRoundRectHelper;
Paint mCornerShadowPaint;
Paint mEdgeShadowPaint;
final RectF mPreShadowBounds;
float mCornerRadius;
Path mCornerShadowPath;
float mShadowSize;
private boolean mDirty = true;
private final int mShadowStartColor;
private final int mShadowEndColor;
private float mPadding;
public CardViewDecoration(Resources resources, int backgroundColor, float radius) {
mShadowStartColor = resources.getColor(R.color.cardview_shadow_start_color);
mShadowEndColor = resources.getColor(R.color.cardview_shadow_end_color);
mShadowSize = resources.getDimension(R.dimen.cardview_shadow_size) * SHADOW_MULTIPLIER;
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
mPaint.setColor(backgroundColor);
mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
mCornerShadowPaint.setStyle(Paint.Style.FILL);
mCornerShadowPaint.setDither(true);
mCornerRadius = radius;
mPreShadowBounds = new RectF();
mEdgeShadowPaint = new Paint(mCornerShadowPaint);
buildShadowCorners();
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
Rect bounds = new Rect();
float edgeShadowTop = -mCornerRadius - mShadowSize;
RecyclerView.LayoutManager lm = parent.getLayoutManager();
float size16dp = 16f;
int padding16dp = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size16dp, parent.getContext().getResources().getDisplayMetrics());
for (int i = 0; i < parent.getChildCount(); i++) {
int save = c.save();
// using decorated values, remove what we set before
View child = parent.getChildAt(i);
bounds.set(lm.getDecoratedLeft(child) + padding16dp - (int) mPadding,
lm.getDecoratedTop(child),
lm.getDecoratedRight(child) - padding16dp + (int) mPadding,
lm.getDecoratedBottom(child));
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int position = params.getViewAdapterPosition();
int viewType = parent.getAdapter().getItemViewType(position);
if (viewType == HeaderItemTestAdapter.HEADER) {
bounds.top = (int) (bounds.top + padding16dp - mPadding);
// LT
c.translate(bounds.left + mCornerRadius, bounds.top + mCornerRadius);
c.drawPath(mCornerShadowPath, mCornerShadowPaint);
c.drawRect(0, edgeShadowTop, bounds.width() - 2 * mCornerRadius, -mCornerRadius, mEdgeShadowPaint);
// RT
c.rotate(90f);
c.translate(0, -bounds.width() + 2 * mCornerRadius);
c.drawPath(mCornerShadowPath, mCornerShadowPaint);
c.drawRect(0, edgeShadowTop, bounds.height() - mCornerRadius, -mCornerRadius, mEdgeShadowPaint);
// LBorder
c.rotate(180f);
c.translate(-bounds.height(), -bounds.width() + 2 * mCornerRadius);
c.drawRect(mCornerRadius, edgeShadowTop, bounds.height(), -mCornerRadius, mEdgeShadowPaint);
} else {
if (parent.getAdapter().getItemViewType(position + 1) == HeaderItemTestAdapter.HEADER) {
bounds.bottom = (int) (bounds.bottom - padding16dp + mPadding);
// last item before next header
c.rotate(180f);
c.translate(-bounds.left - bounds.width() + mCornerRadius, -bounds.top - bounds.height() + mCornerRadius);
c.drawPath(mCornerShadowPath, mCornerShadowPaint);
c.drawRect(0, edgeShadowTop, bounds.width() - 2 * mCornerRadius, -mCornerRadius, mEdgeShadowPaint);
// RT / Right border
c.rotate(90f);
c.translate(0, -bounds.width() + 2 * mCornerRadius);
c.drawPath(mCornerShadowPath, mCornerShadowPaint);
c.drawRect(0, edgeShadowTop, bounds.height() - mCornerRadius, -mCornerRadius, mEdgeShadowPaint);
// Left border
c.rotate(180f);
c.translate(-bounds.height(), -bounds.width() + 2 * mCornerRadius);
c.drawRect(mCornerRadius, edgeShadowTop, bounds.height(), -mCornerRadius, mEdgeShadowPaint);
} else {
// Right border
c.translate(bounds.left, bounds.top);
c.rotate(90f);
c.translate(0, -bounds.width() + mCornerRadius);
c.drawRect(0, edgeShadowTop, bounds.height(), -mCornerRadius, mEdgeShadowPaint);
// Left border
c.rotate(180f);
c.translate(-bounds.height(), -bounds.width() + 2 * mCornerRadius);
c.drawRect(0, edgeShadowTop, bounds.height(), -mCornerRadius, mEdgeShadowPaint);
}
}
c.restoreToCount(save);
}
}
private void buildShadowCorners() {
mPadding = 0f;
RectF innerBounds = new RectF(-mCornerRadius, -mCornerRadius, mCornerRadius, mCornerRadius);
RectF outerBounds = new RectF(innerBounds);
outerBounds.inset(-mShadowSize, -mShadowSize);
if (mCornerShadowPath == null) {
mCornerShadowPath = new Path();
} else {
mCornerShadowPath.reset();
}
mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD);
mCornerShadowPath.moveTo(-mCornerRadius, 0);
mCornerShadowPath.rLineTo(-mShadowSize, 0);
// outer arc
mCornerShadowPath.arcTo(outerBounds, 180f, 90f, false);
// inner arc
mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false);
mCornerShadowPath.close();
float startRatio = mCornerRadius / (mCornerRadius + mShadowSize);
mCornerShadowPaint.setShader(new RadialGradient(0, 0, mCornerRadius + mShadowSize, new int[]{
mShadowStartColor, mShadowStartColor, mShadowEndColor}, new float[]{0f, startRatio, 1f},
Shader.TileMode.CLAMP));
// we offset the content shadowSize/2 pixels up to make it more realistic.
// this is why edge shadow shader has some extra space
// When drawing bottom edge shadow, we use that extra space.
mEdgeShadowPaint.setShader(new LinearGradient(0, -mCornerRadius + mShadowSize, 0, -mCornerRadius - mShadowSize,
new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor}, new float[]{0f, .5f, 1f},
Shader.TileMode.CLAMP));
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
Resources resources = parent.getContext().getResources();
float size16dp = 16f;
int padding16dp = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size16dp, resources.getDisplayMetrics());
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
int position = params.getViewAdapterPosition();
int viewType = parent.getAdapter().getItemViewType(position);
if (viewType == HeaderItemTestAdapter.HEADER) {
// header
outRect.set(0, (int) (padding16dp), 0, 0);
} else {
if (parent.getAdapter().getItemViewType(position + 1) == HeaderItemTestAdapter.HEADER) {
// last item before next header
outRect.set(0, 0, 0, (int) (padding16dp));
}
}
outRect.left = (int) padding16dp;
outRect.right = (int) padding16dp;
}
}