天天看點

Android完美實作在筆記中插入方程公式

Android完美實作在筆記中插入方程公式

    • 一、目标
    • 二、實作過程
      • 1. 定義存儲資料
      • 2. 定義邏輯資料
      • 3. 定義互動資料
      • 4. 添加插入方程
      • 5. 執行插入方程
      • 6. 實作插入方程
      • 7. 加載方程資料
    • 三、回顧實作過程
    • 四、遺留問題
    • 五、接下來
    • 六、Finally

曆經了以下幾個開發階段

  1. iOS Pages插入方程功能分析
  2. Android插入方程技術方案分析
  3. Android實作方程編輯器
  4. Android完美解決LaTeX中文顯示問題
  5. Android判斷LaTeX是否為空方程
  6. Android完美支援MathML顯示公式方程

終于實作了Android最強大的方程公式編輯器,接下來要做的事情就是整合到『神馬筆記』中,以實作在筆記中插入方程公式。

一、目标

将方程編輯器整合到『神馬筆記』中,實作在筆記中插入方程公式。

Android完美實作在筆記中插入方程公式

二、實作過程

方程公式有2種形式,行内及行間。

『神馬筆記』采用

RecyclerView

作為筆記容器,是以隻支援行間公式。

行内公式可以将文本放到公式内實作。

1. 定義存儲資料

FormulaEntry

的關鍵屬性。

  • formula

    ——使用者輸入的原始資料,LaTeX或者MathML
  • latex

    ——轉化後的latex文本,以避免在顯示過程中轉換使用者輸入的MathML文本。
public class FormulaEntry extends ParagraphEntry {

    public static final String TYPE = "formula";

    @SerializedName("formula")
    String formula;

    @SerializedName("latex")
    String latex;

    public FormulaEntry(String id, String formula, String latex) {
        super(id);
        this.type = TYPE;

        this.formula = formula;
        this.latex = latex;
    }

    public String getFormula() {
        return formula;
    }

    public void setFormula(String formula) {
        this.formula = formula;
    }

    public String getLatex() {
        return latex;
    }

    public void setLatex(String latex) {
        this.latex = latex;
    }
}

           

2. 定義邏輯資料

FormulaEntity

沒有添加新的屬性和方法,不過對

FormulaEntry

進行一次包裝。

public class FormulaEntity extends ParagraphEntity<FormulaEntry> {

    public FormulaEntity(Document d, FormulaEntry entry) {
        super(d, entry);
    }

    @Override
    void save() {
        super.save();
    }

    public String getFormula() {
        return entry.getFormula();
    }

    public void setFormula(String formula) {
        entry.setFormula(formula);
    }

    public String getLatex() {
        return entry.getLatex();
    }

    public void setLatex(String latex) {
        entry.setLatex(latex);
    }

    public static final FormulaEntity create(Document d, String formula, String latex) {

        String id = UUIDUtils.next();
        FormulaEntry entry = new FormulaEntry(id, formula, latex);

        FormulaEntity entity = new FormulaEntity(d, entry);

        return entity;
    }
}

           

3. 定義互動資料

FormulaViewHolder

拷貝自

PictureViewHolder

,有不少備援代碼。

FormulaViewHolder

實作以下幾個功能。

  1. 顯示方程内容
  2. 處理使用者點選操作,啟動方程編輯器
  3. 處理使用者長按操作,删除、編輯、描述
  4. 給方程添加描述内容
public class FormulaViewHolder extends ComposeViewHolder<FormulaEntity> {

    public static final int LAYOUT_RES_ID = R.layout.layout_compose_formula_list_item;

    static final String TAG = FormulaViewHolder.class.getSimpleName();

    View pictureLayout;
    JLatexMathView pictureView;

    EditText editText;

    @Keep
    public FormulaViewHolder(Callback callback, View itemView) {
        super(callback, itemView);
    }

    @Override
    public int getLayoutResourceId() {
        return LAYOUT_RES_ID;
    }

    @Override
    public void onViewCreated(@NonNull View view) {

        this.pictureLayout = view.findViewById(R.id.picture_layout);
        this.pictureView = view.findViewById(R.id.iv_picture);

        this.editText = view.findViewById(R.id.edit_text);

        {
            this.setTextChangeListener(new TextChangeListener(this, editText));
        }
    }

