You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android自定义数学符号键盘开发咨询:含可编辑公式输入

Android 富文本数学输入自定义键盘实现方案

推荐实现方法与库

1. LaTeX渲染库 + 自定义KeyboardView

这是最成熟的方案,利用LaTeX语法描述数学公式,配合渲染库实时展示,自定义键盘负责插入对应的LaTeX指令:

  • KaTeX:轻量快速,移动端适配性好,适合实时渲染场景。
  • MathJax:支持更复杂的公式,兼容性强,但体积较大。
    两者都可以通过封装的Android库集成,比如MathView(基于KaTeX),键盘点击时插入LaTeX代码,同时控制光标位置实现符号内内容编辑。

2. 自定义Span原生实现

完全基于Android的Spannable机制,自定义FractionSpan、RootSpan等Span类,直接在EditText中渲染数学符号,无需依赖第三方渲染库。优点是原生流畅,缺点是需要自行处理光标移动、内容编辑等复杂逻辑,适合对性能要求高的场景。

3. 开源数学输入库

直接使用现成的开源方案,比如:

  • MathKeyboardView:专门为Android打造的数学输入键盘,内置根号、分数等符号的编辑逻辑。
  • TeXView:支持LaTeX输入与渲染,自带简易数学键盘。

代码示例:KaTeX + 自定义键盘实现可编辑数学符号

1. 添加依赖

implementation 'io.github.kexanie.library:MathView:0.0.6'

2. 自定义键盘布局(res/xml/math_keyboard.xml)

<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
    android:keyWidth="33%p"
    android:horizontalGap="0dp"
    android:verticalGap="0dp"
    android:keyHeight="60dp">

    <Row>
        <Key android:codes="0" android:keyLabel="√" android:keyEdgeFlags="left"/>
        <Key android:codes="1" android:keyLabel="分数"/>
        <Key android:codes="2" android:keyLabel="x²" android:keyEdgeFlags="right"/>
    </Row>
    <!-- 其他数字、符号按键可按需添加 -->
</Keyboard>

3. 核心逻辑实现

import android.inputmethodservice.Keyboard;
import android.inputmethodservice.KeyboardView;
import android.os.Bundle;
import android.widget.EditText;

import androidx.appcompat.app.AppCompatActivity;

import io.github.kexanie.library.MathView;

public class MathInputActivity extends AppCompatActivity implements KeyboardView.OnKeyboardActionListener {

    private KeyboardView mathKeyboardView;
    private EditText inputEt;
    private MathView mathPreview;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_math_input);

        inputEt = findViewById(R.id.input_et);
        mathPreview = findViewById(R.id.math_preview);
        mathKeyboardView = findViewById(R.id.math_keyboard_view);
        mathKeyboardView.setKeyboard(new Keyboard(this, R.xml.math_keyboard));
        mathKeyboardView.setOnKeyboardActionListener(this);
    }

    @Override
    public void onKey(int primaryCode, int[] keyCodes) {
        int cursorPos = inputEt.getSelectionStart();
        String currentText = inputEt.getText().toString();

        switch (primaryCode) {
            case 0: // 插入根号,光标自动定位到根号内部
                String rootLaTeX = "\\sqrt{}";
                inputEt.getText().insert(cursorPos, rootLaTeX);
                inputEt.setSelection(cursorPos + 6);
                updatePreview();
                break;
            case 1: // 插入分数,光标先定位到分子位置
                String fracLaTeX = "\\frac{}{}";
                inputEt.getText().insert(cursorPos, fracLaTeX);
                inputEt.setSelection(cursorPos + 6);
                updatePreview();
                break;
            case 2: // 插入平方符号
                inputEt.getText().insert(cursorPos, "^2");
                inputEt.setSelection(cursorPos + 2);
                updatePreview();
                break;
            // 其他按键逻辑(数字、删除等)自行补充
        }
    }

    private void updatePreview() {
        // 用KaTeX渲染输入的LaTeX公式
        mathPreview.setText("$$" + inputEt.getText().toString() + "$$");
    }

    // 以下接口方法空实现即可
    @Override public void onPress(int primaryCode) {}
    @Override public void onRelease(int primaryCode) {}
    @Override public void onText(CharSequence text) {}
    @Override public void swipeLeft() {}
    @Override public void swipeRight() {}
    @Override public void swipeDown() {}
    @Override public void swipeUp() {}
}

4. 布局文件(activity_math_input.xml)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <EditText
        android:id="@+id/input_et"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="输入LaTeX公式"/>

    <io.github.kexanie.library.MathView
        android:id="@+id/math_preview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"/>

    <android.inputmethodservice.KeyboardView
        android:id="@+id/math_keyboard_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"/>
</LinearLayout>

自定义Span实现示例(原生渲染分数)

如果不想依赖第三方库,可以自定义Span直接渲染:

import android.graphics.Canvas;
import android.graphics.Paint;
import android.text.style.ReplacementSpan;

public class FractionSpan extends ReplacementSpan {
    private String numerator;
    private String denominator;
    private int subTextSize;

    public FractionSpan(String numerator, String denominator, int textSize) {
        this.numerator = numerator;
        this.denominator = denominator;
        this.subTextSize = textSize * 3 / 4; // 子文本大小为原文本的3/4
    }

    @Override
    public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
        paint.setTextSize(subTextSize);
        // 取分子分母中较宽的作为总宽度
        return (int) Math.max(paint.measureText(numerator), paint.measureText(denominator));
    }

    @Override
    public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
        int originalSize = (int) paint.getTextSize();
        Paint.FontMetricsInt fm = paint.getFontMetricsInt();

        // 绘制分子
        paint.setTextSize(subTextSize);
        float numX = x + (getSize(paint, text, start, end, fm) - paint.measureText(numerator)) / 2;
        canvas.drawText(numerator, numX, y - fm.descent - fm.leading / 2, paint);

        // 绘制分数线
        float lineY = y - fm.leading / 4;
        canvas.drawLine(x, lineY, x + getSize(paint, text, start, end, fm), lineY, paint);

        // 绘制分母
        float denX = x + (getSize(paint, text, start, end, fm) - paint.measureText(denominator)) / 2;
        canvas.drawText(denominator, denX, y + fm.ascent / 2, paint);

        // 恢复原文本大小
        paint.setTextSize(originalSize);
    }
}

插入Span的逻辑:

// 点击分数按键时插入Span
SpannableStringBuilder ssb = new SpannableStringBuilder(inputEt.getText());
FractionSpan fractionSpan = new FractionSpan("", "", (int) inputEt.getTextSize());
ssb.insert(cursorPos, "  "); // 占位符
ssb.setSpan(fractionSpan, cursorPos, cursorPos + 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
inputEt.setText(ssb);
inputEt.setSelection(cursorPos + 1);

内容的提问来源于stack exchange,提问作者Hosein.m.barazandeh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 20:34:57