Android自定义Drawable---灵活多变的矩形背景

news2024/11/20 1:20:40

Android自定义Drawable—灵活多变的矩形背景

在安卓开发中,我们通常需要为不同的按钮设置不同的背景以实现不同的效果,有时还需要这些按钮根据实际情况进行变化。如果采用编写resource中xml文件的形式,就需要重复定义许多只有微小变动的资源文件。这使得整个工程的简洁性和可读性受到影响。本文将介绍一种基于java代码实现的矩形背景自定义工具,该工具是继承Drawable的基础上开发的,它具有如下功能:

  1. 自定义矩形的内部填充颜色和边框颜色;
  2. 自定义矩形四角的弧度,支持分别定义和整体定义;
  3. 自定义矩形的阴影(颜色、宽度及位置);
  4. 自定义矩形的触摸水波纹效果(颜色、显示速度);
  5. 自定义矩形的边框宽度(按比例/按固定宽度),并可与ObjectAnimator配合实现动画效果

综合展示如下:
综合效果展示

直接上源码:

public class FlexibleRectDrawable extends Drawable {
    private Paint paint_stroke;
    private Paint paint_fill;
    private Paint paint_ripple;

    private RectF outerRect;
    private RectF innerRect;
    private RectF zeroRect;
    //矩形内部颜色及边框颜色
    private int solidColor;
    private int strokeColor;
    private int solidColorHolder;
    private int strokeColorHolder;
    //边框设置
    private boolean hasStroke;
    private float strokeWidth;
    private float strokeWidthVariable;//可变动的边框宽度,用于生成后调整宽度
    private float strokeInPercent;//0~1 边框与整体大小的占比
    //圆角半径
    private float rectRadius;
    //圆角位置
    private int corners;
    //阴影设置
    private boolean needShadow;
    private float shadowRange;//阴影粗细
    private float shadowDx;//阴影中心x轴偏移
    private float shadowDy;//阴影中心y轴偏移
    private int shadowColor;//阴影颜色
    //阴影位置
    private float offsetLeft;
    private float offsetTop;
    private float offsetRight;
    private float offsetBottom;
    //Ripple Effect
    enum RippleAnimState{STATE_ENTER, STATE_EXIT, STATE_EXIT_LATER}
    private RippleAnimState ripple_anim_state;
    private ObjectAnimator ripple_alpha_animator;
    private ObjectAnimator ripple_radius_animator;
    private PointF currentPoint;
    private PointF pressedPoint;
    private Path ripple_bound_path;
    private boolean needRipple;
    private int rippleSpeed;//ms
    private float maxRippleRadius;
    private float rippleRadius;//属性动画
    private int rippleColor;
    private int maxRippleAlpha;
    private int rippleAlpha;//属性动画

    public static final int SQUARE_CORNER = 0;
    public static final int CORNER_TOP_LEFT = 1;
    public static final int CORNER_TOP_RIGHT = 1 << 1;
    public static final int CORNER_BOTTOM_LEFT = 1 << 2;
    public static final int CORNER_BOTTOM_RIGHT = 1 << 3;
    public static final int CORNER_HALF_LEFT = CORNER_TOP_LEFT | CORNER_BOTTOM_LEFT;
    public static final int CORNER_HALF_RIGHT = CORNER_TOP_RIGHT | CORNER_BOTTOM_RIGHT;
    public static final int CORNER_ALL = CORNER_TOP_LEFT | CORNER_TOP_RIGHT | CORNER_BOTTOM_LEFT | CORNER_BOTTOM_RIGHT;

    public enum RectType{BORDER_ONLY,SOLID_BLOCK,BORDERED_BLOCK,NOT_DEFINED}

    private RectType type;

    public FlexibleRectDrawable() {
        //默认值
        this.type = RectType.NOT_DEFINED;
        this.solidColor = 0;//透明色
        this.strokeColor = 0;//透明色
        this.solidColorHolder = 0;
        this.strokeColorHolder = 0;
        this.hasStroke = false;
        this.strokeWidth = 0;
        this.strokeWidthVariable = 0;
        this.corners = SQUARE_CORNER;
        this.rectRadius = 0;
        this.needShadow = false;
        this.shadowRange = 0;
        this.shadowDx = 0;
        this.shadowDy = 0;
        this.shadowColor = Color.parseColor("#aa000000");
        this.offsetBottom = 0;
        this.offsetLeft = 0;
        this.offsetTop = 0;
        this.offsetRight = 0;

        this.currentPoint = new PointF();
        this.pressedPoint = new PointF();
        this.rippleColor = Color.parseColor("#21000000");
        this.maxRippleAlpha = this.rippleColor>>24 & 0xFF;
        this.ripple_bound_path = new Path();
    }

