Android自定义控件入门 09 插件式换肤框架搭建-setContentView源码阅读

87 阅读4分钟

1.Activity的setContentView源码阅读

实现换肤:

  1. 每一次打开的都是新的皮肤(更换后的)
  2. 换肤之后所有的activity里面的View都要换肤
  3. 每次重新进入app也需要换肤

解决方案:

  1. 每一个activity里面都把需要换肤的View给找出来,然后调用代码去换肤(死板的方法);
  2. 获取activity里面的根布局,然后通过不断的循环获取子View,通过 tag;
  3. 拦截View的创建,这个目前是比较好的。

系统是怎样加载界面的

Activity->setContentView()->getWindow() instanceof PhoneWindow

PhoneWindow里面的setContentView(activity_main)

    @Override  
    public void setContentView(int layoutResID) {  
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window  
        // decor, when theme attributes and the like are crystalized. Do not check the feature  
        // before this happens.  
        if (mContentParent == null) {  
            installDecor();  
        } 
        //inflate
        mLayoutInflater.inflate(layoutResID, mContentParent);         
    }
private void installDecor() {  
    mForceDecorInstall = false;  
    if (mDecor == null) {  
        mDecor = generateDecor(-1);   
    }  
    if (mContentParent == null) {  
        mContentParent = generateLayout(mDecor);   
    }
}

做一系列的判断,去加载系统的layout资源文件

protected ViewGroup generateLayout(DecorView decor) {
    // Inflate the window decor.  
    int layoutResource;  
    int features = getLocalFeatures();  
    // System.out.println("Features: 0x" + Integer.toHexString(features));  
    if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {  
        if (mIsFloating) {  
            TypedValue res = new TypedValue();  
            getContext().getTheme().resolveAttribute(  
            R.attr.dialogTitleIconsDecorLayout, res, true);  
            layoutResource = res.resourceId;  
        } else {  
            layoutResource = R.layout.screen_title_icons;  
        }  
        // XXX Remove this once action bar supports these features.  
        removeFeature(FEATURE_ACTION_BAR);  
    // System.out.println("Title Icons!");  
    } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0 && (features & (1 << FEATURE_ACTION_BAR)) == 0) {  
        // Special case for a window with only a progress bar (and title).  
        // XXX Need to have a no-title version of embedded windows.  
        layoutResource = R.layout.screen_progress;  
        // System.out.println("Progress!");  
    } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {  
        // Special case for a window with a custom title.  
        // If the window is floating, we need a dialog layout  
        if (mIsFloating) {  
            TypedValue res = new TypedValue();  
            getContext().getTheme().resolveAttribute(R.attr.dialogCustomTitleDecorLayout, res, true);  
            layoutResource = res.resourceId;  
        } else {  
            layoutResource = R.layout.screen_custom_title;  
        }  
        // XXX Remove this once action bar supports these features.  
        removeFeature(FEATURE_ACTION_BAR);  
    } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {  
        // If no other features and not embedded, only need a title.  
        // If the window is floating, we need a dialog layout  
        if (mIsFloating) {  
            TypedValue res = new TypedValue();  
            getContext().getTheme().resolveAttribute(R.attr.dialogTitleDecorLayout, res, true); 
            layoutResource = res.resourceId;  
        } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {  
            layoutResource = a.getResourceId(R.styleable.Window_windowActionBarFullscreenDecorLayout, R.layout.screen_action_bar);  
        } else {  
            layoutResource = R.layout.screen_title;  
        }  
        // System.out.println("Title!");  
    } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {  
        layoutResource = R.layout.screen_simple_overlay_action_mode;  
    } else {  
        // Embedded, so no decoration is needed.  
        layoutResource = R.layout.screen_simple;  
        // System.out.println("Simple!");  
    }
    mDecor.startChanging();  
    //把系统的布局加入到了DecorView
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    //找一个叫做android.R.id.content的一个FrameLayout
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    retuen contentParent;
}

public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

image.png

2.AppCompatActivity的setcontentView源码阅读

AppCompatActivity

@Override  
public void setContentView(@LayoutRes int layoutResID) {  
    initViewTreeOwners();  
    getDelegate().setContentView(layoutResID);  
}

AppCompatDelegateImpl

@Override  
public void setContentView(int resId) {  
    ensureSubDecor();  
    ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);  
    contentParent.removeAllViews();  
    LayoutInflater.from(mContext).inflate(resId, contentParent);  
    mAppCompatWindowCallback.bypassOnContentChanged(mWindow.getCallback());  
}

2.1 extends ActivityAppCompateActivity的View的区别

  • extends AppCompatActivity mImageTv is com.google.android.material.textview.MaterialTextView
  • extends Activity mImageTv is android.widget.TextView
  • Log.d("tag", "onCreate: ________________${tv}")
  • AppCompateActivity创建View的时候会被拦截,不会走系统的Layoutlnfater的创建,就会被替换掉一些特定的View

AppCompatViewInflater

