Demo:
功能:
- 支援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;
}
}
}
複製程式碼
使用:
- 複製BubbleLayout到專案中
- 複製以下屬性到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>
複製程式碼