    public void setupPainters() {
        paint_stroke = new Paint();
        paint_stroke.setAntiAlias(true);
        paint_stroke.setFilterBitmap(true);
        paint_stroke.setDither(true);
        paint_stroke.setStyle(Paint.Style.FILL);
        paint_stroke.setColor(strokeColor);
        //设置阴影
        if(needShadow)
            paint_stroke.setShadowLayer(shadowRange, shadowDx, shadowDy, shadowColor);

        paint_fill = new Paint();
        paint_fill.setAntiAlias(true);
        paint_fill.setFilterBitmap(true);
        paint_fill.setDither(true);
        paint_fill.setStyle(Paint.Style.FILL);
        paint_fill.setColor(solidColor);
        //设置水波纹效果
        paint_ripple = new Paint();
        paint_ripple.setAntiAlias(true);
        paint_ripple.setStyle(Paint.Style.FILL);
        paint_ripple.setColor(rippleColor);

        invalidateSelf();
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        super.onBoundsChange(bounds);


        if (bounds.right - bounds.left > 0 && bounds.bottom - bounds.top > 0) {

            int width = bounds.right - bounds.left;
            int height = bounds.bottom - bounds.top;

            outerRect = new RectF(offsetLeft, offsetTop, width - offsetRight, height - offsetBottom);
            innerRect = new RectF(offsetLeft + strokeWidth, offsetTop + strokeWidth,
                    width - offsetRight - strokeWidth, height - offsetBottom - strokeWidth);
            zeroRect = new RectF(width/2.0f, height/2.0f, width/2.0f, height/2.0f);
            invalidateSelf();
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.Q)
    @Override
    public void draw(Canvas canvas) {
        float[] Radii = {0,0,0,0,0,0,0,0};

        if ((corners & CORNER_TOP_LEFT) != 0) {
            Radii[0] = rectRadius;
            Radii[1] = rectRadius;
        }
        if ((corners & CORNER_TOP_RIGHT) != 0) {
            Radii[2] = rectRadius;
            Radii[3] = rectRadius;
        }
        if ((corners & CORNER_BOTTOM_RIGHT) != 0) {
            Radii[4] = rectRadius;
            Radii[5] = rectRadius;
        }
        if ((corners & CORNER_BOTTOM_LEFT) != 0) {
            Radii[6] = rectRadius;
            Radii[7] = rectRadius;
        }
        switch(type){
            case BORDER_ONLY:
                canvas.drawDoubleRoundRect(outerRect, Radii, innerRect, Radii, paint_stroke);
                break;
            case SOLID_BLOCK:
                canvas.drawDoubleRoundRect(outerRect,Radii,zeroRect,Radii, paint_fill);
                break;
            case BORDERED_BLOCK:
                canvas.drawDoubleRoundRect(outerRect, Radii, innerRect, Radii, paint_stroke);
                canvas.drawDoubleRoundRect(innerRect,Radii,zeroRect,Radii, paint_fill);
                break;
            case NOT_DEFINED:
                throw new RuntimeException("RectType undefined");
            default:
        }
        //draw ripple
        canvas.save();
        ripple_bound_path.addRoundRect(innerRect,Radii,Path.Direction.CW);
        canvas.clipPath(ripple_bound_path);
        if(ripple_anim_state == STATE_ENTER){
            paint_ripple.setAlpha(rippleAlpha);
            canvas.drawCircle(pressedPoint.x, pressedPoint.y, rippleRadius, paint_ripple);
        }else if(ripple_anim_state == STATE_EXIT){
            paint_ripple.setAlpha(rippleAlpha);
            canvas.drawDoubleRoundRect(innerRect,Radii,zeroRect,Radii, paint_ripple);
        }
        canvas.restore();
    }

    @Override
    protected boolean onStateChange(int[] stateSet) {
        boolean enable = false;
        boolean pressed = false;
        for (int st : stateSet) {
            switch (st) {
                case android.R.attr.state_pressed:
                    pressed = true;
                    break;
                case android.R.attr.state_enabled:
                    enable = true;
                    break;
            }
        }

        if (!enable) return false;
        if (!needRipple)return false;
        if (pressed) {
            startRippleAnimation();
            return true;
        } else if (ripple_anim_state == STATE_ENTER) {
            exitRippleAnimation();
            return true;
        } else {
            return false;
        }
    }

    private void startRippleAnimation() {
        ripple_anim_state = STATE_ENTER;
        pressedPoint.set(currentPoint);
        maxRippleRadius = Math.max(innerRect.width(), innerRect.height());
        if(ripple_radius_animator != null && ripple_radius_animator.isRunning()){
            ripple_radius_animator.cancel();
        }
        ripple_radius_animator = new ObjectAnimator();
        ripple_radius_animator.setTarget(this);
        ripple_radius_animator.setPropertyName("rippleRadius");
        ripple_radius_animator.setInterpolator(new LinearInterpolator());
        ripple_radius_animator.setDuration(rippleSpeed);
        ripple_radius_animator.setFloatValues(0,maxRippleRadius);
        ripple_radius_animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                if(ripple_anim_state == STATE_EXIT_LATER){
                    ripple_anim_state = STATE_EXIT;
                    exitRippleAnimation();
                }
            }
        });
        ripple_radius_animator.start();
    }

    private void exitRippleAnimation() {
        ripple_alpha_animator = new ObjectAnimator();
        ripple_alpha_animator.setTarget(this);
        ripple_alpha_animator.setPropertyName("rippleAlpha");
        ripple_alpha_animator.setInterpolator(new LinearInterpolator());
        ripple_alpha_animator.setDuration(300);
        ripple_alpha_animator.setIntValues(maxRippleAlpha,0);
        ripple_alpha_animator.start();
    }

    @Override
    public boolean isStateful() {
        return true;
    }

    @Override
    public void setHotspot(float x, float y) {
        currentPoint.set(x,y);
    }

    public float getOffsetLeft() {
        return offsetLeft;
    }

    public void setOffsetLeft(float offsetLeft) {
        this.offsetLeft = offsetLeft;
    }

    public float getOffsetTop() {
        return offsetTop;
    }

    public void setOffsetTop(float offsetTop) {
        this.offsetTop = offsetTop;
    }

    public float getOffsetRight() {
        return offsetRight;
    }

    public void setOffsetRight(float offsetRight) {
        this.offsetRight = offsetRight;
    }

    public float getOffsetBottom() {
        return offsetBottom;
    }

    public void setOffsetBottom(float offsetBottom) {
        this.offsetBottom = offsetBottom;
    }

    public float getRectRadius() {
        return rectRadius;
    }

    public void setRectRadius(float rectRadius) {
        this.rectRadius = rectRadius;
    }

    public void setCorners(int corners) {
        this.corners = corners;
    }

    public FlexibleRectDrawable setColor(int color) {
        paint_stroke.setColor(color);
        return this;
    }

    public int getSolidColor() {
        return solidColor;
    }

    public void setSolidColor(int solidColor) {
        this.solidColor = solidColor;
    }

    public int getStrokeColor() {
        return strokeColor;
    }

    public void setStrokeColor(int strokeColor) {
        this.strokeColor = strokeColor;
    }

    public boolean isHasStroke() {
        return hasStroke;
    }

    public void setHasStroke(boolean hasStroke) {
        this.hasStroke = hasStroke;
    }

    public boolean isNeedShadow() {
        return needShadow;
    }

    public void setNeedShadow(boolean needShadow) {
        this.needShadow = needShadow;
    }

    public float getShadowRange() {
        return shadowRange;
    }

    public void setShadowRange(float shadowRange) {
        this.shadowRange = shadowRange;
    }

    public float getShadowDx() {
        return shadowDx;
    }

    public void setShadowDx(float shadowDx) {
        this.shadowDx = shadowDx;
    }

    public float getShadowDy() {
        return shadowDy;
    }

    public void setShadowDy(float shadowDy) {
        this.shadowDy = shadowDy;
    }

    public int getShadowColor() {
        return shadowColor;
    }

    public void setShadowColor(int shadowColor) {
        this.shadowColor = shadowColor;
    }

    float getStrokeWidth() {
        return strokeWidth;
    }

    void setStrokeWidth(float strokeWidth) {
        this.strokeWidth = strokeWidth;
        this.strokeWidthVariable = strokeWidth;
    }

    public float getStrokeWidthVariable() {
        if(type==RectType.SOLID_BLOCK)
            return Math.min((getBounds().width() - offsetRight),(getBounds().height() - offsetBottom));
        return strokeWidthVariable;
    }

    public void setStrokeWidthVariable(float strokeWidthVariable) {
        this.strokeWidthVariable = strokeWidthVariable;
        int width = getBounds().width();
        int height = getBounds().height();
        System.out.println("type= " + type.name() + " stroke = " + strokeWidthVariable+" width = "+width+" height = "+height);
        if((width - offsetRight)<=strokeWidthVariable||
            (height - offsetBottom)<=strokeWidthVariable){
            //边框宽大到可以认为是纯色块
            if(type == RectType.BORDERED_BLOCK && this.solidColor!=0){
                this.solidColorHolder = this.solidColor;
                this.solidColor = this.strokeColor;
            }
            //若内部无色,则用边框颜色作为填充
            if(type == RectType.BORDER_ONLY && this.strokeColor!=0) {
                this.solidColor = this.strokeColor;
            }
            type = RectType.SOLID_BLOCK;
        }
        else{
            if(type==RectType.SOLID_BLOCK){
                this.strokeColor = this.solidColor;
                if(this.solidColorHolder == 0){
                    type = RectType.BORDER_ONLY;//纯色块转变为仅带边框的块
                }
                else {
                    this.solidColor = this.solidColorHolder;
                    type = RectType.BORDERED_BLOCK;
                }
            }
            innerRect.set(offsetLeft + strokeWidthVariable, offsetTop + strokeWidthVariable,
                    width - offsetRight - strokeWidthVariable,
                    height - offsetBottom - strokeWidthVariable);
        }
        setupPainters();//重设画笔,并重绘
    }

    public void setStrokeInPercent(@FloatRange(from= 0.0f,to= 1.0f) float strokeInPercent) {
        if(strokeColor==0)throw new IllegalArgumentException("setStrokeInPercent函数仅适用于带边框的Drawable");
        this.strokeInPercent = strokeInPercent;
        float delta_width = outerRect.width()*strokeInPercent/2;
        float delta_height = outerRect.height()*strokeInPercent/2;
        //System.out.println("delta_height= " + delta_height + " delta_width= " + delta_width);
        innerRect.set(outerRect.left+delta_width, outerRect.top+delta_height,
                outerRect.right-delta_width,outerRect.bottom-delta_height);
        if(this.strokeInPercent>0 && this.strokeInPercent<1){
            //带边框的块
            if(this.solidColor==this.strokeColor && this.solidColorHolder == 0)
                this.type = RectType.BORDER_ONLY;//转变为仅带边框的块
            else{
                if(this.solidColorHolder!=0){
                    this.solidColor = this.solidColorHolder;
                    this.solidColorHolder = 0;
                }
                this.type = RectType.BORDERED_BLOCK;
            }
        }
        else if(this.strokeInPercent == 0){
            //内部填充的纯色块
            this.type = RectType.SOLID_BLOCK;
            if(solidColor == 0)Log.e("FlexibleRectDrawable","Drawable被绘制为透明色");
        }
        else if(this.strokeInPercent == 1){
            //边框填充的纯色块
            this.type = RectType.SOLID_BLOCK;
            if(this.solidColorHolder==0 && this.solidColor!=this.strokeColor){
                this.solidColorHolder = this.solidColor;
                this.solidColor = this.strokeColor;
            }
        }
        setupPainters();
    }

    public float getStrokeInPercent() {
        float inner_width = innerRect.width();
        float outer_width = outerRect.width();
        return (1-inner_width/outer_width);
    }

    public RectType getType() {
        return type;
    }

    public void setType(RectType type) {
        this.type = type;
    }

    public float getRippleRadius() {
        return rippleRadius;
    }

    public void setRippleRadius(float rippleRadius) {
        this.rippleRadius = rippleRadius;
        invalidateSelf();
    }

    public int getRippleColor() {
        return rippleColor;
    }

    public void setRippleColor(int rippleColor) {
        this.rippleColor = rippleColor;
    }

    public int getMaxRippleAlpha() {
        return maxRippleAlpha;
    }

    public void setMaxRippleAlpha(int maxRippleAlpha) {
        this.maxRippleAlpha = maxRippleAlpha;
    }

    public int getRippleAlpha() {
        return rippleAlpha;
    }

    public void setRippleAlpha(int rippleAlpha) {
        this.rippleAlpha = rippleAlpha;
        invalidateSelf();
    }

    public boolean isNeedRipple() {
        return needRipple;
    }

    public void setNeedRipple(boolean needRipple) {
        this.needRipple = needRipple;
    }

    public float getMaxRippleRadius() {
        return maxRippleRadius;
    }

    public void setMaxRippleRadius(float maxRippleRadius) {
        this.maxRippleRadius = maxRippleRadius;
    }

    public int getRippleSpeed() {
        return rippleSpeed;
    }

    public void setRippleSpeed(int rippleSpeed) {
        this.rippleSpeed = rippleSpeed;
    }

    @Override
    public void setAlpha(int i) {

    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {

    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }
    }

