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 的吗?

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

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

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



相关文章
|
API 开发工具 git
【打造超酷的GitHub主页】
【打造超酷的GitHub主页】
|
存储 JavaScript BI
GitHub:GitHub简介、使用方法、经验总结(图文教程)之详细攻略(持续更新!)
GitHub:GitHub简介、使用方法、经验总结(图文教程)之详细攻略(持续更新!)
|
4月前
|
CDN
惊呆了、老铁。CSDN竟然有GitHub的加速功能????
这篇文章介绍了几种加速访问GitHub的方法,包括使用镜像网站、代理网站下载、利用CDN加速以及转入Gitee平台进行加速。作者建议,对于较大的项目推荐使用代理网站或Gitee下载,而对于较小的项目,使用CDN加速即可满足需求。
惊呆了、老铁。CSDN竟然有GitHub的加速功能????
|
7月前
|
设计模式 前端开发 JavaScript
20个GitHub仓库助你成为React大师
20个GitHub仓库助你成为React大师
130 0
|
6月前
|
数据采集 搜索推荐 JavaScript
GitHub星标3500的Python爬虫实战入门教程,限时开源!
爬虫的全称为网络爬虫,简称爬虫,别名有网络机器人,网络蜘蛛等等。 网络爬虫是一种自动获取网页内容的程序,为搜索引擎提供了重要的数据支撑。搜索引擎通过网络爬虫技术,将互联网中丰富的网页信息保存到本地,形成镜像备份。我们熟悉的谷歌、百度本质上也可理解为一种爬虫。 如果形象地理解,爬虫就如同一只机器蜘蛛,它的基本操作就是模拟人的行为去各个网站抓取数据或返回数据。
|
7月前
|
数据采集 机器学习/深度学习 JavaScript
5个火爆 GitHub 的 Python 练习项目,快来收藏!
5个火爆 GitHub 的 Python 练习项目,快来收藏!
537 0
|
程序员 开发者
玩转 GitHub profile - 打造自己的特色 GitHub 主页(交友利器🐶)
Github 作为全球最大程序员交友网站,大家在上面交友时一个具有个人特色的自我介绍自然是少不了的。🐶 今天介绍下 GitHub 的一项特色功能 - GitHub profile,以及一些列开源工具、项目来帮助打造自己特色的 GitHub profile。 GitHub profile 也是最近两年 GitHub 才新加的功能,开发者可以通过编写 README 打造属于自己的个人 GitHub 首页。
|
搜索推荐 程序员 开发者
最潮程序员,教你打造超有“个性化”的Github主页
Github称为全球最大的"同性交友"平台,因为这里是程序员的天堂,在这里,很多程序员利用工作之余,无私的贡献了很多优秀的开源代码和框架。开源是一个利人利己的事,一方面,其他开发者在开发某个功能或者实现某个方案的时候,可以借鉴你的思路和经验,甚至是使用你已经封装好的开源库,很方便快速的完成功能开发,另一方面,可以吸引更多优秀的开发者共同参与一个开源项目的开发,也是一个彼此学习和成长的机会。
|
机器学习/深度学习 人工智能 算法
快手开源的这个「斗地主」项目,在 GitHub 火了!
快手开源的这个「斗地主」项目,在 GitHub 火了!
2981 0
快手开源的这个「斗地主」项目,在 GitHub 火了!
|
存储 搜索推荐 前端开发
来Github炫一下~在Github主页显示你的个人简历~
从另一个博主的文章(文章链接在这)那里看到了Github上线了一个新的功能: 可以在Github主页制作一个个性化的介绍页面 也就是每个项目主页通常有的README.md文件,熟悉Github的小伙伴都知道,在README.md文件主要是对项目的简介和使用说明的介绍,那这个对自己的个性化介绍页面怎么做呢