GitHub 精选 #3| 有了它,对于 View ,你无所不能!

简介: GitHub 精选 #3| 有了它,对于 View ,你无所不能!

今天的主角是 ViewPump,可以直接介入布局文件中 View 的创建过程。上能修改 TextView 文字,字体,下能移花接木,替换各种 View。发挥你的想象力,它可以做到更多事情!


Organization github.com/InflationX
Url github.com/InflationX/…
Language Kotlin/Java
Star 757
Fork 39
Issue 22 Open/17 Closed
Commits 54
Last Update 8 Jun 2019
License Apache-2.0


以上数据截止至 2022 年 3 月 3 日。


使用方法


添加依赖:

dependencies {
    implementation 'io.github.inflationx:viewpump:2.0.3'
}
复制代码


ViewPump 基于责任链模式,让用户自由实现 Interceptor ,可以在 View 创建前和创建后做一些自定义的处理。不理解的话,直接类比 Okhttp 的拦截器,可以对 RequestRespone 分别做处理。


下面用 Readme 中的两个简单例子说明一下使用方法。

第一个,在 View 创建之前直接进行替换。下面的例子中,直接将布局文件中的 TextView 在运行时替换为 CustomTextView


public class CustomTextViewInterceptor implements Interceptor {
    @Override
    public InflateResult intercept(Chain chain) {
        InflateRequest request = chain.request();
        if (request.name().endsWith("TextView")) {
            CustomTextView view = new CustomTextView(request.context(), request.attrs());
            return InflateResult.builder()
                    .view(view)
                    .name(view.getClass().getName())
                    .context(request.context())
                    .attrs(request.attrs())
                    .build();
        } else {
            return chain.proceed(request);
        }
    }
}
复制代码


第二个,在 View 创建之后做一些修改。下面的例子中,在 TextView 创建之后修改了它的文字,添加了一个前缀。

public class TextUpdatingInterceptor implements Interceptor {
    @Override
    public InflateResult intercept(Chain chain) {
        InflateResult result = chain.proceed(chain.request());
        if (result.view() instanceof TextView) {
            // Do something to result.view()
            // You have access to result.context() and result.attrs()
            TextView textView = (TextView) result.view();
            textView.setText("[Prefix] " + textView.getText());
        }
        return result;
    }
}
复制代码


别忘了在 Application 中初始化,添加拦截器。

@Override
public void onCreate() {
    super.onCreate();
    ViewPump.init(ViewPump.builder()
                .addInterceptor(new TextUpdatingInterceptor())
                .addInterceptor(new CustomTextViewInterceptor())
                .build());
    //....
}
复制代码


这里要注意拦截器的添加顺序。如果先添加 CustomTextViewInterceptorTextView 全都被替换了,导致 TextUpdatingInterceptor 失效。一般情况下,应该把 事前处理 的拦截器放在 事后处理 的拦截器之前。


最后在 ActivityattachBaseContext() 中加上下面的代码:

@Override
protected void attachBaseContext(Context newBase) {
    super.attachBaseContext(ViewPumpContextWrapper.wrap(newBase));
}
复制代码


除了上面的两种简单用法,wiki 里还介绍了几种:

  1. 模拟 AppCompat 的行为
  2. 隐藏没有 contentDescription 的 View(为了促进无障碍的适配)
  3. 高亮特定的 View
  4. View 的各种功能增强,见 android-geocities-theme
  5. 动态修改 string 资源的文字,见 Philology

开动你的脑袋,肯定会有更多的用法。


实现原理


ViewPump.init() 只要是保存了用户添加的适配器,以及一些参数的配置,不详细展开。

重点看 Activity.attachBaseContext() 中添加的代码:

@Override
protected void attachBaseContext(Context newBase) {
    super.attachBaseContext(ViewPumpContextWrapper.wrap(newBase));
}
复制代码


基于装饰器模式对原来的 Context 进行了增强:

class ViewPumpContextWrapper private constructor(base: Context) : ContextWrapper(base) {
  private val inflater: `-ViewPumpLayoutInflater` by lazy(NONE) {
    `-ViewPumpLayoutInflater`(
        LayoutInflater.from(baseContext), this, false)
  }
  override fun getSystemService(name: String): Any? {
    // 返回自定义的 LayoutInflater
    if (Context.LAYOUT_INFLATER_SERVICE == name) {
      return inflater
    }
    return super.getSystemService(name)
  }
  ...
}
复制代码


