Android Hook View的创建流程

前言

前面我们对setContentView的源码进行了深入的分析Android最全的setContentView源码分析,那了解了View的创建流程,我们可以做些什么呢?答案就是我们可以通过拦截View的创建流程去解析View对应的属性(如textColor、src、background等),然后进行APP的换肤!

具体拦截实现

前面我们分析过View的创建流程会交给Factory2.onCreateView()方法去实现,那我们就通过实现 LayoutInflater.factory2接口进行View的创建拦截!

  • 实现LayoutInflater.factory2接口
class BaseSkinActivity : BaseActivity(), LayoutInflater.Factory2
  • onCreate(savedInstanceState: Bundle?)中设置LayoutInflater.factory2
    override fun onCreate(savedInstanceState: Bundle?) {
       val layoutInflater = LayoutInflater.from(this)
       layoutInflater.factory2 = this  //当前activity实现 LayoutInflater.factory2接口
       super.onCreate(savedInstanceState)
   }
  • 在factory2接口方法中进行View的创建拦截处理
 override fun onCreateView(
       parent: View?,
       name: String,
       context: Context,
       attrs: AttributeSet
   ): View? {
       val view = createView(parent, name, context, attrs)
       if (view != null) {
          	//在这里可以通过attrs解析View的属性和值,进行对应的换肤操作
       }

       return view
   }

   private fun createView(
       parent: View?,
       name: String,
       context: Context,
       attrs: AttributeSet
   ): View {
   	//这里的实现参考系统中的AppCompatDelegateImpl.createView方法,直接抄
       if (appCompatViewInflater == null) {
           appCompatViewInflater = AppCompatViewInflater()
       }
       var inheritContext = false
       if (IS_PRE_LOLLIPOP) {
           if (mLayoutIncludeDetector == null) {
               mLayoutIncludeDetector = LayoutIncludeDetector()
           }
           inheritContext = if (mLayoutIncludeDetector!!.detect(attrs)) {
               true
           } else {
               if (attrs is XmlPullParser // If we have a XmlPullParser, we can detect where we are in the layout
               ) (attrs as XmlPullParser).depth > 1 // Otherwise we have to use the old heuristic
               else shouldInheritContext(parent as ViewParent?)
           }
       }

       return appCompatViewInflater!!.createView(
           parent, name, context, attrs, inheritContext,
           IS_PRE_LOLLIPOP,  /* Only read android:theme pre-L (L+ handles this anyway) */
           true,  /* Read read app:theme as a fallback at all times for legacy reasons */
           VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
       )
   }


   private fun shouldInheritContext(parent: ViewParent?): Boolean {
       var parent: ViewParent? = parent
       val windowDecor: View = window.decorView
       while (true) {
           if (parent == null) {
               return true
           } else if (parent === windowDecor || parent !is View
               || ViewCompat.isAttachedToWindow((parent as View?)!!)
           ) {
               return false
           }
           parent = parent.getParent()
       }
   }

其中AppCompatViewInflater我们无法调用使用系统对应的createView方法,最简单的方式是我们直接复制一份相同的AppCompatViewInflater类,去设置createViewpublic方法即可,createView方法的实现完全可以参考系统中的AppCompatDelegateImpl.createView方法~~

总结

这里只是简单的提供了APP插件换肤的思路,实际开发中插件换肤需要考虑的问题是比较多的,如多套皮肤的管理、自定义View的处理等等问题,但插件换肤的核心就是Hook View的创建流程,从而替换View的属性,说到底还是对源码的深刻理解。

结语

如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!ღ( ´・ᴗ・` )

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值