美文网首页
VirtualView实现动态下发

VirtualView实现动态下发

作者: 土肥圆的诺诺 | 来源:发表于2020-05-12 22:01 被阅读0次

    直播公司,活动做的比较多,端内业务主要是做几个特殊的定制化弹窗,但是每次都需要发版本,公司内部统计,让一个用户升级的成本其实很高的。一直寻求动态化的解决方案,无非自己端内写组件,或者寻求外部方案。但是自己研发的成本很高。之前查过luaViewSDK,但是该项目很久没有维护。后来找到了阿里的VirtualView项目,先做个调研。

    VirtualView简介

    下面简称VV,是阿里Tangram中的动态组件框架,它开创了一种虚拟化开发基础控件的技术,使用方只要按照指定协议实现一个基础控件的尺寸计算、绘制逻辑、布局逻辑,即能实现在宿主容器的 canvas 里实现直接绘制 UI 内容的,让最终渲染出来的视图结构呈现扁平化,提升组件渲染性能。同时为了解决虚拟化 View 带来的原生 View 的能力损失的问题,它支持加载和渲染原生基础控件,两者组合产生合力,既能最大限度发挥性能提升,又能满足特殊场景下的业务需求。 VirtualView 内置实现了一系列基础控件,可以让使用方直接上手尝试;而搭建业务组件的方式采用 XML 模板来编写,这使得业务组件动态更新成为了可能。XML 模板里还支持写数据绑定的表达式,在样式动态化、数据动态化的场景下能非常方便地实现业务需求。(以上为官方文档)。

    项目地址:

    https://github.com/alibaba/Virtualview-Android

    VV项目缺点:

    无对应编辑器,官方提供的编译器 如对自定义控件动态编译 使用很麻烦

    项目优点:

    可以解决现在动态下发的问题。

    实时预览工具使用详解

    开源地址:

    https://github.com/alibaba/Virtualview-Android

    缺点:

    端内自定义组件无法预览,需要运行才可以展示。

    需要依赖的环境:

    (安装方法度娘或者谷哥)

    • python 2

    • java

    • fswatch

    • qrencode

    使用方法:

    下载项目 导入AS后,找到工具类HttpUtil,并编译运行,确保手机和编辑器处于同一网络环境中。
    public static String getHostIp() { //此处返回本机ip地址 return "192.168.4.109"; }

    image.png
    刷新原理简介:

    app启动后会主动链接IP:7788端口,点击刷新会去主动拉取对应端口数据,并对数据进行解析,加载到页面上。

    刷新部分代码如下

    private void refreshByUrl(final String url) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                OkHttpClient client = new OkHttpClient();
                Request request = new Request.Builder()
                        .url(url)
                        .build();
    
                try {
                    Response response = client.newCall(request).execute();
                    if (response != null && response.isSuccessful()) {
                        if (response.body() != null) {
                            String string = response.body().string();
                            final PreviewData previewData = new Gson().fromJson(string, PreviewData.class);
                            if (previewData != null) {
                                loadTemplates(previewData.templates);
                            }
    
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    JsonObject json = previewData.data;
                                    if (json != null) {
                                        try {
                                            mJsonData = JSON.parseObject(json.toString());
                                            preview(mTemplateName, mJsonData);
                                        } catch (Exception e) {
                                            e.printStackTrace();
                                        }
                                    }
                                }
                            });
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (JsonSyntaxException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
    

    预览和编译组件工具使用

    项目地址:

    https://github.com/alibaba/virtualview_tools/

    项目下主要包含两个包目录TemplateWorkSpace和RealtimePreview

    RealtimePreview
    templates
    • data.json 真实预览数据,用于测试下发数据后的样式。

    • 模板.json 如 Earth.json 预览的数据

    • 模板.xml Earth.xml 模板的xml

    <?xml version="1.0" encoding="utf-8"?>
    <VHLayout
              <!--不可删除 否则会报错 -->
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="../../vv.xsd"
         <!--不可删除 否则会报错 -->
              orientation="V" layoutWidth="match_parent"
        layoutHeight="wrap_content" background="#11000000">
    
        <VHLayout layoutWidth="498" layoutHeight="365">
            <NImage layoutWidth="498" layoutHeight="365" src="${bag}"></NImage>
    
            <FrameLayout layoutWidth="498" layoutHeight="365">
    
                <NText text="${showtext}" textSize="24" textColor="#FF19825C" background=""
                    layoutWidth="match_parent" layoutHeight="200" gravity="h_center|v_center" />
            </FrameLayout>
        </VHLayout>
    
    
    </VHLayout>
    

    模板_QR.png 例如 Earth_QR.png, 作用扫描预览效果

    run.sh

    执行用的脚本

    使用方法:在Terminal下, cd 进入RealtimePreview 包下, sh run.sh 启动


    image.png
    注意事项:
    • data.json 名称不可更改 ,否则在预览界面会解析报错
    • 模板.json 和模板.xml 需要名称对应否则会加载不到。
    • xml中的 下面标签不可删除,否则会报错
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="../../vv.xsd" 
    
    • 运行过程中注意log,可能或有异常,会有对应提示。
    TemplateWorkSpace
    template

    编译的模板文件列表

    注意事项

    • 在编译的时候 下面标签要删除
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="../../vv.xsd" 
    
    • xml首字母需要小写
    build

    二进制文件的输出目录

    • out文件夹

    ​ 例如 模板对应.out文件


    image.png
    • sigin文件夹

    输出模板对应的MD5


    image.png
    • java 文件夹

    ​ 输出对应文件.bin文件 为byte数组,加载代码和代码如下

    int result = vafContext.getViewManager().loadBinBufferSync(decode);
    
    public class EARTH{
    public static final byte[] BIN = new byte[] {
    65, 76, 73, 86, 86, 0, 1, 0, 0, 0, 1, 0, 0, 0, 47, 0, 0, 0, -60, 0, 0, 0, -13, 0, 0, 0, 29, 0, 0, 1, 20, 0, 0, 0, 0, 0, 0, 1, 24, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 5, 69, 97, 114, 116, 104, 0, -73, 0, 0, 2, 3, -86, 50, -11, -48, 0, 0, 0, 0, 92, -43, -16, -15, -1, -1, -1, -2, 119, 112, -84, -68, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 92, -43, -16, -15, 0, 0, 1, 109, 119, 112, -84, -68, 0, 0, 1, -14, 0, 0, 0, 0, 0, 0, 0, 0, 9, 2, 92, -43, -16, -15, -1, -1, -1, -1, 119, 112, -84, -68, 0, 0, 1, -14, 0, 0, 0, 1, 0, 1, -67, -28, 68, 97, -58, -52, 0, 0, 1, 0, 0, 1, 3, 92, -43, -16, -15, -1, -1, -1, -1, -80, -104, 85, 46, -1, 0, 0, 0, 119, 112, -84, -68, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 7, 4, -60, 45, 58, -50, 0, 0, 0, 24, 92, -43, -16, -15, 0, 0, 0, -56, 119, 112, -84, -68, -1, -1, -1, -1, -64, -101, 46, 54, -1, 25, -126, 92, 0, 0, 0, 1, 0, 54, 69, 45, -33, 33, 89, 28, 0, 0, 1, 1, 1, 1, 0, 0, 0, 2, 68, 97, -58, -52, 0, 6, 36, 123, 98, 97, 103, 125, -33, 33, 89, 28, 0, 11, 36, 123, 115, 104, 111, 119, 116, 101, 120, 116, 125, 0, 0, 0, 0, 
    };
    }
    
    templatelist.properties

    这里主要定义模板的编译完成后的名称

    样式为:xmlFileName=outFileName,Version[,platform]

    如 earth=Earth,1

    注意事项:

    标识 template 目录下需要编译的 xml 文件名建议不带 .xml 后缀,目前做了兼容(我个人测试带也没事)

    outFileName 输出到 build 目录下的 .out 文件名

    Version 表示 xml 编译后的版本号

    platform 同时兼容 iOS 和 android 时不写,可填的值为 android 和iphone


    image.png
    config.properties

    配置组件 ID、xml 属性对应的 value 类型

    VIEW_ID_名称=id

    注意事项

    自定义组件属性 属性名=类型(不写默认为String)

    这里的id,端内注册的时候要对应上,比如这里写注册id为2002,端内注册的时候也要注册为2002


    image.png
    viewManager.getViewFactory().registerBuilder(2002, new MhtBaseTextView.Builder());
    
    compiler.jar

    java 代码编译后 jar 文件,执行 xml 的编译逻辑(端内用不到)

    buildTemplate.sh

    编译组件使用到的sh文件

    使用方法

    • cd 到TemplateWorkSpace/template 文件目录
    • sh buildTemplate.sh


      image.png

    端内使用

    依赖
    //这里使用官方最新版本
    compile ('com.alibaba.android:virtualview:1.0.5@aar') {
        transitive = true
    }
    
    初始化
     VafContext vafContext = new VafContext(WboApplication.this);
    ViewManager viewManager = vafContext.getViewManager();
    viewManager.init(WboApplication.this);
    
    
    屏幕适配
    //这里代码的意思是设置基本单元 如不设置会自动将屏幕视为750
    com.libra.Utils.setUedScreenWidth(VLDensityUtils.getScreenWidth());
    
    自定义组件
    利用提供的原始控件组合(纯vv原生控件)

    优点:可直接下发,无需端内预先注册,动态能力max

    如需要动态配置对应的数据,可以使用${xxx},json中下发即可

    <?xml version="1.0" encoding="utf-8"?>
    <VHLayout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="../../vv.xsd" orientation="V" layoutWidth="match_parent"
        layoutHeight="match_parent">
    
        <NImage layoutWidth="265" layoutHeight="325" src="${open2}" layoutGravity="h_center|top" />
        <NImage layoutWidth="22" layoutHeight="22" src="${open1}" layoutGravity="h_center" />
    
        <NText layoutWidth="100" layoutHeight="100" text="${showContent}" flag="flag_clickable"
            supportHTMLStyle="true" action="jump to result" layoutGravity="h_center" />
    
    </VHLayout>
    
    {
      "open2": "https://xossimg.2cq.com/system/img/banner/be2cf7f26787b33d443110e8aa03dd14.png",
      "open1": "https://xossimg.2cq.com/system/img/banner/be2cf7f26787b33d443110e8aa03dd14.png",
      "showContent": "<span style=\"color:#E56600;\">321321321312312</span><span style=\"color:#E56600;\"></span>"
    }
    
    利用原生组件封装成特定模板

    如下面xml

    <?xml version="1.0" encoding="utf-8"?>
    <VHLayout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="../../vv.xsd" orientation="V" layoutWidth="match_parent"
        layoutHeight="wrap_content" background="#11000000">
    
        <VHLayout layoutWidth="498" layoutHeight="365">
            <NImage layoutWidth="498" layoutHeight="365" src="${bag}"></NImage>
    
            <FrameLayout layoutWidth="498" layoutHeight="365">
    
                <NText text="${showtext}" textSize="24" textColor="#FF19825C" background=""
                    layoutWidth="match_parent" layoutHeight="200" gravity="h_center|v_center" />
            </FrameLayout>
        </VHLayout>
    
    
    </VHLayout>
    

    使用上面的编译工具,将该模板编译为一个特定组件,这里注意在xml中删除上面提到的两个标签,不然会报出异常,需要在templatelist中编写对应id。规则看上文。


    image.png
    image.png

    编译运行,会输出对应的.out 和.bin文件


    image.png
    image.png
    端内加载:这里动态下发 建议使用.bin文件,因为bin文件中就是byte数组,在json传输中,使用base64转为字符串,在收到后再转回byte数组
    public class EARTH{
    public static final byte[] BIN = new byte[] {
    65, 76, 73, 86, 86, 0, 1, 0, 0, 0, 1, 0, 0, 0, 47, 0, 0, 0, -60, 0, 0, 0, -13, 0, 0, 0, 29, 0, 0, 1, 20, 0, 0, 0, 0, 0, 0, 1, 24, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 5, 69, 97, 114, 116, 104, 0, -73, 0, 0, 2, 3, -86, 50, -11, -48, 0, 0, 0, 0, 92, -43, -16, -15, -1, -1, -1, -2, 119, 112, -84, -68, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 92, -43, -16, -15, 0, 0, 1, 109, 119, 112, -84, -68, 0, 0, 1, -14, 0, 0, 0, 0, 0, 0, 0, 0, 9, 2, 92, -43, -16, -15, -1, -1, -1, -1, 119, 112, -84, -68, 0, 0, 1, -14, 0, 0, 0, 1, 0, 1, -67, -28, 68, 97, -58, -52, 0, 0, 1, 0, 0, 1, 3, 92, -43, -16, -15, -1, -1, -1, -1, -80, -104, 85, 46, -1, 0, 0, 0, 119, 112, -84, -68, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 7, 4, -60, 45, 58, -50, 0, 0, 0, 24, 92, -43, -16, -15, 0, 0, 0, -56, 119, 112, -84, -68, -1, -1, -1, -1, -64, -101, 46, 54, -1, 25, -126, 92, 0, 0, 0, 1, 0, 54, 69, 45, -33, 33, 89, 28, 0, 0, 1, 1, 1, 1, 0, 0, 0, 2, 68, 97, -58, -52, 0, 6, 36, 123, 98, 97, 103, 125, -33, 33, 89, 28, 0, 11, 36, 123, 115, 104, 111, 119, 116, 101, 120, 116, 125, 0, 0, 0, 0, 
    };
    }
    
    //获取下发的bin文件的字符转
    String bin = VLJsonParseUtils.getString(message, "bin");
    //转回原始的byte数组
    byte[] decode = Base64.decode(bin, Base64.DEFAULT);
    //加载该数组
    int result = vafContext.getViewManager().loadBinBufferSync(decode);
    
    try {
      //msgType 在生成的时候定义为什么,就要加载什么,如上文我写成了earth 那这里要为earth
        View vvView = WboApplication.getVafContext().getContainerService().getContainer(msgType, false);
        JSONObject jsonObject = new JSONObject(message);
        IContainer container1 = (IContainer) vvView;
      //设置数据
        container1.getVirtualView().setVData(jsonObject);
        VvRoomDialog vvRoomDialog = new VvRoomDialog(room.getContext());
        vvRoomDialog.addShowView(vvView);
        vvRoomDialog.show();
    
    
    } catch (Exception e) {
    
    }
    

    如该为固定模板,可以端内预加载加载,直接下发,无需在使用getVafContext().getContainerService().getContainer(名称, false)加载

    viewManager.getViewFactory().registerBuilder(2002, new MhtBaseTextView.Builder());
    
    端内自定义模板

    适用于使用原生组件拼装困难,需要需要提前在端内注册,下面为端内代码

    实体view(实现功能的view ,需要实现IView)

    public class MhtTestViewImp extends TextView implements IView {
        public MhtTestViewImp(Context context) {
            super(context);
        }
    
        public MhtTestViewImp(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
        }
    
        public MhtTestViewImp(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            this.setTextSize(40);
        }
    
        @Override
        public void measureComponent(int widthMeasureSpec, int heightMeasureSpec) {
            this.measure(widthMeasureSpec, heightMeasureSpec);
        }
    
        @Override
        public void comLayout(int l, int t, int r, int b) {
            this.layout(l, t, r, b);
        }
    
        @Override
        public void onComMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            this.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    
        @Override
        public void onComLayout(boolean changed, int l, int t, int r, int b) {
            this.onLayout(changed, l, t, r, b);
        }
    
        @Override
        public int getComMeasuredWidth() {
            return this.getMeasuredWidth();
        }
    
        @Override
        public int getComMeasuredHeight() {
            return this.getMeasuredHeight();
        }
    }
    

    包装view 需要继承ViewBase

    public class MhtBaseTextView extends ViewBase {
        private MhtTestViewImp testViewImp;
    
    
        private static final String TAG = "MhtBaseTextView";
        private final int textContentId;
    
        public MhtBaseTextView(VafContext context, ViewCache viewCache) {
            super(context, viewCache);
            testViewImp = new MhtTestViewImp(context.forViewConstruction());
            StringSupport mStringSupport = context.getStringLoader();
          //这里代码意思是找到系统提供的StringSupport,去查找自定义的属性text的key 
            textContentId = mStringSupport.getStringId("text", false);
    
        }
        @Override
        public void onComMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            testViewImp.onComMeasure(widthMeasureSpec, heightMeasureSpec);
    
        }
    
        @Override
        public int getComMeasuredWidth() {
            return testViewImp.getComMeasuredWidth();
        }
    
        @Override
        public int getComMeasuredHeight() {
            return testViewImp.getComMeasuredHeight();
        }
    
        @Override
        public void onComLayout(boolean changed, int l, int t, int r, int b) {
          //这里一定要写不然控件出不来
            testViewImp.comLayout(l, t, r, b);
        }
    
        @Override
        public void comLayout(int l, int t, int r, int b) {
            super.comLayout(l, t, r, b);
            testViewImp.comLayout(l, t, r, b);
        }
    
        @Override
        public void onParseValueFinished() {
            super.onParseValueFinished();
          //所有属性加载结束 这里可以进行处理
          //如 if(xxx){xxx.setxxx} 一般属性不为表达式的,这里要进行处理,如果为表达式则不用
          
    
        }
    
    
        @Override
        protected boolean setAttribute(int key, String stringValue) {
            boolean result = true;
          //系统调用设置属性  如果key和预定的复合进行一些操作
            if (key == textContentId) {
              //这里的判断是当前值是否是一个表达式如${xx} 
                if (Utils.isEL(stringValue)) {
                  //将值存放到缓存中
                    mViewCache.put(this, key, stringValue, ViewCache.Item.TYPE_STRING);
                } else {
                  //如果不是则可以直接取出 String content=stringValue;
                }
            } else {
                result = super.setAttribute(key, stringValue);
            }
          //返回true证明消费掉这个值,反之回继续下传
            return result;
    
        }
    
        @Override
        public View getNativeView() {
          //这个方法虽然对外没用过,但是也要写上
            return testViewImp;
        }
    
        @Override
        public void reset() {
            super.reset();
    
        }
    
        //对外提供的构造器
        public static class Builder implements ViewBase.IBuilder {
            @Override
            public ViewBase build(VafContext context, ViewCache viewCache) {
                return new MhtBaseTextView(context, viewCache);
            }
        }
    
    
        @Override
        protected boolean setAttribute(int key, Object value) {
            return super.setAttribute(key, value);
        }
    }
    

    编写基础模板 ,这里使用动态预览会报错,报MhtBaseTextView不存在

    <?xml version="1.0" encoding="utf-8"?>
    <VHLayout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="../../vv.xsd" orientation="V" layoutWidth="match_parent"
        layoutHeight="match_parent">
    
        <MhtBaseTextView layoutWidth="wrap_content" layoutHeight="wrap_content" flag="flag_clickable"
            id="2020"
            textContentTag="测试是否可以接收到" layoutGravity="h_center|top" />
    
    
    </VHLayout>
    

    使用编译工具

    template下放置xml

    <?xml version="1.0" encoding="utf-8"?>
    <VHLayout orientation="V" layoutWidth="match_parent" layoutHeight="match_parent">
    
        <MhtBaseTextView layoutWidth="wrap_content" layoutHeight="wrap_content" flag="flag_clickable"
            id="2020" text="${text}" colortext="${colortext}" layoutGravity="h_center|top" />
    
    //对应点击事件EventManager.xxx
    vafContext.getEventManager().register(EventManager.TYPE_Click, new IEventProcessor() {
    
        @Override
        public boolean process(EventData data) {
           //这里可以截取到对应的action进行事件
            String action = data.mVB.getAction();
    
            return true;
        }
    });
    

    </VHLayout>

    在config.properties中配置id,这里的id用于在端内注册使用
    
    VIEW_ID_名称=id
    

    VIEW_ID_MhtBaseTextView=2002

    ![image.png](https://img.haomeiwen.com/i2657154/62e9971e0a3e6c2d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    我们在控件中使用了一个text的自定义属性,需要在config.properties中进行配置,如果是String类型,则可以不用配置,默认为String类型。
    ![image.png](https://img.haomeiwen.com/i2657154/ebf17f571a39b7a6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    一般我们也会为该模板生成一个名字 ,方便动态下发
    ![image.png](https://img.haomeiwen.com/i2657154/8a390c21dbd60cde.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    
    

    端内需要注册:这样下发的时候才能找到对应组件

    viewManager.getViewFactory().registerBuilder(1001, new MhtBaseTextView.Builder());
    
    vv常用功能使用
    • 基本控件

    地址:http://tangram.pingguohe.net/docs/virtualview/about-virtualview

    • 点击事件

    flag: flag_software:关闭view的硬件加速,flag_exposure:需要触发曝光事件,flag_clickable:需要响应点击事件,flag_longclickable:需要响应长按事件,flag_touchable:需要响应触摸事件

    <MhtBaseTextView layoutWidth="wrap_content" layoutHeight="wrap_content"         flag="flag_clickable" action="打开包裹"
        id="2020"
        textContentTag="测试是否可以接收到" layoutGravity="h_center|top" />
    
    //对应点击事件EventManager.xxx
    vafContext.getEventManager().register(EventManager.TYPE_Click, new IEventProcessor() {
    
        @Override
        public boolean process(EventData data) {
           //这里可以截取到对应的action进行事件
            String action = data.mVB.getAction();
    
            return true;
        }
    });
    
    • 字体样式(特殊字体样式)

    Ntext 控件 supportHTMLStyle 属性 true支持,false 默认为普通文本,IOS不支持(需要端内实现)

    • 图片加载
    vafContext.setImageLoaderAdapter(new ImageLoader.IImageLoaderAdapter() {
        @Override
        public void bindImage(String uri, ImageBase imageBase, int reqWidth, int reqHeight) {
    
            BitmapRequestBuilder requestBuilder =
                    Glide.with(WboApplication.this).load(uri).asBitmap();
            ImageTarget imageTarget = new ImageTarget(imageBase);
            requestBuilder.into(imageTarget);
    
        }
    
        @Override
        public void getBitmap(String uri, int reqWidth, int reqHeight, ImageLoader.Listener lis) {
            BitmapRequestBuilder requestBuilder =
                    Glide.with(WboApplication.this
                    ).load(uri).asBitmap();
            ImageTarget imageTarget = new ImageTarget(lis);
            requestBuilder.into(imageTarget);
        }
    });
    
    • 获取view和设置数据
    //根据对应组件类型取出对应view
    View vvView = WboApplication.getVafContext().getContainerService().getContainer(msgType, false);
    //下发下来的json 里面包含对应的数据
    JSONObject jsonObject = new JSONObject(message);
    //转为IContainer
    IContainer container1 = (IContainer) vvView;
    //设置数据
    container1.getVirtualView().setVData(jsonObject);
    //将view加载到我们指定布局或者控件
    VvRoomDialog vvRoomDialog = new VvRoomDialog(room.getContext());
    vvRoomDialog.addShowView(vvView);
    vvRoomDialog.show();
    

    相关文章

      网友评论

          本文标题:VirtualView实现动态下发

          本文链接:https://www.haomeiwen.com/subject/jcapnhtx.html