Android 实现控件右上角显示消息数量气泡(badge)



    现在qq或者微信,甚至微博都有一个功能,就是在用户有未读消息时,在桌面图标右上角显示一个红色的小气泡,里面显示未读消息数量,在桌面图标显示的功能主要是利用广播来通知launcher,具体实现网上有不少内容,本文简单介绍下在应用内部的实现;

    首先,右上的圆形气泡其实是一个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);
    }

然后最为关键的就是设置targe view的方法,在这个方法里为目标view添加了当前的textview作为气泡:

  

 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" 属性,保证气泡可以再父控件之外显示



    

你可能感兴趣的:(android)