Android 动态解析生成布局文件的意思是:通过服务器给你发送一段Json 文件,然后根据其中的自己定义的属性,解析成原生的Android 的布局文件,并添加到 View 上作为展示。

该用途是可以实时在线更新多种不同的布局,而不是写死在apk中的不同布局文件,然后根据传进来的不同参数,显示不一样的布局。

这两种有本质的区别,在于一个是静态的(死布局),而另外一个是动态布局(比较灵活),因为如果要新增多种布局的话,又要去改apk的代码去新增,而如果是动态解析布局的话,那么就不用每次都更新apk啦,其次是如果是sdk需要这样的功能给第三方接入sdk的人使用时,是不太可能每次都去更新sdk的。

先看看效果图:
Android 动态解析网络布局_第1张图片
上面的那个卡片布局就是动态解析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布局文件的格式,去模仿改动就可以了。

更多相关文章

  1. 关于Android的反编译apk文件
  2. Android 内嵌WebView之选择文件上传及扩展
  3. 深入探索Android布局优化(上)
  4. android运行时ART加载OAT文件解析
  5. 如何检测android上的多媒体文件属于音频、视频还是图片?

随机推荐

  1. 我就改了一下参数,竟然让Tomcat和JVM的性
  2. 完全理解React Fiber
  3. 跟着鹏哥学C语言
  4. React 16
  5. 保姆级 tomcat 快速入门
  6. Docker简介
  7. Nodejs进程间通信
  8. 如何在 Java 中构造对象(学习 Java 编程语
  9. No.6 关于变量名前加$以及不加$的区别
  10. Node中的流