400-028-6601
建站资讯

网站建设资讯

为你提供网站建设行业资讯、网站优化知识、主机域名邮箱、网站开发常见问题等。

谈谈AndroidMaterialDesign中的Tint

什么是Tint

成都创新互联总部坐落于成都市区,致力网站建设服务有网站建设、成都网站设计、网络营销策划、网页设计、网站维护、公众号搭建、小程序制作、软件开发等为企业提供一整套的信息化建设解决方案。创造真正意义上的网站建设,为互联网品牌在互动行销领域创造价值而不懈努力!

当我开始接触Tint这个词的时候,其实是蛮不理解它的意思的,以及并不清楚Google发明它的目的,它一般搭配Background配合使用,但是现在已经有了Background,为什么还需要Tint呢?

Tint 翻译为着色。

着色,着什么色呢?和背景有关,当然是着背景的色。当我开发客户端,使用了appcompat-v7包的时候,为了实现Material Design的效果,我们会去设置主题里的几个颜色,重要的比如primaryColor,colorControlNormal,colorControlActived 等等,而我们使用的一些组件,比如EditText就会自动变成我们想要的背景颜色,在背景图只有一张的情况下,这样的做法极大的减少了我们apk包的大小。

实现的方式就是用一个颜色为我们的背景图片设置tint(着色)。
例子:

看看即将发布的SegmentFault for Android 2.7中,发布问题功能,这个EditText的颜色和我们的主要颜色相同。它利用了TintManager这个类,为自己的背景进行着色(绿色)。
那么这个原始图是什么样子呢?我们从appcompat-v7包中找到了这个图,是一个.9图,样子如下:

其实它只是一个黑色的条,通过绿色的着色,变成了一个绿色的条。 就是这样的设计方式,使得我们在Material Design中省了多少资源文件呀!

好了,既然理解了tint的含义,我们赶紧看下这一切是如何实现的吧。
其实底层特别简单,了解过渲染的同学应该知道PorterDuffColorFilter这个东西,我们使用SRC_IN的方式,对这个Drawable进行颜色方面的渲染,就是在这个Drawable中有像素点的地方,再用我们的过滤器着色一次。
实际上如果要我们自己实现,只用获取View的backgroundDrawable之后,设置下colorFilter即可。

看下最核心的代码就这么几行

 
 
 
 
  1. if (filter == null) { 
  2. // Cache miss, so create a color filter and add it to the cache 
  3. filter = new PorterDuffColorFilter(color, mode); 
  4. } 
  5.  
  6. d.setColorFilter(filter); 

通常情况下,我们的mode一般都是SRC_IN,如果想了解这个属性相关的资料,这里是传送门: http://blog.csdn.net/t12x3456/article/details/10432935 (中文)

由于API Level 21以前不支持background tint在xml中设置,于是提供了ViewCompat.setBackgroundTintList方法和ViewCompat.setBackgroundTintMode用来手动更改需要着色的颜色,但要求相关的View继承TintableBackgroundView接口。
源码解析

