編輯:Android資訊
之前做過一個項目(隨心壁紙),主要展示過去每期的壁紙主題以及相應的壁紙,而且策劃要求,最好可以動態變換主題呈現方式,這樣用戶體驗會比較好。嗯,好吧,策劃的話,咱們也沒法反駁,畢竟這樣搞,確實很不錯。於是開始去研究這方面的東西。
首先,我想到的是照片牆效果,改變圖片就能有不同的呈現方式。可是這樣的話,文字以及更深層的自定義效果,就無法實現了。然後,思考了下,決定仿照android原生布局文件解析方式,自己去動態解析布局。
先來看下android 原生布局文件解析流程:
第一步:調用LayoutInflater的inflate函數解析xml文件得到一個view,然後來看看inflate函數:
//使用常見的API方法去解析xml布局文件, LayoutInflater layoutInflater = (LayoutInflater)getSystemService(); View root = layoutInflater.inflate(R.layout.main, null,false);
第二步:在inflate函數中,獲取一個XmlResourceParser來解析xml布局文件,再往下跟inflate(parser, root, attachToRoot):
public View inflate(int resource, ViewGroup root, boolean attachToRoot) { if (DEBUG) System.out.println("INFLATING from resource: " + resource); XmlResourceParser parser = getContext().getResources().getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); } }
第三步:inflate函數中會根據布局的節點名創建根視圖,接著根據方法中傳進來的root參數,判斷是否為空,如果不為null,則為該根視圖賦予外面父視圖的布局參數。接著調用rInflate函數來為根視圖添加所有字節點。
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { final AttributeSet attrs = Xml.asAttributeSet(parser); Context lastContext = (Context)mConstructorArgs[0]; mConstructorArgs[0] = mContext; //該mConstructorArgs屬性最後會作為參數傳遞給View的構造函數 View result = root; try { // Look for the root node. int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty } if (type != XmlPullParser.START_TAG) { throw new InflateException(parser.getPositionDescription() + ": No start tag found!"); } final String name = parser.getName(); //節點名,即API中的控件或者自定義View完整限定名 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"); } rInflate(parser, root, attrs, false); } else { // Temp is the root view that was found in the xml View temp; if (TAG_1995.equals(name)) { temp = new BlinkLayout(mContext, attrs); } else { temp = createViewFromTag(root, name, attrs); } ViewGroup.LayoutParams params = null; if (root != null) { // Create layout params that match root, if supplied 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); } } // Inflate all children under temp rInflate(parser, temp, attrs, true); // We are supposed to attach all the views we found (int temp) // to root. Do that now. if (root != null && attachToRoot) { 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) { //... } finally { // Don't retain static reference on context. mConstructorArgs[0] = lastContext; mConstructorArgs[1] = null; } return result; } }
第四步:rInflate方法中主要是去遞歸調用布局文件根視圖的子節點。將解析得到的view添加到parentView。
/** * Recursive method used to descend down the xml hierarchy and instantiate * views, instantiate their children, and then call onFinishInflate(). */ void rInflate(XmlPullParser parser, View parent, final 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 />標簽 parseRequestFocus(parser, parent); } else if (TAG_INCLUDE.equals(name)) { //處理<include />標簽 if (parser.getDepth() == 0) { throw new InflateException("<include /> cannot be the root element"); } parseInclude(parser, parent, attrs); //解析<include />節點 } else if (TAG_MERGE.equals(name)) { //處理<merge />標簽 throw new InflateException("<merge /> must be the root element"); } else if (TAG_1995.equals(name)) { //處理<blink />標簽 final View view = new BlinkLayout(mContext, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); rInflate(parser, view, attrs, true); viewGroup.addView(view, params); } else { //根據節點名構建一個View實例對象 final View view = createViewFromTag(parent, name, attrs); final ViewGroup viewGroup = (ViewGroup) parent; //調用generateLayoutParams()方法返回一個LayoutParams實例對象, final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); rInflate(parser, view, attrs, true); //繼續遞歸調用 viewGroup.addView(view, params); //OK,將該View以特定LayoutParams值添加至父View } } if (finishInflate) parent.onFinishInflate(); //完成解析過程,通知.. }
在rInflate方法的37行代碼中,final View view = createViewFromTag(parent, name, attrs),由節點名等參數構建的一個view實例對象,由於下面的代碼會越來越大,就直接貼出主要實現函數,具體可參見Android源碼。
/** * default visibility so the BridgeInflater can override it. */ View createViewFromTag(View parent, String name, AttributeSet attrs) { //... try { //... if (view == null) { if (-1 == name.indexOf('.')) { view = onCreateView(parent, name, attrs); } else { view = createView(name, null, attrs); } } return view; } catch (InflateException e) { //... } }
然後再往onCreateView()中跟下去,會發現,它其實主要還是實現了createView();所以我們直接CreateView實現。
protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException { return createView(name, "android.view.", attrs); }
在createView(name, “android.view.”, attrs)中,會用反射機制創建android.view.XXX(比如TextView)的實例對象,並返回。
這也就是LayoutInflater.inflate的布局解析流程了。
當你熟悉了流程,接下來為你講解的,隨心壁紙的動態解析布局思路,你就基本懂的大半了!
由於最初的layoutInflater.inflate(R.layout.main, null,false)函數,傳入的是R.layout.main資源id,而對於我們的項目,布局文件是在線更新的,是間接存儲在sd卡中,所以這種解析方式就不行了,所幸LayoutInflater的api方法還提供了inflate(XmlPullParser parser, ViewGroup root,boolean attachToRoot)解析,根據文件保存路徑生成需要的xmlPullParser:
public XmlPullParser getXmlPullParser(String resource) { XmlPullParser parser = Xml.newPullParser(); try { // InputStream is=mContext.getAssets().open("transfer_main.xml"); FileInputStream is = new FileInputStream(resource); parser.setInput(is, "utf-8"); } catch (Exception e) { e.printStackTrace(); } return parser; }
後面的第二步等其實都是一樣的流程(我會在後面貼出實現demo)。但是有一點需要特別注意,也是必須實現的一點:
因為所下載的布局文件得到的解析流,跟程序裡res/layout/xxx.xml有一個非常大的不同,資源id!!程序中的布局文件,裡面所注冊的id以及text,或者drawble都是可以真實在R目錄下找到的,而下載的布局文件是沒有這個福利的,它是我們外面生成的,並沒有經過apk編譯過程。所以為了能得到下載文件裡的布局各個id,我們需要自己實現對View的屬性解析。
protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException { // return createView(name, "android.view.", attrs); return createView(name, "com.xxx.xxxx.viewanalysis.view.VA", attrs); //比如com.xx.view.VATextView 項目中自定義view 繼承自TextView }
布局中的控件代碼編寫,可以是TextView等原生的,也可以是自定義過的,因為TextView經過加前綴,再通過後面的反射方法,也會跑到相應的自定義方法,返回一個自定義View對象。不過因為最終都是要跑自定義view,所以要求我們需要事先定義好會用到的,目前我定義好了9種,比如Button,GridView,RelativeLayout等。如果沒有定義的view直接用在文件中,會導致編譯出錯(ClassNotFoundException)。
VAButton類實現:
public class VAButton extends android.widget.Button { public VAButton(Context context, AttributeSet attrs) { super(context); setAttributeSet(attrs); } @SuppressWarnings("deprecation") public void setAttributeSet(AttributeSet attrs) { HashMap<String, ParamValue> map = YDResource.getInstance().getViewMap(); int count = attrs.getAttributeCount(); for (int i = 0; i < count; i++) { ParamValue key = map.get(attrs.getAttributeName(i)); if (key == null) { continue; } switch (key) { case id: this.setTag(attrs.getAttributeValue(i)); break; case text: String value = YDResource.getInstance().getString( attrs.getAttributeValue(i)); this.setText(value); break; //case... default: break; } } }
Ps: view id通過設置標志在外面可以獲取到(具體可以看demo)
屬性方面的解析,我就不多說了,網上的資料很多,有興趣的可以去了解下。
demo效果圖:
項目中的效果圖:
接下來看看demo中的代碼結構:
這裡的代碼主體在於YDLayoutInflater與ParamValue的實現,以及9種自定義view。
YDLayoutInflater主要是仿照LayoutInflater實現的,做了一些適應布局文件的修改處理,上面已經說過了。
具體的代碼下載地址
動態解析布局的思路講完了,應用到項目中,效果也不錯,雖然有一些限制規范,但是對於總體的功能設計是無關大雅的。關於主題呈現動態更新,如果有大神有更好的方式,請私信我哦!
第一部分 插件的介紹 Google 在2013年5月的I/O開發者大會推出了基於IntelliJ IDEA java ide上的Android Studio。An
一、概述 在上一篇博文中,我們給大家介紹了Android自定義控件系列的基礎篇。鏈接:http://www.codeceo.com/article/android
通常一個App的成長過程都是這樣的: 第一階:先用最少的成本和時間快速把東西做出來。 第二階段:積累一定用戶量之後在小步快跑的迭代功能。 第三階段:性能和體驗上逐
這裡逐條記錄下最容易遇到的 React native android 相關case 1.app啟動後,紅色界面,unable load jsbundle 解決辦法