【问题标题】:Make ripple that covers only part of a custom View制作仅覆盖自定义视图一部分的波纹
【发布时间】:2021-02-25 05:06:52
【问题描述】:

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

点击时它就像一个按钮。我希望波纹效果只覆盖视图的圆角矩形部分,但我正在努力实现这一点。这是波纹现在的样子:

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, null, 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), 0, 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, 7 * scaledUnit, paint);
    }
}

【问题讨论】:

    标签: android android-custom-view


    【解决方案1】:

    尝试将您的 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, 0);
    ColorStateList stateList = ColorStateList.valueOf(ContextCompat.getColor(getContext(), R.color.colorError));
    setBackgroundDrawable(new RippleDrawable(stateList, null, layerDrawable));
    

    【讨论】:

    • 由于某种原因,设置顶部填充没有任何效果,无论有没有 LayerDrawable。我尝试直接在 ShapeDrawable 上设置顶部填充,但也没有效果。
    • 好吧,现在除了将您的自定义视图分成两个放在ViewGroup 中并管理这些内部Views 填充之外,我没有任何其他想法。但是您当前的方法在性能和干净代码方面要好得多,所以我希望有人会为当前的代码库发布正确的答案。祝你好运!
    • 实际上我意识到 setPadding() 正在将视图的实际内容向下推,而不是波纹本身。我一定是用错了。
    • 也许您应该将RippleDrawable 放入带有填充的LayerDrawable,而不是mask? (盲猜)
    • 我所做的任何事情都不会使波纹层移动。不过感谢您的建议。设置顶部填充在 XML 中有效,所以奇怪的是它根本不能以编程方式工作。
    【解决方案2】:

    尝试以编程方式实现波纹似乎是一条死胡同。但我能够想出一个使用 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, getContext()), 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, getContext()), paint);
    
            // Removes the portion of the rounded rectangle that's behind the text.
            canvas.clipRect(textStart - (dpToPx(7, getContext())), 0, textEnd + (dpToPx(7, getContext())), 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, getContext()), dpToPx(3, getContext()), 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());
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-02-12
      • 1970-01-01
      • 1970-01-01
      • 2015-03-04
      • 1970-01-01
      • 2015-07-23
      • 1970-01-01
      • 2021-03-08
      相关资源
      最近更新 更多