此外,设置一个Builder让自定义构建变得更容易:

public static class Builder{
        private FlexibleRectDrawable drawable;

        public Builder() {
            this.drawable = new FlexibleRectDrawable();
        }

        public static Builder create(){
            return new Builder();
        }

        public Builder setSolidFill(@ColorInt int color){
            this.drawable.setSolidColor(color);
            switch(this.drawable.getType()){
                case BORDER_ONLY:
                    this.drawable.setType(RectType.BORDERED_BLOCK);
                    break;
                case SOLID_BLOCK:case BORDERED_BLOCK:
                    Log.i("DrawableBuilder","cover solid color");
                    break;
                case NOT_DEFINED:
                    this.drawable.setType(RectType.SOLID_BLOCK);
                    break;
                default:
            }
            return this;
        }

        public Builder setStroke(float width,@ColorInt int color){
            this.drawable.setHasStroke(true);
            this.drawable.setStrokeColor(color);
            this.drawable.setStrokeWidth(width);
            switch(this.drawable.getType()){
                case BORDER_ONLY:case BORDERED_BLOCK:
                    Log.i("DrawableBuilder","cover solid color");
                    break;
                case SOLID_BLOCK:
                    this.drawable.setType(RectType.BORDERED_BLOCK);
                    break;
                case NOT_DEFINED:
                    this.drawable.setType(RectType.BORDER_ONLY);
                    break;
                default:
            }
            return this;
        }

