Summary of triangle bubble effect in Android

Time:2021-5-10

In the development process, we may often encounter such requirement styles:

Summary of triangle bubble effect in Android

This picture is a pop-up box for intercepting Jingdong’s message notification. We can see the bubble effect of a triangle on the top right. This is just one of them. The direction of the triangle can also be up, down, left and right.

Through the screenshot, we can find that the bubble is composed of an equilateral triangle and a rounded rectangle, so we can combine them to form the effect of a triangular bubble. Next, we can achieve it in three ways.

Implementation mode:

1. It is realized by. 9 diagram;
2. It is realized by shape;
3. It is realized by the way of user-defined view;

Implementation logic:

1. Through. 9 diagram to achieve

This way, needless to say, find your sister UI cut a. 9 picture, you can use it, but this way of pictures need to occupy a certain volume.

2. It is realized by shape

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <rotate
            android:fromDegrees="45"
            android:pivotX="-40%"
            android:pivotY="80%">
            <shape android:shape="rectangle">
                <size
                    android:width="15dp"
                    android:height="15dp" />
                <solid android:color="#ffffff" />
            </shape>
        </rotate>
    </item>
</layer-list>
  • Inverted triangle
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
     <item>
        <rotate
            android:fromDegrees="45"
            android:pivotX="135%"
            android:pivotY="15%">
            <shape android:shape="rectangle">
                <size
                    android:width="15dp"
                    android:height="15dp" />
                <solid android:color="#ffffff" />
            </shape>
        </rotate>
    </item>
</layer-list>
  • Left triangle
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <rotate
            android:fromDegrees="-45"
            android:pivotX="85%"
            android:pivotY="-35%">>
            <shape android:shape="rectangle">
                <size
                    android:width="15dp"
                    android:height="15dp" />
                <solid android:color="#ffffff" />
            </shape>
        </rotate>
    </item>
</layer-list>
  • Right triangle
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <rotate
            android:fromDegrees="-45"
            android:pivotX="15%"
            android:pivotY="135%">>
            <shape android:shape="rectangle">
                <size
                    android:width="15dp"
                    android:height="15dp" />
                <solid android:color="#ffffff" />
            </shape>
        </rotate>
    </item>
</layer-list>

The above is to realize the code in all directions through shape, which has obvious disadvantages. If you want to change the position of different corners, you need to write different layouts.

3. Through the way of custom view

Because it’s relatively simple, I won’t explain how to do it. I can copy the past and use it directly

  • Add custom properties
<declare-styleable name="TriangleView">
        <attr name="trv_color" format="color" />
        <attr name="trv_direction">
            <enum name="top" value="0" />
            <enum name="bottom" value="1" />
            <enum name="right" value="2" />
            <enum name="left" value="3" />
        </attr>
 </declare-styleable>
  • Custom code file

public class TriangleView extends View {
    private static final int TOP = 0;
    private static final int BOTTOM = 1;
    private static final int RIGHT = 2;
    private static final int LEFT = 3;
    private static final int DEFUALT_WIDTH = 10;
    private static final int DEFUALT_HEIGHT = 6;
    private static final int DEFUALT_COLOR = R.color.FFF;
    private Paint mPaint;
    private int mColor;
    private int mWidth;
    private int mHeight;
    private int mDirection;
    private Path mPath;

    public TriangleView(final Context context) {
        this(context, null);
    }

    public TriangleView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TriangleView(final Context context, final AttributeSet attrs, final int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
        TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.TriangleView, 0, 0);
        mColor = typedArray.getColor(R.styleable.TriangleView_trv_color, ContextCompat.getColor(getContext(), DEFUALT_COLOR));
        mDirection = typedArray.getInt(R.styleable.TriangleView_trv_direction, mDirection);
        typedArray.recycle();
        mPaint.setColor(mColor);
    }

    private void init() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.FILL);
        mPath = new Path();
        mDirection = TOP;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth = MeasureSpec.getSize(widthMeasureSpec);
        mHeight = MeasureSpec.getSize(heightMeasureSpec);
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        if (mWidth == 0 || widthMode != MeasureSpec.EXACTLY) {
            mWidth = (int) PixelUtil.dp2px(DEFUALT_WIDTH);
        }
        if (mHeight == 0 || heightMode != MeasureSpec.EXACTLY) {
            mHeight = (int) PixelUtil.dp2px(DEFUALT_HEIGHT);
        }
        setMeasuredDimension(mWidth, mHeight);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        switch (mDirection) {
            case TOP:
                mPath.moveTo(0, mHeight);
                mPath.lineTo(mWidth, mHeight);
                mPath.lineTo(mWidth / 2, 0);
                break;
            case BOTTOM:
                mPath.moveTo(0, 0);
                mPath.lineTo(mWidth / 2, mHeight);
                mPath.lineTo(mWidth, 0);
                break;
            case RIGHT:
                mPath.moveTo(0, 0);
                mPath.lineTo(0, mHeight);
                mPath.lineTo(mWidth, mHeight / 2);
                break;
            case LEFT:
                mPath.moveTo(0, mHeight / 2);
                mPath.lineTo(mWidth, mHeight);
                mPath.lineTo(mWidth, 0);
                break;
            default:
                break;
        }

        mPath.close();
        canvas.drawPath(mPath, mPaint);
    }
}
  • Layout file add
<com.sjl.keeplive.triange.TriangleView
        android:layout_width="10dp"
        android:layout_height="6dp"
        app:trv_color="@color/FFF"
        app:trv_direction="top" />

Four directions can be done by the way of customization, and it can also be used in the code. It’s a better way to add and change colors dynamically.

It’s done here