public final View createView(@Nullable View parent, @NonNull final String name,@NonNull Context context,@NonNull AttributeSet attrs, boolean inheritContext,boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {  
    View view = null;  
    // We need to 'inject' our tint aware Views in place of the standard framework versions  
    switch (name) {  
        case "TextView":  
            view = createTextView(context, attrs);  
            verifyNotNull(view, name);  
            break;  
        case "ImageView":  
            view = createImageView(context, attrs);  
            verifyNotNull(view, name);  
            break;  
        case "Button":  
            view = createButton(context, attrs);  
            verifyNotNull(view, name);  
            break;  
        case "EditText":  
            view = createEditText(context, attrs);  
            verifyNotNull(view, name);  
            break;  
        case "Spinner":  
            view = createSpinner(context, attrs);  
            verifyNotNull(view, name);  
            break;  
        case "ImageButton":  
            view = createImageButton(context, attrs);  
            verifyNotNull(view, name);  
            break;  
        case "CheckBox":  
            view = createCheckBox(context, attrs);  
            verifyNotNull(view, name);  
            break;  
        case "RadioButton":  
            view = createRadioButton(context, attrs);  
            verifyNotNull(view, name);  
            break;  
        case "CheckedTextView":  
            view = createCheckedTextView(context, attrs);  
            verifyNotNull(view, name);  
            break;  
        case "AutoCompleteTextView":  
            view = createAutoCompleteTextView(context, attrs);  
            verifyNotNull(view, name);  
            break;  
        case "MultiAutoCompleteTextView":  
            view = createMultiAutoCompleteTextView(context, attrs);  
            verifyNotNull(view, name);  
            break;  
        case "RatingBar":  
            view = createRatingBar(context, attrs);  
            verifyNotNull(view, name);  
            break;  
        case "SeekBar":  
            view = createSeekBar(context, attrs);  
            verifyNotNull(view, name);  
            break;  
        case "ToggleButton":  
            view = createToggleButton(context, attrs);  
            verifyNotNull(view, name);  
            break;  
        default:  
            // The fallback that allows extending class to take over view inflation  
            // for other tags. Note that we don't check that the result is not-null.  
            // That allows the custom inflater path to fall back on the default one  
            // later in this method.  
            view = createView(context, name, attrs);  
    }
    return view;  
}

3.LayoutInflater源码解读

LayoutInflater主要用来inflater我们的layout布局

  • View.inflate(this, R.layout.activity_main, null)
  • LayoutInflater.from(this).inflate(R.layout.activity_main, null)
  • LayoutInflater.from(this).inflate(R.layout.activity_main, null, false)

3.1LayoutInflater.from(this)获取的源码

//获取系统的服务
public static LayoutInflater from(@UiContext Context context) {  
    LayoutInflater LayoutInflater =  (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
    if (LayoutInflater == null) {  
        throw new AssertionError("LayoutInflater not found.");  
    }  
    return LayoutInflater;  
}

#ContextImpl

@Override  
public Object getSystemService(String name) {  
    if (vmIncorrectContextUseEnabled()) {  
        // Check incorrect Context usage.  
        if (WINDOW_SERVICE.equals(name) && !isUiContext()) {  
            final String errorMessage = "Tried to access visual service " + SystemServiceRegistry.getSystemServiceClassName(name)+ " from a non-visual Context:" + getOuterContext();  
            final String message = "WindowManager should be accessed from Activity or other "+ "visual Context. Use an Activity or a Context created with " + "Context#createWindowContext(int, Bundle), which are adjusted to "  + "the configuration and visual bounds of an area on screen.";  
            final Exception exception = new IllegalAccessException(errorMessage);  
            StrictMode.onIncorrectContextUsed(message, exception);  
            Log.e(TAG, errorMessage + " " + message, exception);  
        }  
    }  
    return SystemServiceRegistry.getSystemService(this, name);  
}

SystemServiceRegistry.getSystemService(this, name)里:

//从一个静态的Map集合
final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);

#SystemServiceRegistry.#static{}

registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,  new CachedServiceFetcher<LayoutInflater>() {  
    @Override  
    public LayoutInflater createService(ContextImpl ctx) {  
        return new PhoneLayoutInflater(ctx.getOuterContext());  
    }
});

LayoutInflater.from(this)是系统服务,是的单例设计模式(单个实例在内存中)

3.2 实例化View的inflater方法的源码

LayoutInflater->

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {}->

private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {}->

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) {}->

public final View tryCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {}

前提是看你有没有设置Factory继承AppCompatActivity他就设置了

public final View tryCreateView(@Nullable View parent, @NonNull String name,@NonNull Context context,@NonNull AttributeSet attrs) {  
    if (name.equals(TAG_1995)) {  
        // Let's party like it's 1995!  
        return new BlinkLayout(context, attrs);  
    }  

    View view;  
    if (mFactory2 != null) {  
        view = mFactory2.onCreateView(parent, name, context, attrs);  
    } else if (mFactory != null) {  
        view = mFactory.onCreateView(name, context, attrs);  
    } else {  
        view = null;  
    }  

    if (view == null && mPrivateFactory != null) {  
        view = mPrivateFactory.onCreateView(parent, name, context, attrs);  
    }  

    return view;  
}

最终会调用@Nullable public final View createView(@NonNull Context viewContext, @NonNull String name, @Nullable String prefix, @Nullable AttributeSet attrs) throws ClassNotFoundException, InflateException {}通过反射创建View

try {  
    final View view = constructor.newInstance(args);  
    return view;  
}

4.拦截View的创建

abstract class BaseSkinActivity : AppCompatActivity() {  
    override fun onCreate(savedInstanceState: Bundle?) {  
        val inflater = LayoutInflater.from(this)  
        LayoutInflaterCompat.setFactory2(inflater, object : LayoutInflater.Factory2 {  
            override fun onCreateView(parent: View?, name: String, context: Context, attrs: AttributeSet): View? {  
                //拦截到View的创建  
                Log.d("BaseSkinActivity", "拦截到View的创建")  
                if (name == "Button") {  
                    return TextView(this@BaseSkinActivity).apply {  
                        text = "换肤"  
                    }  
                }  
                return null  
            }  

            override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {  
                return null  
            }  
        })  
        super.onCreate(savedInstanceState)  
    }  
}