        public Builder setShadow(float shadowRange,@ColorInt int color){
            this.drawable.setNeedShadow(true);
            this.drawable.setShadowRange(shadowRange);
            this.drawable.setShadowColor(color);
            return this;
        }

        public Builder setShadowOffset(float top, float bottom, float left, float right){
            if(!this.drawable.isNeedShadow())throw new IllegalArgumentException("必须先调用setShadow,再设置阴影位置");
            this.drawable.setOffsetTop(top);
            this.drawable.setOffsetBottom(bottom);
            this.drawable.setOffsetLeft(left);
            this.drawable.setOffsetRight(right);
            return this;
        }

        public Builder setShadowOffsetCenter(float offset){
            if(!this.drawable.isNeedShadow())throw new IllegalArgumentException("必须先调用setShadow,再设置阴影位置");
            this.drawable.setOffsetTop(offset);
            this.drawable.setOffsetBottom(offset);
            this.drawable.setOffsetLeft(offset);
            this.drawable.setOffsetRight(offset);
            return this;
        }

        public Builder setCorners(int radius,int corner_type){
            this.drawable.setRectRadius(radius);
            this.drawable.setCorners(corner_type);
            return this;
        }

        public Builder setRipple(int color, int speed_millisecond){
            this.drawable.setNeedRipple(true);
            int check = color >>24;
            if (check==-1)throw new IllegalArgumentException("ripple颜色必须具有透明色");
            this.drawable.setRippleColor(color);
            this.drawable.setMaxRippleAlpha(color>>24 & 0xFF);
            this.drawable.setRippleSpeed(speed_millisecond);
            return this;
        }

