您的位置:首页 > 其它

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

2017-11-04 17:58 357 查看
一.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(ty
1750a
peName)) {
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();
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息