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

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

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

(0)

相关推荐

  • Android贝塞尔曲线初步学习第一课

    贝塞尔曲线有一阶.二阶.三阶.N阶 一阶就是一条直线,有起点终点,没有控制点,对应方法就是 canvas.drawLine(float startX, float startY, float stopX, float stopY, @NonNull Paint paint) ; 二阶为曲线,有起点终点,一个控制点,对应方法就是 path.quadTo(float x1, float y1, float x2, float y2); 其中x1.y1为控制点坐标, x2.y2为终点坐标,效果如下:

  • Android Path绘制贝塞尔曲线实现QQ拖拽泡泡

    这两天学习了使用Path绘制贝塞尔曲线相关,然后自己动手做了一个类似QQ未读消息可拖拽的小气泡,效果图如下: 最终效果图 接下来一步一步的实现整个过程. 基本原理 其实就是使用Path绘制三点的二次方贝塞尔曲线来完成那个妖娆的曲线的.然后根据触摸点不断绘制对应的圆形,根据距离的改变改变原始固定圆形的半径大小.最后就是松手后返回或者爆裂的实现. Path介绍: 顾名思义,就是一个路径的意思,Path里面有很多的方法,本次设计主要用到的相关方法有 moveTo() 移动Path到一个指定的点 qua

  • Android带气泡的第三方Tab选项卡

    效果 依赖 compile 'com.ashokvarma.android:bottom-navigation-bar:1.3.0' OnCreate中初始化 initBottom(); 认选中0 showFragment(0); 然后具体实现 private void showFragment(int position) { FragmentManager supportFragmentManager = getSupportFragmentManager(); FragmentTransac

  • Android把商品添加到购物车的动画效果(贝塞尔曲线)

    当我们写商城类的项目的时候,一般都会有加入购物车的功能,加入购物车的时候会有一些抛物线动画,具体代码如下: 实现效果如图: 思路: 确定动画的起终点 在起终点之间使用二次贝塞尔曲线填充起终点之间的点的轨迹 设置属性动画,ValueAnimator插值器,获取中间点的坐标 将执行动画的控件的x.y坐标设为上面得到的中间点坐标 开启属性动画 当动画结束时的操作 难点: PathMeasure的使用 - getLength() - boolean getPosTan(float distance, f

  • Android贝塞尔曲线初步学习第二课 仿QQ未读消息气泡拖拽黏连效果

    上一节初步了解了Android端的贝塞尔曲线,这一节就举个栗子练习一下,仿QQ未读消息气泡,是最经典的练习贝塞尔曲线的东东,效果如下 附上github源码地址:https://github.com/MonkeyMushroom/DragBubbleView 欢迎star~ 大体思路就是画两个圆,一个黏连小球固定在一个点上,一个气泡小球跟随手指的滑动改变坐标.随着两个圆间距越来越大,黏连小球半径越来越小.当间距小于一定值,松开手指气泡小球会恢复原来位置:当间距超过一定值之后,黏连小球消失,气泡小球

  • Android气泡效果实现方法

    本文实例讲述了Android气泡效果实现方法.分享给大家供大家参考,具体如下: 最近在看以前在eoe上收藏的一些源代码,准备将这些代码加上一些自己的注释,然后贴出来,方便自己日后查阅,和刚入门的人来学习. 今天先看一个气泡窗口,先看一下效果图和目录结构,然后再上代码 通过第一幅图,我们可以看到一个气泡窗口,这个窗口的结构是里面有2个TextView和1个气泡的背景,这个2个TextView放在了overlay_pop.xml文件中,气泡窗口是这个layout的背景,overlay_pop.xml

  • android 仿微信聊天气泡效果实现思路

    微信聊天窗口的信息效果类似iphone上的短信效果,以气泡的形式展现,在Android上,实现这种效果主要用到ListView和BaseAdapter,配合布局以及相关素材,就可以自己做出这个效果,素材可以下一个微信的APK,然后把后缀名改成zip,直接解压,就可以得到微信里面的所有素材了.首先看一下我实现的效果: 以下是工程目录结构: 接下来就是如何实现这个效果的代码: main.xml,这个是主布局文件,显示listview和上下两部分内容. 复制代码 代码如下: <?xml version

  • Android中贝塞尔曲线的绘制方法示例代码

    贝塞尔曲线,很多人可能不太了解,什么叫做贝塞尔曲线呢?这里先做一下简单介绍:贝塞尔曲线也可以叫做贝济埃曲线或者贝兹曲线,它由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋.一般的矢量图形软件常利用贝塞尔曲线来精确画出曲线. 上面的介绍中,"线段像可伸缩的皮筋"这句话非常关键,但也特别好理解.至于贝塞尔曲线的详细内容大家可以查阅相关资料.        Android提供的贝塞尔曲线绘制接口 在Android开发中,要实现贝塞尔曲线其实还是很简单的,因为Android已经给我们提

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

    Android 自定义布局实现气泡弹窗,可控制气泡尖角方向及偏移量. 效果图 实现 首先自定义一个气泡布局. /** * 气泡布局 */ public class BubbleRelativeLayout extends RelativeLayout { /** * 气泡尖角方向 */ public enum BubbleLegOrientation { TOP, LEFT, RIGHT, BOTTOM, NONE } public static int PADDING = 30; public

  • Android PopupWindow实现左侧弹窗效果

    本文实例为大家分享了Android PopupWindow实现左侧弹窗的具体代码,供大家参考,具体内容如下 效果图: MainActivity.java页面核心代码: protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //在setContentView之前添加,未添加的话home键监听无效,设置窗体属性 this.getWindow().setFlags(0x80000

  • WPF气泡样式弹窗效果代码分享

    页面设计需求,做了一个气泡形状的弹出框,效果如下: 设计思路如下: 1. 使用Path绘制气泡的尖尖,将这个放到顶层: 2. 在用border绘制长方形框,将这个放到底层,并且设置Margin值,使得Path图层和border看起来衔接在一起. 代码如下: <Window x:Class="BubblePanelTest.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentatio

  • Android实现底部支付弹窗效果

    Android底部支付弹窗实现的效果: 实现的思路: 1.通过继承PopupWindow自定义View来达到弹窗的弹出效果: 2.通过回调将输入的密码由弹窗传入到主界面中: 2.恩,这就够了-->有些注意点在代码中备注: 自定义View的代码: public class BottomDialogView extends PopupWindow { private View dialogView; private EditText payPassEt; private Button cancelB

  • Android开发实现多进程弹窗效果

    安卓开发之多进程弹窗,供大家参考,具体内容如下 背景 有时在弹窗绘图时,需要弹窗在新的进程中,以保证在弹窗绘图的过程中不会占用过多的内存导致主进程被关. 代码实现 子进程弹窗 首先我们需要一个透明的activity来作为弹窗展示,并且这个透明activity就存在于子进程中,这一切都可以在清单文件中实现: <activity android:name=".ProcessActivity" android:process=":process_test" andr

  • Android使用ViewDragHelper实现QQ聊天气泡拖动效果

    QQ聊天气泡拖动效果Android实现代码,供大家参考,具体内容如下 概述 本文的目的是实现类似于QQ消息提醒的气泡的拖拽效果.网上已有大神的实现效果是通过监听控件的OnTouchEvent事件的ACTION_DOWN,ACTION_MOVE,ACTION_UP事件来处理相应的拖拽效果,这里采用ViewDragHelper的方式去实现拖拽,顺便学习了一下ViewDragHelper的使用方式,拖拽时的粘连效果采用贝塞尔曲线来实现. 用ViewDragHelper实现拖拽效果 ViewDragHe

  • Android使用贝塞尔曲线仿QQ聊天消息气泡拖拽效果

    本文实例为大家分享了Android仿QQ聊天消息气泡拖拽效果展示的具体代码,供大家参考,具体内容如下 先画圆,都会吧.代码如下: public class Bezier extends View { private final Paint mGesturePaint = new Paint(); private final Path mPath = new Path(); private float mX1 = 100, mY1 = 150; private float mX2 = 300, m

  • Android 实现仿QQ拖拽气泡效果的示例

    目录 效果图: 一.实现思路 二.功能实现 三.全屏拖拽效果实现 源码地址: 效果图: 一.实现思路 在列表中默认使用自定义的TextView控件来展示消息气泡,在自定义的TextView控件中重写onTouchEvent方法,然后在DOWN.MOVE.UP事件中分别处理拖拽效果. 整个拖拽效果我们可以拆分成以下几步来实现: 1.默认状态 2.两气泡相连状态 3.两气泡分离状态 4.气泡消失状态 二.功能实现 默认状态:用来做一个状态的标识,无需特别处理. 两气泡相连状态:绘制一个固定圆和一个移

  • Android实现好看的微信聊天气泡效果

    目录 前言 代码实现 踩坑记录 总结 前言 在聊天类应用中,通常用气泡作为聊天内容的背景色,比如微信的聊天背景,别人发过来的是白色的气泡,自己发的是绿色的气泡. 上面这种是比较普通的,这篇我们玩点有趣的,让聊天气泡是渐变色的.可能很多人会觉得渐变很简单,给 Container 来个decoration或者使用 DecoratedBox,使用渐变填充色就可以了,比如下面这种效果: 这个感觉也太丑了,本篇我们来一个高级的 —— 整个聊天窗口的气泡颜色是渐变的,而且随着滚动还会变化!先看看实现的效果,

  • Android 自定义加载动画Dialog弹窗效果的示例代码

    效果图 首先是创建弹窗的背景 这是上面用到的 以shape_bg_5_blue.xml为例,其他的三个无非就是里面的颜色不一样而已 <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <corners android:radius="5dp"

随机推荐