Android着色器Tint研究

Tint 这个东西 主要用来减少apk体积的,比如说我现在有一个textview,他的背景图 有两种,一种是当获得焦点时显示的a图,另一种是 失去焦点时显示的b图。

相信大家开发的时候 这种需求做过很多次了,我们一般都会发现 这种a图和b图 除了颜色不一样,其他都是一样的,但是我们做的时候呢,通常是找ui要了两张图。

如果要适配分辨率的话 很有可能图片会更多,而且在切换的时候 因为是重新加载一次bitmap 效率也会下降很多。所以谷歌就给了一套解决方案 这个就是tint了。

他的目的就是当你发现有这种需求的时候,只需要 放一张图 在apk里即可,当你需要改变背景图的颜色的时候 就用Tint即可!

下面就来简单说一下,tint的使用 以及需要注意的地方。

首先 我们定义一个简单的布局文件:

我们发现这2个imageview 都是引用的同样一个drawable资源,并且 在studio这个xml编辑界面里面 我们很明显的 能看出来 这个图片的颜色是黑色的 对吧!

那 现在 我们想改一下,想把iv1 这个imageview的 背景色 改成绿色的! 我们想当然的 当然会这么写:


  1. iv1 = (ImageView) this.findViewById(R.id.iv1); 
  2.   iv2 = (ImageView) this.findViewById(R.id.iv2); 
  3.   final Drawable originBitmapDrawable = getResources().getDrawable(R.drawable.ic_account_circle_black_18dp); 
  4.   iv1.setImageDrawable(tintDrawable(originBitmapDrawable, ColorStateList.valueOf(Color.GREEN))); 

应该很好理解对吧,代码就不解释了。但是我们运行以后发现:

卧槽 怎么2个都变绿色了!

回顾一下 我们的代码 我们应该能明白 2个imageview 都是引用的同样的一个drawable,要知道 既然是一个drawable,那系统肯定为了优化资源 把这2个drawable 在内存里的拷贝弄成了一份!

还记得 我们以前讲的bitmap优化那篇么?http://www.cnblogs.com/punkisnotdead/p/4881771.html 和这个里面的inBitmap 属性有异曲同工之妙,如果还不理解 你看下面的图就理解了:

所以才会造成上面的情况。你修改了共同变量,所以2个图就都被影响了。

解决方法 其实也很简单:


  1. final Drawable originBitmapDrawable = getResources().getDrawable(R.drawable. 
  2. ic_account_circle_black_18dp).mutate();  

修改以后 我们再看:

你看这么做就一切正常了。

那有人就要问了,卧槽 你这么做 不是把谷歌给我们做好的图片内存优化方案给损坏了么,其实这种担心是多余的,这个http://android-developers.blogspot.hk/2009/05/drawable-mutations.html

这个地址会告诉你 其实我们做 只是把单独的受到影响的那部分 内存给单独拿出来了,其他没受到影响的还是共享的数据!换句话说 我们内存里 会另外存放的就是一些纯的标志位 之类的 类似于状态值这种东西。

大部分的内存还是公用的!

然后接着来,我们看下一个例子 关于editext的。

你看这个edittext 的颜色是这样的。那现在我们来修改一下 这个edittext的背景色


  1. et1 = (EditText) this.findViewById(R.id.et); 
  2.   final Drawable originBitmapDrawable = et1.getBackground(); 
  3.   et1.setBackgroundDrawable(tintDrawable(originBitmapDrawable, ColorStateList.valueOf(Color.GREEN))); 

背景色是修改成功了 但是这个光标的颜色 还没变 非常不协调, 有人又要说了 我们可以用:

这个xml 属性来修改呀,当然了这个方法确实是可以的 但是你想 你这么做的话 又要增加资源文件了,不是与我们的tint 背道而驰了么?

所以 这个地方 我们就要想办法 突破一下。其实很多人都能想到方法了,对于android 没有 提供给我们的api 比如那些private 函数,

我们通常都是通过反射的方法 去调用的。 这里也是一样,稍微想一下 我们就能明白, 这个地方 我们就先通过反射来获取到这个cursorDrawable

然后给他着色,然后在反射调用方法 给他set进去不就行了么?

首先我们都知道 editext 实际上就是textview,所以我们看一下textview 的源码 找找看 这个属性到底叫啥名字。经过一番努力发现 在这:


  1. // Although these fields are specific to editable text, they are not added to Editor because 
  2.   // they are defined by the TextView's style and are theme-dependent. 
  3.   int mCursorDrawableRes;  

并且我们要看下editor的源码 这是和edittext息息相关的:


  1. /** 
  2.      * EditText specific data, created on demand when one of the Editor fields is used. 
  3.      * See {<a href="http://www.jobbole.com/members/57845349">@link</a> #createEditorIfNeeded()}. 
  4.      */ 
  5.     private Editor mEditor; 
  6.  
  7.  
  8. //注意这段代码属于editor   
  9. final Drawable[] mCursorDrawable = new Drawable[2];  

