Hello everyone, I am Lu Yao, every Friday I will recommend you a high-quality Github project of pan-mobile terminal.

Today’s main character is ViewPump, which can be directly involved in the layout file View creation process. Can modify TextView text, font, can graft, replace various views. Use your imagination, it can do so much more!

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

Above data as of March 3, 2022.

Method of use

Add dependencies:

dependencies {
    implementation 'the IO. Making. Inflationx: viewpump: 2.0.3'
}
Copy the code

The ViewPump is based on the responsibility chain mode, so you can implement the Interceptor freely. You can do some custom processing before and after a View is created. If you don’t understand, it’s a direct analogy to Okhttp’s interceptor, which can handle Request and Respone separately.

Here are two simple examples from Readme to illustrate how to use it.

The first is to replace the View directly before it is created. In the following example, replace the TextView in the layout file directly with the CustomTextView at run time.

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());
            returnInflateResult.builder() .view(view) .name(view.getClass().getName()) .context(request.context()) .attrs(request.attrs())  .build(); }else {
            returnchain.proceed(request); }}}Copy the code

Second, make some changes after the View is created. In the following example, we modify the text of the TextView after it is created to add a prefix.

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());
        }
        returnresult; }}Copy the code

Don’t forget to initialize the Application and add interceptors.

@Override
public void onCreate(a) {
    super.onCreate();
    ViewPump.init(ViewPump.builder()
                .addInterceptor(new TextUpdatingInterceptor())
                .addInterceptor(new CustomTextViewInterceptor())
                .build());
    //....
}
Copy the code

Note the order in which interceptors are added. If you add CustomTextViewInterceptor TextView all be replaced, cause TextUpdatingInterceptor failure. In general, pre-processed interceptors should be placed before post-processed interceptors.

Finally, attach the following code to the Activity attachBaseContext() :

@Override
protected void attachBaseContext(Context newBase) {
    super.attachBaseContext(ViewPumpContextWrapper.wrap(newBase));
}
Copy the code

In addition to the two simple uses above, there are several more described in wiki:

  1. Simulate the behavior of AppCompat
  2. Hide views without contentDescription (to facilitate frictionless adaptation)
  3. Highlight a particular View
  4. See Android-geocities-theme for various enhancements to View
  5. To dynamically modify the text of a string resource, see Philology

Use your head, and there will be more.

Realize the principle of

Viewpump.init () does not expand as long as it saves the configuration of the user added adapter and some parameters.

Focus on the Activity. AttachBaseContext () to add the code:

@Override
protected void attachBaseContext(Context newBase) {
    super.attachBaseContext(ViewPumpContextWrapper.wrap(newBase));
}
Copy the code

The original Context is enhanced based on the decorator pattern:

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? {
    // Returns a custom LayoutInflater
    if (Context.LAYOUT_INFLATER_SERVICE == name) {
      return inflater
    }
    return super.getSystemService(name)
  }
	...
}
Copy the code

Rewrote the getSystemService() method to return custom ViewPumpLayoutInflater when the service name is layout_inflater.

internal class` -ViewPumpLayoutInflater` (original: LayoutInflater.newContext: Context.cloned: Boolean
) : LayoutInflater(original.newContext), ` -ViewPumpActivityFactory` {... init { setUpLayoutFactories(cloned) }// Use a custom 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 } } ... }Copy the code

ViewPumpLayoutInflater uses custom Factory and Factory2 inflaters.

  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
    }
  }
Copy the code

The onCreateView method of Factory/Factory2 ends up pointing to viewpump.get ().inflate().

  fun inflate(originalRequest: InflateRequest): InflateResult {
    val chain = `-InterceptorChain`(interceptorsWithFallback, 0,
        originalRequest)
    return chain.proceed(originalRequest)
  }
Copy the code

Interceptor implementation corresponding to the chain of responsibility pattern.

With custom LayoutInflaters, custom Factory/Factory2, it’s no wonder ViewPump can do whatever it wants.

If you are not familiar with the loading process of XML layout files, you may not be able to understand the implementation principle. I recommend you to read the principle of LayoutInflater in Master LAN’s article summarizing UI Principles and Advanced UI Optimization methods: juejin.cn/post/684490… .

The last

In fact, this is not the only way to get involved in the layout file View creation process.

You know how AppCompat turns TextView into AppCompatTextView?

Do you know how the MaterialComponent turns a Button into a MaterialButton?

Read my translation for some of the wonders of view loading.

That’s all for this introduction, and I’ll see you next Friday.

If you have a good project recommendation, feel free to leave a message to me.