使涟漪仅覆盖自定义视图的一部分

问题描述

我正在尝试制作一个如下所示的自定义视图:

Text

它在点击时就像一个按钮。我希望涟漪效应仅覆盖视图的圆角矩形部分,但我正在努力实现这一目标。下面是涟漪现在的样子:

https://youtu.be/mCR0C7QYrpU

注意波纹是如何一直延伸到顶部,而不是停在圆角矩形的边缘。这是我第一次制作自定义视图,我完全迷失了。

public class HeaderButton extends androidx.appcompat.widget.AppCompatTextView {

    Paint paint;
    float scaledUnit;
    RectF rectBounds;
    String text = "My text";

    public HeaderButton(Context context) {
        super(context);
        init();
    }

    public HeaderButton(Context context,@Nullable AttributeSet attrs) {
        super(context,attrs);
        init();
    }

    public HeaderButton(Context context,@Nullable AttributeSet attrs,int defStyleAttr) {
        super(context,attrs,defStyleAttr);
        init();
    }


    @Override
    protected void onSizeChanged(int w,int h,int oldw,int oldh) {
        super.onSizeChanged(w,h,oldw,oldh);

        float screenWidthPixel  = this.getResources().getdisplayMetrics().widthPixels;
        scaledUnit = screenWidthPixel * 0.001f;

        rectBounds = new RectF(3 * scaledUnit,15 * scaledUnit,getWidth() - (3 * scaledUnit),getHeight() - (3 * scaledUnit));
    }



    private void init() {
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);

        float[] outerRadii = new float[8];
        Arrays.fill(outerRadii,7 * scaledUnit);
        RoundRectShape shape = new RoundRectShape(outerRadii,null,null);
        ShapeDrawable mask = new ShapeDrawable(shape);
        ColorStateList stateList = ColorStateList.valueOf(ContextCompat.getColor(getContext(),R.color.colorError));
        setBackgroundDrawable(new rippledrawable(stateList,mask));
    }



    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        float textWidth = paint.measureText(text);
        float textStart = Math.round(getWidth() / 2.) - (textWidth / 2);
        float textEnd = Math.round(getWidth() / 2.) + (textWidth / 2);

        paint.setStyle(Paint.Style.FILL);
        int scaledTextSize = getResources().getDimensionPixelSize(R.dimen.header_button_label_text_size);
        paint.setTextSize(scaledTextSize);
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.parseColor("#777777"));
        paint.setTypeface(Typeface.create(Typeface.DEFAULT,Typeface.BOLD));

        canvas.drawText(text,Math.round(getWidth() / 2.) - (textWidth / 2),24 * scaledUnit,paint);

        // Removes the portion of the rounded rectangle that's behind the text.
        canvas.clipRect(textStart - (10 * scaledUnit),textEnd + (10 * scaledUnit),20,Region.Op.DIFFERENCE);

        paint.setColor(getResources().getColor(R.color.colorMainActivityBottomButtonBorderBackground));
        paint.setStyle(Paint.Style.stroke);
        paint.setstrokeWidth(3 * scaledUnit);

        canvas.drawRoundRect(rectBounds,7 * scaledUnit,paint);
    }
}

解决方法

尝试将您的 shape 包装到 LayerList - 在那里,至少在 XML 中,每个 <item 标记可能包含 padding

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:left="8dp"
        android:right="8dp"
        android:top="8dp"
        android:bottom="8dp">

        <shape
            ...
    </item>
    <item ...
</layer-list>

没有在下面尝试过,但在编程上它可能看起来与此类似

ShapeDrawable mask = new ShapeDrawable(shape);
Drawable[] layers = {mask};
LayerDrawable layerDrawable = new LayerDrawable(layers);
layerDrawable.setPadding(0,someValue,0);
ColorStateList stateList = ColorStateList.valueOf(ContextCompat.getColor(getContext(),R.color.colorError));
setBackgroundDrawable(new RippleDrawable(stateList,null,layerDrawable));
,

似乎试图以编程方式实现涟漪是一个死胡同。但是我找到了一个使用 XML 的解决方案:

button_ripple.xml:

<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="@color/colorControlHighlight">
    <item
        android:id="@android:id/mask"
        android:bottom="3dp"
        android:left="3dp"
        android:right="3dp"
        android:top="8dp">
        <shape>
            <solid android:color="@color/colorBackground"/>
            <corners android:radius="3dp"/>
        </shape>
    </item>
</ripple>

layout.xml:

... 

<myPackage.HeaderButton
        android:id="@+id/headerButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Enter phone number"
        android:textSize="17dp"
        android:gravity="center"
        android:background="@drawable/button_ripple"/>
...

HeaderButton.java:

public class HeaderButton extends androidx.appcompat.widget.AppCompatTextView {

    Paint paint;
    RectF rectBounds;
    String text = "My text";

    public HeaderButton(Context context) {
        super(context);
        init();
    }

    public HeaderButton(Context context,@Nullable AttributeSet attrs) {
        super(context,attrs);
        init();
    }

    public HeaderButton(Context context,@Nullable AttributeSet attrs,int defStyleAttr) {
        super(context,attrs,defStyleAttr);
        init();
    }


    @Override
    protected void onSizeChanged(int w,int h,int oldw,int oldh) {
        super.onSizeChanged(w,h,oldw,oldh);
        // These measurements must exactly match the paddings you set in button_ripple.xml
        rectBounds = new RectF(dpToPx(3,getContext()),dpToPx(8,getWidth() - (dpToPx(3,getContext())),getHeight() - (dpToPx(3,getContext())));
        isLayoutRequested();
    }


    private void init() {
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        float textWidth = paint.measureText(text);
        float textStart = Math.round(getWidth() / 2.) - (textWidth / 2);
        float textEnd = Math.round(getWidth() / 2.) + (textWidth / 2);

        paint.setStyle(Paint.Style.FILL);
        int scaledTextSize = getResources().getDimensionPixelSize(R.dimen.header_button_label_text_size);
        paint.setTextSize(scaledTextSize);
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.parseColor("#777777"));
        paint.setTypeface(Typeface.create(Typeface.DEFAULT,Typeface.BOLD));

        canvas.drawText(text,Math.round(getWidth() / 2.) - (textWidth / 2),dpToPx(12,paint);

        // Removes the portion of the rounded rectangle that's behind the text.
        canvas.clipRect(textStart - (dpToPx(7,textEnd + (dpToPx(7,getHeight() / 2f,Region.Op.DIFFERENCE);

        paint.setColor(getResources().getColor(R.color.colorMainActivityBottomButtonBorderBackground));
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(dpToPx(1.5f,getContext()));

        canvas.drawRoundRect(rectBounds,dpToPx(3,paint);
    }
}

我最初尝试过这种方法并拒绝了它,因为它没有在所有设备上提供一致的结果。但后来我意识到我的 dpToPx() 方法与 Android 内部使用的方法不同。这很重要,因为您在 button_ripple.xml 和 HeaderButton.java 中设置的填充值必须完美匹配。所以请确保您使用的是这个:

    //Converts a dp measurement to a px measurement.
    public static int dpToPx(float dp,Context context) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dp,context.getResources().getDisplayMetrics());
    }