LayoutInflater的獲取
在我們寫listview的adapter的getView方法中我們都會通過LayoutInflater.from(mContext)
獲取LayoutInflater實(shí)例浴讯。
現(xiàn)在我們通過源碼來分析一下LayoutInflater實(shí)例的獲瓤肾:
//LayoutInflater的獲取
public abstract class LayoutInflater {
/**
* Obtains the LayoutInflater from the given context.
*/
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
}
context.getSystemService
是Android很重要的一個API敲霍,它是Activity的一個方法评雌,根據(jù)傳入的NAME來取得對應(yīng)的Object,然后轉(zhuǎn)換成相應(yīng)的服務(wù)對象。以下介紹系統(tǒng)相應(yīng)的服務(wù)。
Name | 返回的對象 | 說明 |
---|---|---|
WINDOW_SERVICE | WindowManager | 管理打開的窗口程序 |
LAYOUT_INFLATER_SERVICE | LayoutInflater | 取得xml里定義的view |
ACTIVITY_SERVICE | ActivityManager | 管理應(yīng)用程序的系統(tǒng)狀態(tài) |
POWER_SERVICE | PowerManger | 電源的服務(wù) |
ALARM_SERVICE | AlarmManager | 鬧鐘的服務(wù) |
NOTIFICATION_SERVICE | NotificationManager | 狀態(tài)欄的服務(wù) |
KEYGUARD_SERVICE | KeyguardManager | 鍵盤鎖的服務(wù) |
LOCATION_SERVICE | LocationManager | 位置的服務(wù)饰序,如GPS |
SEARCH_SERVICE | SearchManager | 搜索的服務(wù) |
VEBRATOR_SERVICE | Vebrator | 手機(jī)震動的服務(wù) |
CONNECTIVITY_SERVICE | Connectivity | 網(wǎng)絡(luò)連接的服務(wù) |
WIFI_SERVICE | WifiManager | Wi-Fi服務(wù) |
TELEPHONY_SERVICE | TeleponyManager | 電話服務(wù) |
獲取LayoutInflater服務(wù)
class ContextImpl extends Context {
/***部分代碼省略****/
static {
/***部分代碼省略****/
registerService(LAYOUT_INFLATER_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext());
}});
/***部分代碼省略****/
}
/***部分代碼省略****/
}
從源碼可以看出LayoutInflater實(shí)例是由 PolicyManager.makeNewLayoutInflater
獲取的,PolicyManager有沒有感覺很熟悉闻妓。上一章 Activity中的Window的setContentView 中我們獲取Activity中的Window的實(shí)例的時候就是通過PolicyManager獲取的菌羽,我們進(jìn)一步往下跟進(jìn)。
public final class PolicyManager {
private static final String POLICY_IMPL_CLASS_NAME =
"com.android.internal.policy.impl.Policy";
private static final IPolicy sPolicy;
static {
// Pull in the actual implementation of the policy at run-time
try {
Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
sPolicy = (IPolicy)policyClass.newInstance();
} catch (ClassNotFoundException ex) {
throw new RuntimeException(
POLICY_IMPL_CLASS_NAME + " could not be loaded", ex);
} catch (InstantiationException ex) {
throw new RuntimeException(
POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
} catch (IllegalAccessException ex) {
throw new RuntimeException(
POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
}
}
/***部分代碼省略****/
public static LayoutInflater makeNewLayoutInflater(Context context) {
//反射獲取實(shí)例
return sPolicy.makeNewLayoutInflater(context);
}
}
public class Policy implements IPolicy {
/***部分代碼省略****/
public LayoutInflater makeNewLayoutInflater(Context context) {
//LayoutInflater的最終實(shí)例
return new PhoneLayoutInflater(context);
}
/***部分代碼省略****/
}
PhoneLayoutInflater的實(shí)現(xiàn)
public class PhoneLayoutInflater extends LayoutInflater {
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
public PhoneLayoutInflater(Context context) {
super(context);
}
protected PhoneLayoutInflater(LayoutInflater original, Context newContext) {
super(original, newContext);
}
/** Override onCreateView to instantiate names that correspond to the
widgets known to the Widget factory. If we don't find a match,
call through to our super class.
*/
@Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
for (String prefix : sClassPrefixList) {
try {
View view = createView(name, prefix, attrs);
if (view != null) {
return view;
}
} catch (ClassNotFoundException e) {
// In this case we want to let the base class take a crack
// at it.
}
}
return super.onCreateView(name, attrs);
}
public LayoutInflater cloneInContext(Context newContext) {
return new PhoneLayoutInflater(this, newContext);
}
}
LayoutInflater最常使用的方法
在Android中LayoutInflater中最常使用的情況基本都是調(diào)用inflate方法用來構(gòu)造View對象由缆。
public abstract class LayoutInflater {
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
return inflate(parser, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
/**
* @param parser xml數(shù)據(jù)結(jié)構(gòu)
* @param root 一個可依附的rootview
* @param attachToRoot 是否將parser解析生產(chǎn)的View添加在root上
*/
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
//當(dāng)前上下文環(huán)境
final Context inflaterContext = mContext;
//所有的屬性集合獲取類
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
//根節(jié)點(diǎn)
View result = root;
try {
// Look for the root node.尋找根節(jié)點(diǎn)
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
//找不到根節(jié)點(diǎn)拋出異常
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
final String name = parser.getName();
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
//merge標(biāo)簽解析
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
//遞歸調(diào)用注祖,添加root的孩子節(jié)點(diǎn)
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml。根據(jù)當(dāng)前的attrs和xml創(chuàng)建一個xml根view
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied均唉。構(gòu)造LayoutParams
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp against its context.遞歸調(diào)用是晨,添加temp的孩子節(jié)點(diǎn)
rInflateChildren(parser, temp, attrs, true);
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
//將xml解析出來的viewgroup添加在root的根下
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
InflateException ex = new InflateException(e.getMessage());
ex.initCause(e);
throw ex;
} catch (Exception e) {
InflateException ex = new InflateException(
parser.getPositionDescription()
+ ": " + e.getMessage());
ex.initCause(e);
throw ex;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
return result;
}
}
}
這四個重載的inflate方法最終都是通過inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
進(jìn)行實(shí)現(xiàn)的。
LayoutInflater的使用中重點(diǎn)關(guān)注inflate方法的參數(shù)含義:
- inflate(xmlId, null); 只創(chuàng)建temp的View舔箭,然后直接返回temp罩缴。
- inflate(xmlId, parent); 創(chuàng)建temp的View,然后執(zhí)行root.addView(temp, params);最后返回root层扶。
- inflate(xmlId, parent, false); 創(chuàng)建temp的View箫章,然后執(zhí)行temp.setLayoutParams(params);然后再返回temp。
- inflate(xmlId, parent, true); 創(chuàng)建temp的View镜会,然后執(zhí)行root.addView(temp, params);最后返回root檬寂。
- inflate(xmlId, null, false); 只創(chuàng)建temp的View,然后直接返回temp戳表。
- inflate(xmlId, null, true); 只創(chuàng)建temp的View桶至,然后直接返回temp。
LayoutInflater解析視圖xml
- xml視圖樹解析
遞歸執(zhí)行rInflate生產(chǎn)View并添加給父容器
public abstract class LayoutInflater {
/**
* 將parser解析器中包含的view結(jié)合屬性標(biāo)簽attrs生產(chǎn)view添加在parent容器中
* @param parser xml解析器
* @param parent 父容器
* @param attrs 屬性標(biāo)簽集合
* @param finishInflate 生產(chǎn)view之后是否執(zhí)行父容器的onFinishInflate方法匾旭。
*/
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) { //requestFocus標(biāo)簽解析
parseRequestFocus(parser, parent);
} else if (TAG_TAG.equals(name)) { //tag標(biāo)簽解析
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) { //include標(biāo)簽解析
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) { //merge標(biāo)簽解析
throw new InflateException("<merge /> must be the root element");
} else {
//View標(biāo)簽解析
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
//View所在容器(ViewGroup)的屬性解析
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
//循環(huán)遍歷xml的子節(jié)點(diǎn)
rInflateChildren(parser, view, attrs, true);
//將解析出的view和其對于的屬性參數(shù)添加在父容器中
viewGroup.addView(view, params);
}
}
if (finishInflate) {
parent.onFinishInflate();
}
}
}
- 單個View布局的解析
調(diào)用createViewFromTag镣屹,設(shè)置View的Theme屬性。再調(diào)用CreateView方法創(chuàng)建view
public abstract class LayoutInflater {
protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException {
return createView(name, "android.view.", attrs);
}
protected View onCreateView(View parent, String name, AttributeSet attrs)
throws ClassNotFoundException {
return onCreateView(name, attrs);
}
/**
* 將parser解析器中包含的view結(jié)合屬性標(biāo)簽attrs生產(chǎn)view添加在parent容器中
* @param parent 父容器
* @param name view名稱
* @param context 上下文環(huán)境
* @param attrs 屬性標(biāo)簽集合
*/
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
return createViewFromTag(parent, name, context, attrs, false);
}
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
// Apply a theme wrapper, if allowed and one is specified.//應(yīng)用theme
if (!ignoreThemeAttr) {
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
if (themeResId != 0) {
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
}
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
try {
View view;
/*************************start Factory*/
//使用LayoutInflater的Factory价涝,對View進(jìn)行修改
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);
}
/*************************end Factory*/
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
//創(chuàng)建Android原生的View(android.view包下面的view)
view = onCreateView(parent, name, attrs);
} else {
//創(chuàng)建自定義View或者依賴包中的View(xml中聲明的是全路徑)
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
throw e;
} catch (ClassNotFoundException e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name);
ie.initCause(e);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name);
ie.initCause(e);
throw ie;
}
}
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Constructor<? extends View> constructor = sConstructorMap.get(name);
Class<? extends View> clazz = null;
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
//判斷View的構(gòu)造是否進(jìn)行緩存
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
}
//static final Class<?>[] mConstructorSignature = new Class[] {Context.class, AttributeSet.class};
constructor = clazz.getConstructor(mConstructorSignature);
sConstructorMap.put(name, constructor);
} else {
// If we have a filter, apply it to cached constructor
if (mFilter != null) {
// Have we seen this name before?
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
// New class -- remember whether it is allowed
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
mFilterMap.put(name, allowed);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
} else if (allowedState.equals(Boolean.FALSE)) {
failNotAllowed(name, prefix, attrs);
}
}
}
Object[] args = mConstructorArgs;
args[1] = attrs;
constructor.setAccessible(true);
//讀取View的構(gòu)造函數(shù)女蜈,傳入context、attrs作為參數(shù)。View(Context context, AttributeSet attrs)伪窖;
final View view = constructor.newInstance(args);
//處理ViewStub標(biāo)簽
if (view instanceof ViewStub) {
// Use the same context when inflating ViewStub later.
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
return view;
} catch (NoSuchMethodException e) {
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class "
+ (prefix != null ? (prefix + name) : name));
ie.initCause(e);
throw ie;
} catch (ClassCastException e) {
// If loaded class is not a View subclass
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Class is not a View "
+ (prefix != null ? (prefix + name) : name));
ie.initCause(e);
throw ie;
} catch (ClassNotFoundException e) {
// If loadClass fails, we should propagate the exception.
throw e;
} catch (Exception e) {
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class "
+ (clazz == null ? "<unknown>" : clazz.getName()));
ie.initCause(e);
throw ie;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
}
LayoutInflater創(chuàng)建View的總結(jié)
- 在inflate方法中吏廉,通過Resource.getLayout(resource)生產(chǎn)XmlResourceParser對象;
- 利用該對象實(shí)例生產(chǎn)XmlPullAttributes以便于xml標(biāo)簽中的屬性惰许。然后將這個兩個對象傳遞到rInflate方法中,解析layout對應(yīng)的xml文件史辙;
- 接著將(父容器汹买、xml中View的名稱、屬性標(biāo)簽)傳遞給createViewFromTag方法創(chuàng)建對應(yīng)的View聊倔;
- 在createViewFromTag方法中執(zhí)行LayoutInflater.Factory或者LayoutInflater的createView方法晦毙。
- 在createView方法中我們已知View的類名和View的屬性標(biāo)簽集合,通過Java反射執(zhí)行View的構(gòu)造方法創(chuàng)建View對象耙蔑。這也就是為什么我們在自定義View的時候必須復(fù)寫View的構(gòu)造函數(shù)View(Context context, AttributeSet attrs)见妒;
LayoutInflater.Factory簡介
LayoutInflater.Factory這個類在我們開發(fā)的過程中很少越到。但是我們在查看LayoutInflater解析View源碼的過程中可以看到如果LayoutInflater中有mFactory這個實(shí)例那么可以通過mFactory創(chuàng)建View,同時也能修改入?yún)ttributeSet屬性值甸陌。
public abstract class LayoutInflater {
/***部分代碼省略****/
public interface Factory {
public View onCreateView(String name, Context context, AttributeSet attrs);
}
public interface Factory2 extends Factory {
public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}
/***部分代碼省略****/
}
- LayoutInflater有兩個工廠類须揣,F(xiàn)actory和Factory2,區(qū)別只是在于Factory2可以傳入父容器作為參數(shù)钱豁。
public abstract class LayoutInflater {
/***部分代碼省略****/
public void setFactory(Factory factory) {
if (mFactorySet) {
throw new IllegalStateException("A factory has already been set on this LayoutInflater");
}
if (factory == null) {
throw new NullPointerException("Given factory can not be null");
}
mFactorySet = true;
if (mFactory == null) {
mFactory = factory;
} else {
mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
}
}
public void setFactory2(Factory2 factory) {
if (mFactorySet) {
throw new IllegalStateException("A factory has already been set on this LayoutInflater");
}
if (factory == null) {
throw new NullPointerException("Given factory can not be null");
}
mFactorySet = true;
if (mFactory == null) {
mFactory = mFactory2 = factory;
} else {
mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
}
}
/***部分代碼省略****/
}
這兩個方法的功能基本是一致的耻卡,setFactory2是在Android3.0之后以后引入的,所以我們要根據(jù)SDK的版本去選擇調(diào)用上述方法牲尺。
在supportv4下邊也有LayoutInflaterCompat可以做相同的操作卵酪。
public class LayoutInflaterCompat {
/***部分代碼省略****/
static final LayoutInflaterCompatImpl IMPL;
static {
final int version = Build.VERSION.SDK_INT;
if (version >= 21) {
IMPL = new LayoutInflaterCompatImplV21();
} else if (version >= 11) {
IMPL = new LayoutInflaterCompatImplV11();
} else {
IMPL = new LayoutInflaterCompatImplBase();
}
}
private LayoutInflaterCompat() {
}
public static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
IMPL.setFactory(inflater, factory);
}
}
LayoutInflater.Factory的使用
找到當(dāng)前Activity中的id=R.id.text的TextView將其替換為Button,并修改BackgroundColor谤碳。
public class MainActivity extends Activity {
final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LayoutInflater.from(this).setFactory(new LayoutInflater.Factory() {
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
if ("TextView".equals(name)) {
Log.e(TAG, "name = " + name);
int n = attrs.getAttributeCount();
//打印所有屬性標(biāo)簽
for (int i = 0; i < n; i++) {
Log.e(TAG, attrs.getAttributeName(i) + " , " + attrs.getAttributeValue(i));
}
for (int i = 0; i < n; i++) {
if (attrs.getAttributeName(i).equals("id")) {
String attributeValue = attrs.getAttributeValue(i);
String id = attributeValue.substring(1, attributeValue.length());
if (R.id.text == Integer.valueOf(id)) {
Button button = new Button(context, attrs);
button.setBackgroundColor(Color.RED);
return button;
}
}
}
}
return null;
}
});
setContentView(R.layout.activity_main);
}
}
Console輸出:
MainActivity: name = TextView
MainActivity: id , @2131492944
MainActivity: layout_width , -2
MainActivity: layout_height , -2
MainActivity: text , Hello World!
是不是發(fā)現(xiàn)LayoutInflater的Factory功能很好很強(qiáng)大溃卡。
這里提一個問題,如果把上面代碼中的MainActivity的父類修改為AppCompatActivity會怎么樣呢蜒简?我們試著運(yùn)行一下瘸羡。
java.lang.RuntimeException: Unable to start activity
ComponentInfo{com.example.tzx.dexload/com.example.tzx.dexload.MainActivity}:
java.lang.IllegalStateException: A factory has already been set on this LayoutInflater
程序運(yùn)行報錯:A factory has already been set on this LayoutInflater
。這個是在執(zhí)行LayoutInflater的setFactory方法時拋出的異常臭蚁。因?yàn)閙FactorySet=true最铁。。垮兑。冷尉。這個時候我們發(fā)現(xiàn)LayoutInflater的Factory已經(jīng)被設(shè)置過了。具體是在哪里設(shè)置的呢系枪?我們看看下邊LayoutInflater.Factory在Android源碼中的使用
部分內(nèi)容雀哨。
LayoutInflater.Factory在Android源碼中的使用
在我們開發(fā)過程是很少使用到LayoutInflater.Factory,但是Android在supportv7中就使用,我們來學(xué)習(xí)一下雾棺。
在AppComPatActivity中的onCreate就進(jìn)行了LayoutInflater.Factory的設(shè)置膊夹。
public class AppCompatActivity extends FragmentActivity implements AppCompatCallback,
TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
}
- 根據(jù)不通的sdk版本做適配
public abstract class AppCompatDelegate {
public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
return create(activity, activity.getWindow(), callback);
}
/***部分代碼省略****/
//對于不同的版本做適配
private static AppCompatDelegate create(Context context, Window window,
AppCompatCallback callback) {
final int sdk = Build.VERSION.SDK_INT;
if (sdk >= 23) {
return new AppCompatDelegateImplV23(context, window, callback);
} else if (sdk >= 14) {
return new AppCompatDelegateImplV14(context, window, callback);
} else if (sdk >= 11) {
return new AppCompatDelegateImplV11(context, window, callback);
} else {
return new AppCompatDelegateImplV7(context, window, callback);
}
}
}
LayoutInflaterFactory的實(shí)現(xiàn)類,以及觸發(fā)LayoutInflater.setFactory的調(diào)用捌浩。
class AppCompatDelegateImplV7 extends AppCompatDelegateImplBase
implements MenuBuilder.Callback, LayoutInflaterFactory {
/***部分代碼省略****/
@Override
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
//進(jìn)行setFactory的設(shè)置
LayoutInflaterCompat.setFactory(layoutInflater, this);
} else {
Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
+ " so we can not install AppCompat's");
}
}
/***部分代碼省略****/
@Override
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
final boolean isPre21 = Build.VERSION.SDK_INT < 21;
if (mAppCompatViewInflater == null) {
//具體的實(shí)現(xiàn)類放刨,下文會有講到
mAppCompatViewInflater = new AppCompatViewInflater();
}
// We only want the View to inherit it's context if we're running pre-v21
final boolean inheritContext = isPre21 && mSubDecorInstalled
&& shouldInheritContext((ViewParent) parent);
return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
isPre21, /* Only read android:theme pre-L (L+ handles this anyway) */
true /* Read read app:theme as a fallback at all times for legacy reasons */
);
}
}
- 根據(jù)不同的版本找到LayoutInflater的包裝類
public final class LayoutInflaterCompat {
interface LayoutInflaterCompatImpl {
public void setFactory(LayoutInflater layoutInflater, LayoutInflaterFactory factory);
public LayoutInflaterFactory getFactory(LayoutInflater layoutInflater);
}
static class LayoutInflaterCompatImplBase implements LayoutInflaterCompatImpl {
@Override
public void setFactory(LayoutInflater layoutInflater, LayoutInflaterFactory factory) {
LayoutInflaterCompatBase.setFactory(layoutInflater, factory);
}
@Override
public LayoutInflaterFactory getFactory(LayoutInflater layoutInflater) {
return LayoutInflaterCompatBase.getFactory(layoutInflater);
}
}
static final LayoutInflaterCompatImpl IMPL;
static {
final int version = Build.VERSION.SDK_INT;
if (version >= 21) {
IMPL = new LayoutInflaterCompatImplV21();
} else if (version >= 11) {
IMPL = new LayoutInflaterCompatImplV11();
} else {
IMPL = new LayoutInflaterCompatImplBase();
}
}
/***部分代碼省略****/
private LayoutInflaterCompat() {
}
public static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
IMPL.setFactory(inflater, factory);
}
public static LayoutInflaterFactory getFactory(LayoutInflater inflater) {
return IMPL.getFactory(inflater);
}
}
- FactoryWrapper類通過調(diào)用LayoutInflaterFactory的onCreateView方法,實(shí)現(xiàn)了LayoutInflater.Factory接口尸饺。最終調(diào)用了LayoutInflater的setFactory方法进统,使得在LayoutInflater.createViewFromTag中創(chuàng)建View的時候通過Factory進(jìn)行床架。
public interface LayoutInflaterFactory {
public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}
class LayoutInflaterCompatBase {
static class FactoryWrapper implements LayoutInflater.Factory {
final LayoutInflaterFactory mDelegateFactory;
FactoryWrapper(LayoutInflaterFactory delegateFactory) {
mDelegateFactory = delegateFactory;
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
//調(diào)用LayoutInflaterFactory實(shí)現(xiàn)類的onCreateView(null, name, context, attrs)方法
return mDelegateFactory.onCreateView(null, name, context, attrs);
}
public String toString() {
return getClass().getName() + "{" + mDelegateFactory + "}";
}
}
static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
//最終調(diào)用了LayoutInflater的setFactory方法浪听,對Factory進(jìn)行設(shè)置
inflater.setFactory(factory != null ? new FactoryWrapper(factory) : null);
}
static LayoutInflaterFactory getFactory(LayoutInflater inflater) {
LayoutInflater.Factory factory = inflater.getFactory();
if (factory instanceof FactoryWrapper) {
return ((FactoryWrapper) factory).mDelegateFactory;
}
return null;
}
}
- 在LayoutInflaterFactory的實(shí)現(xiàn)類之一AppCompatDelegateImplV7中螟碎,找到了setFactory的實(shí)際使用意義實(shí)際意思秫舌。
- 在LayoutInflater.createViewFromTag方法中調(diào)用
Factory.onCreateView(name, context, attrs)
方法- Factory的實(shí)現(xiàn)類FactoryWrapper中够挂,調(diào)用
LayoutInflaterFactory的onCreateView(null, name, context, attrs)
方法
class AppCompatViewInflater {
/***部分代碼省略****/
public final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
final Context originalContext = context;
// We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
// by using the parent's context
if (inheritContext && parent != null) {
context = parent.getContext();
}
if (readAndroidTheme || readAppTheme) {
// We then apply the theme on the context, if specified
context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
}
if (wrapContext) {
context = TintContextWrapper.wrap(context);
}
View view = null;
// We need to 'inject' our tint aware Views in place of the standard framework versions
switch (name) {
case "TextView":
view = new AppCompatTextView(context, attrs);
break;
case "ImageView":
view = new AppCompatImageView(context, attrs);
break;
case "Button":
view = new AppCompatButton(context, attrs);
break;
case "EditText":
view = new AppCompatEditText(context, attrs);
break;
case "Spinner":
view = new AppCompatSpinner(context, attrs);
break;
case "ImageButton":
view = new AppCompatImageButton(context, attrs);
break;
case "CheckBox":
view = new AppCompatCheckBox(context, attrs);
break;
case "RadioButton":
view = new AppCompatRadioButton(context, attrs);
break;
case "CheckedTextView":
view = new AppCompatCheckedTextView(context, attrs);
break;
case "AutoCompleteTextView":
view = new AppCompatAutoCompleteTextView(context, attrs);
break;
case "MultiAutoCompleteTextView":
view = new AppCompatMultiAutoCompleteTextView(context, attrs);
break;
case "RatingBar":
view = new AppCompatRatingBar(context, attrs);
break;
case "SeekBar":
view = new AppCompatSeekBar(context, attrs);
break;
}
if (view == null && originalContext != context) {
// If the original context does not equal our themed context, then we need to manually
// inflate it using the name so that android:theme takes effect.
view = createViewFromTag(context, name, attrs);
}
if (view != null) {
// If we have created a view, check it's android:onClick
checkOnClickListener(view, attrs);
}
return view;
}
}
- AppCompatViewInflater作為LayoutInflaterFactory的的onCreateView方法的最終實(shí)現(xiàn)類,通過createView方法替換了一些我們想要自己替換的View刑赶。比如:
原始View | 實(shí)際創(chuàng)建的View |
---|---|
TextView | AppCompatTextView |
ImageView | AppCompatImageView |
Button | AppCompatButton |
…… | …… |
在appcompat使用自定義的LayoutInflater.Factory
這里我們有兩種書寫方式:
這里必須寫在
super.oncreate
之前克伊,否則還會繼續(xù)報錯酥郭。
- 繼續(xù)使用 LayoutInflater.from(this).setFactory 方法。
LayoutInflater.from(this).setFactory(new LayoutInflater.Factory() {
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
AppCompatDelegate delegate = getDelegate();
//調(diào)用AppCompatDelegate的createView方法將第一個參數(shù)設(shè)置為null
View view = delegate.createView(null, name, context, attrs);
if ("TextView".equals(name)) {
Log.e(TAG, "name = " + name);
int n = attrs.getAttributeCount();
for (int i = 0; i < n; i++) {
Log.e(TAG, attrs.getAttributeName(i) + " , " + attrs.getAttributeValue(i));
}
for (int i = 0; i < n; i++) {
if (attrs.getAttributeName(i).equals("id")) {
String attributeValue = attrs.getAttributeValue(i);
String id = attributeValue.substring(1, attributeValue.length());
if (R.id.text == Integer.valueOf(id)) {
Button button = new Button(context, attrs);
button.setBackgroundColor(Color.RED);
button.setAllCaps(false);
return button;
}
}
}
}
return view;
}
});
- 使用LayoutInflaterCompat.setFactory方法
LayoutInflaterCompat.setFactory(LayoutInflater.from(this), new LayoutInflaterFactory() {
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
//appcompat 創(chuàng)建view代碼
AppCompatDelegate delegate = getDelegate();
View view = delegate.createView(parent, name, context, attrs);
if ("TextView".equals(name)) {
Log.e(TAG, "name = " + name);
int n = attrs.getAttributeCount();
for (int i = 0; i < n; i++) {
Log.e(TAG, attrs.getAttributeName(i) + " , " + attrs.getAttributeValue(i));
}
for (int i = 0; i < n; i++) {
if (attrs.getAttributeName(i).equals("id")) {
String attributeValue = attrs.getAttributeValue(i);
String id = attributeValue.substring(1, attributeValue.length());
if (R.id.text == Integer.valueOf(id)) {
Button button = new Button(context, attrs);
button.setBackgroundColor(Color.RED);
button.setAllCaps(false);
return button;
}
}
}
}
return view;
}
});
兩種寫法的原理是相同的答毫,因?yàn)樯厦嬷v述的LayoutInflater.Factory的實(shí)現(xiàn)類FactoryWrapper實(shí)現(xiàn)onCreateView方法的時候調(diào)用的AppCompatDelegate.onCreateView的時候第一個參數(shù)傳遞的值就是null褥民。
Android應(yīng)用中的換膚(夜間模式)是不是也利用的是LayoutInflater.Factory原理實(shí)現(xiàn)的呢,我們一起期待下一篇關(guān)于Android換膚文章洗搂。
參考文章:
Android 探究 LayoutInflater setFactory
文章到這里就全部講述完啦消返,若有其他需要交流的可以留言哦~!~耘拇!