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对象,来创建View
View sampleView = DynamicView.createView(this, jsonObject, ViewHolder.class);

那现在重点就是这个createView 方法啦:

public static View createView (Context context, JSONObject jsonObject, ViewGroup parent, Class holderClass) {
        ...
        HashMap<String, Integer> ids = new HashMap<>();

        View container = createViewInternal(context, jsonObject, parent, ids);
        if (container.getTag(INTERNAL_TAG_ID) != null)
        //解析布局属性
            DynamicHelper.applyLayoutProperties(container, (List<DynamicProperty>) 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<DynamicProperty>) container.getTag(
INTERNAL_TAG_ID), parent, ids);

先看第一个:

private static View createViewInternal (Context context, JSONObject jsonObject, ViewGroup parent, HashMap<String, Integer> ids) {

        View view = null;
        ArrayList<DynamicProperty> 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<count;i++) {
                    //递归调用布局
                        View dynamicChildView = DynamicView.createViewInternal(context, 
                  jViews.getJSONObject(i), parent, ids);
                       ...
                    }
                }
                for(View v : views) {
                //遍历View解析属性
                    DynamicHelper.applyLayoutProperties(v, (List<DynamicProperty>) 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<DynamicProperty> properties, ViewGroup viewGroup, HashMap<String, Integer> 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<DynamicProperty> 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布局文件的格式,去模仿改动就可以了。

  • 0
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值