Blockly之工具Block模块的加入流程

一.Block小模块的加入过程:

在Blockly中侧边栏中的ToolBox是的加入经过assets目录下的json文件数据进行转化成Block的小模块。json中的数据标签通过BlockDefinition类解析一个个的具有属性Block模块,之后把Block模块加入到BlockFactory的工厂中。在需要的地方绘制到Fragment中去。



二.添加自定义的模块的流程:

1.在DefaultBlocks.java中添加要加入模块的json路径如:

添加路径的常量值:

public static final String WANGYONGYAO_BLOCKS_PATH = "default/wangyongyao_blocks.json";
在getAllBlockDefinitions方法中添加定义的路径常量值,在这里加入是通过一系列的方法进行json数据的解析成Block中的属性值:


2.在assets目录中的toolbox.xml中添加自己定义的block的category种类:

<toolbox>
    <category name="王永耀" colour="200">
        <block type="wangyongyao_type"></block>
    </category>
</toolbox>

3.自己定义的json文件wangyongyao.json格式如下,如是图片转化成 base64码的网站

[
  {
    "type": "wangyongyao_type",     //自定的type模式在toolbox.xml中进行定义
    "message0": "%1 王永耀 %2",      //%后的是type数量值如有两个就是这种模式,如是三个就在后面加上%3以此类推
    "args0": [
      {
        "type": "field_image",      //添加图片模式,src后添加的是图片转成的base64的值
        "src": "data:image/gif;base64,R0lGODlhDwAPAMQfAF9h4RYVnZeQJC0u0lRQU42R6P/7Fv74L05NrRkZxi4tW52XXv71D8nAIWxnjnRxr3NuMJKOluXbBe7kCa2x7UFD1vPoB77D8Jqe6n6B5tvTUr62BMrP8lJPh1xbuv///yH5BAEAAB8ALAAAAAAPAA8AAAWD4CeOWQKMaDpESepi3tFlLgpExlK9RT9ohkYi08N8KhWP8nEwMBwIDyJRSTgO2CaDYcBOCAlMgtDYmhmTDSFQ+HAqgbLZIlAMLqiKw7m1EAYuFQsGEhITEwItKBc/EgIEAhINAUYkCBIQAQMBEGonIwAKa21iCgo7IxQDFRQjF1VtHyEAOw==",
        "width": 15,
        "height": 15,
        "alt": "*"
      },
      {
        "type": "input_value",
        "name": "yuan_text",
        "check": "String"
      }
    ],
    "colour": 330,
    "tooltip": "",
    "helpUrl": ""
  }
]
这样就简单添加了一个自定义模块的block。


三.json及xml数据加入的源码分析:

1.在assets资产目录下的json文件,通过AbstractBlockActivity抽象类中的抽象方法getBlockDedinitionJsonPaths():

private static final List<String> BLOCK_DEFINITIONS = DefaultBlocks.getAllBlockDefinitions();
@NonNull
@Override
protected List<String> getBlockDefinitionsJsonPaths() {             //获取模块中默认assets的json数据
    return BLOCK_DEFINITIONS;
}

2.接下来传给BlockActivityHelper类的resetBlockFactory():

protected void resetBlockFactory() {
    mBlocklyActivityHelper.resetBlockFactory(               //将获取到的assets中的路径信息交给BlockActivityHelper处理
            getBlockDefinitionsJsonPaths());

    configureBlockExtensions();
    configureMutators();
    configureCategoryFactories();

    // Reload the toolbox?
}

3.在resetBlockFactort()方法中获取到DefaltBlock中定义的json的路径常量。

public void resetBlockFactory(
        @Nullable List<String> blockDefinitionsJsonPaths) {
    AssetManager assets = mActivity.getAssets();
    BlockFactory factory = mController.getBlockFactory();
    factory.clear();

    String assetPath = null;
    try {
        if (blockDefinitionsJsonPaths != null) {
            Log.i("everb","blockDefinitionsJsonPaths:"+blockDefinitionsJsonPaths);
            for (String path : blockDefinitionsJsonPaths) {
                assetPath = path;
                factory.addJsonDefinitions(assets.open(path));  //传给BlockFactory工厂进行Block成型的处理
            }
        }
    } catch (IOException | BlockLoadingException e) {
        throw new IllegalStateException(
                "Failed to load block definition asset file: " + assetPath, e);
    }
}

4.之后交给BlockFactory进行InputStream输入流的的处理:

