Android實(shí)現(xiàn)氣泡布局/彈窗效果 氣泡尖角方向及偏移量可控
Android 自定義布局實(shí)現(xiàn)氣泡彈窗,可控制氣泡尖角方向及偏移量。
效果圖
實(shí)現(xiàn)
首先自定義一個氣泡布局。
/** * 氣泡布局 */ 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>
然后自定義一個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(); } } /** * 測量高度 * * @return */ public int getMeasureHeight() { getContentView().measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); int popHeight = getContentView().getMeasuredHeight(); return popHeight; } /** * 測量寬度 * * @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); // 顯示彈窗
依賴
dependencies { compile 'com.yuyh.bubble:library:1.0.0' }
項(xiàng)目地址:https://github.com/smuyyh/BubblePopupWindow
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android屏幕鎖屏彈窗的正確姿勢DEMO詳解
- Android仿支付寶支付從底部彈窗效果
- Android監(jiān)聽輸入法彈窗和關(guān)閉的實(shí)現(xiàn)方法
- Android仿支付寶微信支付密碼界面彈窗封裝dialog
- Android如何實(shí)現(xiàn)鎖屏狀態(tài)下彈窗
- Android開發(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實(shí)現(xiàn)旋轉(zhuǎn),放大,縮小圖片的方法
這篇文章主要介紹了Android實(shí)現(xiàn)旋轉(zhuǎn),放大,縮小圖片的方法,結(jié)合實(shí)例形式分析了Android基于Drawable針對圖片的縮放與旋轉(zhuǎn)等處理技巧,具有一定參考借鑒價值,需要的朋友可以參考下2016-10-10Android多點(diǎn)觸控技術(shù)實(shí)戰(zhàn) 針對圖片自由縮放和移動
這篇文章主要為大家詳細(xì)介紹了Android多點(diǎn)觸控技術(shù)實(shí)戰(zhàn),自由地對圖片進(jìn)行縮放和移動,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-10-10Android實(shí)現(xiàn)快遞物流跟蹤布局效果
本篇文章主要介紹了Android實(shí)現(xiàn)快遞跟蹤布局效果,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-05-05Android中監(jiān)聽系統(tǒng)網(wǎng)絡(luò)連接打開或者關(guān)閉的實(shí)現(xiàn)代碼
本篇文章對Android中監(jiān)聽系統(tǒng)網(wǎng)絡(luò)連接打開或者關(guān)閉的實(shí)現(xiàn)用實(shí)例進(jìn)行了介紹。需要的朋友參考下2013-05-05Android獲取手機(jī)系統(tǒng)版本等信息的方法
這篇文章主要介紹了Android獲取手機(jī)系統(tǒng)版本等信息的方法,涉及Android獲取手機(jī)版本中各種常見信息的技巧,非常具有實(shí)用價值,需要的朋友可以參考下2015-04-04Android自定義View實(shí)現(xiàn)數(shù)字密碼鎖
這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)數(shù)字密碼鎖,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-06-06Android中簡單的電話管理與短信管理App編寫實(shí)例
這篇文章主要介紹了Android中簡單的電話管理與短信管理App編寫實(shí)例,包括監(jiān)聽電話的呼叫狀態(tài)以及短信群發(fā)聯(lián)系人選擇等基本功能的實(shí)現(xiàn),代碼突出要點(diǎn),需要的朋友可以參考下2016-04-04Android開發(fā)實(shí)現(xiàn)撥打電話與發(fā)送信息的方法分析
這篇文章主要介紹了Android開發(fā)實(shí)現(xiàn)撥打電話與發(fā)送信息的方法,結(jié)合實(shí)例形式分析了Android撥打電話及發(fā)送信息相關(guān)布局、功能實(shí)現(xiàn)及權(quán)限控制操作技巧,需要的朋友可以參考下2017-12-12Android通過多點(diǎn)觸控的方式對圖片進(jìn)行縮放的實(shí)例代碼
這篇文章主要介紹了Android通過多點(diǎn)觸控的方式對圖片進(jìn)行縮放的實(shí)例代碼,完成了點(diǎn)擊圖片就能瀏覽大圖的功能,并且在瀏覽大圖的時候還可以通過多點(diǎn)觸控的方式對圖片進(jìn)行縮放。2018-05-05