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 就尽量使用。 对内存和性能 比图片更好一点