public int addJsonDefinitions(InputStream jsonStream)
        throws IOException, BlockLoadingException {
    // Read stream as single string.
    String inString = new Scanner( jsonStream ).useDelimiter("\\A").next();
    Log.i("everb","inString:"+inString);
    return addJsonDefinitions(inString);//返回block的数量
}
public void addDefinition(BlockDefinition definition) {
    String typeName = definition.getTypeName();
    if (mDefinitions.containsKey(typeName)) {
        throw new IllegalArgumentException(
                "Definition \"" + typeName + "\" already defined. Prior must remove first.");
    }
    mDefinitions.put(typeName, definition);    //添加到mDefinitions的map中放在BlockFatoryTest去检测是否合法
}

5.在BlockDefinition类中解析在assets中的json文件,通过CreateInputList()方法生成Input的集合提供给block类调用生成一个模块:Field的bean类包涵着json数据中的所有type对应的标签.BlockDefinition对块block之间的输入输出关系进行了解析和check检查。Input是每个在工具栏中能拖动的block块的数据块,在Input中包涵着Filed最基础的模块,相当于一条语句的模块如:一个if语句就代表着一个Filed。

一个json块对等的关系:

Filed对等于json中的:

"args0": [
  {
    "type": "field_image",      //添加图片模式,src后添加的是图片转成的base64的值
    "src": "data:image/gif;base64,R0lGODlhDwAPAMQfAF9h4RYVnZeQJC0u0lRQU42R6P/7Fv74L05NrRkZxi4tW52XXv71D8nAIWxnjnRxr3NuMJKOluXbBe7kCa2x7UFD1vPoB77D8Jqe6n6B5tvTUr62BMrP8lJPh1xbuv///yH5BAEAAB8ALAAAAAAPAA8AAAWD4CeOWQKMaDpESepi3tFlLgpExlK9RT9ohkYi08N8KhWP8nEwMBwIDyJRSTgO2CaDYcBOCAlMgtDYmhmTDSFQ+HAqgbLZIlAMLqiKw7m1EAYuFQsGEhITEwItKBc/EgIEAhINAUYkCBIQAQMBEGonIwAKa21iCgo7IxQDFRQjF1VtHyEAOw==",
    "width": 15,
    "height": 15,
    "alt": "*"
  },
  {
    "type": "input_value",
    "name": "yuan_text",
    "check": "String"
  }
],
Input对等于json中的:

{
  "type": "wangyongyao_type",     //自定的type模式在toolbox.xml中进行定义
  "message0": "%1 王永耀 %2",      //%后的是type数量值如有两个就是这种模式,如是三个就在后面加上%3以此类推
  "args0": [
    {
      "type": "field_image",      //添加图片模式,src后添加的是图片转成的base64的值
      "src": "data:image/gif;base64,R0lGODlhDwAPAMQfAF9h4RYVnZeQJC0u0lRQU42R6P/7Fv74L05NrRkZxi4tW52XXv71D8nAIWxnjnRxr3NuMJKOluXbBe7kCa2x7UFD1vPoB77D8Jqe6n6B5tvTUr62BMrP8lJPh1xbuv///yH5BAEAAB8ALAAAAAAPAA8AAAWD4CeOWQKMaDpESepi3tFlLgpExlK9RT9ohkYi08N8KhWP8nEwMBwIDyJRSTgO2CaDYcBOCAlMgtDYmhmTDSFQ+HAqgbLZIlAMLqiKw7m1EAYuFQsGEhITEwItKBc/EgIEAhINAUYkCBIQAQMBEGonIwAKa21iCgo7IxQDFRQjF1VtHyEAOw==",
      "width": 15,
      "height": 15,
      "alt": "*"
    },
    {
      "type": "input_value",
      "name": "yuan_text",
      "check": "String"
    }
  ],
  "colour": 330,
  "tooltip": "",
  "helpUrl": ""
}
以下是createInputList方法将所有的json文件数据转成Input的过程:

/**
 * @return A new list of {@link Input} objects for a new block of this type, complete with
 *         fields.
 */
