[安卓自定义控件]BadgeView源码解析

来源:互联网 发布:宏业斯维尔软件 编辑:程序博客网 时间:2024/06/06 23:55

[安卓自定义控件]BadgeView源码解析

说明

  • BadgeView主要用于实现微信未读消息数目提示之类的小红点效果。
  • 源代码为github上的BadgeView项目
  • 项目的使用方法为直接将代码粘贴到本地,随后即可按照官方的写法使用
BadgeView badge = new BadgeView(getActivity());badge.setTargetView(myView);badge.setBadgeCount(42);

源码解析

继承自TextView的优点

项目实现的BadgeView继承自TextView,这相比于平时编写自定义View通常直接扩展自View有几个好处:
- 不用自己编写覆盖onMeasure、onSizeChanged、onLayout和onDraw这些方法而是利用TextView本身提供的机制来设置自身布局等。
- 由于扩展自TextView,也使得能够利用TextView提供的方法简单更改设置字体风格、大小,更改文字内容和背景等。我们可以看到相关的接口都最后调用了父类的方法:

//如构造器中初始化自身的init函数public BadgeView(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        init();    }private void init() {        if (!(getLayoutParams() instanceof LayoutParams)) {            LayoutParams layoutParams =                    new LayoutParams(                            android.view.ViewGroup.LayoutParams.WRAP_CONTENT,                            android.view.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, 11);        setPadding(dip2Px(5), dip2Px(1), dip2Px(5), dip2Px(1));        // 这里调用了自定义的setBackground方法,但只是为了提供一个圆形背景的ShapeDrawable对象,最终设置背景时仍调用了父类的setBackgroundDrawable。具体见setBackground源码。        setBackground(9, Color.parseColor("#d3321b"));    //调用了父类的方法        setGravity(Gravity.CENTER);        // default values        setHideOnNull(true);        //最终调用父类的方法,见setBadgeCount源码。        setBadgeCount(0);    } public void setBackground(int dipRadius, int badgeColor) {        int radius = dip2Px(dipRadius);        float[] radiusArray = new float[] { radius, radius, radius, radius, radius, radius, radius, radius };        RoundRectShape roundRect = new RoundRectShape(radiusArray, null, null);        ShapeDrawable bgDrawable = new ShapeDrawable(roundRect);        bgDrawable.getPaint().setColor(badgeColor);        //调用了父类的方法        setBackgroundDrawable(bgDrawable);    }//调用了父类的方法        setText(String.valueOf(count));    }

支持setTargetView这一API

  • 整个项目主要难点在于支持通过setTargetView这一API直接给需要提示的View加上提示。这样的API形式使得使用非常容易灵活,值得借鉴。
  • 从实现原理上,BadgeView需要一直保持自身处于一个FrameLayout的父布局中(在init方法中已经通过getLayoutParams()判断自身所使用的布局参数并强制设置自己的布局参数为FrameLayout.LayoutParams,在这一布局参数设定下它只能使用FrameLayout作为父布局)。
  • 具体当要将自己设置到targetView上时,步骤如下:

    • 当需要将自己添加到指定的targetView上时,先将自己从自己所处的父布局中取出(如果有父布局的话)。
    • 再检查targetView的父布局P,如果P已经是FrameLayout则直接将自己添加到P中。
    • 否则,将targetView从其自身的父布局P中取出,并记录targetView原本在P中所处的位置索引。
    • 在该位置索引处插入一个新的FrameLayout,假设称为F。
    • 修改targetView的布局参数为FrameLayout.LayoutParams类型,且具体宽高参数均为ViewGroup.LayoutParams.MATCH_PARENT。于是保证了在视觉上targetView在P中所处的位置不变,尽管其实从此时开始它和P之间已经插入了一层FrameLayout。
    • 由于所有放在FrameLayout里的控件,都按照层次堆叠在FrameLayout的左上角,后加进来的控件覆盖前面的控件,所以为了使得BadgeView能够优先于targetView显示,需要先往F里添加targetView,再添加BadgeView自己。至此,BadgeView又处于一个FrameLayout中了。
      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");    }}

    其他说明

    • BadgeView中其他类似修改布局参数,转换dip到px工具函数等方法都比较简单,可以直接阅读源码,这里不再赘述。
0 0
原创粉丝点击