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
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。