    @Override
    public void onBind(FormulaEntity item, int position) {
        super.onBind(item, position);

        {
            editText.setEnabled(callback.isEnable());
            editText.setOnFocusChangeListener(this::onEditFocusChanged);
        }

        {
            // set break strategy to request layout
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                editText.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE);
            }

            editText.setText(item.getText());
            editText.setVisibility(editText.length() > 0? View.VISIBLE: View.GONE);
        }

        {
            pictureLayout.setEnabled(callback.isEnable());
            pictureLayout.setOnClickListener(this::onItemClick);
            pictureLayout.setOnLongClickListener(this::onItemLongClick);
        }

        {
            String latex = item.getLatex();
            if (TextUtils.isEmpty(latex)) {

                String formula = item.getFormula();

                if (MathMLTransformer.isMathML(formula)) {
                    MathMLTransformer transformer = WhatsApp.getInstance().getMathMLTransformer();
                    try {
                        formula = transformer.transform(formula);
                    } catch (TransformerException e) {
                        e.printStackTrace();
                    }
                }

                if (formula == null) {
                    formula = item.getFormula();
                }

                latex = formula;
            }

            try {
                pictureView.setLatex(latex, true);
            } catch (Exception e) {

            }

        }

    }

    @Override
    public void onSoftInputChanged(SoftInputHelper helper, boolean visible) {
        super.onSoftInputChanged(helper, visible);
        if (!visible && editText.hasFocus()) {
            editText.clearFocus();
        }
    }

    void onItemClick(View view) {
        if (editText.hasFocus()) {
            SoftInputUtils.hide(getContext(), editText);
        }

        this.requestView();
    }

    boolean onItemLongClick(View view) {

        MenuItemClickListener listener = new MenuItemClickListener();

        {
            listener.put(R.id.menu_delete, (id) -> this.requestRemove());
            listener.put(R.id.menu_compose, (id) -> this.requestCompose());
            listener.put(R.id.menu_name, (id) -> this.requestEdit());
        }

        {
            int menuRes = R.menu.menu_compose_picture;
            PopupMenu popupMenu = new PopupMenu(getContext(), itemView);
            popupMenu.inflate(menuRes);
            popupMenu.setOnMenuItemClickListener(listener);
            popupMenu.show();
        }

        return true;
    }

    void onEditFocusChanged(View v, boolean hasFocus) {
        if (!hasFocus) {
            editText.setVisibility(editText.length() > 0? View.VISIBLE: View.GONE);
            SoftInputUtils.hide(getContext(), editText);
        }
    }

    @Override
    void requestRemove() {

        // save document first
        {
            callback.requestSave(this);
        }

        {
            Context context = getContext();
            CharSequence title = "确定删除方程?";
            CharSequence msg = null;
            CharSequence negativeButton = context.getString(android.R.string.cancel);
            CharSequence positiveButton = context.getString(android.R.string.yes);
            DialogInterface.OnClickListener listener = ((dialog, which) -> {
                if (which == DialogInterface.BUTTON_POSITIVE) {
                    super.requestRemove();
                }
            });

            AlertDialogUtils.showConfirm(context, title, msg, negativeButton, positiveButton, listener);
        }
    }

    void requestCompose() {
        callback.compose(this);
    }

    void requestEdit() {
        editText.setVisibility(View.VISIBLE);
        editText.requestFocus();
        editText.setSelection(editText.length());
        editText.post(()-> SoftInputUtils.show(getContext(), editText));
    }

    @Override
    public void save() {
        entity.setText(editText.getText());
    }

}
           

4. 添加插入方程

在拍照、照片圖庫插入圖檔的基礎上,添加插入方程菜單項。

menu_compose_insertion.xml