重写了 getSystemService() 方法,当获取的服务名称是 layout_inflater 时,返回自定义的 ViewPumpLayoutInflater

internal class `-ViewPumpLayoutInflater`(
    original: LayoutInflater,
    newContext: Context,
    cloned: Boolean
) : LayoutInflater(original, newContext), `-ViewPumpActivityFactory` {
  ...
  init {
    setUpLayoutFactories(cloned)
  }
    // 使用自定义的 Factory/Factory2
    private fun setUpLayoutFactories(cloned: Boolean) {
    if (cloned) return
    // If we are HC+ we get and set Factory2 otherwise we just wrap Factory1
    if (factory2 != null && factory2 !is WrapperFactory2) {
      // Sets both Factory/Factory2
      factory2 = factory2
    }
    // We can do this as setFactory2 is used for both methods.
    if (factory != null && factory !is WrapperFactory) {
      factory = factory
    }
  }
  ...
}
复制代码


ViewPumpLayoutInflater 使用了自定义的 FactoryFactory2

private class WrapperFactory(factory: LayoutInflater.Factory) : LayoutInflater.Factory {
    private val viewCreator: FallbackViewCreator = WrapperFactoryViewCreator(factory)
    override fun onCreateView(name: String, context: Context, attrs: AttributeSet?): View? {
      return ViewPump.get()
          .inflate(InflateRequest(
              name = name,
              context = context,
              attrs = attrs,
              fallbackViewCreator = viewCreator
          ))
          .view
    }
  }
  private open class WrapperFactory2(factory2: LayoutInflater.Factory2) : LayoutInflater.Factory2 {
    private val viewCreator = WrapperFactory2ViewCreator(factory2)
    override fun onCreateView(name: String, context: Context, attrs: AttributeSet?): View? {
      return onCreateView(null, name, context, attrs)
    }
    override fun onCreateView(
        parent: View?,
        name: String,
        context: Context,
        attrs: AttributeSet?
    ): View? {
      return ViewPump.get()
          .inflate(InflateRequest(
              name = name,
              context = context,
              attrs = attrs,
              parent = parent,
              fallbackViewCreator = viewCreator
          ))
          .view
    }
  }
复制代码


Factory/Factory2 的 onCreateView 方法最后都指向 ViewPump.get().inflate()

fun inflate(originalRequest: InflateRequest): InflateResult {
    val chain = `-InterceptorChain`(interceptorsWithFallback, 0,
        originalRequest)
    return chain.proceed(originalRequest)
  }
复制代码


对应责任链模式的拦截器实现。

自定义的 LayoutInflater,自定义的 Factory/Factory2 ,难怪 ViewPump 可以为所欲为。


不熟悉 xml 布局文件加载流程的同学,可能还不不大能理解实现原理,推荐阅读蓝师傅的 《总结UI原理和高级的UI优化方式》 一文中的 LayoutInflater 原理 部分: juejin.cn/post/684490…


最后


其实,介入布局文件 View 创建流程的方法并不止这一种。

你知道 AppCompat 是如何把 TextView 变成 AppCompatTextView 的吗?

你知道 MaterialComponent 是如何把 Button 变成 MaterialButton 的吗?

不妨阅读我的一篇译文 关于视图加载的一些奇技淫巧

这一期的介绍就到这里了,我们下周五见。

如果你有好的项目推荐,欢迎给我留言。



