Android實(shí)現(xiàn)氣泡布局/彈窗效果 氣泡尖角方向及偏移量可控
Android 自定義布局實(shí)現(xiàn)氣泡彈窗,可控制氣泡尖角方向及偏移量。
效果圖

實(shí)現(xiàn)
首先自定義一個(gè)氣泡布局。
/**
* 氣泡布局
*/
public class BubbleRelativeLayout extends RelativeLayout {
/**
* 氣泡尖角方向
*/
public enum BubbleLegOrientation {
TOP, LEFT, RIGHT, BOTTOM, NONE
}
public static int PADDING = 30;
public static int LEG_HALF_BASE = 30;
public static float STROKE_WIDTH = 2.0f;
public static float CORNER_RADIUS = 8.0f;
public static int SHADOW_COLOR = Color.argb(100, 0, 0, 0);
public static float MIN_LEG_DISTANCE = PADDING + LEG_HALF_BASE;
private Paint mFillPaint = null;
private final Path mPath = new Path();
private final Path mBubbleLegPrototype = new Path();
private final Paint mPaint = new Paint(Paint.DITHER_FLAG);
private float mBubbleLegOffset = 0.75f;
private BubbleLegOrientation mBubbleOrientation = BubbleLegOrientation.LEFT;
public BubbleRelativeLayout(Context context) {
this(context, null);
}
public BubbleRelativeLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BubbleRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
private void init(final Context context, final AttributeSet attrs) {
//setGravity(Gravity.CENTER);
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
setLayoutParams(params);
if (attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.bubble);
try {
PADDING = a.getDimensionPixelSize(R.styleable.bubble_padding, PADDING);
SHADOW_COLOR = a.getInt(R.styleable.bubble_shadowColor, SHADOW_COLOR);
LEG_HALF_BASE = a.getDimensionPixelSize(R.styleable.bubble_halfBaseOfLeg, LEG_HALF_BASE);
MIN_LEG_DISTANCE = PADDING + LEG_HALF_BASE;
STROKE_WIDTH = a.getFloat(R.styleable.bubble_strokeWidth, STROKE_WIDTH);
CORNER_RADIUS = a.getFloat(R.styleable.bubble_cornerRadius, CORNER_RADIUS);
} finally {
if (a != null) {
a.recycle();
}
}
}
mPaint.setColor(SHADOW_COLOR);
mPaint.setStyle(Style.FILL);
mPaint.setStrokeCap(Cap.BUTT);
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(STROKE_WIDTH);
mPaint.setStrokeJoin(Paint.Join.MITER);
mPaint.setPathEffect(new CornerPathEffect(CORNER_RADIUS));
if (Build.VERSION.SDK_INT >= 11) {
setLayerType(LAYER_TYPE_SOFTWARE, mPaint);
}
mFillPaint = new Paint(mPaint);
mFillPaint.setColor(Color.WHITE);
mFillPaint.setShader(new LinearGradient(100f, 0f, 100f, 200f, Color.WHITE, Color.WHITE, TileMode.CLAMP));
if (Build.VERSION.SDK_INT >= 11) {
setLayerType(LAYER_TYPE_SOFTWARE, mFillPaint);
}
mPaint.setShadowLayer(2f, 2F, 5F, SHADOW_COLOR);
renderBubbleLegPrototype();
setPadding(PADDING, PADDING, PADDING, PADDING);
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
/**
* 尖角path
*/
private void renderBubbleLegPrototype() {
mBubbleLegPrototype.moveTo(0, 0);
mBubbleLegPrototype.lineTo(PADDING * 1.5f, -PADDING / 1.5f);
mBubbleLegPrototype.lineTo(PADDING * 1.5f, PADDING / 1.5f);
mBubbleLegPrototype.close();
}
public void setBubbleParams(final BubbleLegOrientation bubbleOrientation, final float bubbleOffset) {
mBubbleLegOffset = bubbleOffset;
mBubbleOrientation = bubbleOrientation;
}
/**
* 根據(jù)顯示方向,獲取尖角位置矩陣
* @param width
* @param height
* @return
*/
private Matrix renderBubbleLegMatrix(final float width, final float height) {
final float offset = Math.max(mBubbleLegOffset, MIN_LEG_DISTANCE);
float dstX = 0;
float dstY = Math.min(offset, height - MIN_LEG_DISTANCE);
final Matrix matrix = new Matrix();
switch (mBubbleOrientation) {
case TOP:
dstX = Math.min(offset, width - MIN_LEG_DISTANCE);
dstY = 0;
matrix.postRotate(90);
break;
case RIGHT:
dstX = width;
dstY = Math.min(offset, height - MIN_LEG_DISTANCE);
matrix.postRotate(180);
break;
case BOTTOM:
dstX = Math.min(offset, width - MIN_LEG_DISTANCE);
dstY = height;
matrix.postRotate(270);
break;
}
matrix.postTranslate(dstX, dstY);
return matrix;
}
@Override
protected void onDraw(Canvas canvas) {
final float width = canvas.getWidth();
final float height = canvas.getHeight();
mPath.rewind();
mPath.addRoundRect(new RectF(PADDING, PADDING, width - PADDING, height - PADDING), CORNER_RADIUS, CORNER_RADIUS, Direction.CW);
mPath.addPath(mBubbleLegPrototype, renderBubbleLegMatrix(width, height));
canvas.drawPath(mPath, mPaint);
canvas.scale((width - STROKE_WIDTH) / width, (height - STROKE_WIDTH) / height, width / 2f, height / 2f);
canvas.drawPath(mPath, mFillPaint);
}
}
樣式 attrs.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="bubble"> <attr name="shadowColor" format="color" /> <attr name="padding" format="dimension" /> <attr name="strokeWidth" format="float" /> <attr name="cornerRadius" format="float" /> <attr name="halfBaseOfLeg" format="dimension" /> </declare-styleable> </resources>
然后自定義一個(gè)PopupWindow,用于顯示氣泡。
public class BubblePopupWindow extends PopupWindow {
private BubbleRelativeLayout bubbleView;
private Context context;
public BubblePopupWindow(Context context) {
this.context = context;
setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
setFocusable(true);
setOutsideTouchable(false);
setClippingEnabled(false);
ColorDrawable dw = new ColorDrawable(0);
setBackgroundDrawable(dw);
}
public void setBubbleView(View view) {
bubbleView = new BubbleRelativeLayout(context);
bubbleView.setBackgroundColor(Color.TRANSPARENT);
bubbleView.addView(view);
setContentView(bubbleView);
}
public void setParam(int width, int height) {
setWidth(width);
setHeight(height);
}
public void show(View parent) {
show(parent, Gravity.TOP, getMeasuredWidth() / 2);
}
public void show(View parent, int gravity) {
show(parent, gravity, getMeasuredWidth() / 2);
}
/**
* 顯示彈窗
*
* @param parent
* @param gravity
* @param bubbleOffset 氣泡尖角位置偏移量。默認(rèn)位于中間
*/
public void show(View parent, int gravity, float bubbleOffset) {
BubbleRelativeLayout.BubbleLegOrientation orientation = BubbleRelativeLayout.BubbleLegOrientation.LEFT;
if (!this.isShowing()) {
switch (gravity) {
case Gravity.BOTTOM:
orientation = BubbleRelativeLayout.BubbleLegOrientation.TOP;
break;
case Gravity.TOP:
orientation = BubbleRelativeLayout.BubbleLegOrientation.BOTTOM;
break;
case Gravity.RIGHT:
orientation = BubbleRelativeLayout.BubbleLegOrientation.LEFT;
break;
case Gravity.LEFT:
orientation = BubbleRelativeLayout.BubbleLegOrientation.RIGHT;
break;
default:
break;
}
bubbleView.setBubbleParams(orientation, bubbleOffset); // 設(shè)置氣泡布局方向及尖角偏移
int[] location = new int[2];
parent.getLocationOnScreen(location);
switch (gravity) {
case Gravity.BOTTOM:
showAsDropDown(parent);
break;
case Gravity.TOP:
showAtLocation(parent, Gravity.NO_GRAVITY, location[0], location[1] - getMeasureHeight());
break;
case Gravity.RIGHT:
showAtLocation(parent, Gravity.NO_GRAVITY, location[0] + parent.getWidth(), location[1] - (parent.getHeight() / 2));
break;
case Gravity.LEFT:
showAtLocation(parent, Gravity.NO_GRAVITY, location[0] - getMeasuredWidth(), location[1] - (parent.getHeight() / 2));
break;
default:
break;
}
} else {
this.dismiss();
}
}
/**
* 測(cè)量高度
*
* @return
*/
public int getMeasureHeight() {
getContentView().measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
int popHeight = getContentView().getMeasuredHeight();
return popHeight;
}
/**
* 測(cè)量寬度
*
* @return
*/
public int getMeasuredWidth() {
getContentView().measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
int popWidth = getContentView().getMeasuredWidth();
return popWidth;
}
}
view_popup_window.xml
<?xml version="1.0" encoding="utf-8"?> <com.yuyh.library.BubbleRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/brlBackground" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@android:color/transparent" app:cornerRadius="10" app:halfBaseOfLeg="18dp" app:padding="18dp" app:shadowColor="#64000000" app:strokeWidth="5"> </com.yuyh.library.BubbleRelativeLayout>
調(diào)用
BubblePopupWindow leftTopWindow = new BubblePopupWindow(MainActivity.this);
View bubbleView = inflater.inflate(R.layout.layout_popup_view, null);
TextView tvContent = (TextView) bubbleView.findViewById(R.id.tvContent);
tvContent.setText("HelloWorld");
leftTopWindow.setBubbleView(bubbleView); // 設(shè)置氣泡內(nèi)容
leftTopWindow.show(view, Gravity.BOTTOM, 0); // 顯示彈窗
依賴(lài)
dependencies {
compile 'com.yuyh.bubble:library:1.0.0'
}
項(xiàng)目地址:https://github.com/smuyyh/BubblePopupWindow
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android屏幕鎖屏彈窗的正確姿勢(shì)DEMO詳解
- Android仿支付寶支付從底部彈窗效果
- Android監(jiān)聽(tīng)輸入法彈窗和關(guān)閉的實(shí)現(xiàn)方法
- Android仿支付寶微信支付密碼界面彈窗封裝dialog
- Android如何實(shí)現(xiàn)鎖屏狀態(tài)下彈窗
- Android開(kāi)發(fā)實(shí)現(xiàn)仿京東商品搜索選項(xiàng)卡彈窗功能
- Android實(shí)現(xiàn)隱私政策彈窗與鏈接功能
- Android實(shí)現(xiàn)彈窗進(jìn)度條效果
- Android自定義彈窗提醒控件使用詳解
- Android?Studio實(shí)現(xiàn)彈窗設(shè)置
相關(guān)文章
Android 系統(tǒng)相機(jī)拍照后相片無(wú)法在相冊(cè)中顯示解決辦法
這篇文章主要介紹了Android 系統(tǒng)相機(jī)拍照后相片無(wú)法在相冊(cè)中顯示解決辦法的相關(guān)資料,需要的朋友可以參考下2016-12-12
Android編程設(shè)計(jì)模式之工廠(chǎng)方法模式實(shí)例詳解
這篇文章主要介紹了Android編程設(shè)計(jì)模式之工廠(chǎng)方法模式,結(jié)合實(shí)例形式詳細(xì)分析了Android工廠(chǎng)方法模式的概念、原理、使用方法及相關(guān)注意事項(xiàng),需要的朋友可以參考下2017-12-12
Android編程實(shí)現(xiàn)給Button添加圖片和文字的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)給Button添加圖片和文字的方法,涉及Android針對(duì)按鈕元素屬性的相關(guān)操作技巧,需要的朋友可以參考下2015-11-11
Android利用滑動(dòng)菜單框架實(shí)現(xiàn)滑動(dòng)菜單效果
這篇文章主要介紹了Android實(shí)現(xiàn)滑動(dòng)菜單特效之滑動(dòng)菜單框架完全解析,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05
Android模擬器接收UDP數(shù)據(jù)包的若干問(wèn)題分析
這篇文章主要介紹了Android模擬器接收UDP數(shù)據(jù)包的若干問(wèn)題,結(jié)合實(shí)例形式較為詳細(xì)的分析了Android模擬器接收UDP數(shù)據(jù)的使用方法與相關(guān)注意事項(xiàng),需要的朋友可以參考下2016-04-04
Android中RecyclerView實(shí)現(xiàn)分頁(yè)滾動(dòng)的方法詳解
RecyclerView實(shí)現(xiàn)滾動(dòng)相信對(duì)大家來(lái)說(shuō)都不陌生,但是本文主要給大家介紹了利用Android中RecyclerView實(shí)現(xiàn)分頁(yè)滾動(dòng)的思路和方法,可以實(shí)現(xiàn)翻頁(yè)功能,一次翻一頁(yè),也可以實(shí)現(xiàn)翻至某一頁(yè)功能。文中給出了詳細(xì)的示例代碼,需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-04-04
Android Studio preview 不固定及常見(jiàn)問(wèn)題的解決辦法
preview 可以幫助您預(yù)覽您的布局文件將如何在用戶(hù)的設(shè)備上呈現(xiàn)。這篇文章主要介紹了Android Studio preview 不固定及常見(jiàn)問(wèn)題的解決辦法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05
Android 實(shí)現(xiàn)IOS 滾輪選擇控件的實(shí)例(源碼下載)
這篇文章主要介紹了 Android 實(shí)現(xiàn)IOS 滾輪選擇控件的實(shí)例(源碼下載)的相關(guān)資料,需要的朋友可以參考下2017-03-03
在Flutter中制作翻轉(zhuǎn)卡片動(dòng)畫(huà)的完整實(shí)例代碼
最近Flutter的勢(shì)頭是越來(lái)越猛了,作為一個(gè)Android程序猿,我自然也是想要趕緊嘗試一把,這篇文章主要給大家介紹了關(guān)于在Flutter中制作翻轉(zhuǎn)卡片動(dòng)畫(huà)的相關(guān)資料,需要的朋友可以參考下2021-10-10
Android編程實(shí)現(xiàn)通訊錄中聯(lián)系人的讀取,查詢(xún),添加功能示例
這篇文章主要介紹了Android編程實(shí)現(xiàn)通訊錄中聯(lián)系人的讀取,查詢(xún),添加功能,涉及Android權(quán)限控制及通訊錄相關(guān)操作技巧,需要的朋友可以參考下2017-07-07