有了这段代码 我们就知道 剩下反射的代码怎么写了。


  1. //参数就是要反射修改光标的edittext对象 
  2.     private void invokeEditTextCallCursorDrawable(EditText et) { 
  3.         try { 
  4.             Field fCursorDrawableRes = TextView.class.getDeclaredField("mCursorDrawableRes"); 
  5.             // 看源码知道 这个变量不是public的 所以要设置下这个可访问属性 
  6.             fCursorDrawableRes.setAccessible(true); 
  7.             //取得 editext对象里的mCursorDrawableRes 属性的值 看源码知道这是一个int值 
  8.             int mCursorDrawableRes = fCursorDrawableRes.getInt(et); 
  9.             //下面的代码 是通过获取mEditor对象 然后再通过拿到的mEditor对象来获取最终我们的mCursorDrawable这个光标的drawable 
  10.             Field fEditor = TextView.class.getDeclaredField("mEditor"); 
  11.             fEditor.setAccessible(true); 
  12.             Object editor = fEditor.get(et); 
  13.             Class<?> clazz = editor.getClass(); 
  14.             Field fCursorDrawable = clazz.getDeclaredField("mCursorDrawable"); 
  15.             fCursorDrawable.setAccessible(true); 
  16.             if (mCursorDrawableRes <= 0) { 
  17.                 return; 
  18.             } 
  19.             //到这里 我们终于拿到了默认主题下 edittext的光标的那个小图标的drawable 
  20.             Drawable cursorDrawable = et.getContext().getResources().getDrawable(mCursorDrawableRes); 
  21.             if (cursorDrawable == null) { 
  22.                 return; 
  23.             } 
  24.             //既然都拿到了这个drawble 那就修改他。 
  25.             Drawable tintDrawable = tintDrawable(cursorDrawable, ColorStateList.valueOf(Color.GREEN)); 
  26.             //前面贴出的mCursorDrawable源码 可以知道 这是一个二维数组。所以我们要构造出一个全新的二维数组 
  27.             Drawable[] drawables = new Drawable[]{tintDrawable, tintDrawable}; 
  28.             //然后再通过反射 把这个二维数组的值 放到editor里面 即可! 
  29.             fCursorDrawable.set(editor, drawables); 
  30.         } catch (NoSuchFieldException e) { 
  31.             e.printStackTrace(); 
  32.         } catch (IllegalAccessException e) { 
  33.             e.printStackTrace(); 
  34.         } 
  35.   
  36.     }  

最后调用这个方法以后看一下效果:


很完美 对吧~~

最后tintDrawable这个方法是用来向下兼容用的。你如果不考虑向下兼容的问题 用系统自带的方法 就可以了,这里就不做过多介绍了。


  1. public static Drawable tintDrawable(Drawable drawable, ColorStateList colors) { 
  2.         final Drawable wrappedDrawable = DrawableCompat.wrap(drawable); 
  3.         DrawableCompat.setTintList(wrappedDrawable, colors); 
  4.         return wrappedDrawable; 
  5.     }  

当然你也可以用http://andraskindler.com/blog/2015/tinting_drawables/ 这个文章里的方法来做向下兼容:public final class TintedBitmapDrawable extends BitmapDrawable { 


  1.   private int tint; 
  2.   private int alpha; 
  3.   
  4.   public TintedBitmapDrawable(final Resources res, final Bitmap bitmap, final int tint) { 
  5.     super(res, bitmap); 
  6.     this.tint = tint; 
  7.     this.alpha = Color.alpha(tint); 
  8.   } 
  9.   
  10.   public TintedBitmapDrawable(final Resources res, final int resId, final int tint) { 
  11.     super(res, BitmapFactory.decodeResource(res, resId)); 
  12.     this.tint = tint; 
  13.     this.alpha = Color.alpha(tint); 
  14.   } 
  15.   
  16.   public void setTint(final int tint) { 
  17.     this.tint = tint; 
  18.     this.alpha = Color.alpha(tint); 
  19.   } 
  20.   
  21.   @Override public void draw(final Canvas canvas) { 
  22.     final Paint paint = getPaint(); 
  23.     if (paint.getColorFilter() == null) { 
  24.       paint.setColorFilter(new LightingColorFilter(tint, 0)); 
  25.       paint.setAlpha(alpha); 
  26.     } 
  27.     super.draw(canvas); 
  28.   } 
  29. }  

本文作者:佚名

来源:51CTO

时间: 2024-10-18 23:32:32

Android着色器Tint研究的相关文章

《OpenGL ES应用开发实践指南:Android卷》—— 3.2 编译着色器

3.2 编译着色器 现在我们已经把着色器源代码从文件中读出来了,下一步就是编译每个着色器了.我们要创建一个新的辅助类,它可以创建新的OpenGL着色器对象.编译着色器代码并且返回代表那段着色器代码的着色器对象.一旦写出样板代码,在未来的项目中就可以重用了. 作为开始,创建一个名为ShaderHelper的新类,并在类中添加如下代码: 这些代码会作为着色器辅助类的基础.与以前一样,不要忘了把导入加进代码中:如果你在使用静态导入时碰到什么问题,请参考1.5节:在本书的剩余部分,我们会一直遵循这个样式