        public FlexibleRectDrawable build(){
            this.drawable.setupPainters();
            return this.drawable;
        }
    }

辅助函数:dp转px

public int dp2Px(float dpValue) {
        final float scale = getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

使用方法

1. 一个普通的圆角按钮

按钮1

FlexibleRectDrawable drawable1 = FlexibleRectDrawable.Builder.create()
                .setStroke(dp2Px(3), Color.parseColor("#4682B4"))
                .setSolidFill(Color.parseColor("#DAA520"))
                .setCorners(dp2Px(50), FlexibleRectDrawable.CORNER_ALL)
                .build();
LinearLayout view1 = findViewById(R.id.frd_view1);
view1.setBackground(drawable1);

2. 一个带水波纹效果的按钮

实心/空心:
按钮2按钮3

//实心按钮
FlexibleRectDrawable drawabled1 = FlexibleRectDrawable.Builder.create()
                .setSolidFill(Color.parseColor("#4682B4"))
                .setCorners(dp2Px(50), FlexibleRectDrawable.CORNER_ALL)
                .setRipple(Color.parseColor("#22FFFFFF"),300)
                .build();
//空心按钮
FlexibleRectDrawable drawable2 = FlexibleRectDrawable.Builder.create()
                .setStroke(dp2Px(3), Color.parseColor("#4682B4"))
                .setCorners(dp2Px(50), FlexibleRectDrawable.CORNER_ALL)
                .setRipple(Color.parseColor("#22000000"),300)
                .build();

3. 一个带阴影的按钮

实心/空心:
按钮4按钮5

//空心
FlexibleRectDrawable drawabled2 = FlexibleRectDrawable.Builder.create()
                .setStroke(dp2Px(3),Color.parseColor("#1E90FF"))
                .setCorners(dp2Px(50), FlexibleRectDrawable.CORNER_ALL)
                .setShadow(dp2Px(7), Color.parseColor("#fe00FFFF"))
                .setShadowOffsetCenter(dp2Px(5))
                .build();
//实心
FlexibleRectDrawable drawable2 = FlexibleRectDrawable.Builder.create()
                .setStroke(dp2Px(3), Color.parseColor("#4682B4"))
                .setCorners(dp2Px(50), FlexibleRectDrawable.CORNER_ALL)
                .setSolidFill(Color.parseColor("#FFFFFF"))
                .setShadow(dp2Px(5), Color.parseColor("#FEA9A9A9"))
                .setShadowOffsetCenter(dp2Px(5))
                .build();

4. 两个左右合并的按钮

按钮6

布局文件(.xml):

<LinearLayout
            android:id="@+id/frd_view_d3"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_margin="10dp"
            android:layout_below="@id/frd_view_d2">
            <Button
                android:id="@+id/frd_btn1"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="btn1"/>
            <Button
                android:id="@+id/frd_btn2"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="btn2"
                android:textColor="@color/white"/>
        </LinearLayout>

对应java代码:

FlexibleRectDrawable drawable_btn_left = FlexibleRectDrawable.Builder.create()
                .setStroke(dp2Px(3),Color.parseColor("#1E90FF"))
                .setSolidFill(Color.parseColor("#1E90FF"))
                .setCorners(dp2Px(50), FlexibleRectDrawable.CORNER_HALF_LEFT)
                .setRipple(Color.parseColor("#33000000"),300)
                .build();
FlexibleRectDrawable drawable_btn_right = FlexibleRectDrawable.Builder.create()
                .setStroke(dp2Px(3),Color.parseColor("#1E90FF"))
                .setCorners(dp2Px(50), FlexibleRectDrawable.CORNER_HALF_RIGHT)
                .setRipple(Color.parseColor("#33FFFFFF"),300)
                .build();
Button btn_left = findViewById(R.id.frd_btn1);
btn_left.setBackground(drawable_btn_left);
Button btn_right = findViewById(R.id.frd_btn2);
btn_right.setBackground(drawable_btn_right);

5. 按固定宽度设置矩形边框

改变边框的宽度和颜色

按钮7

private boolean on_view3 = false;
...

FlexibleRectDrawable drawable3 = FlexibleRectDrawable.Builder.create()
                .setStroke(dp2Px(3), Color.parseColor("#4682B4"))
                .setSolidFill(Color.parseColor("#131313"))
                .setCorners(dp2Px(50), FlexibleRectDrawable.CORNER_ALL)
                .build();

LinearLayout view3 = findViewById(R.id.frd_view3);
view3.setBackground(drawable3);

view3.setOnClickListener(v -> {
            on_view3 = !on_view3;
            if(on_view3){
                drawable3.setStrokeColor(Color.RED);
                drawable3.setStrokeWidthVariable(dp2Px(5));
            }
            else{
                drawable3.setStrokeColor(Color.parseColor("#4682B4"));
                drawable3.setStrokeWidthVariable(dp2Px(3));
            }
        });

通过属性动画实现按钮状态切换

按钮8

private boolean on_view5 = false;
...

FlexibleRectDrawable drawabled5 = FlexibleRectDrawable.Builder.create()
                .setStroke(dp2Px(3),Color.parseColor("#4682B4"))
                .setSolidFill(Color.parseColor("#3CB371"))
                .setCorners(dp2Px(5), FlexibleRectDrawable.CORNER_ALL)
                .build();
LinearLayout viewd5 = findViewById(R.id.frd_view_d5);
viewd5.setBackground(drawabled5);
viewd5.setOnClickListener(v -> {
            on_view5 = !on_view5;
            if(on_view5){
                ObjectAnimator animator = new ObjectAnimator();
                animator.setTarget(drawabled5);
                animator.setPropertyName("strokeWidthVariable");
                animator.setDuration(1000);
                animator.setFloatValues(drawabled5.getStrokeWidthVariable(),dp2Px(300));
                animator.start();
            }
            else {
                ObjectAnimator animator = new ObjectAnimator();
                animator.setTarget(drawabled5);
                animator.setPropertyName("strokeWidthVariable");
                animator.setDuration(1000);
                animator.setFloatValues(drawabled5.getStrokeWidthVariable(),dp2Px(3));
                animator.start();
            }
        });

6. 按所占百分比设置矩形边框

按钮9

private boolean on_view6 = false;
private float origin_percent = 0.0f;
...

FlexibleRectDrawable drawabled6 = FlexibleRectDrawable.Builder.create()
                .setStroke(dp2Px(3),Color.parseColor("#4682B4"))
                .setCorners(dp2Px(5), FlexibleRectDrawable.CORNER_ALL)
                .build();
LinearLayout viewd6 = findViewById(R.id.frd_view_d6);
viewd6.setBackground(drawabled6);
viewd6.setOnClickListener(v -> {
            on_view6 = !on_view6;
            if (on_view6) {
                origin_percent = drawabled6.getStrokeInPercent();
                ObjectAnimator animator = new ObjectAnimator();
                animator.setTarget(drawabled6);
                animator.setPropertyName("strokeInPercent");
                animator.setDuration(500);
                animator.setFloatValues(origin_percent, 1f);
                animator.start();
            }
            else {
                ObjectAnimator animator = new ObjectAnimator();
                animator.setTarget(drawabled6);
                animator.setPropertyName("strokeInPercent");
                animator.setDuration(500);
                animator.setFloatValues(drawabled6.getStrokeInPercent(), origin_percent);
                animator.start();
            }
        });

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1062310.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue中如何进行表单验证码与滑动验证

在Vue中实现表单验证码与滑动验证功能 验证码和滑动验证是Web应用程序中常见的安全功能&#xff0c;用于验证用户的身份并防止恶意活动。Vue.js作为一个流行的JavaScript框架&#xff0c;提供了许多工具和库来实现这些功能。本文将介绍如何使用Vue来实现表单验证码和滑动验证功…

solidity 合约转java

Generate a Java Wrapper from your Smart Contract Solidity Gradle Plugin - Web3j web3j / web3j-maven-plugin GitLab

ARM汇编与C言语的混合编程

1. C言语如何与汇编进行交互 有些时候&#xff0c;我们需要在汇编代码中调用C代码&#xff0c;或者说C代码中调用汇编代码。 那么&#xff0c;汇编调用C代码&#xff0c;或者C代码调用汇编函数&#xff0c;他们的函数参数、返回值是如何传递的&#xff1f; 对应ARM架构来说&…

学习开发一个RISC-V上的操作系统(汪辰老师) — unrecognized opcode `csrr t0,mhartid‘报错问题

前言 &#xff08;1&#xff09;此系列文章是跟着汪辰老师的RISC-V课程所记录的学习笔记。 &#xff08;2&#xff09;该课程相关代码gitee链接&#xff1b; &#xff08;3&#xff09;PLCT实验室实习生长期招聘&#xff1a;招聘信息链接 正文 &#xff08;1&#xff09;在跟着…

【重拾C语言】五、模块化程序设计——函数(定义、调用、参数传递、结果返回、函数原型;典例:打印字符图形、验证哥德巴赫猜想)

目录 前言 五、模块化程序设计——函数 5.1 计算三角形的重心 5.2 函数 5.2.1 函数定义 5.2.2 函数调用 a. 函数调用的形式和过程 b. 参数传递 值传递 指针传递 c. 函数结果返回 5.2.3 函数原型&#xff08;先调用后定义&#xff09; 5.3 程序设计实例 5.3.1 打印…

【14】c++设计模式——>工厂模式

简单工厂模式的弊端 简单工厂模式虽然简单&#xff0c;但是违反了设计模式中的开放封闭原则&#xff0c;即工厂类在数据增加时需要被修改&#xff0c;而我们在设计时对于已经设计好的类需要避免修改的操作&#xff0c;而选用扩展的方式。 工厂模式设计 简单工厂模式只有一个…

【Java】接口 interface

目录 概述 示例代码&#xff1a; 接口成员访问特点 示例代码&#xff1a; 概述 什么是接口 接口就是一种公共的规范标准&#xff0c;只要符合规范标准&#xff0c;大家都可以调用。 Java 中的接口更多的体现在对行为的抽象&#xff01; 1. 接口 用关键字 interface 修饰 pub…

AtCoder Beginner Contest 231(D-F,H)

D - Neighbors (atcoder.jp) &#xff08;1&#xff09;题意 给出M组关系&#xff0c;问是否有一个排列&#xff0c;能表示A[i]和B[i]相邻 &#xff08;2&#xff09;思路 考虑如果有环&#xff0c;显然不能满足排列&#xff0c;因为排列中度数最多为2&#xff0c;若有超过2的显…

JavaScript操作CSS样式

上节课我们基本完成了游戏的主体&#xff0c;这节课我们来学习如果使用JavaScript去操作CSS样式 ● 例如&#xff0c;我们现在想当玩家输入对的数字之后&#xff0c;我们讲背景改为绿色&#xff0c;并且把number的框宽度变大 const secretnumber Math.trunc(Math.random() * …

第十六章 类和对象——运算符重载

运算符重载概念&#xff1a;对已有的运算符重新进行定义&#xff0c;赋予其另一种功能&#xff0c;以适应不同的数据类型。 一、加号运算符重载 作用&#xff1a;实现两个自定义数据类型相加的运算 class Person {public:Person() {};Person(int a, int b){this->m_A a;t…

5个适合初学者的初级网络安全工作,网络安全就业必看

前言 网络安全涉及保护计算机系统、网络和数据免受未经授权的访问、破坏和盗窃 - 防止数字活动和数据访问的中断 - 同时也保护用户的资产和隐私。鉴于公共事业、医疗保健、金融以及联邦政府等行业的网络犯罪攻击不断升级&#xff0c;对网络专业人员的需求很高&#xff0c;这并…

【异常错误】WSL2设置为全核cpu和全部内存

今天偶尔发现 WSL占用的内存是真实内存的一半&#xff08;通过htop命令查看即可&#xff09; 现在需要修改配置到使用全部的CPU资源&#xff1a; Windows R 键&#xff0c; 输入 %UserProfile% 并运行进入用户文件夹, 新建文件 .wslconfig&#xff0c;文件内容如下&#xf…

light client轻节点简介

1. 引言 前序博客&#xff1a; Helios——a16z crypto构建的去中心化以太坊轻节点 去中心化和自我主权对于Web3的未来至关重要,但是这些理想并不总适用于每个项目或应用程序。在非托管钱包和bridges等工具中严格优先考虑安全性而不是便利性的用户&#xff0c;可选择运行全节…

【JavaEE】多线程(五)- 基础知识完结篇

多线程&#xff08;五&#xff09; 文章目录 多线程&#xff08;五&#xff09;volatile关键字保证内存可见性JMM&#xff08;Java Memory Model&#xff09; 不保证原子性 wait 和 notifywait()notify()线程饿死 上文我们主要讲了 synchronized以及线程安全的一些话题 可重入…

【Unity】3D贪吃蛇游戏制作/WebGL本地测试及项目部署

本文是Unity3D贪吃蛇游戏从制作到部署的相关细节 项目开源代码&#xff1a;https://github.com/zstar1003/3D_Snake 试玩链接&#xff1a;http://xdxsb.top/Snake_Game_3D 效果预览&#xff1a; 试玩链接中的内容会和该效果图略有不同&#xff0c;后面会详细说明。 游戏规则 …

图像分割中的色块的提取

一 色块提取方法&#xff1a; ①首先是色彩模型的转换 由RGB颜色空间转到HSV颜色空间 原因&#xff1a;RGB颜色空间适合显示系统&#xff0c;但是各分量间相关性很强&#xff0c;比如当图像亮度发生变化时&#xff0c;RGB三个分量都会发生相应改变 但是HSV颜色空间更能感知颜色…

【Java 进阶篇】JDBC 数据库连接池 C3P0 详解

数据库连接池是数据库编程中常用的一种技术&#xff0c;它可以有效地管理数据库连接&#xff0c;提高数据库访问的性能和效率。在 Java 编程中&#xff0c;有多种数据库连接池可供选择&#xff0c;其中之一就是 C3P0。本文将详细介绍 C3P0 数据库连接池的使用&#xff0c;包括原…

LabVIEW使用ZigBee无线传感器开发住宅负载电力应用

LabVIEW使用ZigBee无线传感器开发住宅负载电力应用 长期以来&#xff0c;住宅客户的需求一直是电力行业的一部分。由于公用事业需要建设基础设施以满足即时和长期需求&#xff0c;因此公用事业账单既包含能源费用&#xff0c;其中衡量客户随时间消耗的总电量&#xff0c;也包含…

网络攻击常见手段总结

网络攻击常见手段总结 IP 欺骗 IP 欺骗技术是什么&#xff1f; IP 欺骗技术就是伪造某台主机的 IP 地址的技术。通过 IP 地址的伪装使得某台主机能够伪装另外的一台主机&#xff0c;而这台主机往往具有某种特权或者被另外的主机所信任。 攻击时&#xff0c;伪造大量的 IP 地…

文件操作 和 IO - 详解

一&#xff0c;认识文件 1.1 树形结构组织和目录 文件是对于"硬盘"数据的一种抽象&#xff0c;在一台计算机上&#xff0c;有非常多的文件&#xff0c;这些文件是通过 "文件系统" 来进行组织的&#xff0c;本质上就是通过 "目录"(文件夹) 这样…