button是Android 中常用的控件, 父类是 Textview, 在之前的文章中对 Textview的一些 基本信息都介绍过, 还包括Textview的 之 setText方法 带来的一些 性能问题。 这篇文章就讲到了 button的一些使用要注意的地方。
这些需要注意的 还包括了 Textview 已经一些 Textview的 子类 如 CheckBox,EditText 等等 一些控件。 问题的产生
selector 或者 shape ,layer-list等等一些 资源。
有的做的漂亮的项目 会给 按钮 两张图片 ,一张 是选中 或者点击状态,另外一张 则是 正常状态。
<span style="font-size:18px;"><?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- checked -->
<item android:drawable="@drawable/language_press" android:state_checked="true"/>
<!-- default -->
<item android:drawable="@drawable/language_no_press" android:state_checked="false"/>
</selector></span>
那么这样使用 就使得 xml加载的时候 这两张图片也加载了, 也就是一个按钮 就占用了 两张 图片的内存,这是不合理的。 那有人说 我都这么用 没一点事, 确实 随着现在 机器硬件的提升,以及Android本身系统的优化 等 大幅提高了效率, 使得这种 问题 变的不会很明显, 你一个xml 可能 使用带有图片的 selector 可能也就那么几个 不会很多。 那么我们讲的是为什么 会出现这个问题 ,怎么避免。 至于一些 临界点(使用多少会出现性能问题 造成卡顿)暂不关心。
那么 为什么会加载两张,因为我们在 xml中配置,Drawable.java的createFromXmlInner方法中对图片进行解析,最终调用Drawable的inflate方法),相当于一个按钮占用了两张相同大小图片所使用的内存。 我们来看看 源码
<span style="font-size:18px;"> public static Drawable createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs)
throws XmlPullParserException, IOException {
Drawable drawable;
final String name = parser.getName();
if (name.equals("selector")) {
drawable = new StateListDrawable();
} else if (name.equals("level-list")) {
drawable = new LevelListDrawable();
/* Probably not doing this.
} else if (name.equals("mipmap")) {
drawable = new MipmapDrawable();
*/
} else if (name.equals("layer-list")) {
drawable = new LayerDrawable();
} else if (name.equals("transition")) {
drawable = new TransitionDrawable();
} else if (name.equals("color")) {
drawable = new ColorDrawable();
} else if (name.equals("shape")) {
drawable = new GradientDrawable();
} else if (name.equals("scale")) {
drawable = new ScaleDrawable();
} else if (name.equals("clip")) {
drawable = new ClipDrawable();
} else if (name.equals("rotate")) {
drawable = new RotateDrawable();
} else if (name.equals("animated-rotate")) {
drawable = new AnimatedRotateDrawable();
} else if (name.equals("animation-list")) {
drawable = new AnimationDrawable();
} else if (name.equals("inset")) {
drawable = new InsetDrawable();
} else if (name.equals("bitmap")) {
drawable = new BitmapDrawable(r);
if (r != null) {
((BitmapDrawable) drawable).setTargetDensity(r.getDisplayMetrics());
}
} else if (name.equals("nine-patch")) {
drawable = new NinePatchDrawable();
if (r != null) {
((NinePatchDrawable) drawable).setTargetDensity(r.getDisplayMetrics());
}
} else {
throw new XmlPullParserException(parser.getPositionDescription() +
": invalid drawable tag " + name);
}
drawable.inflate(r, parser, attrs);
return drawable;
}</span>
可以看到 源码中 做了 很多判断,根据xml中的配置 加载了 不同的资源(我的源码可能是老版本的,新版本的 我看了 做了优化 匹配都变为了 switch/case模式)。可以看到 使用图片的话 会用BitmapDrawable, 使用BitmapDrawable 这个类里面的inflate的方法。 会将图片加载到内存中。并绘制出来
如果在内存吃紧的情况下 做一些 小优化还是非常有必要的。
比如 在 图片是 纯颜色的 时候 建议selector 里面使用 颜色 而不使用 图片。具体原因 还是源码
BitmapDrawable的inflate方法
<span style="font-size:18px;"> @Override
public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
throws XmlPullParserException, IOException {
super.inflate(r, parser, attrs);
TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.BitmapDrawable);
final int id = a.getResourceId(com.android.internal.R.styleable.BitmapDrawable_src, 0);
if (id == 0) {
throw new XmlPullParserException(parser.getPositionDescription() +
": <bitmap> requires a valid src attribute");
}
final Bitmap bitmap = BitmapFactory.decodeResource(r, id);
if (bitmap == null) {
throw new XmlPullParserException(parser.getPositionDescription() +
": <bitmap> requires a valid src attribute");
}
mBitmapState.mBitmap = bitmap;
setBitmap(bitmap);
setTargetDensity(r.getDisplayMetrics());
final Paint paint = mBitmapState.mPaint;
paint.setAntiAlias(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_antialias,
paint.isAntiAlias()));
paint.setFilterBitmap(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_filter,
paint.isFilterBitmap()));
paint.setDither(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_dither,
paint.isDither()));
setGravity(a.getInt(com.android.internal.R.styleable.BitmapDrawable_gravity, Gravity.FILL));
int tileMode = a.getInt(com.android.internal.R.styleable.BitmapDrawable_tileMode, -1);
if (tileMode != -1) {
switch (tileMode) {
case 0:
setTileModeXY(Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
break;
case 1:
setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
break;
case 2:
setTileModeXY(Shader.TileMode.MIRROR, Shader.TileMode.MIRROR);
break;
}
}
a.recycle();
}</span>
下面是 ColorDrawable的inflate的源码
<span style="font-size:18px;"> @Override
public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
throws XmlPullParserException, IOException {
super.inflate(r, parser, attrs);
TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.ColorDrawable);
int color = mState.mBaseColor;
color = a.getColor(com.android.internal.R.styleable.ColorDrawable_color, color);
mState.mBaseColor = mState.mUseColor = color;
a.recycle();
}</span>
具体一对比,不说细节,光说代码量就能说明一点问题了吧。 如果非要使用 图片,就用代码 进行动态设置。
给要使用的view加 onTouch事件,在 down,和move的时候为 点击状态, up为正常状态。觉得每一个设置太麻烦?
那就封装一个方法
<span style="font-size:18px;"> public void changeImage(View view, final int normalResId, final int pressResId){
view.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
v.setBackgroundResource(pressResId);
break;
case MotionEvent.ACTION_MOVE:
v.setBackgroundResource(pressResId);
break;
case MotionEvent.ACTION_UP:
v.setBackgroundResource(normalResId);
break;
}
// 为了不影响其他事件
return false;
}
});
}</span>
这样不仅 节省了内存, 而且 能减小apk包的大小(不需要每个都写 selector.xml了)
有些 边框,线条,圆角等等 能自己写 stroke,layer-list,shape 就尽量使用。 对内存和性能 比图片更好一点