Reprint Please specify source: http://blog.csdn.net/llew2011/article/details/51287391
In the previous article, we focused on the process of Layoutinflater rendering XML layout files, as described in the article, if you set the factory for Layoutinflater before rendering, Factory's Oncreateview () method is called when each view is rendered, so you can use the Oncreateview () method to do a theme switching function. If you do not know the Layoutinflater rendering process, please click here. Today we start from the actual combat to achieve their own theme switching function.
Since the theme switch is dependent on factory, then need to define their own factory, custom factory is actually the implementation of the factory interface of the system, the code is as follows:
public class Skinfactory implements Factory {@Overridepublic View oncreateview (String name, context context, AttributeSet Attrs) {log.e ("skinfactory", "==============start=============="); int attrcounts = Attrs.getattributecount (); for ( int i = 0; i < attrcounts; i++) {String attrname = attrs.getattributename (i); String AttrValue = Attrs.getattributevalue (i); LOG.E ("Skinfactory", "attrname =" + Attrname + " attrValue =" + AttrValue);} LOG.E ("Skinfactory", "==============end=============="); return null;}}
The custom skinfactory did nothing, only looping through the Oncreateview () method to print the property name and the corresponding property value that the Attrs contains, and then returns NULL. After you've created Skinfactory, you're using it, and in the last article we talked about getting layoutinflater instance objects through the Getlayoutinflater () method in the activity. Once the object has been obtained, it can be assigned a value of factory, with the following code:
public class Mainactivity extends Activity {private Layoutinflater minflater;private skinfactory mfactory;@ overrideprotected void OnCreate (Bundle savedinstancestate) {super.oncreate (savedinstancestate); mfactory = new Skinfactory (); minflater = Getlayoutinflater (); minflater.setfactory (mfactory); Setcontentview (R.layout.activity_ skin);}}
It is important to note that the Layoutinflater setting factory for the activity must not work until the Setcontentview () method is called. After setting up factory, let's take a look at how the Activity_skin.xml layout file is defined, with the following code:
<?xml version= "1.0" encoding= "Utf-8"? ><framelayout xmlns:android= "http://schemas.android.com/apk/res/ Android " android:layout_width=" match_parent " android:layout_height=" Match_parent " android: background= "@color/color_app_bg" > <textview android:id= "@+id/text" android:layout_width= " Wrap_content " android:layout_height=" wrap_content " android:layout_gravity=" center " android:text=" Factory's little practice " android:textcolor=" @color/color_title_bar_text "/></framelayout>
The layout file centers on a textview, and sets the text to TextView "Factory's little Exercise" and runs the program, printing the results as follows:
Here only TextView printed data, from the printed data can be found if the property value is at the beginning of the value of the property is an application (you can later use the @ symbol to determine whether the current property is a reference). Because we can get all the attributes defined in the layout file in the Attrs, we can guess: if you add a custom attribute to the view, you can identify the view that you want to switch to by parsing the custom attribute in the Oncreateview () method. This conjecture is not correct, let's try it out.
Create the Attrs.xml property file under the Values folder, define the property name to enable, the property value to be Boolean (true indicates that theme switching is required, false means no theme switching), and the code is as follows:
<?xml version= "1.0" encoding= "Utf-8"?><resources> <attr name= "Enable" format= "Boolean"/>< /resources>
After defining the properties, to use the property, you need to declare the namespace, such as the system comes with: xmlns:android= "Http://sckemas.android.com/apk/res/android", the declaration namespace there are two ways: xmlns: skin= "http://schemas.android.com/apk/package name" or xmlns:skin= "Http://schemas.android.com/apk/res-auto". We use the second notation, the following code:
<?xml version= "1.0" encoding= "Utf-8"? ><framelayout xmlns:android= "http://schemas.android.com/apk/res/ Android " xmlns:skin=" Http://schemas.android.com/apk/res-auto " android:layout_width=" Match_parent " android:layout_height= "Match_parent" android:background= "@color/color_app_bg" > <textview Android:id= "@+id/text" android:layout_width= "wrap_content" android:layout_height= "Wrap_content " android:layout_gravity= "center" android:text= "factory's little Exercise" skin:enable= "true" android:textcolor= "@color/color_title_bar_text"/></framelayout>
Added a custom enable property to TextView in the Activity_skin.xml layout file and set the value to true, and after adding the property, the compiler error message said TextView No such attribute, as long as a manual cleanup. Then run the code and print the results as follows:
See the print results we're in a good happy. Adding custom properties to a view is OK, and then we can distinguish which view needs to do a theme switch based on this property. The subject of the theme switch is to cache those who need to do the theme switch view, but the view theme switch may need to change the background, text and so on. Also say that a view may want to change multiple properties, that this property requires different scenarios under different types, so you can abstract the class representing the property Baseattr,baseattr class has a property name, property value, attribute type and other member variables, there is an abstract method ( This method has different implementations in different scenarios, such as the current property is background, that should be set in the backgroundattr implementation of the background, if the current property is TextColor, then in the TEXTCOLORATTR implementation should be set text color. So baseattr can be abstracted as follows:
Public abstract class Baseattr {public string attrname;public int attrvalue;public string Entryname;public string Entrytyp E;public abstract void Apply (view view);}
After defining the Baseattr class, you can define specific implementation classes, such as Background attribute class backgroundattr, font color change class textcolorattr, Backgroundattr code as follows:
public class Backgroundattr extends baseattr {@Overridepublic void apply (view view) {if (null! = view) {View.setbackgroundx XX ();}}}
Abstract after the attribute class Baseattr we also have to consider the problem of caching view, because a view may have to correspond to multiple baseattr, so we also encapsulate a class Skinview, which represents a view corresponding to multiple baseattr, It also provides a way to update itself, so the code is as follows:
public class Skinview {public View view;public list<baseattr> viewattrs;public void Apply () {if (null! = View &&A mp Null! = Viewattrs) {for (baseattr attr:viewattrs) {attr.apply (view);}}}}
Abstract attribute Classes Baseattr and Skinview are defined, and the next step is to cache logic in Skinfactory, as follows:
public class Skinfactory implements Factory {private static final String Default_schema_name = "http://schemas.android.co M/apk/res-auto ";p rivate static final String default_attr_name =" Enable ";p rivate list<skinview> mskinviews = new Ar Raylist<skinview> (); @Overridepublic view Oncreateview (String name, context context, AttributeSet attrs) {view View = null;final Boolean skinenable = Attrs.getattributebooleanvalue (Default_schema_name, Default_attr_name, false); if (skinenable) {view = CreateView (name, context, attrs), if (null! = view) {Parseattrs (name, context, attrs, view);}} return view;} Public final View CreateView (String name, context context, AttributeSet attrs) {View view = null;if ( -1 = = Name.indexof ('. ') ) {if ("View". Equalsignorecase (name)) {view = CreateView (name, context, Attrs, "Android.view.");} if (null = = view) {view = CreateView (name, context, Attrs, "android.widget.");} if (null = = view) {view = CreateView (name, context, Attrs, "Android.webkit.");}} else {view = CreatevIew (name, context, attrs, null);} return view;} View CreateView (string name, context context, AttributeSet attrs, string prefix) {View view = null;try {view = Layoutinfla Ter.from (context). CreateView (name, prefix, attrs);} catch (Exception e) {}return view;} private void Parseattrs (String name, context context, AttributeSet attrs, view view) {int attrcount = Attrs.getattributeco UNT (); final Resources temp = context.getresources (); list<baseattr> viewattrs = new arraylist<baseattr> (); for (int i = 0; i < Attrcount; i++) {String attrname = Attrs.getattributename (i); String AttrValue = Attrs.getattributevalue (i), if (Issupportedattr (attrname)) {if (Attrvalue.startswith ("@")) {int id = Integer.parseint (attrvalue.substring (1)); String EntryName = temp.getresourceentryname (ID); String entrytype = temp.getresourcetypename (ID); Baseattr viewattr = createattr (Attrname, AttrValue, id, entryName, entrytype); if (null! = viewattr) {Viewattrs.add ( viewattr);}}} if (viewattrs.size () > 0) {Skinview SKInView = new Skinview (); skinview.view = View;skinview.viewattrs = Viewattrs;mskinviews.add (Skinview);}} Attrname:textcolor attrvalue:2130968576 entryname:common_bg_color entrytype:colorprivate BaseAttr createAttr (Stri Ng Attrname, string attrValue, int id, string entryName, String entrytype) {baseattr viewattr = null;if ("Background". Equal Signorecase (Attrname)) {viewattr = new backgroundattr ();} else if ("TextColor". Equalsignorecase (Attrname)) {viewattr = New Textcolorattr ();} if (null! = viewattr) {viewattr.attrname = Attrname;viewattr.attrvalue = Id;viewattr.entryname = EntryName; Viewattr.entrytype = EntryType;} return viewattr;} Private Boolean issupportedattr (String attrname) {if ("background". Equalsignorecase (Attrname)) {return true;} else if (" TextColor ". Equalsignorecase (Attrname)) {return true;} return false;} public void Applayskin () {if (null! = Mskinviews) {for (Skinview skinview:mskinviews) {if (null! = Skinview.view) {Skinview . apply ();}}}}
The Mskinviews cache collection that loads the Skinview type is defined in Skinfactory and is cached in the collection when it resolves to a qualifying view. Calling AttributeSet's Getattributebooleanvalue () method in the Oncreateview () method detects if the Enable property is included, If there is an enable property and the property value is true, we call the system API ourselves to create the view, parse the view if it is created successfully, and get its attrname,attrvalue,entryname, respectively. After the EntryType value is taken, the corresponding baseattr is created and then added to the cache collection Mskinviews, otherwise null is returned.
After creating Skinfactory, you also need to create a theme explorer Skinmanager, which is determined by the manager. So it mainly has the following functions: To read the additional theme resources, restore the default theme features, update the theme features and so on.
Let's take a look at how to read additional topic resource issues. Theme switching needs to prepare multiple sets of themes, which are actually some pictures, colors and so on. After having the material, we also have to consider how to provide the form of the app material, is directly provide a ZIP package file or to make an apk file in the form of providing to the app? If the ZIP package is provided the next processing is to extract the zip package to get inside the footage and then parse the read, theoretically this way is feasible, but the operation is a bit complicated. So we take the form of apk, if we want to access the resources in the footage apk as in the app to access resources, we have to get to the material APK resource instance, below I directly provide a generic can get APK resource instance code, the code is as follows:
Public final Resources getresources (context context, String Apkpath) {try {Assetmanager Assetmanager = Assetmanager.class . newinstance (); Method Addassetpath = Assetmanager.getclass (). Getdeclaredmethod ("Addassetpath", String.class); Addassetpath.setaccessible (True); Addassetpath.invoke (Assetmanager, Apkpath); Resources r = context.getresources (); Resources Skinresources = new resources (Assetmanager, R.getdisplaymetrics (), r.getconfiguration ()); return Skinresources;} catch (Exception e) {}return null;}
This code can effectively get to the resources instance in the APK, and then access the resource through the source instance as we access our resources directly in the app, if you are familiar with the Android resource access mechanism, it is clear why this code is written. Not clear also does not matter, first temporarily this use, I will in the subsequent article from the source point of view analysis the Android's resource access mechanism and explains this writes the reason.
OK, now that we have solved the problem of accessing the material resources, then the next is to write our Skinmanager class, the function of the Skinmanager class is to load the material resource file, there may be a failure when loading the file, so we need to give the app callback to notify the results of loading resources, We define the interface Iloadlistener, the code is as follows:
public interface Iloadlistener {void OnStart (); void onsuccess (); void OnFailure ();}
The Iloadlistener interface has three methods that indicate the callback that the resource starts to load, the callback after the load succeeds, and the callback after the failed load. We then complete our Skinmanager code as follows:
Public final class Skinmanager {private static final object Mclock = new Object ();p rivate static Skinmanager Minstance;pri Vate Context mcontext;private Resources mresources;private String mskinpkgname;private Skinmanager () {}public static Skinmanager getinstance () {if (null = = Minstance) {synchronized (Mclock) {if (null = = minstance) {minstance = new skinmanage R ();}}} return minstance;} public void init (context context) {Enablecontext (context); mcontext = Context.getapplicationcontext ();} public void Loadskin (String skinpath) {Loadskin (skinpath, null);} public void Loadskin (final String Skinpath, final Iloadlistener listener) {enablecontext (mcontext); Textutils.isempty (Skinpath)) {return;} New asynctask<string, Void, resources> () {@Overrideprotected void OnPreExecute () {if (null! = Listener) { Listener.onstart ();}} @Overrideprotected Resources doinbackground (String ... params) {if (null! = Params && Params.length = = 1) {String SK Inpath = Params[0]; File File = new file (Skinpath), if (nUll! = File && file.exists ()) {Packagemanager Packagemanager = Mcontext.getpackagemanager (); PackageInfo PackageInfo = Packagemanager.getpackagearchiveinfo (Skinpath, 1); if (null! = PackageInfo) {MSkinPkgName = Packageinfo.packagename;} Return Getresources (Mcontext, Skinpath);}} return null;} @Overrideprotected void OnPostExecute (Resources result) {if (null! = result) {mresources = Result;if (null! = Listener) {lis Tener.onsuccess ();}} else {if (null! = Listener) {listener.onfailure ();}}}}. Execute (skinpath);} Public Resources getresources (context context, String Apkpath) {try {Assetmanager Assetmanager = AssetManager.class.newInstance (); Method Addassetpath = Assetmanager.getclass (). Getdeclaredmethod ("Addassetpath", String.class); Addassetpath.setaccessible (True); Addassetpath.invoke (Assetmanager, Apkpath); Resources r = context.getresources (); Resources Skinresources = new resources (Assetmanager, R.getdisplaymetrics (), r.getconfiguration ()); return Skinresources;} catch (Exception e) {}returnnull;} public void Restoredefaultskin () {if (null! = mresources) {mresources = Null;mskinpkgname = null;}} public int getColor (int id) {enablecontext (mcontext); Resources originresources = mcontext.getresources (); int origincolor = Originresources.getcolor (ID); if (null = = mresources | | Textutils.isempty (Mskinpkgname)) {return origincolor;} String EntryName = mresources.getresourceentryname (ID), int resourceId = Mresources.getidentifier (entryName, "color", Mskinpkgname); try {return mresources.getcolor (resourceId);} catch (Exception e) {}return origincolor;} Public drawable getdrawable (int id) {enablecontext (mcontext); Resources originresources = mcontext.getresources ();D rawable origindrawable = originresources.getdrawable (ID); Null = = Mresources | | Textutils.isempty (Mskinpkgname)) {return origindrawable;} String EntryName = mresources.getresourceentryname (ID), int resourceId = Mresources.getidentifier (EntryName, "drawable ", mskinpkgname); try {return mresources.getdrawable (resourceId);} catch (ExcEption e) {}return origindrawable;} private void Enablecontext (context context) {if (null = = Context) {throw new NullPointerException ();}}}
Skinmanager We use a singleton pattern to ensure that there is only one instance of the application, which needs to be initialized before it can be thrown. Skinmanager not only defines the properties Mcontext and Mresources (Mcontext represents the running context of the app, mresources the resources instance object that represents the resource apk, and if NULL means using the default app theme Resource) , and it also provides a series of methods, such as reading the Resource GetColor () and Getdrawable () method, loading the resource apk method Loadskin () and so on.
Now that the core logic of the theme switch is there, let's look at what the package structure diagram looks like, and cut the graph as follows:
The core of the theme switch is almost finished, the next step is to practice using to see if the effect can become, first modify the Activity_skin.xml layout file, modified as follows:
<?xml version= "1.0" encoding= "Utf-8"? ><linearlayout xmlns:android= "http://schemas.android.com/apk/res/ Android "xmlns:skin=" Http://schemas.android.com/apk/res-auto "android:layout_width=" Match_parent "Android:layout_ height= "Match_parent" android:background= "@color/common_bg_color" android:orientation= "vertical" skin:enable= "Tru E "> <framelayout android:layout_width=" match_parent "android:layout_height=" 65DP "android:b ackground= "@color/common_title_bg_color" skin:enable= "true" > <textview Android:layout_widt H= "Wrap_content" android:layout_height= "wrap_content" android:layout_gravity= "center" and roid:text= "Theme Toggle title" Android:textcolor= "@color/common_title_text_color" android:textsize= "18SP" Skin:enable= "true"/> <button android:layout_width= "Wrap_content" Android:layout_h Eight= "Wrap_content" andRoid:layout_gravity= "right|center_vertical" android:onclick= "Updateskin" android:text= "Toggle Theme"/> </FrameLayout></LinearLayout>
The Enable property is added to the view node that requires theme switching in the Activity_skin.xml layout text, and its value is set to true. Next is to do a theme APK package, the simple way to do the theme package is to create a new project, do not add activity, etc., and then in the resource folder created corresponding resources, it is important to note that the resource file name must be consistent with the resource name in the app. The compilation is then packaged into an APK file, which is no longer demonstrated here. After packaging the apk we import into the emulator root, then modify the mainactivity, add the Updateskin () method, the code is as follows:
public void Updateskin (view view) {String Skinpath = Environment.getexternalstoragedirectory (). GetAbsolutePath () + File.separator + "skin.apk"; Skinmanager.getinstance (). Loadskin (Skinpath, New Iloadlistener () {@Overridepublic void onsuccess () { Mfactory.applayskin ();} @Overridepublic void OnStart () {} @Overridepublic void OnFailure () {}});
After adding the Updateskin () method, you can implement the switch theme, in order to facilitate me directly to the skin.apk file directly into the SD card root directory,need to be awareSome phones do not have external memory card to make a judgment, do not forget to add the file read and write permissions in the configuration file, and then run the program, the effect is as follows:
OK, now in the current page theme switch looks OK, but there are insufficient, when the page jumps such as from A→b→c→d and then in D for theme switching, this time ABC is no effect, and the code is not very versatile, so in the next article to deal with these issues, please look forward to ...
Android Source Series < five > in-depth understanding of Layoutinflater.factory theme switching from the source point of view (middle)