代码之家  ›  专栏  ›  技术社区  ›  Chad Schultz

如何在RecyclerView项装饰的画布上绘制CardView阴影?

  •  0
  • Chad Schultz  · 技术社区  · 6 年前

    所以我想我应该对另一个RecyclerView做同样的事情,尝试消除多个视图类型,这样RecyclerView中的每一行都对应于基础PagedList中的一个项。问题是,这次它不是一个简单的节头文本视图。这是一张卡片。

    我有点麻烦得到正确的宽度(这个cardwiew是为了匹配父母)。我想我知道了,但我还有一个问题。CardView正在绘制,但没有背景阴影。我从这些问题中看到 Why is my cardview not being drawn onto canvas? 其他人也有同样的问题。似乎没有使用常规布局/测量/绘制功能绘制立面阴影。

    这是我目前的情况:

    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 回复  |  直到 6 年前
        1
  •  1
  •   Annie Aye Myat    6 年前

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

    它真的帮助了我!!