Android 自定义带箭头对话框背景

简介

自定义drawable,带箭头对话框背景,三角形+矩形组合。应用于对话框背景、提示语背景等。

可设置箭头显示方向、箭头大小、箭头导圆角尺寸、矩形尺寸、矩形导圆角尺寸、背景颜色、drawable padding值(影响宿主控件padding)。

希望能给大家带来方便。

一、效果图

 示例代码:

        ArrowsDrawable drawable = new ArrowsDrawable(ArrowsDrawable.RIGHT, ConvertUtils.dp2px(8));
        drawable.setArrowsRadius(ConvertUtils.dp2px(3));
        drawable.setArrowsHeight(ConvertUtils.dp2px(8));
        drawable.setArrowsWidth(ConvertUtils.dp2px(12));
        drawable.setPadding(ConvertUtils.dp2px(10));
        drawable.setColor(Color.DKGRAY);
        textView.setBackground(drawable);

二、API详解

构造方法

//箭头方向,箭头高度 
public ArrowsDrawable(@Position int arrowsPosition, float arrowsHeight)

设置drawable padding

drawable设置padding之后,会影响使用该drawable控件的padding。例如示例中drawable设置了padding,宿主控件是TextView, 即该TextView也会设置了相同的padding值,这是TextView的特性。另外,该api将箭头高度加到对应的padding值中了,比如箭头方向朝上,drawable设置4个padding均为10dp, 那么drawable paddingTop的值 = 10dp + 箭头高度值,其余均是10dp。

 public void setPadding(int p) 
 public void setPadding(int left, int top, int right, int bottom) 
设置箭头方向
LEFT, RIGHT, TOP, BOTTOM

备注:设置箭头方向,如果该drawable的宿主控件已经显示,需要动态设置箭头方向,需要宿主控件再次设置backgroup或src,否则drawable的padding不会生效。

public void setArrowsPosition(@Position int arrowsPosition)
public void setArrowsPosition(@Position int arrowsPosition, View hostView) 
设置箭头距离矩形边(相对于矩形上边或左边)的距离, 默认10dp
    public void setArrowsPadding(float arrowsPadding)
设置箭头圆角

设置箭头突出角的圆角尺寸,给该角导圆角,默认2dp

 public void setArrowsRadius(float arrowsRadius)

其它API

//设置箭头高度
        drawable.setArrowsHeight(ConvertUtils.dp2px(8));
        //设置箭头宽度,默认是箭头高度的2倍
        drawable.setArrowsWidth(ConvertUtils.dp2px(12));
        //设置背景色
        drawable.setColor(Color.DKGRAY);

扩展

如果 drawable需要设置margin效果, 可以通过InsetDrawable实现。

        InsetDrawable insetDrawable = new InsetDrawable(arrowsDrawable, padding);
        tv.setBackground(insetDrawable);

三、源码

package com.ttkx.deviceinfo.bkchart;

import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.GradientDrawable;
import android.view.Gravity;
import android.view.View;
import android.widget.ImageView;

import com.blankj.utilcode.util.ConvertUtils;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import androidx.annotation.IntDef;


/**
 * 带箭头的Drawable
 * <p>
 * Created by liuyu
 * on 2021/2/4
 */
public class ArrowsDrawable extends GradientDrawable {

    //箭头显示位置
    public static final int LEFT = Gravity.LEFT;
    public static final int TOP = Gravity.TOP;
    public static final int RIGHT = Gravity.RIGHT;
    public static final int BOTTOM = Gravity.BOTTOM;
    private int mLeft;
    private int mTop;
    private int mRight;
    private int mBottom;
    private Rect mPadding;

    @IntDef({LEFT, RIGHT, TOP, BOTTOM})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Position {
    }

    private Paint mPaint;
    private int arrowsPosition = BOTTOM;
    private float arrowsRadius = dp2px(2);
    private float arrowsHeight = dp2px(5);
    private float arrowsWidth;
    private float mRadius = dp2px(8);
    private float arrowsPadding = mRadius + dp2px(10);//箭头padding
    private Path mPath;

    public ArrowsDrawable(@Position int arrowsPosition, float arrowsHeight) {
        this.arrowsPosition = arrowsPosition;
        this.arrowsHeight = arrowsHeight;
        initPaint();
    }

    @Override
    public boolean getPadding(Rect padding) {
        if (mPadding != null) {
            padding.set(mPadding);
            return true;
        } else {
            return super.getPadding(padding);
        }
    }

    public void setPadding(int p) {
        setPadding(p, p, p, p);
    }

    /**
     * 设置drawable padding
     *
     * @param left
     * @param top
     * @param right
     * @param bottom
     */
    public void setPadding(int left, int top, int right, int bottom) {
        mLeft = left;
        mTop = top;
        mRight = right;
        mBottom = bottom;
        if (mPadding == null) {
            mPadding = new Rect();
        }
        switch (arrowsPosition) {
            case LEFT:
                left += arrowsHeight;
                break;
            case TOP:
                top += arrowsHeight;
                break;
            case RIGHT:
                right += arrowsHeight;
                break;
            default:
                bottom += arrowsHeight;
                break;
        }
        mPadding.set(left, top, right, bottom);
        invalidateSelf();
        //super.setPadding(left, top, right, bottom);api=29,10.0
    }

