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);
 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,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,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);

 }

 @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,width - PADDING,height - PADDING),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,location[0] + parent.getWidth(),location[1] - (parent.getHeight() / 2));
     break;
    case Gravity.LEFT:
     showAtLocation(parent,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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程小技巧。

以上是来客网为你收集整理的Android实现气泡布局/弹窗效果 气泡尖角方向及偏移量可控全部内容,希望文章能够帮你解决Android实现气泡布局/弹窗效果 气泡尖角方向及偏移量可控所遇到的程序开发问题。

如果觉得来客网网站内容还不错,欢迎将来客网网站推荐给程序员好友。