Reputation: 7860
I've recently been running into varied issues using a RecyclerView and PagedListAdapter with multiple view types. The reason for different view types was essentially to add section headers. Switching to using ItemDecoration seemed much more stable and a better pattern.
So I thought I'd do the same thing with another RecyclerView, trying to eliminate multiple view types so each row in the RecyclerView corresponded to an item in the underlying PagedList. The problem is, this time it isn't a simple section header TextView. It's a CardView.
I had a bit of trouble getting the width right (this CardView is meant to be MATCH_PARENT). I think I figured that out, but I have another problem. The CardView is drawing, but without the background shadow. I see from StackOverflow questions like Why is my cardview not being drawn onto canvas? that other people have the same problem. It seems elevation shadows are not drawn with regular layout/measure/draw functionality.
How can I get my CardView shadow in the ItemDecoration? Is there a way?
This is what I have currently:
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
}
}
Upvotes: 1
Views: 1285
Reputation: 247
This is card view decoration example. This is my reference: https://github.com/bleeding182/recyclerviewItemDecorations
Paint 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;
}
}
It really helps me!!
Upvotes: 1