Android完美實作在筆記中插入方程公式
-
- 一、目标
- 二、實作過程
-
- 1. 定義存儲資料
- 2. 定義邏輯資料
- 3. 定義互動資料
- 4. 添加插入方程
- 5. 執行插入方程
- 6. 實作插入方程
- 7. 加載方程資料
- 三、回顧實作過程
- 四、遺留問題
- 五、接下來
- 六、Finally
曆經了以下幾個開發階段
- iOS Pages插入方程功能分析
- Android插入方程技術方案分析
- Android實作方程編輯器
- Android完美解決LaTeX中文顯示問題
- Android判斷LaTeX是否為空方程
- Android完美支援MathML顯示公式方程
終于實作了Android最強大的方程公式編輯器,接下來要做的事情就是整合到『神馬筆記』中,以實作在筆記中插入方程公式。
一、目标
将方程編輯器整合到『神馬筆記』中,實作在筆記中插入方程公式。
二、實作過程
方程公式有2種形式,行内及行間。
『神馬筆記』采用
RecyclerView
作為筆記容器,是以隻支援行間公式。
行内公式可以将文本放到公式内實作。
1. 定義存儲資料
FormulaEntry
的關鍵屬性。
-
——使用者輸入的原始資料,LaTeX或者MathMLformula
-
——轉化後的latex文本,以避免在顯示過程中轉換使用者輸入的MathML文本。latex
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
實作以下幾個功能。
- 顯示方程内容
- 處理使用者點選操作,啟動方程編輯器
- 處理使用者長按操作,删除、編輯、描述
- 給方程添加描述内容
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件事情
- 插入方程——交由
完成FormulaInsertion
- 更新方程内容
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
中注冊方程資料結構。
- 将JSON資料轉化為
對象FormulaEntry
- 将
對象轉化為FormulaEntry
對象FormulaEntity
三、回顧實作過程
過程 | 相關代碼 | 說明 |
---|---|---|
定義資料結構 | | 存儲資料 |
| 邏輯資料 | |
| 互動資料 | |
添加新元素入口 | 修改界面,添加菜單項 | |
完成新元素互動 | | 插入及更新元素 |
| 實作插入新元素 | |
加載新元素資料 | | 注冊新元素資料 |
四、遺留問題
需要7個步驟,涉及至少10個代碼檔案才能完成添加一種新的筆記元素。
是否有優化的空間???
五、接下來
目前已經實作在筆記中添加方程元素,接下來
- 導出圖檔、文本、Markdown時,包含方程元素
- 輔助編輯時,移動及删除方程元素
六、Finally
~汝說~劉伶~古今達者~醉後何妨死便埋~