Android 自定义气泡布局

Demo:

《Android 自定义气泡布局》 demo.png

功能:

  • 支持4个方向
  • 设置三角形位置
  • 设置阴影颜色,大小
  • 设置圆角
  • 自定义背景颜色

代码:

public class BubbleLayout extends FrameLayout {
    public static final int LEFT = 1;
    public static final int TOP = 2;
    public static final int RIGHT = 3;
    public static final int BOTTOM = 4;

    @IntDef({LEFT, TOP, RIGHT, BOTTOM})
    private @interface Direction {
    }

    /**
     * 圆角大小
     */
    private int mRadius;

    /**
     * 三角形的方向
     */
    @Direction
    private int mDirection;

    /**
     * 三角形的底边中心点
     */
    private Point mDatumPoint;

    /**
     * 三角形位置偏移量(默认居中)
     */
    private int mOffset;

    private Paint mBorderPaint;

    private Path mPath;

    private RectF mRect;

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

    private void init(Context context, AttributeSet attrs) {
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.BubbleLayout);
        //背景颜色
        int backGroundColor = ta.getColor(R.styleable.BubbleLayout_background_color, Color.WHITE);
        //阴影颜色
        int shadowColor = ta.getColor(R.styleable.BubbleLayout_shadow_color,
                Color.parseColor("#999999"));
        int defShadowSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX,
                4, getResources().getDisplayMetrics());
        //阴影尺寸
        int shadowSize = ta.getDimensionPixelSize(R.styleable.BubbleLayout_shadow_size, defShadowSize);
        mRadius = ta.getDimensionPixelSize(R.styleable.BubbleLayout_radius, 0);
        //三角形方向
        mDirection = ta.getInt(R.styleable.BubbleLayout_direction, BOTTOM);
        mOffset = ta.getDimensionPixelOffset(R.styleable.BubbleLayout_offset, 0);
        ta.recycle();

        mBorderPaint = new Paint();
        mBorderPaint.setAntiAlias(true);
        mBorderPaint.setColor(backGroundColor);
        mBorderPaint.setShadowLayer(shadowSize, 0, 0, shadowColor);

        mPath = new Path();
        mRect = new RectF();
        mDatumPoint = new Point();

        setWillNotDraw(false);
        //关闭硬件加速
        setLayerType(LAYER_TYPE_SOFTWARE, null);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mDatumPoint.x > 0 && mDatumPoint.y > 0)
            switch (mDirection) {
                case LEFT:
                    drawLeftTriangle(canvas);
                    break;
                case TOP:
                    drawTopTriangle(canvas);
                    break;
                case RIGHT:
                    drawRightTriangle(canvas);
                    break;
                case BOTTOM:
                    drawBottomTriangle(canvas);
                    break;
            }
    }

    private void drawLeftTriangle(Canvas canvas) {
        int triangularLength = getPaddingLeft();
        if (triangularLength == 0) {
            return;
        }

        mPath.addRoundRect(mRect, mRadius, mRadius, Path.Direction.CCW);
        mPath.moveTo(mDatumPoint.x, mDatumPoint.y - triangularLength / 2);
        mPath.lineTo(mDatumPoint.x - triangularLength / 2, mDatumPoint.y);
        mPath.lineTo(mDatumPoint.x, mDatumPoint.y + triangularLength / 2);
        mPath.close();
        canvas.drawPath(mPath, mBorderPaint);
    }

    private void drawTopTriangle(Canvas canvas) {
        int triangularLength = getPaddingTop();
        if (triangularLength == 0) {
            return;
        }

        mPath.addRoundRect(mRect, mRadius, mRadius, Path.Direction.CCW);
        mPath.moveTo(mDatumPoint.x + triangularLength / 2, mDatumPoint.y);
        mPath.lineTo(mDatumPoint.x, mDatumPoint.y - triangularLength / 2);
        mPath.lineTo(mDatumPoint.x - triangularLength / 2, mDatumPoint.y);
        mPath.close();
        canvas.drawPath(mPath, mBorderPaint);
    }

    private void drawRightTriangle(Canvas canvas) {
        int triangularLength = getPaddingRight();
        if (triangularLength == 0) {
            return;
        }

        mPath.addRoundRect(mRect, mRadius, mRadius, Path.Direction.CCW);
        mPath.moveTo(mDatumPoint.x, mDatumPoint.y - triangularLength / 2);
        mPath.lineTo(mDatumPoint.x + triangularLength / 2, mDatumPoint.y);
        mPath.lineTo(mDatumPoint.x, mDatumPoint.y + triangularLength / 2);
        mPath.close();
        canvas.drawPath(mPath, mBorderPaint);
    }

    private void drawBottomTriangle(Canvas canvas) {
        int triangularLength = getPaddingBottom();
        if (triangularLength == 0) {
            return;
        }

        mPath.addRoundRect(mRect, mRadius, mRadius, Path.Direction.CCW);
        mPath.moveTo(mDatumPoint.x + triangularLength / 2, mDatumPoint.y);
        mPath.lineTo(mDatumPoint.x, mDatumPoint.y + triangularLength / 2);
        mPath.lineTo(mDatumPoint.x - triangularLength / 2, mDatumPoint.y);
        mPath.close();
        canvas.drawPath(mPath, mBorderPaint);
    }

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

        mRect.left = getPaddingLeft();
        mRect.top = getPaddingTop();
        mRect.right = w - getPaddingRight();
        mRect.bottom = h - getPaddingBottom();

        switch (mDirection) {
            case LEFT:
                mDatumPoint.x = getPaddingLeft();
                mDatumPoint.y = h / 2;
                break;
            case TOP:
                mDatumPoint.x = w / 2;
                mDatumPoint.y = getPaddingTop();
                break;
            case RIGHT:
                mDatumPoint.x = w - getPaddingRight();
                mDatumPoint.y = h / 2;
                break;
            case BOTTOM:
                mDatumPoint.x = w / 2;
                mDatumPoint.y = h - getPaddingBottom();
                break;
        }

        if (mOffset != 0) {
            applyOffset();
        }
    }

    /**
     * 设置三角形偏移位置
     *
     * @param offset 偏移量
     */
    public void setTriangleOffset(int offset) {
        this.mOffset = offset;
        applyOffset();
        invalidate();
    }

    private void applyOffset() {
        switch (mDirection) {
            case LEFT:
            case RIGHT:
                mDatumPoint.y += mOffset;
                break;
            case TOP:
            case BOTTOM:
                mDatumPoint.x += mOffset;
                break;
        }
    }
}

使用:

  1. 复制BubbleLayout到项目中
  2. 复制以下属性到res/values/attrs中
    <declare-styleable name="BubbleLayout">
        <attr name="background_color" format="color" />
        <attr name="shadow_color" format="color" />
        <attr name="shadow_size" format="dimension" />
        <attr name="radius" format="dimension" />
        <attr name="direction" format="enum">
            <enum name="left" value="1" />
            <enum name="top" value="2" />
            <enum name="right" value="3" />
            <enum name="bottom" value="4" />
        </attr>
        <attr name="offset" format="dimension" />
    </declare-styleable>

3.xml中使用

    <me.wy.app.BubbleLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        //必须设置足够的padding才可以绘制三角形和阴影
        android:padding="16dp"
        //背景颜色
        app:background_color="#FF4081"
        //三角形方向
        app:direction="left"
        //三角形相对偏移量
        app:offset="-40dp"
        //圆角大小
        app:radius="4dp"
        //阴影颜色
        app:shadow_color="#999999"
        //阴影大小
        app:shadow_size="4dp">

        ...
    </me.wy.app.BubbleLayout>

Github地址

    原文作者:Wang_Yi
    原文地址: https://www.jianshu.com/p/dee9c6efefbf
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