編輯:關於Android編程
Android 自定義布局實現氣泡彈窗,可控制氣泡尖角方向及偏移量。
首先自定義一個氣泡布局。
/** * 氣泡布局 */ 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; } /** * 根據顯示方向,獲取尖角位置矩陣 * @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
然後自定義一個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 氣泡尖角位置偏移量。默認位於中間 */ 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); // 設置氣泡布局方向及尖角偏移 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
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); // 設置氣泡內容 leftTopWindow.show(view, Gravity.BOTTOM, 0); // 顯示彈窗
dependencies { compile 'com.yuyh.bubble:library:1.0.0' }
https://github.com/smuyyh/BubblePopupWindow
問題背景: 參考鏈接 做了一個圖片浏覽,用ContentResolver掃描圖庫照片,且嚴格按照時間拍攝順序排好序顯示在listview裡。如下圖所示:遇到的問題是在4.
Android圖表庫MPAndroidChart(十三)——簡約的底部柱狀圖。我們繼續上一講,今天還是說下柱狀圖,這個圖的話應該是用的比較多的,所
在OC中,我們可以通過Category 對已有的類進行擴展,這得益於OC的Runtime機制,讓類可以‘動態’的添加方法以及實現。但是,在Cate
前言:相信Android開發者沒有人不知道Context,每一個工程、每一個組件都在用。但是正是因為太熟悉了,反而不會仔細了解它。這篇文章就來說說大家熟視而無睹的Cont