【问题标题】:How can I draw a CardView shadow to a canvas in a RecyclerView ItemDecoration?如何在 RecyclerView ItemDecoration 中将 CardView 阴影绘制到画布上?
【发布时间】:2018-09-29 21:53:46
【问题描述】:

我最近在使用具有多种视图类型的 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
    }
}

【问题讨论】:

    标签: android android-recyclerview


    【解决方案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;
    }
    }
    

    对我很有帮助!!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-04-15
      • 2020-06-26
      • 1970-01-01
      • 2022-01-10
      • 2013-08-08
      • 2012-03-03
      • 2015-05-21
      • 1970-01-01
      相关资源
      最近更新 更多