看下源码是如何实现的吧,我们以AppCompatEditText为例:
看下构造函数(省略无关代码)

 
 
 
 
  1. public AppCompatEditText(Context context, AttributeSet attrs, int defStyleAttr) { 
  2. super(TintContextWrapper.wrap(context), attrs, defStyleAttr); 
  3.  
  4. ... 
  5.  
  6. ColorStateList tint = a.getTintManager().getTintList(a.getResourceId(0, -1)); //根据背景的resource id获取内置的着色颜色。 
  7. if (tint != null) { 
  8. setInternalBackgroundTint(tint); //设置着色 
  9. } 
  10.  
  11. ... 
  12. } 
  13.  
  14. private void setInternalBackgroundTint(ColorStateList tint) { 
  15. if (tint != null) { 
  16. if (mInternalBackgroundTint == null) { 
  17. mInternalBackgroundTint = new TintInfo(); 
  18. } 
  19. mInternalBackgroundTint.mTintList = tint; 
  20. mInternalBackgroundTint.mHasTintList = true; 
  21. } else { 
  22. mInternalBackgroundTint = null; 
  23. } 
  24. //上面的代码是记录tint相关的信息。 
  25. applySupportBackgroundTint(); //对背景应用tint 
  26. } 
  27.  
  28.  
  29. private void applySupportBackgroundTint() { 
  30. if (getBackground() != null) { 
  31. if (mBackgroundTint != null) { 
  32. TintManager.tintViewBackground(this, mBackgroundTint); 
  33. } else if (mInternalBackgroundTint != null) { 
  34. TintManager.tintViewBackground(this, mInternalBackgroundTint); //最重要的,对tint进行应用 
  35. } 
  36. } 
  37. } 
  38.  
  39. 然后我们进入tintViewBackground看下TintManager里面的源码 
  40.  
  41. public static void tintViewBackground(View view, TintInfo tint) { 
  42. final Drawable background = view.getBackground(); 
  43. if (tint.mHasTintList) { 
  44. //如果设置了tint的话,对背景设置PorterDuffColorFilter 
  45. setPorterDuffColorFilter( 
  46. background, 
  47. tint.mTintList.getColorForState(view.getDrawableState(), 
  48. tint.mTintList.getDefaultColor()), 
  49. tint.mHasTintMode ? tint.mTintMode : null); 
  50. } else { 
  51. background.clearColorFilter(); 
  52. } 
  53.  
  54. if (Build.VERSION.SDK_INT <= 10) { 
  55. // On Gingerbread, GradientDrawable does not invalidate itself when it's ColorFilter 
  56. // has changed, so we need to force an invalidation 
  57. view.invalidate(); 
  58. } 
  59. } 
  60.  
  61.  
  62. private static void setPorterDuffColorFilter(Drawable d, int color, PorterDuff.Mode mode) { 
  63. if (mode == null) { 
  64. // If we don't have a blending mode specified, use our default 
  65. mode = DEFAULT_MODE; 
  66. } 
  67.  
  68. // First, lets see if the cache already contains the color filter 
  69. PorterDuffColorFilter filter = COLOR_FILTER_CACHE.get(color, mode); 
  70.  
  71. if (filter == null) { 
  72. // Cache miss, so create a color filter and add it to the cache 
  73. filter = new PorterDuffColorFilter(color, mode); 
  74. COLOR_FILTER_CACHE.put(color, mode, filter); 
  75. } 
  76.  
  77. // 最最重要,原来是对background drawable设置了colorFilter 完成了我们要的功能。 
  78. d.setColorFilter(filter); 
  79. } 

以上是对API21以下的兼容。
如果我们要实现自己的AppCompat组件实现tint的一些特性的话,我们就可以指定好ColorStateList,利用TintManager对自己的背景进行着色,当然需要对外开放设置的接口的话,我们还要实现TintableBackgroundView接口,然后用ViewCompat.setBackgroundTintList进行设置,这样能完成对v7以上所有版本的兼容。
实例

比如我现在要对一个自定义组件实现对Tint的支持,其实只用继承下,加一些代码就好了,代码如下(几乎通用):

 
 
 
 
  1. public class AppCompatFlowLayout extends FlowLayout implements TintableBackgroundView { 
  2.  
  3. private static final int[] TINT_ATTRS = { 
  4. android.R.attr.background 
  5. }; 
  6.  
  7. private TintInfo mInternalBackgroundTint; 
  8. private TintInfo mBackgroundTint; 
  9. private TintManager mTintManager; 
  10.  
  11. public AppCompatFlowLayout(Context context) { 
  12. this(context, null); 
  13. } 
  14.  
  15. public AppCompatFlowLayout(Context context, AttributeSet attributeSet) { 
  16. this(context, attributeSet, 0); 
  17. } 
  18.  
  19. public AppCompatFlowLayout(Context context, AttributeSet attributeSet, int defStyle) { 
  20. super(context, attributeSet, defStyle); 
  21.  
  22. if (TintManager.SHOULD_BE_USED) { 
  23. TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(), attributeSet, 
  24. TINT_ATTRS, defStyle, 0); 
  25. if (a.hasValue(0)) { 
  26. ColorStateList tint = a.getTintManager().getTintList(a.getResourceId(0, -1)); 
  27. if (tint != null) { 
  28. setInternalBackgroundTint(tint); 
  29. } 

名称栏目:谈谈AndroidMaterialDesign中的Tint
文章位置:http://www.hnjierui.cn/article/cddggii.html

其他资讯