    /**
     * 设置箭头方向
     * <p>
     * LEFT, RIGHT, TOP, BOTTOM
     *
     * @param arrowsPosition 箭头方向
     * @param hostView           宿主view (设置宿主view,是为了当箭头方向发生改变时,drawable的padding值也会改变,hostView重新设置drawable,让其执行getPadding(Rect rect)方法,让自定义的padding生效)
     */
    public void setArrowsPosition(@Position int arrowsPosition, View hostView) {
        boolean update = arrowsPosition != this.arrowsPosition;
        setArrowsPosition(arrowsPosition);
        if (update && hostView != null) {
            if (hostView instanceof ImageView && ((ImageView) hostView).getDrawable() != null) {
                ((ImageView) hostView).setImageDrawable(this);
            } else {
                hostView.setBackground(this);
            }
        }
    }

    public void setArrowsPosition(@Position int arrowsPosition) {
        this.arrowsPosition = arrowsPosition;
        setPadding(mLeft, mTop, mRight, mBottom);
        invalidateSelf();
    }

    /**
     * 设置箭头圆角
     *
     * @param arrowsRadius
     */
    public void setArrowsRadius(float arrowsRadius) {
        this.arrowsRadius = arrowsRadius;
        invalidateSelf();
    }

    /**
     * 设置箭头高度
     *
     * @param arrowsHeight
     */
    public void setArrowsHeight(float arrowsHeight) {
        this.arrowsHeight = arrowsHeight;
        invalidateSelf();
    }

    /**
     * 设置箭头宽度
     *
     * @param arrowsWidth
     */
    public void setArrowsWidth(float arrowsWidth) {
        this.arrowsWidth = arrowsWidth;
    }

    /**
     * 设置箭头离矩形边的距离(参照:矩形的左边或上边)
     *
     * @param arrowsPadding
     */
    public void setArrowsPadding(float arrowsPadding) {
        this.arrowsPadding = arrowsPadding;
        invalidateSelf();
    }

    private void initPaint() {
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setStrokeWidth(1);
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.RED);