添加新元素入口。

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
            android:id="@+id/menu_camera"
            android:title="拍照"
            android:icon="@drawable/ic_add_a_photo_white_24dp"
            android:iconTint="@color/colorPrimary"
            app:showAsAction="always"
            android:visible="true"/>

    <item
            android:id="@+id/menu_photo"
            android:title="照片圖庫"
            android:icon="@drawable/ic_insert_photo_white_24dp"
            android:iconTint="@color/colorPrimary"
            app:showAsAction="always"
            android:visible="true"/>

    <item
            android:id="@+id/menu_insert_formula"
            android:title="方程"
            android:icon="@drawable/ic_insert_formula_white_24dp"
            android:iconTint="@color/colorPrimary"
            app:showAsAction="always"
            android:visible="true"/>

    <item
            android:id="@+id/menu_insert_paragraph"
            android:title="分頁"
            android:icon="@drawable/ic_insert_paragraph_white"
            android:iconTint="@color/colorPrimary"
            app:showAsAction="always"
            android:visible="false"/>

</menu>
           

5. 執行插入方程

FormulaDelegate

負責2件事情

  1. 插入方程——交由

    FormulaInsertion

    完成
  2. 更新方程内容
public class FormulaDelegate extends BaseRequestDelegate {

    FormulaEntity entity;

    BaseInsertion.Callback callback;

    public FormulaDelegate(Fragment f, FormulaEntity entity, BaseInsertion.Callback callback) {
        super(f);

        this.entity = entity;
        this.callback = callback;
    }

    @Override
    public boolean request() {
        SoftInputUtils.hide(context);

        try {
            String formula = entity == null? "": entity.getFormula();
            ComposeFormulaActivity.startForResult(parent, formula, getRequestCode());

            return true;
        } catch (Exception e) {

        }

        return false;
    }

    @Override
    public void accept(Integer resultCode, Intent data) {

        if ((resultCode != RESULT_OK) || (data == null)) {
            return;
        }

        String formula = data.getStringExtra("formula");
        if (TextUtils.isEmpty(formula)) {
            return;
        }

        String latex = data.getStringExtra("latex");
        if (TextUtils.isEmpty(latex)) {
            return;
        }

        if (entity == null) {
            new FormulaInsertion(callback, formula, latex).execute();
        } else {
            entity.setFormula(formula);
            entity.setLatex(latex);

            Document document = callback.getDocument();
            int index = document.indexOf(entity);
            if (index >= 0) {
                callback.getRecyclerView().getAdapter().notifyItemChanged(index);
            }

        }
    }

}

           

6. 實作插入方程

建立

FormulaEntity

對象,并添加到筆記

Document

中,最後更新界面。

public class FormulaInsertion extends BaseInsertion {

    String formula;
    String latex;

    public FormulaInsertion(Callback callback, String formula, String latex) {
        super(callback);

        this.formula = formula;
        this.latex = latex;
    }

    @Override
    ParagraphEntity insert(int position, CharSequence text) {
        Document document = callback.getDocument();

        ParagraphEntity entity = null;

        int index = position;
        for (int i = 0, size = 1; i < size; i++) {

            FormulaEntity pic = FormulaEntity.create(document, formula, latex);

            if (pic != null) { // add target
                document.add(index, pic);
                ++index;

                CharSequence s = ((i + 1) == size)? text: "";
                if (s != null) {
                    ParagraphEntity en = ParagraphEntity.create(document, s);

                    document.add(index, en);
                    ++index;

                    entity = en;
                }
            }
        }

        int count = (index - position);
        if (count > 0) {
            getAdapter().notifyItemRangeInserted(position, count);
        }

        return entity;
    }
}


           

7. 加載方程資料

DocumentManager

中注冊方程資料結構。

  1. 将JSON資料轉化為

    FormulaEntry

    對象
  1. FormulaEntry

    對象轉化為

    FormulaEntity

    對象

三、回顧實作過程

過程 相關代碼 說明
定義資料結構

FormulaEntry

存儲資料

FormulaEntity

邏輯資料

FormulaViewHolder

互動資料
添加新元素入口 修改界面,添加菜單項
完成新元素互動

FormulaDelegate

插入及更新元素

FormulaInsertion

實作插入新元素
加載新元素資料

DocumentManager

注冊新元素資料

四、遺留問題

需要7個步驟,涉及至少10個代碼檔案才能完成添加一種新的筆記元素。

是否有優化的空間???

五、接下來

目前已經實作在筆記中添加方程元素,接下來

  1. 導出圖檔、文本、Markdown時,包含方程元素
  2. 輔助編輯時,移動及删除方程元素

六、Finally

~汝說~劉伶~古今達者~醉後何妨死便埋~