Android开发经验谈Android开发半栈工程师

可设置阴影颜色的 CardView

2018-10-10  本文已影响96人  WangJie0822
image

需求由来

最近项目中来了新的需求,一大堆卡片式布局,还有不同的阴影颜色,甚至不同的状态下颜色还不一样,UI 给的切图各种错位,而 Google 的 CardView 是无法设置阴影颜色的,我能怎么办,我也很绝望啊(:з」∠)
没办法,百度了一堆都没有找到解决方案,最后借鉴了各位大神的思路,才有了这篇文章。

实现思路

怎么实现?当然是参照 Google 的 CardView 啦,毕竟是亲生的,各种实现优化都是非常棒的,然后就是在 CardView 的基础上,给他添加上设置阴影颜色的功能,很简单的是吧。

开始干活啦

    <color name="cardview_shadow_end_color">#03000000</color>
    <color name="cardview_shadow_start_color">#37000000</color>
    public void setCardElevation(float elevation) {
        IMPL.setElevation(mCardViewDelegate, elevation);
    }
    // CardViewBaseImpl
    @Override
    public void setElevation(CardViewDelegate cardView, float elevation) {
        getShadowBackground(cardView).setShadowSize(elevation);
    }
    // CardViewApi21Impl
    @Override
    public void setElevation(CardViewDelegate cardView, float elevation) {
        cardView.getCardView().setElevation(elevation);
    }

实现可设置阴影颜色的 CardView

    <attr name="cardShadowColorStart" format="color" />
    <attr name="cardShadowColorEnd" format="color" />
    ColorStateList shadowColorStart = a.getColorStateList(R.styleable.CardView_cardShadowColorStart);
    ColorStateList shaodwColorEnd = a.getColorStateList(R.styleable.CardView_cardShadowColorEnd);
void initialize(CardViewDelegate cardView, Context context, ColorStateList backgroundColor,
                    float radius, float elevation, float maxElevation, ColorStateList shadowColorStart, ColorStateList shadowColorEnd);
    // 阴影颜色默认是 int 类型的颜色值,要修改成 ColorStateList
    private final ColorStateList mShadowStartColor;

    private final ColorStateList mShadowEndColor;
    
    RoundRectDrawableWithShadow(Resources resources, ColorStateList backgroundColor, float radius,
                                float shadowSize, float maxShadowSize, ColorStateList shadowColorStart, ColorStateList shadowColorEnd) {
        // 如果没有设置阴影颜色,使用默认颜色
        if (shadowColorStart == null) {
            mShadowStartColor = ColorStateList.valueOf(resources.getColor(R.color.cardview_shadow_start_color));
        } else {
            mShadowStartColor = shadowColorStart;
        }
        if (shadowColorEnd == null) {
            mShadowEndColor = ColorStateList.valueOf(resources.getColor(R.color.cardview_shadow_end_color));
        } else {
            mShadowEndColor = shadowColorEnd;
        }
        mInsetShadow = resources.getDimensionPixelSize(R.dimen.cardview_compat_inset_shadow);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        setBackground(backgroundColor);
        mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        mCornerShadowPaint.setStyle(Paint.Style.FILL);
        mCornerRadius = (int) (radius + .5f);
        mCardBounds = new RectF();
        mEdgeShadowPaint = new Paint(mCornerShadowPaint);
        mEdgeShadowPaint.setAntiAlias(false);
        setShadowSize(shadowSize, maxShadowSize);
    }
    
    private void buildShadowCorners() {
        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);
        
        // 获取当前状态下的阴影颜色
        int starColor = mShadowStartColor.getColorForState(getState(), mShadowStartColor.getDefaultColor());
        int endColor = mShadowEndColor.getColorForState(getState(), mShadowEndColor.getDefaultColor());
        // 设置阴影颜色
        mCornerShadowPaint.setShader(new RadialGradient(0, 0, mCornerRadius + mShadowSize,
                new int[]{starColor, starColor, endColor},
                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[]{starColor, starColor, endColor},
                new float[]{0f, .5f, 1f}, Shader.TileMode.CLAMP));
        mEdgeShadowPaint.setAntiAlias(false);
    }
    <cn.wj.android.colorcardview.CardView
        android:layout_width="200dp"
        android:layout_height="50dp"
        app:cardPreventCornerOverlap="true"
        app:cardBackgroundColor="#069ff1"
        app:cardShadowColorStart="#2dfd0000"
        app:cardShadowColorEnd="#03fd0000"
        app:cardUseCompatPadding="true"
        app:cardElevation="8dp" />

适配高版本

@RequiresApi(21)
class CardViewApi21Impl extends CardViewApi17Impl {

    // 标记 - 是否使用低版本实现
    private boolean useLower = false;

    @Override
    public void initialize(CardViewDelegate cardView, Context context,
                           ColorStateList backgroundColor, float radius, float elevation, float maxElevation,
                           ColorStateList shadowColorStart, ColorStateList shadowColorEnd) {

        // 没有自定义阴影颜色,不使用低版本实现
        if (shadowColorStart == null && shadowColorEnd == null) {
            useLower = false;

            final RoundRectDrawable background = new RoundRectDrawable(backgroundColor, radius);
            cardView.setCardBackground(background);

            View view = cardView.getCardView();
            view.setClipToOutline(true);
            view.setElevation(elevation);
            setMaxElevation(cardView, maxElevation);
        } else {
            // 配置了自定义颜色,使用低版本实现
            useLower = true;
            super.initialize(cardView, context, backgroundColor, radius, elevation, maxElevation, shadowColorStart, shadowColorEnd);
        }
    }

