一.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种类:
[
{
"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 ListBLOCK_DEFINITIONS = DefaultBlocks.getAllBlockDefinitions();
@NonNull @Override protected ListgetBlockDefinitionsJsonPaths() { //获取模块中默认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 ListblockDefinitionsJsonPaths) { 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 createInputList(BlockFactory factory) throws BlockLoadingException { ArrayList inputs = new ArrayList<>(); ArrayListfields = 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 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(", " \"" + 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() { Map3.接下来DefaultBlocks中对block参数及种类进行对应的生成:, CustomCategory> factoryMap = DefaultBlocks.getToolboxCustomCategories(mController); //在默认的DefaultBlocks获取到toolbox中Categories种类 for (String key : factoryMap.keySet()) { mController.registerCategoryFactory(key, factoryMap.get(key));//在BlocklyController对toolbox中Categories种类进行登记 } }
public static Map4.ProcedurCustomCategory种类生成器初始化时对BlockTemlate检查和categorry的item的数据设置:, CustomCategory> getToolboxCustomCategories( BlocklyController controller) { // Don't store this map, because of the reference to the controller. Map , 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); }
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, Block> definitions = mProcedureManager.getDefinitionBlocks(); SortedSet sortedProcNames = new TreeSet<>(new Comparator () { @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(); } }