《OpenGL ES应用开发实践指南:Android卷》—— 3.1 加载着色器

3.1 加载着色器 我们既然已经为着色器写了代码,下一步就要把它们加载到内存中:为此,我们首先需要写一个可以从资源文件夹读取那些代码的方法. 3.1.1 从资源中加载文本 在项目中创建一个新的Java源代码包,命名为"com.airhockey.android.util",在这个包中创建一个名为"TextResourceReader"的新类.在类中加入如下代码: 我们已经定义了一个方法从资源中读取文本,该方法就是readTextFileFromResource().

《OpenGL ES应用开发实践指南:Android卷》—— 3.3 把着色器一起链接进OpenGL的程序

3.3 把着色器一起链接进OpenGL的程序 既然我们已经加载并编译了一个顶点着色器和一个片段着色器,下一步就是把它们绑定在一起放入一个单个的程序(program)里. 3.3.1 理解OpenGL的程序 简单来说,一个OpenGL程序就是把一个顶点着色器和一个片段着色器链接在一起变成单个对象.顶点着色器和片段着色器总是一起工作的.没有片段着色器,OpenGL就不知道怎么绘制那些组成每个点.直线和三角形的片段:如果没有顶点着色器,OpenGL就不知道在哪里绘制这些片段.我们知道顶点着色器计算屏幕

《OpenGL ES应用开发实践指南:Android卷》——第3章 编译着色器及在屏幕上绘图

第3章 编译着色器及在屏幕上绘图 本章会继续上一章开始的工作.作为本章的开发计划,我们首先加载并编译前面定义的着色器,然后把它们链接在一起放在OpenGL的一个程序里.我们接下来就可以用着色器程序在屏幕上绘制空气曲棍球桌子了.打开上一章启动的AirHockey1项目,并从那里开始.

《Android 应用案例开发大全(第3版)》——第2.8节壁纸中的着色器开发

2.8 壁纸中的着色器开发前面已经对3D动态壁纸--百纳水族馆的相关类进行了简要的介绍.本节将对本案例中用到的相关着色器进行介绍.本案例中用到的着色器共有四对,即气泡着色器.背景着色器.鱼类着色器及珍珠贝着色器.下面就对本壁纸中用到的着色器的开发进行一一介绍. 2.8.1 气泡的着色器气泡着色器分为顶点着色器与片元着色器,下面便分别对气泡着色器的顶点着色器和片元着色器的开发进行详细介绍. (1)首先介绍的是气泡着色器中的顶点着色器的开发,其详细代码如下. 1 uniform mat4 uMVPM

《OpenGL ES应用开发实践指南:Android卷》—— 第2章 定义顶点和着色器

第2章 定义顶点和着色器 本章介绍我们的第一个项目:一个简单的空气曲棍球游戏.在我们开发这个项目的过程中,会学习到一些主要的OpenGL组件.作为起始,我们会学习如何使用独立的点集合构建物体,这些点称为顶点:之后,我们会学习怎样使用着色器绘制这些物体,以及告诉OpenGL如何绘制这些物体的一些小程序.顶点和着色器这两个概念极其重要,因为每个物体的构建都是通过顶点的聚合形成点.直线和三角形,并且这些基本图形都要使用着色器绘制.我们会首先学习顶点,这样就可以构建空气曲棍球游戏中用到的桌子,并且使用O

《Android 应用案例开发大全(第3版)》——第2章,第2.8节壁纸中的着色器开发

2.8 壁纸中的着色器开发前面已经对3D动态壁纸--百纳水族馆的相关类进行了简要的介绍.本节将对本案例中用到的相关着色器进行介绍.本案例中用到的着色器共有四对,即气泡着色器.背景着色器.鱼类着色器及珍珠贝着色器.下面就对本壁纸中用到的着色器的开发进行一一介绍. 2.8.1 气泡的着色器气泡着色器分为顶点着色器与片元着色器,下面便分别对气泡着色器的顶点着色器和片元着色器的开发进行详细介绍. (1)首先介绍的是气泡着色器中的顶点着色器的开发,其详细代码如下. 代码位置:见随书光盘源代码/第2章/My

OpenGL ES 同样的灯光计算在顶点着色器和在片元着色器中计算有什么不同

OpenGL ES 同样的灯光计算在顶点着色器和在片元着色器中计算有什么不同 太阳火神的美丽人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:太阳火神的美丽人生 -  本博客专注于 敏捷开发及移动和物联设备研究:iOS.Android.Html5.Arduino.pcDuino,否则,出自本博客的文章拒绝转载或再转载,谢谢合作. 同样一段计算灯光的算法如下: vec4 combineLight (

OpenGL ES 着色器中问题解决技巧

OpenGL ES 着色器中问题解决技巧 太阳火神的美丽人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:太阳火神的美丽人生 -  本博客专注于 敏捷开发及移动和物联设备研究:iOS.Android.Html5.Arduino.pcDuino,否则,出自本博客的文章拒绝转载或再转载,谢谢合作. 灰暗的一天, 终于见到一丝曙光, 心情终于从沉重变得略有轻松, 虽然, 一早就明白, 曙光终究会闪现,