首先,右上的圆形气泡其实是一个textview,参考网上一个比较流行的开源代码 (地址https://github.com/stefanjauker/BadgeView),该项目提供了一个jar包,不过这种小功能应该代码量不多,打开源码发现就是一个自定义view类。
该扩展使用方法也相当简单,首先确定要添加气泡的view ,然后
final BadgeView badgeView = new BadgeView(this);
badgeView.setTargetView(target);
badgeView.setBadgeCount(3);
显示效果如图:
下面分析一下源码:
public class BadgeView extends TextView {
private boolean mHideOnNull = true;
public BadgeView(Context context) {
this(context, null);
}
public BadgeView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.textViewStyle);
}
public BadgeView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
该view的构造方法,其最终都调用了init()方法,这个方法主要是对本身设置了下layout属性,如下:
private void init() {
if (!(getLayoutParams() instanceof LayoutParams)) {
LayoutParams layoutParams =
new LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
Gravity.RIGHT | Gravity.TOP);
setLayoutParams(layoutParams);
}
// set default font
setTextColor(Color.WHITE);
setTypeface(Typeface.DEFAULT_BOLD);
setTextSize(TypedValue.COMPLEX_UNIT_SP, 17);
setPadding(dip2Px(5), dip2Px(1), dip2Px(5), dip2Px(1));
// set default background
setBackground(9, Color.parseColor("#d3321b"));
setGravity(Gravity.CENTER);
// default values
setHideOnNull(true);
setBadgeCount(0);
}
public void setTargetView(View target) {
if (getParent() != null) {
((ViewGroup) getParent()).removeView(this);
}
if (target == null) {
return;
}
if (target.getParent() instanceof FrameLayout) {
((FrameLayout) target.getParent()).addView(this);
} else if (target.getParent() instanceof ViewGroup) {
// use a new Framelayout container for adding badge
ViewGroup parentContainer = (ViewGroup) target.getParent();
int groupIndex = parentContainer.indexOfChild(target);
parentContainer.removeView(target);
FrameLayout badgeContainer = new FrameLayout(getContext());
ViewGroup.LayoutParams parentLayoutParams = target.getLayoutParams();
badgeContainer.setLayoutParams(parentLayoutParams);
target.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
parentContainer.addView(badgeContainer, groupIndex, parentLayoutParams);
badgeContainer.addView(target);
badgeContainer.addView(this);
} else if (target.getParent() == null) {
Log.e(getClass().getSimpleName(), "ParentView is needed");
}
}
很明显,这个扩展的实现方式就是frameLayout,当设置target时,根据target的父控件的类型进行不同的设置,比如,当父控件就是framelayout时,直接添加,当父控件不是frameLayout时,记录下target在父控件中的位置,
int groupIndex = parentContainer.indexOfChild(target);
然后新建一个frameLayout,
FrameLayout badgeContainer = new FrameLayout(getContext());
将target与气泡的textview一同添加进去,最后在添加到初始父控件的groupIndex位置就可以了。
不过这种方式有些问题,在判断父控件为frameLayout时如果父控件指定了宽高,就会出现问题,比如当布局文件是:
显示效果为:
相当尴尬
不过按照这个原理,我们要实现气泡只要在布局中手动修改反而更简单(个人认为),只需修改target 为一个复合的view完全没问题,只需要在父控件中添加一个Android:clipChildren="false" 属性,保证气泡可以再父控件之外显示