protected ArrayList<Input> createInputList(BlockFactory factory) throws BlockLoadingException {
    ArrayList<Input> inputs = new ArrayList<>();
    ArrayList<Field> fields = new ArrayList<>();
    for (int i = 0; ; i++) {
        String messageKey = "message" + i;
        String argsKey = "args" + i;
        String lastDummyAlignKey = "lastDummyAlign" + i;
        if (!mJson.has(messageKey)) {
            break;
        }
        String message = mJson.optString(messageKey);
        JSONArray args = mJson.optJSONArray(argsKey);
        if (args == null) {
            // If there's no args for this message use an empty array.
            args = new JSONArray();
        }

        if (message.matches("^%[a-zA-Z][a-zA-Z_0-9]*$")) {
            // TODO(#83): load the message from resources.
        }
        // Split on all argument indices of the form "%N" where N is a number from 1 to
        // the number of args. Arguments indices are returned as "%N" strings.
        List<String> tokens = Block.tokenizeMessage(message);     //将json数据中的"message+index"标签后的属性进行切割取出,如:“if %1 do %2”把if和do取出赋值给tokens
        // Indices start at 1, make the array 1 bigger so we don't have to offset things
        boolean[] seenIndices = new boolean[args.length() + 1];

        for (String token : tokens) {
            // Check if this token is an argument index of the form "%N"
            Log.i("everb","token:"+token);
            if (token.matches("^%\\d+$")) {
                int index = Integer.parseInt(token.substring(1));
                if (index < 1 || index > args.length()) {
                    throw new BlockLoadingException("Message index " + index
                            + " is out of range.");
                }
                if (seenIndices[index]) {
                    throw new BlockLoadingException(("Message index " + index
                            + " is duplicated"));
                }
                seenIndices[index] = true;

                JSONObject element;
                try {
                    element = args.getJSONObject(index - 1);      //取出每个Input中的参数args相对于Field
                    Log.i("everb","element:"+element);
                } catch (JSONException e) {
                    throw new BlockLoadingException("Error reading arg %" + index, e);
                }
                while (element != null) {
                    String elementType = element.optString("type");
                    if (TextUtils.isEmpty(elementType)) {
                        throw new BlockLoadingException("No type for arg %" + index);
                    }

                    if (Field.isFieldType(elementType)) {       //判断Filed中的type类型是否为空
                        fields.add(factory.loadFieldFromJson(mTypeName, element));
                        break;
                    } else if (Input.isInputType(elementType)) {
                        Input input = Input.fromJson(element, fields);
                        fields.clear();
                        inputs.add(input);
                        break;
                    } else {
                        // Try getting the fallback block if it exists
                        Log.w(TAG, "Unknown element type: " + elementType);
                        element = element.optJSONObject("alt");
                    }
                }
            } else {
                token = token.replace("%%", "%").trim();
                if (!TextUtils.isEmpty(token)) {
                    fields.add(new FieldLabel(null, token));  //添加Filed的Label标签名
                }
            }
        }

        // Verify every argument was used
        for (int j = 1; j < seenIndices.length; j++) {
            if (!seenIndices[j]) {
                throw new BlockLoadingException("Argument " + j + " was never used.");
            }
        }
        // If there were leftover fields we need to add a dummy input to hold them.
        if (fields.size() != 0) {
            String align = mJson.optString(lastDummyAlignKey, Input.ALIGN_LEFT_STRING);
            Input input = new Input.InputDummy(null, fields, align);
            inputs.add(input);
            fields.clear();
        }
        Log.i("everb","input:"+inputs.get(i).getType());
    }

    return  inputs;
}

6.BlockFactory中的ObtainBlockFrom(BlockTemplate template)方法里获取BlockTemlate获取对应的样板:

public Block obtainBlockFrom(BlockTemplate template) throws BlockLoadingException {
    if (mController == null) {
        throw new IllegalStateException("Must set BlockController before creating block.");
    }

    String id = getCheckedId(template.mId);

    // Existing instance not found. Constructing a new Block.
    BlockDefinition definition;
    boolean isShadow = (template.mIsShadow == null) ? false : template.mIsShadow;
    Block block;
    if (template.mCopySource != null) {
        try {
            // TODO: Improve copy overhead. Template from copy to avoid XML I/O?
            String xml = BlocklyXmlHelper.writeBlockToXml(template.mCopySource,       //通过BlockTemplate获取对应的样板Block  
                    IOOptions.WRITE_ROOT_ONLY_WITHOUT_ID);
            String escapedId = BlocklyXmlHelper.escape(id);
            xml = xml.replace("<block", "<block id=\"" + escapedId + "\"");
            block = BlocklyXmlHelper.loadOneBlockFromXml(xml, this);
        } catch (BlocklySerializerException e) {
            throw new BlockLoadingException(
                    "Failed to serialize original " + template.mCopySource, e);
        }
    } else {
        // Start a new block from a block definition.
        if (template.mDefinition != null) {
            if (template.mTypeName != null
                    && !template.mTypeName.equals(template.mDefinition.getTypeName())) {
                throw new BlockLoadingException("Conflicting block definitions referenced.");
            }
            definition = template.mDefinition;
        } else if (template.mTypeName != null) {
            definition = mDefinitions.get(template.mTypeName.trim());
            if (definition == null) {
                throw new BlockLoadingException("Block definition named \""
                        + template.mTypeName + "\" not found.");
            }
        } else {
            throw new BlockLoadingException(template.toString() + " missing block definition.");
        }

        block = new Block(mController, this, definition, id, isShadow);
    }

    // Apply mutable state last.
    template.applyMutableState(block);
    mBlockRefs.put(block.getId(), new WeakReference<>(block));

    return block;
}

