You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
miNote2/java/net/micode/notes/ui/NoteEditText.java

221 lines
8.7 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* 版权声明: MiCode 开源社区 版权所有
* 本文件遵循 Apache License, Version 2.0 许可协议
*/
package net.micode.notes.ui;
import android.content.Context;
import android.graphics.Rect;
import android.text.Layout;
import android.text.Selection;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.URLSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.MotionEvent;
import android.widget.EditText;
import net.micode.notes.R;
import java.util.HashMap;
import java.util.Map;
// 定义 NoteEditText 类,继承自 EditText用于笔记编辑文本框
public class NoteEditText extends EditText {
private static final String TAG = "NoteEditText"; // 日志标签
private int mIndex; // 当前编辑文本的索引
private int mSelectionStartBeforeDelete; // 删除前的光标起始位置
private static final String SCHEME_TEL = "tel:"; // 电话 URI scheme
private static final String SCHEME_HTTP = "http:"; // HTTP URI scheme
private static final String SCHEME_EMAIL = "mailto:"; // 邮件 URI scheme
// 定义 URI scheme 和对应操作资源 ID 的映射关系
private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();
static {
sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel);
sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web);
sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email);
}
/**
* 由 NoteEditActivity 调用,用于删除或添加编辑文本
*/
public interface OnTextViewChangeListener {
/**
* 当按下删除键且文本为空时,删除当前编辑文本
*/
void onEditTextDelete(int index, String text);
/**
* 当按下回车键时,添加新的编辑文本
*/
void onEditTextEnter(int index, String text);
/**
* 当文本变化时,隐藏或显示选项菜单项
*/
void onTextChange(int index, boolean hasText);
}
private OnTextViewChangeListener mOnTextViewChangeListener; // 编辑文本变化监听器
// 构造函数
public NoteEditText(Context context) {
super(context, null);
mIndex = 0; // 默认索引为 0
}
// 设置编辑文本的索引
public void setIndex(int index) {
mIndex = index;
}
// 设置编辑文本变化监听器
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener;
}
// 构造函数
public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle);
}
// 构造函数
public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
// 处理触摸事件,用于设置文本选择
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 计算触摸位置相对于文本的偏移量
int x = (int) event.getX();
int y = (int) event.getY();
x -= getTotalPaddingLeft();
y -= getTotalPaddingTop();
x += getScrollX();
y += getScrollY();
Layout layout = getLayout();
int line = layout.getLineForVertical(y); // 获取垂直位置对应的行号
int off = layout.getOffsetForHorizontal(line, x); // 获取水平位置对应的偏移量
Selection.setSelection(getText(), off); // 设置文本选择
break;
}
return super.onTouchEvent(event); // 调用父类的触摸事件处理方法
}
// 处理键盘按下事件
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER:
// 如果设置了编辑文本变化监听器,则不允许处理回车键事件
if (mOnTextViewChangeListener != null) {
return false;
}
break;
case KeyEvent.KEYCODE_DEL:
mSelectionStartBeforeDelete = getSelectionStart(); // 保存删除前的光标起始位置
break;
default:
break;
}
return super.onKeyDown(keyCode, event); // 调用父类的键盘按下事件处理方法
}
// 处理键盘弹起事件
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_DEL:
// 如果设置了编辑文本变化监听器,则处理删除事件
if (mOnTextViewChangeListener != null) {
if (0 == mSelectionStartBeforeDelete && mIndex != 0) { // 如果光标在起始位置且不是第一个编辑文本
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString()); // 删除当前编辑文本
return true;
}
} else {
Log.d(TAG, "未设置编辑文本变化监听器");
}
break;
case KeyEvent.KEYCODE_ENTER:
// 如果设置了编辑文本变化监听器,则处理回车键事件
if (mOnTextViewChangeListener != null) {
int selectionStart = getSelectionStart(); // 获取光标起始位置
String text = getText().subSequence(selectionStart, length()).toString(); // 获取当前选择的文本
setText(getText().subSequence(0, selectionStart)); // 设置文本为选择位置之前的内容
mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text); // 添加新的编辑文本
} else {
Log.d(TAG, "未设置编辑文本变化监听器");
}
break;
default:
break;
}
return super.onKeyUp(keyCode, event); // 调用父类的键盘弹起事件处理方法
}
// 处理焦点变化事件
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
// 如果设置了编辑文本变化监听器,则通知文本变化
if (mOnTextViewChangeListener != null) {
if (!focused && TextUtils.isEmpty(getText())) { // 如果失去焦点且文本为空
mOnTextViewChangeListener.onTextChange(mIndex, false); // 通知文本为空
} else {
mOnTextViewChangeListener.onTextChange(mIndex, true); // 通知文本不为空
}
}
super.onFocusChanged(focused, direction, previouslyFocusedRect); // 调用父类的焦点变化事件处理方法
}
// 创建上下文菜单
@Override
protected void onCreateContextMenu(ContextMenu menu) {
// 如果文本是 Spanned 类型,则处理 URLSpan
if (getText() instanceof Spanned) {
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
int min = Math.min(selStart, selEnd);
int max = Math.max(selStart, selEnd);
final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class); // 获取选中文本中的 URLSpan
if (urls.length == 1) { // 如果只有一个 URLSpan
int defaultResId = 0;
// 遍历 URI scheme 和操作资源 ID 的映射关系,找到对应的资源 ID
for (String schema : sSchemaActionResMap.keySet()) {
if (urls[0].getURL().indexOf(schema) >= 0) {
defaultResId = sSchemaActionResMap.get(schema);
break;
}
}
if (defaultResId == 0) { // 如果未找到对应的资源 ID则使用默认资源 ID
defaultResId = R.string.note_link_other;
}
// 添加菜单项并设置点击监听器
menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener(
new OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
urls[0].onClick(NoteEditText.this); // 跳转到对应链接
return true;
}
});
}
}
super.onCreateContextMenu(menu); // 调用父类的上下文菜单创建方法
}
}