最近做项目时用到了BadgeView,当时没有看源码,现在闲下来了,就分析一下。
public class BadgeView extends TextView
可以看到,BadgeView是通过继承了TextView来实现的。接下来,不要看源码,先从它的用法入手,分析哪个函数真正实现了它。它的用法很简单,如下面demo所示
public class MainActivity extends ActionBarActivity {
public final String tag = this.getClass().getName();
private BadgeView bv1;
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.tv);
bv1 = new BadgeView(this);
bv1.setBadgeCount(1);
bv1.setTargetView(tv);
}
我们看到只要三条语句就可以使用BadgeView,现在再让我们返回源码去看看这三个函数。首先是它的构造函数。
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();
}
里面只有一个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));
// set default background
setBackground(9, Color.parseColor("#d3321b"));
setGravity(Gravity.CENTER);
// default values
setHideOnNull(true);
setBadgeCount(0);
}
看完之后,发现这个函数大多数是一些样式的设定语句,先给BadgeView设定layout_width,layout_height和布局中的位置,接下来就是其他的样式,就不一一说了。函数的末尾我们又看到了setBadgeCount函数,虽然从名字看就知道这只是用来设置显示的数字的,但我们还是去看看源码。
public void setBadgeCount(int count) {
setText(String.valueOf(count));
}
发现调用了setText方法
/*
* (non-Javadoc)
*
* @see android.widget.TextView#setText(java.lang.CharSequence,
* android.widget.TextView.BufferType)
*/
@Override
public void setText(CharSequence text, BufferType type) {
if (isHideOnNull()
&& (text == null || text.toString().equalsIgnoreCase("0"))) {
setVisibility(View.GONE);
} else {
setVisibility(View.VISIBLE);
}
super.setText(text, type);
}
我们看到,这个重载方法加上了条件判断来决定是否显示内容,setHideOnNull方法只是单纯用来设定是否显示BadgeView而已。那么最后就看看setTargetView函数。
/*
* Attach the BadgeView to the target view
*
* @param target the view to attach the BadgeView
*/
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的实现原理了。BadgeView先是将target(demo中是TextView)从原来的布局中移除,再动态创建了一个FrameLayout,并将LayoutParams设置为target原来布局的LayoutParams,然后就将创建的FrameLayout添加到target的原有布局中,最后将target和BadgeView添加到FrameLayout中。总体看来,源码不算复杂,但是从中我们可以学习到源码作者解决问题的思路,隔空膜拜一下大神。
最后让我们来找找碴。
1、为什么先添加target再添加BadgeView
ViewGroup源码中,addView是这样子的
public void addView(View child) {
addView(child, -1);
}
-1的位置下标表示将View添加的ViewGroup的最后一个位置,所以如果没有先添加target再添加BadgeView,由于这是个FrameLayout,BadgeView会被遮盖,怎么都看不到。
2、为什么要先添加badgeContainer布局再添加View
实测先添加View到badgeContainer布局,最后再添加badgeContainer布局也没影响……
3、为什么BadgeView是圆形的
@SuppressWarnings("deprecation")
public void setBackground(int dipRadius, int badgeColor) {
int radius = dip2Px(dipRadius);
float[] radiusArray = new float[] { radius, radius, radius, radius, radius, radius, radius, radius };
//set the shape,you can change it to create other new shape
RoundRectShape roundRect = new RoundRectShape(radiusArray, null, null);
ShapeDrawable bgDrawable = new ShapeDrawable(roundRect);
bgDrawable.getPaint().setColor(badgeColor);
setBackgroundDrawable(bgDrawable);
}
看到了吧,你完全可以改为其他形状,尽管其他形状很丑~