如何在RecyclerView ItemDecoration中将CardView阴影绘制到画布上?

时间:2018-09-29 21:53:47

标签: android android-recyclerview

最近,我使用具有多种视图类型的RecyclerView和PagedListAdapter遇到了各种各样的问题。使用不同视图类型的原因实质上是添加节标题。切换到使用ItemDecoration似乎更稳定,而且模式也更好。

所以我想我将对另一个RecyclerView做同样的事情,试图消除多种视图类型,因此RecyclerView中的每一行都对应于基础PagedList中的一项。问题是,这不是一个简单的节标题TextView。这是CardView。

正确设置宽度有些麻烦(此CardView应该是MATCH_PARENT)。我想我已经知道了,但是还有另一个问题。 CardView正在绘制,但没有背景阴影。我从Why is my cardview not being drawn onto canvas?之类的StackOverflow问题中看到,其他人也有同样的问题。似乎没有使用常规的布局/测量/绘制功能绘制高程阴影。

如何在ItemDecoration中获得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
    }
}

1 个答案:

答案 0 :(得分:1)

这是卡片视图装饰示例。这是我的参考: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;
}
}

这真的对我有帮助!