相关文章
|
存储 JavaScript BI
GitHub:GitHub简介、使用方法、经验总结(图文教程)之详细攻略(持续更新!)
GitHub:GitHub简介、使用方法、经验总结(图文教程)之详细攻略(持续更新!)
|
17天前
|
设计模式 前端开发 JavaScript
20个GitHub仓库助你成为React大师
20个GitHub仓库助你成为React大师
|
11月前
|
设计模式 监控 算法
Github 助你实现“家国梦”
首先一点,这个游戏有30个建筑,但是只有9块地,同时会有各种不同的政策影响建筑的收益。所以,安放不同建筑是会影响收益高低的,且在一定的条件之下必然存在一个最优的摆放方式。这实际上就是算法中一个典型的最优化问题。
|
程序员 开发者
玩转 GitHub profile - 打造自己的特色 GitHub 主页(交友利器🐶)
Github 作为全球最大程序员交友网站,大家在上面交友时一个具有个人特色的自我介绍自然是少不了的。🐶 今天介绍下 GitHub 的一项特色功能 - GitHub profile,以及一些列开源工具、项目来帮助打造自己特色的 GitHub profile。 GitHub profile 也是最近两年 GitHub 才新加的功能,开发者可以通过编写 README 打造属于自己的个人 GitHub 首页。
|
12月前
|
消息中间件 设计模式 算法
硬核!Github星标79.4K的阿里强推Java面试参考指南到底有多强?
Java面试 谈到Java面试,相信大家第一时间脑子里想到的词肯定是金三银四,金九银十。好像大家的潜意识里做Java开发的都得在这个时候才能出去面试,跳槽成功率才高!但LZ不这么认为,LZ觉得我们做技术的一生中会遇到很多大大小小的面试,金三银四(金九银十)只是机会比平时多一些,但也未必每个人都能在这个时间段找到自己理想的岗位。我们能做的就是时刻准备着,当机会来临的时候能把握住就行。 这不借此机会,本着好东西就是要拿出来分享的原则,LZ就把前段时间从阿里的老哥手上白嫖到的面试参考指南分享出来,希望能对你们有所帮助。
|
搜索推荐 程序员 开发者
最潮程序员,教你打造超有“个性化”的Github主页
Github称为全球最大的"同性交友"平台,因为这里是程序员的天堂,在这里,很多程序员利用工作之余,无私的贡献了很多优秀的开源代码和框架。开源是一个利人利己的事,一方面,其他开发者在开发某个功能或者实现某个方案的时候,可以借鉴你的思路和经验,甚至是使用你已经封装好的开源库,很方便快速的完成功能开发,另一方面,可以吸引更多优秀的开发者共同参与一个开源项目的开发,也是一个彼此学习和成长的机会。
|
Arthas 算法 Java
这5个GitHub项目+3个网站,助你一飞冲天!
这5个GitHub项目+3个网站,助你一飞冲天! 小伙伴们周末好呀,这次来更新一波学习资源啦~ 👍 之前推荐过一些书,这次我们就来看看 4ye 平时常关顾的一些学习网站叭!😋 (同时会更新在菜单栏的 宝藏资源 中,方便查找) 资源一览 image-20210807235820378 CyC2018 / CS-Notes 高达 136K star 的项目! 😄 📚 技术面试必备基础知识、Leetcode、计算机操作系统、计算机网络、系统设计 概览图 地址👉 github.com/CyC2018/CS-… ima
708 0
|
运维 Kubernetes Cloud Native
Higress GitHub star 突破 1k,来自社区开发者和用户的寄语
一个遵循开源Ingress/Gateway API标准,提供流量调度、服务治理、安全防护三合一的高集成、易使用、易扩展、热更新的下一代云原生网关。
Higress GitHub star 突破 1k,来自社区开发者和用户的寄语
|
设计模式 算法 前端开发
GitHub上最火的、最值得前端学习的几个数据结构与算法项目!没有之一!
GitHub上最火的、最值得前端学习的几个数据结构与算法项目!没有之一!
580 0
GitHub上最火的、最值得前端学习的几个数据结构与算法项目!没有之一!
|
机器学习/深度学习 算法 搜索推荐
强烈推荐 | 竟然有这么优质的github项目!
不是一个标题党,想出这么一个名称的确有一些自卖自夸的感觉。 一直以来,我推荐了很多其他人的项目和开源的实用工具,这一次我鼓起勇气来推荐一下自己的开源项目,虽然对比于这个标题存在很大差距,甚至在有一些高手眼里有一些班门弄斧的感觉,但是这也的确是整合八个月来的心血,我也一向坚持一个原则,如果能够让一个同学觉得有用,这就值了。
强烈推荐 | 竟然有这么优质的github项目!