    @Override
    public void setRadius(CardViewDelegate cardView, float radius) {
        if (useLower) {
            super.setRadius(cardView, radius);
        } else {
            getCardBackground(cardView).setRadius(radius);
        }
    }

    @Override
    public void setMaxElevation(CardViewDelegate cardView, float maxElevation) {
        if (useLower) {
            super.setMaxElevation(cardView, maxElevation);
        } else {
            getCardBackground(cardView).setPadding(maxElevation,
                    cardView.getUseCompatPadding(), cardView.getPreventCornerOverlap());
            updatePadding(cardView);
        }
    }

    @Override
    public float getMaxElevation(CardViewDelegate cardView) {
        if (useLower) {
            return super.getMaxElevation(cardView);
        } else {
            return getCardBackground(cardView).getPadding();
        }
    }

    @Override
    public float getMinWidth(CardViewDelegate cardView) {
        if (useLower) {
            return super.getMinWidth(cardView);
        } else {
            return getRadius(cardView) * 2;
        }
    }

    @Override
    public float getMinHeight(CardViewDelegate cardView) {
        if (useLower) {
            return super.getMinHeight(cardView);
        } else {
            return getRadius(cardView) * 2;
        }
    }

    @Override
    public float getRadius(CardViewDelegate cardView) {
        if (useLower) {
            return super.getRadius(cardView);
        } else {
            return getCardBackground(cardView).getRadius();
        }
    }

    @Override
    public void setElevation(CardViewDelegate cardView, float elevation) {
        if (useLower) {
            super.setElevation(cardView, elevation);
        } else {
            cardView.getCardView().setElevation(elevation);
        }
    }

    @Override
    public float getElevation(CardViewDelegate cardView) {
        if (useLower) {
            return super.getElevation(cardView);
        } else {
            return cardView.getCardView().getElevation();
        }
    }

    @Override
    public void updatePadding(CardViewDelegate cardView) {
        if (useLower) {
            super.updatePadding(cardView);
        } else {
            if (!cardView.getUseCompatPadding()) {
                cardView.setShadowPadding(0, 0, 0, 0);
                return;
            }
            float elevation = getMaxElevation(cardView);
            final float radius = getRadius(cardView);
            int hPadding = (int) Math.ceil(RoundRectDrawableWithShadow
                    .calculateHorizontalPadding(elevation, radius, cardView.getPreventCornerOverlap()));
            int vPadding = (int) Math.ceil(RoundRectDrawableWithShadow
                    .calculateVerticalPadding(elevation, radius, cardView.getPreventCornerOverlap()));
            cardView.setShadowPadding(hPadding, vPadding, hPadding, vPadding);
        }
    }

    @Override
    public void onCompatPaddingChanged(CardViewDelegate cardView) {
        if (useLower) {
            super.onCompatPaddingChanged(cardView);
        } else {
            setMaxElevation(cardView, getMaxElevation(cardView));
        }
    }

    @Override
    public void onPreventCornerOverlapChanged(CardViewDelegate cardView) {
        if (useLower) {
            super.onPreventCornerOverlapChanged(cardView);
        } else {
            setMaxElevation(cardView, getMaxElevation(cardView));
        }
    }

    @Override
    public void setBackgroundColor(CardViewDelegate cardView, @Nullable ColorStateList color) {
        if (useLower) {
            super.setBackgroundColor(cardView, color);
        } else {
            getCardBackground(cardView).setColor(color);
        }
    }

    @Override
    public ColorStateList getBackgroundColor(CardViewDelegate cardView) {
        if (useLower) {
            return super.getBackgroundColor(cardView);
        } else {
            return getCardBackground(cardView).getColor();
        }
    }

    private RoundRectDrawable getCardBackground(CardViewDelegate cardView) {
        return ((RoundRectDrawable) cardView.getCardBackground());
    }
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <cn.wj.android.colorcardview.CardView
        android:id="@+id/cv1"
        android:layout_width="200dp"
        android:layout_height="100dp"
        app:cardBackgroundColor="@color/app_selector_card"
        app:cardElevation="8dp"
        app:cardPreventCornerOverlap="true"
        app:cardShadowColorEnd="@color/app_selector_shadow_end"
        app:cardShadowColorStart="@color/app_selector_shadow_start"
        app:cardUseCompatPadding="true" />

    <android.support.v7.widget.CardView
        android:id="@+id/cv2"
        android:layout_width="200dp"
        android:layout_height="100dp"
        app:cardBackgroundColor="@color/app_selector_card"
        app:cardElevation="8dp"
        app:cardPreventCornerOverlap="true"
        app:cardUseCompatPadding="true" />

</LinearLayout>
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        cv1.setOnClickListener { onClick(it) }
        cv2.setOnClickListener { onClick(it) }
    }

    fun onClick(v: View) {
        v.isSelected = !v.isSelected
    }
}
2018-10-10 14.49.23.gif

最后

上一篇 下一篇

猜你喜欢

热点阅读