        mPath = new Path();
    }

    @Override
    protected void onBoundsChange(Rect r) {
        super.onBoundsChange(r);
        int width = r.width();
        int height = r.height();

        float aw = getArrowsWidth();
        float ar = aw / 2;

        //三个点的坐标和矩形
        float px1 = r.left + arrowsPadding;
        float py1 = r.top + height - this.arrowsHeight;
        float px2 = px1 + aw;
        float py2 = py1;
        float px3 = px1 + ar;
        float py3 = py1 + this.arrowsHeight;
        //矩形起始点坐标
        float sx = r.left;
        float sy = r.top;
        //矩形宽高度
        float rectWidth = width;
        float rectHeight = height - this.arrowsHeight;
        switch (arrowsPosition) {
            case LEFT:
                px1 = r.left + this.arrowsHeight;
                py1 = r.top + arrowsPadding;
                px2 = px1;
                py2 = py1 + aw;
                px3 = px1 - this.arrowsHeight;
                py3 = py1 + ar;

                sx = px1;
                sy = r.top;
                rectWidth = width - this.arrowsHeight;
                rectHeight = height;
                break;
            case TOP:
                px1 = r.left + arrowsPadding;
                py1 = r.top + this.arrowsHeight;
                px2 = px1 + aw;
                py2 = py1;
                px3 = px1 + ar;
                py3 = py1 - this.arrowsHeight;

                sx = r.left;
                sy = r.top + this.arrowsHeight;
                rectWidth = width;
                rectHeight = height - this.arrowsHeight;
                break;
            case RIGHT:
                px1 = r.left + width - this.arrowsHeight;
                py1 = r.top + arrowsPadding;
                px2 = px1;
                py2 = py1 + aw;
                px3 = px1 + this.arrowsHeight;
                py3 = py1 + ar;

                sx = r.left;
                sy = r.top;
                rectWidth = width - this.arrowsHeight;
                rectHeight = height;
                break;
        }

        mPath.reset();
        addTrianglePath(mPath, arrowsRadius, px1, py1, px2, py2, px3, py3);

        RectF rectF = new RectF(sx, sy, sx + rectWidth, sy + rectHeight);
        mPath.addRoundRect(rectF, mRadius, mRadius, Path.Direction.CW);
    }

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
        canvas.drawPath(mPath, mPaint);
    }

    /**
     * 添加三角形path
     *
     * @param path  路径
     * @param radiu 三角形突出角圆角半径
     * @param x1    与内容框连接1点X坐标 (起始点)
     * @param y1    与内容框连接1点y坐标
     * @param x2    与内容框连接2点X坐标
     * @param y2    与内容框连接2点Xy坐标
     * @param x3    三角形突出角x坐标
     * @param y3    三角形突出角y坐标
     */
    private void addTrianglePath(Path path, float radiu, float x1, float y1, float x2, float y2, float x3, float y3) {
        //起始点
        path.moveTo(x1, y1);
        path.lineTo(x2, y2);
        float[] ps2 = getCornerCoordinateEnd(radiu, x2, y2, x3, y3);
        path.lineTo(ps2[0], ps2[1]);
        float[] ps3 = getCornerCoordinateStart(radiu, x3, y3, x1, y1);
        //绘制圆角
        path.cubicTo(ps2[0], ps2[1], x3, y3, ps3[0], ps3[1]);
        //关闭三角形
        path.close();
    }

    /**
     * 获取圆角坐标(圆角半径长度相对于起始点x1,y1)
     *
     * @param radiu 圆角半径
     * @param x1    起始点x坐标
     * @param y1    起始点y坐标
     * @param x2    结束点x坐标
     * @param y2    结束点y坐标
     * @return
     */
    private float[] getCornerCoordinateStart(float radiu, float x1, float y1, float x2, float y2) {
        double degree = MathHelper.getDegree(x1, y1, x2, y2);
        double lx = MathHelper.getRightSideFromDegree(degree, radiu);
        double ly = MathHelper.getLeftSideFromDegree(degree, radiu);
        double v2 = x1 + lx;
        double v3 = y1 + ly;
        return new float[]{(float) v2, (float) v3};
    }

    /**
     * 获取圆角坐标(圆角半径长度相对于结束点x2,y2)
     *
     * @param radiu 圆角半径
     * @param x1    起始点x坐标
     * @param y1    起始点y坐标
     * @param x2    结束点x坐标
     * @param y2    结束点y坐标
     * @return
     */
    private float[] getCornerCoordinateEnd(float radiu, float x1, float y1, float x2, float y2) {
        double degree = MathHelper.getDegree(x1, y1, x2, y2);
        double lx = MathHelper.getRightSideFromDegree(degree, radiu);
        double ly = MathHelper.getLeftSideFromDegree(degree, radiu);
        double v2 = x2 - lx;
        double v3 = y2 - ly;
        return new float[]{(float) v2, (float) v3};
    }

    @Override
    public void setColor(int color) {
        if (mPaint != null) {
            mPaint.setColor(color);
        }
        invalidateSelf();
    }

    /**
     * 设置矩形的圆角
     *
     * @param radius
     */
    @Override
    public void setCornerRadius(float radius) {
        mRadius = radius;
    }

    public float getArrowsHeight() {
        return arrowsHeight;
    }

    public int getArrowsPosition() {
        return arrowsPosition;
    }

    public float getArrowsRadius() {
        return arrowsRadius;
    }

    public float getArrowsWidth() {
        return arrowsWidth <= 0 ? getArrowsHeight() * 2 : arrowsWidth;
    }

    public float getRadius() {
        return mRadius;
    }

    public float getArrowsPadding() {
        return arrowsPadding;
    }

    public float getArrowsCenterDistance() {
        return (int) (getArrowsPadding() + getArrowsWidth() / 2);
    }

    private static int dp2px(final float dpValue) {
        final float scale = Resources.getSystem().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }
}



public class MathHelper {

    private static MathHelper instance = null;

    //Position位置
    private float mPosX = 0.0f;
    private float mPosY = 0.0f;
    private PointF mPointF = new PointF();

    //除法运算精度
    private static final int DEFAULT_DIV_SCALE = 10;

    private MathHelper() {
    }

    public static synchronized MathHelper getInstance() {
        if (instance == null) {
            instance = new MathHelper();
        }
        return instance;
    }

    //两点间的角度
    public static double getDegree(float sx, float sy, float tx, float ty) {
        float nX = tx - sx;
        float nY = ty - sy;
        double angrad = 0d, angel = 0d, tpi = 0d;
        float tan = 0.0f;
        if (Float.compare(nX, 0.0f) != 0) {
            tan = Math.abs(nY / nX);
            angel = Math.atan(tan);
            if (Float.compare(nX, 0.0f) == 1) {
                if (Float.compare(nY, 0.0f) == 1 || Float.compare(nY, 0.0f) == 0) {
                    angrad = angel;
                } else {
                    angrad = 2 * Math.PI - angel;
                }
            } else {
                if (Float.compare(nY, 0.0f) == 1 || Float.compare(nY, 0.0f) == 0) {
                    angrad = Math.PI - angel;
                } else {
                    angrad = Math.PI + angel;
                }
            }
        } else {
            tpi = Math.PI / 2;
            if (Float.compare(nY, 0.0f) == 1) {
                angrad = tpi;
            } else {
                angrad = -1 * tpi;
            }
        }
        return Math.toDegrees(angrad);
    }

    /**
     * 直角三角形 根据角度和斜边求直角边
     *
     * @param degree 角度
     * @param width  斜边
     * @return 直角边长
     */
    public static double getRightSideFromDegree(double degree, double width) {
        double cos = Math.cos(Math.toRadians(degree));
        return width * cos;
    }

    public static double getLeftSideFromDegree(double degree, double width) {
        double sin = Math.sin(Math.toRadians(degree));
        return width * sin;
    }

}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值