Android着色器Tint研究

SylvesterHU 8年前
   <p>Tint 这个东西 主要用来减少apk体积的,比如说我现在有一个textview,他的背景图 有两种,一种是当获得焦点时显示的a图,另一种是 失去焦点时显示的b图。</p>    <p>相信大家开发的时候 这种需求做过很多次了,我们一般都会发现 这种a图和b图 除了颜色不一样,其他都是一样的,但是我们做的时候呢,通常是找ui要了两张图。</p>    <p>如果要适配分辨率的话 很有可能图片会更多,而且在切换的时候 因为是重新加载一次bitmap 效率也会下降很多。所以谷歌就给了一套解决方案 这个就是tint了。</p>    <p>他的目的就是当你发现有这种需求的时候,只需要 放一张图 在apk里即可,当你需要改变背景图的颜色的时候 就用Tint即可!</p>    <p>下面就来简单说一下,tint的使用 以及需要注意的地方。</p>    <p>首先 我们定义一个简单的布局文件:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/6e373916945e59e33a5aa485c6e04d35.jpg"></p>    <p>我们发现这2个imageview 都是引用的同样一个drawable资源,并且 在studio这个xml编辑界面里面 我们很明显的 能看出来 这个图片的颜色是黑色的 对吧!</p>    <p>那 现在 我们想改一下,想把iv1 这个imageview的 背景色 改成绿色的! 我们想当然的 当然会这么写:</p>    <pre>  <code class="language-java">iv1 = (ImageView) this.findViewById(R.id.iv1);     iv2 = (ImageView) this.findViewById(R.id.iv2);     final Drawable originBitmapDrawable = getResources().getDrawable(R.drawable.ic_account_circle_black_18dp);     iv1.setImageDrawable(tintDrawable(originBitmapDrawable, ColorStateList.valueOf(Color.GREEN)));   </code></pre>    <p>应该很好理解对吧,代码就不解释了。但是我们运行以后发现:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/53ff761596527c01dfdc1ac2fbbb40be.jpg"></p>    <p>卧槽 怎么2个都变绿色了!</p>    <p>回顾一下 我们的代码 我们应该能明白 2个imageview 都是引用的同样的一个drawable,要知道 既然是一个drawable,那系统肯定为了优化资源 把这2个drawable 在内存里的拷贝弄成了一份!</p>    <p>还记得 我们以前讲的bitmap优化那篇么?http://www.cnblogs.com/punkisnotdead/p/4881771.html 和这个里面的inBitmap 属性有异曲同工之妙,如果还不理解 你看下面的图就理解了:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/9fc82534c0d96a17153b97bc44a76c73.jpg"></p>    <p>所以才会造成上面的情况。你修改了共同变量,所以2个图就都被影响了。</p>    <p>解决方法 其实也很简单:</p>    <pre>  <code class="language-java">final Drawable originBitmapDrawable = getResources().getDrawable(R.drawable.   ic_account_circle_black_18dp).mutate();    </code></pre>    <p>修改以后 我们再看:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/81ba1615cff04a8a8993bff4e5c3def1.jpg"></p>    <p>你看这么做就一切正常了。</p>    <p>那有人就要问了,卧槽 你这么做 不是把谷歌给我们做好的图片内存优化方案给损坏了么,其实这种担心是多余的,这个http://android-developers.blogspot.hk/2009/05/drawable-mutations.html</p>    <p>这个地址会告诉你 其实我们做 只是把单独的受到影响的那部分 内存给单独拿出来了,其他没受到影响的还是共享的数据!换句话说 我们内存里 会另外存放的就是一些纯的标志位 之类的 类似于状态值这种东西。</p>    <p>大部分的内存还是公用的!</p>    <p>然后接着来,我们看下一个例子 关于editext的。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/f63ee4c25abf828f2de20c83167ff474.jpg"></p>    <p>你看这个edittext 的颜色是这样的。那现在我们来修改一下 这个edittext的背景色</p>    <pre>  <code class="language-java">et1 = (EditText) this.findViewById(R.id.et);     final Drawable originBitmapDrawable = et1.getBackground();     et1.setBackgroundDrawable(tintDrawable(originBitmapDrawable, ColorStateList.valueOf(Color.GREEN)));   </code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/768d5e6e523b00d9e0006cb2e40cc9ec.jpg"></p>    <p>背景色是修改成功了 但是这个光标的颜色 还没变 非常不协调, 有人又要说了 我们可以用:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/aa095fbf6f6dae4a19cb9425733a7ae5.jpg"></p>    <p>这个xml 属性来修改呀,当然了这个方法确实是可以的 但是你想 你这么做的话 又要增加资源文件了,不是与我们的tint 背道而驰了么?</p>    <p>所以 这个地方 我们就要想办法 突破一下。其实很多人都能想到方法了,对于android 没有 提供给我们的api 比如那些private 函数,</p>    <p>我们通常都是通过反射的方法 去调用的。 这里也是一样,稍微想一下 我们就能明白, 这个地方 我们就先通过反射来获取到这个cursorDrawable</p>    <p>然后给他着色,然后在反射调用方法 给他set进去不就行了么?</p>    <p>首先我们都知道 editext 实际上就是textview,所以我们看一下textview 的源码 找找看 这个属性到底叫啥名字。经过一番努力发现 在这:</p>    <pre>  <code class="language-java">// Although these fields are specific to editable text, they are not added to Editor because     // they are defined by the TextView's style and are theme-dependent.     int mCursorDrawableRes;    </code></pre>    <p>并且我们要看下editor的源码 这是和edittext息息相关的:</p>    <pre>  <code class="language-java">/**        * EditText specific data, created on demand when one of the Editor fields is used.        * See {<a href="http://www.jobbole.com/members/57845349">@link</a> #createEditorIfNeeded()}.        */       private Editor mEditor;         //注意这段代码属于editor     final Drawable[] mCursorDrawable = new Drawable[2];    </code></pre>    <p>有了这段代码 我们就知道 剩下反射的代码怎么写了。</p>    <pre>  <code class="language-java">//参数就是要反射修改光标的edittext对象       private void invokeEditTextCallCursorDrawable(EditText et) {           try {               Field fCursorDrawableRes = TextView.class.getDeclaredField("mCursorDrawableRes");               // 看源码知道 这个变量不是public的 所以要设置下这个可访问属性               fCursorDrawableRes.setAccessible(true);               //取得 editext对象里的mCursorDrawableRes 属性的值 看源码知道这是一个int值               int mCursorDrawableRes = fCursorDrawableRes.getInt(et);               //下面的代码 是通过获取mEditor对象 然后再通过拿到的mEditor对象来获取最终我们的mCursorDrawable这个光标的drawable               Field fEditor = TextView.class.getDeclaredField("mEditor");               fEditor.setAccessible(true);               Object editor = fEditor.get(et);               Class<?> clazz = editor.getClass();               Field fCursorDrawable = clazz.getDeclaredField("mCursorDrawable");               fCursorDrawable.setAccessible(true);               if (mCursorDrawableRes <= 0) {                   return;               }               //到这里 我们终于拿到了默认主题下 edittext的光标的那个小图标的drawable               Drawable cursorDrawable = et.getContext().getResources().getDrawable(mCursorDrawableRes);               if (cursorDrawable == null) {                   return;               }               //既然都拿到了这个drawble 那就修改他。               Drawable tintDrawable = tintDrawable(cursorDrawable, ColorStateList.valueOf(Color.GREEN));               //前面贴出的mCursorDrawable源码 可以知道 这是一个二维数组。所以我们要构造出一个全新的二维数组               Drawable[] drawables = new Drawable[]{tintDrawable, tintDrawable};               //然后再通过反射 把这个二维数组的值 放到editor里面 即可!               fCursorDrawable.set(editor, drawables);           } catch (NoSuchFieldException e) {               e.printStackTrace();           } catch (IllegalAccessException e) {               e.printStackTrace();           }           }    </code></pre>    <p>最后调用这个方法以后看一下效果:</p>    <p>很完美 对吧~~</p>    <p>最后tintDrawable这个方法是用来向下兼容用的。你如果不考虑向下兼容的问题 用系统自带的方法 就可以了,这里就不做过多介绍了。</p>    <pre>  <code class="language-java">public static Drawable tintDrawable(Drawable drawable, ColorStateList colors) {           final Drawable wrappedDrawable = DrawableCompat.wrap(drawable);           DrawableCompat.setTintList(wrappedDrawable, colors);           return wrappedDrawable;       }    </code></pre>    <p>当然你也可以用http://andraskindler.com/blog/2015/tinting_drawables/ 这个文章里的方法来做向下兼容: public  final class TintedBitmapDrawable extends BitmapDrawable { </p>    <pre>  <code class="language-java">  private int tint;     private int alpha;         public TintedBitmapDrawable(final Resources res, final Bitmap bitmap, final int tint) {       super(res, bitmap);       this.tint = tint;       this.alpha = Color.alpha(tint);     }         public TintedBitmapDrawable(final Resources res, final int resId, final int tint) {       super(res, BitmapFactory.decodeResource(res, resId));       this.tint = tint;       this.alpha = Color.alpha(tint);     }         public void setTint(final int tint) {       this.tint = tint;       this.alpha = Color.alpha(tint);     }         @Override public void draw(final Canvas canvas) {       final Paint paint = getPaint();       if (paint.getColorFilter() == null) {         paint.setColorFilter(new LightingColorFilter(tint, 0));         paint.setAlpha(alpha);       }       super.draw(canvas);     }   }    </code></pre>    <p> </p>    <p>来自:http://mobile.51cto.com/android-527646.htm</p>    <p> </p>