Android 动态解析网络布局
Android 动态解析生成布局文件的意思是:通过服务器给你发送一段Json 文件,然后根据其中的自己定义的属性,解析成原生的Android 的布局文件,并添加到 View 上作为展示。
该用途是可以实时在线更新多种不同的布局,而不是写死在apk中的不同布局文件,然后根据传进来的不同参数,显示不一样的布局。
这两种有本质的区别,在于一个是静态的(死布局),而另外一个是动态布局(比较灵活),因为如果要新增多种布局的话,又要去改apk的代码去新增,而如果是动态解析布局的话,那么就不用每次都更新apk啦,其次是如果是sdk需要这样的功能给第三方接入sdk的人使用时,是不太可能每次都去更新sdk的。
先看看效果图:
上面的那个卡片布局就是动态解析json 文件生成的。
下面是整个Demo的地址:
https://download.csdn.net/download/m0_37094131/10494440
其实采用了第三方的框架,然后在它的框架中加上一些自己需要的功能,比如drawable中的xml文件,比如上面的查询按钮中的圆角背景,就是框架中没有提供的功能,是自己改着源码添加的,然后话不多说,开始看代码吧:
{ "widget": "android.widget.RelativeLayout", "properties": [{ "name": "background", "type": "color", "value": "#e5e5e5" }, { "name": "layout_width", "type": "dimen", "value": "match_parent" }, "views": [{ "widget": "android.widget.RelativeLayout", "properties": [{ "name": "layout_width", "type": "dimen", "value": "362dp" }, { "name": "layout_height", "type": "dimen", "value": "wrap_content" }, ... { "name": "background", "type": "ref", "value": "shape|corner:8|color:'#ffffff'" }, ...
这只是部分json布局文件,可以提前写好,也可以用网络请求从后台服务器中获取,由于是Demo,所以我就没有放在服务器上,而是放在本地文件中去获取。真实情况下,是需要请求网络获取的。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); JSONObject jsonObject; try { jsonObject = new JSONObject(readFile("TextIT.json", this)); } catch (JSONException je) { je.printStackTrace(); jsonObject = null; } if (jsonObject != null) { //根据json 解析得到的json对象,来创建View View sampleView = DynamicView.createView(this, jsonObject, ViewHolder.class); sampleView.setLayoutParams(new WindowManager.LayoutParams(MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT)); setContentView(sampleView); } else { Log.e("cx", "Could not load valid json file"); } }
重点关注这一行代码
//根据json 解析得到的json对象,来创建ViewView sampleView = DynamicView.createView(this, jsonObject, ViewHolder.class);
那现在重点就是这个createView 方法啦:
public static View createView (Context context, JSONObject jsonObject, ViewGroup parent, Class holderClass) { ... HashMap ids = new HashMap<>(); View container = createViewInternal(context, jsonObject, parent, ids); if (container.getTag(INTERNAL_TAG_ID) != null) //解析布局属性 DynamicHelper.applyLayoutProperties(container, (List) container.getTag(INTERNAL_TAG_ID), parent, ids); ... container.setTag(INTERNAL_TAG_ID, null); if (holderClass!= null) { try { Object holder = holderClass.getConstructor().newInstance(); //解析View DynamicHelper.parseDynamicView(holder, container, ids); container.setTag(holder); } catch (Exception e) { e.printStackTrace(); } } return container; }
重点是这两行代码
//创建View(1) View container = createViewInternal(context, jsonObject, parent, ids);//解析布局参数(2) DynamicHelper.applyLayoutProperties(container, (List) container.getTag(INTERNAL_TAG_ID), parent, ids);
先看第一个:
private static View createViewInternal (Context context, JSONObject jsonObject, ViewGroup parent, HashMap ids) { View view = null; ArrayList properties; try { String widget = jsonObject.getString("widget"); if (!widget.contains(".")) { widget = "android.widget." + widget; } //反射构建Android view 的布局对象 Class viewClass = Class.forName(widget); view = (View) viewClass.getConstructor(Context.class).newInstance(new Object[] { context }); } catch (Exception e) { e.printStackTrace(); } ... try { //创建布局参数 ViewGroup.LayoutParams params = DynamicHelper.createLayoutParams(parent); view.setLayoutParams(params); properties = new ArrayList<>(); JSONArray jArray = jsonObject.getJSONArray("properties"); ... view.setTag(INTERNAL_TAG_ID, properties); //解析style String id = DynamicHelper.applyStyleProperties(context,view, properties); if (!TextUtils.isEmpty(id)) { ids.put(id, mCurrentId); view.setId( mCurrentId ); mCurrentId++; } if (view instanceof ViewGroup) { ... JSONArray jViews = jsonObject.optJSONArray("views"); if (jViews != null) { int count=jViews.length(); for (int i=0;i... } } for(View v : views) { //遍历View解析属性 DynamicHelper.applyLayoutProperties(v, (List) v.getTag(INTERNAL_TAG_ID), viewGroup, ids); v.setTag(INTERNAL_TAG_ID, null); } } } catch (JSONException e) { e.printStackTrace(); } return view; }
通过反射来构造一个View 对象,而至于是什么View 取决于你在Json文件中获取到的属性,可以是LinearLayout也可以是ReleativeLayout等等。通过递归去遍历Json 文件中的属性,然后生成不同的嵌套的控件。
生成原生控件之后,就开始得适配属性值,以及控件位置了。
public static void applyLayoutProperties(View view, List properties, ViewGroup viewGroup, HashMap ids) { if (viewGroup == null) return; ViewGroup.LayoutParams params = createLayoutParams(viewGroup); for (DynamicProperty dynProp : properties) { try { switch (dynProp.name) { case LAYOUT_HEIGHT: { params.height = dynProp.getValueInt(); } break; case LAYOUT_WIDTH: { params.width = dynProp.getValueInt(); } break; ... case LAYOUT_GRAVITY: { switch (dynProp.type) { case INTEGER: { if (params instanceof LinearLayout.LayoutParams) ((LinearLayout.LayoutParams) params).gravity = dynProp.getValueInt(); } break; case STRING: { if (params instanceof LinearLayout.LayoutParams) ((LinearLayout.LayoutParams) params).gravity = (Integer) dynProp.getValueInt(Gravity.class, dynProp.getValueString().toUpperCase()); } break; } } break; case LAYOUT_WEIGHT: { switch (dynProp.type) { case FLOAT: { if (params instanceof LinearLayout.LayoutParams) ((LinearLayout.LayoutParams) params).weight = dynProp.getValueFloat(); } break; } } break; } } catch (Exception e) { } } view.setLayoutParams(params); }
可以适配Android 所有原生位置的属性,Layout_Width,Layout_Height,Layout_toLeftof等等,属性太多,就不占太多篇幅了,因为demo中有完整的例子。
这是为每个控件生成Params.
public static String applyStyleProperties(Context context ,View view, List properties) { mContext = context; String id = ""; for (DynamicProperty dynProp : properties) { switch (dynProp.name) { case ID: { id = dynProp.getValueString(); Log.e("cx" , "ID applyStyleProperties :" + id); } break; case BACKGROUND: { applyBackground(context ,view, dynProp); } break; case TEXT: { applyText(view, dynProp); } ... break; case DRAWABLELEFT: { applyCompoundDrawable(view, dynProp, 0); } break; case SELECTED: { applySelected(view, dynProp); } break; case SCALEX: { applyScaleX(view, dynProp); } break; ... case VISIBILITY:{ applyVisibility(view, dynProp); } break; } } return id; }
这是的属性设置,也适配许多,由于篇幅问题,只能省略显示,具体的demo中全部都有!
看到这里估计很多人就懂了,解析布局,其实类似Android 的代码LayoutInflate.from(context).inflate(R.layout.main,container,false),和里面的源码思路差不多,读者感兴趣的话,可以自己去了解一下Android 加载布局的源码,会很有意思,不断的递归递归。
总结一下,就是解析Json 文件中的不同属性,生成不同的View,包括嵌套View,以及同级View都行。然后在解析View中设置好位置,以及id,还有background,以及你自己想要定制什么属性都可以,你也可以扩展一些你想要实现的属性,包括自定义View等。另外多说一句,其实写TextIT.json 文件可以按照你写好的xml布局文件的格式,去模仿改动就可以了。
更多相关文章
- 关于Android的反编译apk文件
- Android 内嵌WebView之选择文件上传及扩展
- 深入探索Android布局优化(上)
- android运行时ART加载OAT文件解析
- 如何检测android上的多媒体文件属于音频、视频还是图片?