Android 自定义气泡布局

作者: Wang_Yi | 来源:发表于2018-01-16 16:09 被阅读115次

    Demo:

    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地址

    相关文章

      网友评论

        本文标题:Android 自定义气泡布局

        本文链接:https://www.haomeiwen.com/subject/ysnsoxtx.html