7.BlocklyCategory提供解析出toolbox.xml文件的种类及子种类。给BlockActivityHelper中的reloadToolbox调用:

调用过程是:将AbstractBlocklyAcitity获取到的toolbox的文件路径一步步地交给BlockActivityHelper->BlocklyController->Workspace->BlockXmlHelper进行解析XML中的数据类型及属性。

在BlockActivityHelper中:

public void reloadToolbox(String toolboxContentsXmlPath) {
    AssetManager assetManager = mActivity.getAssets();
    BlocklyController controller = getController();
    try {
        controller.loadToolboxContents(assetManager.open(toolboxContentsXmlPath));          //加载出toolbox.xml文件中的BlocklyCategory的种类
    } catch (IOException | BlockLoadingException e) {
        // compile time assets such as assets are assumed to be good.
        throw new IllegalStateException("Failed to load toolbox XML.", e);
    }
}

在BlocklyController中:

public void loadToolboxContents(InputStream toolboxJsonStream)
        throws IOException, BlockLoadingException {
    mWorkspace.loadToolboxContents(toolboxJsonStream);
    updateToolbox();
}
在Workspace中:

public void loadToolboxContents(InputStream source) throws BlockLoadingException {
    mFlyoutCategory = BlocklyXmlHelper.loadToolboxFromXml(source, mBlockFactory, BlocklyEvent.WORKSPACE_ID_TOOLBOX);
}
在BlockXmlHelper中:

public static BlocklyCategory loadToolboxFromXml(InputStream is, BlockFactory blockFactory,
                                                 String workspaceId)
        throws BlockLoadingException {
    try {
        XmlPullParser parser = PARSER_FACTORY.newPullParser();
        parser.setInput(is, null);
        return BlocklyCategory.fromXml(parser, blockFactory, workspaceId);//BlocklyCategory进行toolbox.xml的种类解析

    } catch (XmlPullParserException e) {
        throw new BlockLoadingException(e);
    }
}

四、FlyoutFragment中的Block的加入的流程:

1.AbstractBlocklyAcitity中onCreate()方法中初始化来自assets下的json文件之后,在进行Category种类的的设置:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    onCreateActivityRootView();
    mBlocklyActivityHelper = onCreateActivityHelper();
    if (mBlocklyActivityHelper == null) {
        throw new IllegalStateException("BlocklyActivityHelper is null. "
                + "onCreateActivityHelper must return a instance.");
    }
    resetBlockFactory();  // Initial load of block definitions, extensions, and mutators.
    configureCategoryFactories();  // After BlockFactory; before Toolbox
    reloadToolbox();

    // Load the workspace.
    boolean loadedPriorInstance = checkAllowRestoreBlocklyState(savedInstanceState)
            && (getController().onRestoreSnapshot(savedInstanceState) || onAutoload());
    if (!loadedPriorInstance) {
        onLoadInitialWorkspace();
    }
}
2.AbstractBlocklyAcitity交给BlockActivityHelper中的configureCategoryFactories方法里对默认的category种类获取及登记:

protected void configureCategoryFactories() {
    mBlocklyActivityHelper.configureCategoryFactories();
}
public void configureCategoryFactories() {
    Map <String, CustomCategory> factoryMap =
            DefaultBlocks.getToolboxCustomCategories(mController);  //在默认的DefaultBlocks获取到toolbox中Categories种类
    for (String key : factoryMap.keySet()) {
        mController.registerCategoryFactory(key, factoryMap.get(key));//在BlocklyController对toolbox中Categories种类进行登记
    }
}
3.接下来DefaultBlocks中对block参数及种类进行对应的生成:

public static Map<String, CustomCategory> getToolboxCustomCategories(
        BlocklyController controller) {
    // Don't store this map, because of the reference to the controller.
    Map<String, CustomCategory> map = new ArrayMap<>(2);
    map.put(VARIABLE_CATEGORY_NAME, new VariableCustomCategory(controller));//参数Category种类的添加
    map.put(PROCEDURE_CATEGORY_NAME, new ProcedureCustomCategory(controller));//block的Category种类在ProcedureCustomCategory生成器中生成
    return Collections.unmodifiableMap(map);
}
4.ProcedurCustomCategory种类生成器初始化时对BlockTemlate检查和categorry的item的数据设置:
public void initializeCategory(BlocklyCategory category) throws BlockLoadingException {
    checkRequiredBlocksAreDefined();  //检查Block的定义是否在BlockFactory对BlockTemplate进行了生成
    rebuildItems(category);         //把category种类的item通过一系列的回调中添加最终设置到FlyoutFragment布局RecyclerView的adapter中

private void rebuildItems(BlocklyCategory category) throws BlockLoadingException {
    category.clear();

    Block block = mBlockFactory.obtainBlockFrom(DEFINE_NO_RETURN_BLOCK_TEMPLATE);//从BlockFactory工厂中获取ProcedureManager中的block的模板
    ((FieldInput)block.getFieldByName(NAME_FIELD)).setText(mDefaultProcedureName);//设置名字和do something
    category.addItem(new BlocklyCategory.BlockItem(block));//添加到BlocklyCategory中的item中

    block = mBlockFactory.obtainBlockFrom(DEFINE_WITH_RETURN_BLOCK_TEMPLATE);
    ((FieldInput)block.getFieldByName(NAME_FIELD)).setText(mDefaultProcedureName);
    category.addItem(new BlocklyCategory.BlockItem(block));

    if (!mProcedureManager.hasProcedureDefinitionWithReturn()) {
        block = mBlockFactory.obtainBlockFrom(IF_RETURN_TEMPLATE);
        category.addItem(new BlocklyCategory.BlockItem(block));
    }

    // Create a call block for each definition.
    final Map<String, Block> definitions = mProcedureManager.getDefinitionBlocks();
    SortedSet<String> sortedProcNames = new TreeSet<>(new Comparator<String>() {
        @Override
        public int compare(String procName1, String procName2) {
            Block def1 = definitions.get(procName1);
            Block def2 = definitions.get(procName2);
            String type1 = def1.getType();
            String type2 = def2.getType();

            // procedures_defnoreturn < procedures_defreturn
            int typeComp = type1.compareTo(type2);
            if (typeComp != 0) {
                return typeComp;
            }
            // Otherwise sort by procedure name, alphabetically
            int nameComp = procName1.compareToIgnoreCase(procName2);
            if (nameComp != 0) {
                return nameComp;
            }
            return def1.getId().compareTo(def2.getId()); // Last resort, by block id
        }
    });
    sortedProcNames.addAll(definitions.keySet());
    for (String procName : sortedProcNames) {
        Block defBlock = definitions.get(procName);
        ProcedureInfo procedureInfo = ((AbstractProcedureMutator) defBlock.getMutator())
                .getProcedureInfo();
        BlockTemplate callBlockTemplate;
        if (defBlock.getType().equals(ProcedureManager.DEFINE_NO_RETURN_BLOCK_TYPE)) {
            callBlockTemplate = CALL_NO_RETURN_BLOCK_TEMPLATE;  // without return value
        } else {
            callBlockTemplate = CALL_WITH_RETURN_BLOCK_TEMPLATE;  // with return value
        }
        Block callBlock = mBlockFactory.obtainBlockFrom(callBlockTemplate);
        ((ProcedureCallMutator) callBlock.getMutator()).mutate(procedureInfo);
        category.addItem(new BlocklyCategory.BlockItem(callBlock));
    }
}

5.BlocklyCateory的addItem方法把item通过回调通知刷新的方式添加到BlcokRecyclerViewHelper的adapter中,并最终的设置到Recycler中。实现了FlyoutFragment中关于toolbox所有的block的显示:

/**
 * Add a {@link Block} to the blocks displayed in this category.
 *
 * @param item The {@link Block} to add.
 */
public void addItem(CategoryItem item) {
    mItems.add(item);
    if (mCallback != null) {
        mCallback.onItemAdded(mItems.size() - 1, item);//通过Callback回调的形式把item添加到BlockRecyclerViewHelper中的adapter中去
    }
}

6.BlcokRecyclerViewHelper中的设置CategoryCallback回调来监听item的事件变化:

protected class CategoryCallback extends BlocklyCategory.Callback {

    @Override
    public void onItemAdded(int index, BlocklyCategory.CategoryItem item) {
        mAdapter.notifyItemInserted(index);
    }

    @Override
    public void onItemRemoved(int index, BlocklyCategory.CategoryItem block) {
        mAdapter.notifyItemRemoved(index);
    }

    @Override
    public void onCategoryCleared() {
        mAdapter.notifyDataSetChanged();
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值