Android 实现气泡布局/弹窗,可控制气泡尖角方向及偏移量

来源:互联网 发布:ssl证书申请 阿里云 编辑:程序博客网 时间:2024/05/16 05:52

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

<?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 气泡尖角位置偏移量。默认位于中间     */    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

<?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>

调用

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

7 2