Blockly安卓原生图形化编程应用(新增在Fragment实现Blockly)

    xiaoxiao2021-03-25  76

    Google原生的blockly下载地址:https://github.com/google/blockly-android 1.自己创建Brockly工程后,发现运行起来会闪退。最终问题出在所有blockly界面相关的Activity需要在mainfest中定义android:windowSoftInputMode=”stateHidden|adjustPan” 2.所谓的xml转c语言其实是转换成javascript,然后根据generate函数转化的界面显示。 3.自定义block最简单的方法是在block factory 该网址下https://blockly-demo.appspot.com/static/demos/blockfactory/index.html 然后把生成的json放入相关模块的xml文件下,然后将type声明在toolbox中。 4.需要了解自定义block的定义规则。 代码块连接接口定义: previousStatement 上部接口 nextStatement 下部接口 type: input_value 右侧接口 input_statement 内部接口 input_dummy 右侧无接口 field_dropdown下拉列表:与 options[[],[]]选择列表连用 field_colour 会显示颜色 伴随 output colour 代码块属性定义: colour 设置颜色 最大值为360 tooltip 声明代码块的含义 name 可自定义 在toolbox中可写入具体限制和类型 message 显示内容 %1 %2 ……为arg0[]中的内容 以[]包裹 inputsInline 为 true 表示在代码块内部 output 输出值 (colour boolean text) check 检查类型 5.由于项目需求很多都不符合原生携带的功能,原来的blockly已经被我改的面目全非,具体面向需求的问题如下: ①需求:模块上的填写框需要换成文本框或图片都能显示,图片模块需要设置为res里的drawable文件。field_image就根据src值来判断显示,判断逻辑写在com.google.blockly.android.ui.fieldview.BasicFieldImageView中,相应的BasicFieldInputView继承EditText改为继承Linearlayout,注意改为继承TextView会有bug,点击对应区域会出现很长一段乱码字符。 ②整个BlockView的点击事件要写在Dragger处理拖动事件中,就相当于判断ACTION_DOWN和ACTION_UP的时间差,小于指定值触发自定义点击事件。 ③设置BlockView颜色,如果按照原生的给出hsv值中的h值去设置颜色会出现颜色较给出颜色偏暗,原因是sv都是默认的值,解决方案: 首先将toolbox中的颜色配置为RGB格式: 然后在依赖包blocklylib-core中修改com.google.blockly.utils.ColorUtils中的parseColor()方法:

    /** * Parses a string as an opaque color, either as a decimal hue (example: {@code 330}) using a * standard set of saturation and value) or as six digit hex code (example: {@code #BB66FF}). * If the string cannot be parsed, a {@link ParseException} is thrown. * * @param str The input to parse. * @param tempHsvArray An optional previously allocated array for HSV calculations. * @return The parsed color, in {@code int} form. * @throws ParseException */ public static int parseColor(@NonNull String str, @Nullable float[] tempHsvArray) throws ParseException { Integer result = null; char firstChar = str.charAt(0); if (firstChar == '#' && str.length() == 7) { try { //result = Integer.parseInt(str.substring(1, 7), 16); float[] hsv = new float[3]; //如果不来回转换一次,会出现BlockView中其他内容显示不正常。 Color.RGBToHSV(Integer.parseInt(str.substring(1, 3), 16), Integer.parseInt(str.substring(3, 5), 16), Integer.parseInt(str.substring(5, 7), 16),hsv); result = Color.HSVToColor(hsv); } catch (NumberFormatException e) { throw new ParseException("Invalid hex color: " + str, 0); } return result; } else if (Character.isDigit(firstChar) && str.length() <= 3) { try { int hue = Integer.parseInt(str); result = getBlockColorForHue(hue, tempHsvArray); } catch (NumberFormatException e) { throw new ParseException("Invalid color hue: " + str, 0); } } // Maybe other color formats? 3 digit hex, CSS color functions, etc. return result; } 6.新需求:要在slidingmenu里的fragment的实现google blockly的功能,踩了几天的坑,简单总结一下: ①首先写一个基类Fragment将blockly—core库中的AbstractBlocklyActivity中的功能整合进去(这个过程需要把context和activity部分内容重新定义,比较繁琐,但难度不大,这里只贴代码) package com.tysd.eosthird.base; import android.content.SharedPreferences; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.v4.app.Fragment; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import com.google.blockly.android.BlocklyActivityHelper; import com.google.blockly.android.codegen.CodeGenerationRequest; import com.google.blockly.android.codegen.LanguageDefinition; import com.google.blockly.android.control.BlocklyController; import com.google.blockly.android.ui.BlockViewFactory; import com.google.blockly.android.ui.MutatorFragment; import com.google.blockly.model.BlockExtension; import com.google.blockly.model.BlocklySerializerException; import com.google.blockly.model.CustomCategory; import com.google.blockly.model.DefaultBlocks; import com.google.blockly.model.Mutator; import com.google.blockly.utils.BlockLoadingException; import com.tysd.eosthird.R; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.List; /** * Created by hxl on 2018/5/24 0024. */ public abstract class BaseBlocklyFragment extends BaseFragment { /** * Per the design guidelines, you should show the drawer on launch until the user manually * expands it. This shared preference tracks this. */ private static final String PREF_USER_LEARNED_DRAWER = "navigation_drawer_learned"; private static final String TAG = "AbstractBlocklyActivity"; protected BlocklyActivityHelper mBlocklyActivityHelper; protected ActionBar mActionBar; protected DrawerLayout mDrawerLayout; // These two may be null if {@link #onCreateAppNavigationDrawer} returns null. protected View mNavigationDrawer; protected ActionBarDrawerToggle mDrawerToggle; private boolean mUserLearnedDrawer; //ADD public void onLoadWorkspaceFromString(String str) { try{ mBlocklyActivityHelper.loadWorkspaceFromString(str); }catch (Exception e){ } } /** * 获取字符串 */ protected String getCodeString() { return mBlocklyActivityHelper.toCodeString(); } /** * Opens or closes the navigation drawer. * @param open Opens the navigation drawer if true and closes it if false. */ public void setNavDrawerOpened(boolean open) { boolean alreadyOpen = mDrawerLayout.isDrawerOpen(mNavigationDrawer); if (open != alreadyOpen) { if (open) { mDrawerLayout.openDrawer(mNavigationDrawer); } else { mDrawerLayout.closeDrawer(mNavigationDrawer); } restoreActionBar(); } } /** * Called when the user clicks the save action. Default implementation delegates handling to * {@link BlocklyActivityHelper#saveWorkspaceToAppDir(String)} using * {@link #getWorkspaceSavePath()}. */ public void onSaveWorkspace() { mBlocklyActivityHelper.saveWorkspaceToAppDirSafely(getWorkspaceSavePath()); } /** * Called when the user clicks the load action. Default implementation delegates handling to * {@link BlocklyActivityHelper#loadWorkspaceFromAppDir(String)}. */ public void onLoadWorkspace() { mBlocklyActivityHelper.loadWorkspaceFromAppDirSafely(getWorkspaceSavePath()); } /** * Called when the user clicks the clear action. Default implementation resets the * workspace, removing all blocks from the workspace, and then calls * {@link #onInitBlankWorkspace()}. */ public void onClearWorkspace() { getController().resetWorkspace(); onInitBlankWorkspace(); } /** * Saves a snapshot of the workspace to {@code outState}. * * @param outState The {@link Bundle} to save to. */ @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); getController().onSaveSnapshot(outState); } /** * @return The {@link BlocklyController} controlling the workspace in this activity. */ public final BlocklyController getController() { return mBlocklyActivityHelper.getController(); } /** * Create a {@link BlocklyActivityHelper} to use for this Activity. */ protected BlocklyActivityHelper onCreateActivityHelper() { return new BlocklyActivityHelper(getBaseActivity(),this); } /** Propagate lifecycle event to BlocklyActivityHelper. */ @Override public void onStart() { super.onStart(); mBlocklyActivityHelper.onStart(); } /** Propagate lifecycle event to BlocklyActivityHelper. */ @Override public void onPause() { super.onPause(); mBlocklyActivityHelper.onPause(); onAutosave(); } /** Propagate lifecycle event to BlocklyActivityHelper. */ @Override public void onResume() { super.onResume(); mBlocklyActivityHelper.onResume(); if (mNavigationDrawer != null) { // Read in the flag indicating whether or not the user has demonstrated awareness of the // drawer. See PREF_USER_LEARNED_DRAWER for details. SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getBaseActivity()); mUserLearnedDrawer = sp.getBoolean(PREF_USER_LEARNED_DRAWER, false); if (!mUserLearnedDrawer) { mDrawerLayout.openDrawer(mNavigationDrawer); } } } /** Propagate lifecycle event to BlocklyActivityHelper. */ @Override public void onStop() { super.onStop(); mBlocklyActivityHelper.onStop(); } public LayoutInflater mInflater = null; public View mLayout = null; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if (null == mLayout) { mLayout = getContentView(inflater); boolean loadedPriorInstance = checkAllowRestoreBlocklyState(savedInstanceState) && (getController().onRestoreSnapshot(savedInstanceState) || onAutoload()); if (!loadedPriorInstance) { onLoadInitialWorkspace(); } } if (null != mLayout.getParent()) { ((ViewGroup) mLayout.getParent()).removeView(mLayout); } return mLayout; } @Override protected View getContentView(LayoutInflater inflater) { mLayout = inflater.inflate(com.google.blockly.android.R.layout.drawers_and_action_bar, null); mInflater =inflater; onCreateActivityRootView(); mBlocklyActivityHelper = onCreateActivityHelper(); if (mBlocklyActivityHelper == null) { throw new IllegalStateException("BlocklyActivityHelper is null. " + "onCreateActivityHelper must return a instance."); } onConfigureTrashIcon(); resetBlockFactory(); // Initial load of block definitions, extensions, and mutators. configureCategoryFactories(); // After BlockFactory; before Toolbox reloadToolbox(); addAction(); return mLayout; } protected void onConfigureTrashIcon() { View trashIcon = mLayout.findViewById(R.id.blockly_trash_icon); if (mBlocklyActivityHelper.getController() != null && trashIcon != null) { mBlocklyActivityHelper.getController().setTrashIcon(trashIcon); } } public abstract void addAction(); /** * * Returns true if the app should proceed to restore the blockly state from the * {@code savedInstanceState} Bundle or the {@link #onAutoload() auto save} file. By default, it * always returns true, but Activity developers can override this method to add conditional * logic. * <p/> * This does not prevent the state from saving to a Bundle during {@link #onSaveInstanceState} * or saving to a file in {@link #onAutosave()}. * * @param savedInstanceState The Bundle to restore state from. * @return True if Blockly state should be restored. Otherwise, null. */ protected boolean checkAllowRestoreBlocklyState(Bundle savedInstanceState) { return true; } /** * Hook for subclasses to load an initial workspace. Default implementation just calls * {@link #onInitBlankWorkspace()}. */ protected void onLoadInitialWorkspace() { onInitBlankWorkspace(); getController().closeFlyouts(); } /** * Called when an autosave of the workspace is triggered, typically by {@link #onPause()}. * By default this saves the workspace to a file in the app's directory. */ protected void onAutosave() { try { mBlocklyActivityHelper.saveWorkspaceToAppDir(getWorkspaceAutosavePath()); } catch (FileNotFoundException | BlocklySerializerException e) { Log.e(TAG, "Failed to autosaving workspace.", e); } } /** * Called when the activity tries to restore the autosaved workspace, typically by * {@link #onCreate(Bundle)} if there was no workspace data in the bundle. * * @return true if a previously saved workspace was loaded, false otherwise. */ protected boolean onAutoload() { String filePath = getWorkspaceAutosavePath(); try { mBlocklyActivityHelper.loadWorkspaceFromAppDir(filePath); return true; } catch (FileNotFoundException e) { // No workspace was saved previously. } catch (BlockLoadingException | IOException e) { Log.e(TAG, "Failed to load workspace", e); mBlocklyActivityHelper.getController().resetWorkspace(); File file = getBaseActivity().getFileStreamPath(filePath); if (!file.delete()) { Log.e(TAG, "Failed to delete corrupted autoload workspace: " + filePath); } } return false; } /** * Hook for subclasses to initialize a new blank workspace. Initialization may include * configuring default variables or other setup. */ protected void onInitBlankWorkspace() {} /** * @return The name to show in the {@link ActionBar}. Defaults to the activity name. */ @NonNull protected CharSequence getWorkspaceTitle() { return getBaseActivity().getTitle(); } /** * @return The asset path for the xml toolbox config. */ @NonNull abstract protected String getToolboxContentsXmlPath(); /** * @return The asset path for the json block definitions. */ @NonNull abstract protected List<String> getBlockDefinitionsJsonPaths(); /** * @return The asset path for the core language file used to generate code. */ @NonNull protected LanguageDefinition getBlockGeneratorLanguage() { return DefaultBlocks.LANGUAGE_DEFINITION; } /** * This method provides a hook to register {@link BlockExtension}s that support the block * definitions in this activity. By default, it adds all extensions in * {@link DefaultBlocks#getExtensions() DefaultBlocks} to the block factory, via the * {@link #onCreateActivityHelper() BlocklyActivityHelper} * {@link BlocklyActivityHelper#configureExtensions() implementation}. * <p/> * Extensions with the same key will replace existing extensions, so it is safe * to call super and then update specific extensions. * <p/> * Called from {@link #resetBlockFactory()}. */ protected void configureBlockExtensions() { mBlocklyActivityHelper.configureExtensions(); } /** * This method provides a hook to register {@link Mutator.Factory}s and * {@link MutatorFragment.Factory}s that support the block definitions in this activity. By * default, it adds the mutators in {@link DefaultBlocks#getMutators() DefaultBlocks} to the * BlockFactory, via the {@link #onCreateActivityHelper() BlocklyActivityHelper} * {@link BlocklyActivityHelper#configureMutators() implementation}. * <p/> * Mutators with the same key will replace existing mutators, so it is safe * to call super and then update specific mutators. * <p/> * Called from {@link #resetBlockFactory()}. */ protected void configureMutators() { mBlocklyActivityHelper.configureMutators(); } /** * This method provides a hook to register custom {@link CustomCategory}s that support * the toolboxes in this activity. By default, it registers the categories in * {@link DefaultBlocks}, via the {@link #onCreateActivityHelper() BlocklyActivityHelper} * {@link BlocklyActivityHelper#configureMutators() implementation}. * <p/> * Category factories with the same {@code custom} key will replace existing * {@link CustomCategory}s, so it is safe to call super and then update specific categories. * <p/> * Called once at activity creation. */ protected void configureCategoryFactories() { mBlocklyActivityHelper.configureCategoryFactories(); } /** * Returns the asset file paths to the generators (JS files) to use for the most * recently requested "Run" action. Called from {@link #onRunCode()}. This is expected to be a * list of JavaScript files that contain the block generators. * * @return The list of file paths to the block generators. */ @NonNull abstract protected List<String> getGeneratorsJsPaths(); /** * Returns a generation callback to use for the most recently requested "Run" action. * Called from {@link #onRunCode()}. * * @return The generation callback. */ @NonNull abstract protected CodeGenerationRequest.CodeGeneratorCallback getCodeGenerationCallback(); /** * @return The path to the saved workspace file on the local device. By default, * "workspace.xml". */ @NonNull protected String getWorkspaceSavePath() { return "workspace.xml"; } /** * @return The path to the automatically saved workspace file on the local device. By default, * "autosave_workspace.xml". */ @NonNull protected String getWorkspaceAutosavePath() { return "autosave_workspace.xml"; } /** * Creates or loads the root content view (by default, {@link com.google.blockly.android.R.layout#drawers_and_action_bar}) * for the Activity. It is also responsible for assigning {@link #mActionBar} and * {@link #mDrawerLayout}, and adding the view returned by {@link #onCreateContentView}. */ protected void onCreateActivityRootView() { mDrawerLayout = (DrawerLayout) mLayout.findViewById(com.google.blockly.android.R.id.drawer_layout); mActionBar = getBaseActivity().getSupportActionBar(); //mActionBar.setDisplayShowTitleEnabled(true); // Create and attach content view into content container. If content is a fragment, content // will be null here and the container will be populated during the FragmentTransaction. View content = onCreateContentView(com.google.blockly.android.R.id.content_container); if (content != null) { FrameLayout contentContainer = (FrameLayout) mLayout.findViewById(com.google.blockly.android.R.id.content_container); FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); if (content.getParent() != contentContainer) { contentContainer.addView(content, lp); } else { content.setLayoutParams(lp); } } mNavigationDrawer = onCreateAppNavigationDrawer(); if (mNavigationDrawer != null) { setupAppNaviagtionDrawer(); } } /** * Constructs (or inflates) the primary content view of the Activity. * * @param containerId The container id to target if using a {@link Fragment} * @return The {@link View} constructed. If using a {@link Fragment}, return null. */ protected View onCreateContentView(int containerId) { return mInflater.inflate(com.google.blockly.android.R.layout.blockly_unified_workspace, null); } /** * @return The {@link View} to be used for the navigation menu. Otherwise null. */ protected View onCreateAppNavigationDrawer() { return null; } /** * Configures the activity to support a navigation menu and drawer provided by * {@link #onCreateAppNavigationDrawer}. */ protected void setupAppNaviagtionDrawer() { DrawerLayout.LayoutParams lp = new DrawerLayout.LayoutParams( getResources().getDimensionPixelSize(com.google.blockly.android.R.dimen.navigation_drawer_width), ViewGroup.LayoutParams.MATCH_PARENT, Gravity.START); // Add navigation drawer above the content view, as the first drawer. mDrawerLayout.addView(mNavigationDrawer, 1, lp); // set a custom shadow that overlays the main content when the drawer opens mDrawerLayout.setDrawerShadow(com.google.blockly.android.R.drawable.drawer_shadow, GravityCompat.START); mActionBar.setDisplayHomeAsUpEnabled(true); mActionBar.setHomeButtonEnabled(true); // ActionBarDrawerToggle ties together the the proper interactions // between the navigation drawer and the action bar app icon. mDrawerToggle = new ActionBarDrawerToggle(getActivity(), mDrawerLayout, com.google.blockly.android.R.string.navigation_drawer_open, /* "open drawer" description for accessibility */ com.google.blockly.android.R.string.navigation_drawer_close /* "close drawer" description for accessibility */ ) { @Override public void onDrawerClosed(View drawerView) { super.onDrawerClosed(drawerView); getBaseActivity().supportInvalidateOptionsMenu(); // calls onPrepareOptionsMenu() } @Override public void onDrawerOpened(View drawerView) { super.onDrawerOpened(drawerView); if (!mUserLearnedDrawer) { // The user manually opened the drawer; store this flag to prevent auto-showing // the navigation drawer automatically in the future. mUserLearnedDrawer = true; SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences( getActivity()); sp.edit().putBoolean(PREF_USER_LEARNED_DRAWER, true).apply(); } getBaseActivity().supportInvalidateOptionsMenu(); // calls onPrepareOptionsMenu() } }; // Defer code dependent on restoration of previous instance state. mDrawerLayout.post(new Runnable() { @Override public void run() { mDrawerToggle.syncState(); } }); mDrawerLayout.addDrawerListener(mDrawerToggle); } /** * Runs the code generator. Called when user selects "Run" action. * <p/> * Gets the latest block definitions and generator code by calling * {@link #getBlockDefinitionsJsonPaths()} and {@link #getGeneratorsJsPaths()} just before * invoking generation. * * @see #getCodeGenerationCallback() */ protected void onRunCode() { mBlocklyActivityHelper.requestCodeGeneration( getBlockGeneratorLanguage(), getBlockDefinitionsJsonPaths(), getGeneratorsJsPaths(), getCodeGenerationCallback()); } /** * Restores the {@link ActionBar} contents when the navigation window closes, per <a * href="http://developer.android.com/design/material/index.html">Material design * guidelines</a>. */ protected void restoreActionBar() { ActionBar actionBar = getBaseActivity().getSupportActionBar(); if (actionBar != null) { actionBar.setDisplayShowTitleEnabled(true); actionBar.setTitle(getWorkspaceTitle()); } } /** * Reloads the toolbox contents using the path provided by {@link #getToolboxContentsXmlPath()}. */ protected void reloadToolbox() { mBlocklyActivityHelper.reloadToolbox(getToolboxContentsXmlPath()); } /** * Reloads the block definitions, including extensions and mutators. Calls * {@link #getBlockDefinitionsJsonPaths()} and {@link #configureBlockExtensions()}. * * @throws IOException If there is a fundamental problem with the input. * @throws BlockLoadingException If the definition is malformed. */ protected void resetBlockFactory() { mBlocklyActivityHelper.resetBlockFactory( getBlockDefinitionsJsonPaths()); configureBlockExtensions(); configureMutators(); configureCategoryFactories(); // Reload the toolbox? } /** * @return True if the navigation menu was closed and the back event should be consumed. * Otherwise false. */ protected boolean onBackToCloseNavMenu() { if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) { mDrawerLayout.closeDrawer(GravityCompat.START); return true; } return false; } }

    ②简单改写后Fragment中实现blockly操作会有两个问题:第一,拖动带有BasicFieldInputView的控件会有放手后先返回初始位置又到放手的位置,这个做安卓相关的EditText拖动都知道,重写EditText的onDragEvent方法 return false即可。第二,拖至垃圾桶无法删除,这是因为BlocklyActivityHelper的onConfigureTrashIcon()方法中:

    protected void onConfigureTrashIcon() { View trashIcon = mActivity.findViewById(R.id.blockly_trash_icon); if (mController != null && trashIcon != null) { mController.setTrashIcon(trashIcon); } }

    trashIcon 为空,无法设置拖动监听事件,上面的fragment基类已解决此问题,将该方法改写到fragment基类中:

    protected void onConfigureTrashIcon() { View trashIcon = mLayout.findViewById(R.id.blockly_trash_icon); if (mBlocklyActivityHelper.getController() != null && trashIcon != null) { mBlocklyActivityHelper.getController().setTrashIcon(trashIcon); } }
    转载请注明原文地址: https://ju.6miu.com/read-41078.html

    最新回复(0)