From 5a8c2841fab88578bc1c5796fc4c6e9e06d5c1ac Mon Sep 17 00:00:00 2001 From: sue <1405477765@qq.com> Date: Thu, 29 May 2025 14:13:22 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A41?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/net/micode/notes/ui/NoteEditText.java | 469 +++++++++++++--------- 1 file changed, 269 insertions(+), 200 deletions(-) diff --git a/src/net/micode/notes/ui/NoteEditText.java b/src/net/micode/notes/ui/NoteEditText.java index 2afe2a8..7bc8a11 100644 --- a/src/net/micode/notes/ui/NoteEditText.java +++ b/src/net/micode/notes/ui/NoteEditText.java @@ -14,204 +14,273 @@ * limitations under the License. */ -package net.micode.notes.ui; + 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; - -public class NoteEditText extends EditText { - private static final String TAG = "NoteEditText"; - private int mIndex; - private int mSelectionStartBeforeDelete; - - private static final String SCHEME_TEL = "tel:" ; - private static final String SCHEME_HTTP = "http:" ; - private static final String SCHEME_EMAIL = "mailto:" ; - - private static final Map sSchemaActionResMap = new HashMap(); - 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); - } - - /** - * Call by the {@link NoteEditActivity} to delete or add edit text - */ - public interface OnTextViewChangeListener { - /** - * Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens - * and the text is null - */ - void onEditTextDelete(int index, String text); - - /** - * Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER} - * happen - */ - void onEditTextEnter(int index, String text); - - /** - * Hide or show item option when text change - */ - void onTextChange(int index, boolean hasText); - } - - private OnTextViewChangeListener mOnTextViewChangeListener; - - public NoteEditText(Context context) { - super(context, null); - mIndex = 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); - // TODO Auto-generated constructor stub - } - - @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, "OnTextViewChangeListener was not seted"); - } - 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, "OnTextViewChangeListener was not seted"); - } - 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) { - 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); - if (urls.length == 1) { - int defaultResId = 0; - for(String schema: sSchemaActionResMap.keySet()) { - if(urls[0].getURL().indexOf(schema) >= 0) { - defaultResId = sSchemaActionResMap.get(schema); - break; - } - } - - if (defaultResId == 0) { - defaultResId = R.string.note_link_other; - } - - menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener( - new OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - // goto a new intent - urls[0].onClick(NoteEditText.this); - return true; - } - }); - } - } - super.onCreateContextMenu(menu); - } -} + 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; + + /** + * 自定义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:" ; + private static final String SCHEME_HTTP = "http:" ; + private static final String SCHEME_EMAIL = "mailto:" ; + + // 协议与菜单项资源ID的映射 + private static final Map sSchemaActionResMap = new HashMap(); + 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 { + /** + * 当按下删除键且文本为空时,删除当前编辑框 + * @param index 当前编辑框的索引 + * @param text 当前编辑框的文本内容 + */ + void onEditTextDelete(int index, String text); + + /** + * 当按下回车键时,在当前编辑框后添加新的编辑框 + * @param index 新编辑框的索引位置 + * @param text 当前编辑框中回车键后的剩余文本 + */ + void onEditTextEnter(int index, String text); + + /** + * 当文本内容变化时,隐藏或显示菜单项 + * @param index 当前编辑框的索引 + * @param hasText 是否有文本内容 + */ + void onTextChange(int index, boolean hasText); + } + + private OnTextViewChangeListener mOnTextViewChangeListener; // 文本变化监听器 + + /** + * 构造函数,用于在代码中创建NoteEditText实例 + * @param context 应用上下文 + */ + public NoteEditText(Context context) { + super(context, null); + mIndex = 0; + } + + /** + * 设置编辑框的索引位置 + * @param index 索引值 + */ + public void setIndex(int index) { + mIndex = index; + } + + /** + * 设置文本变化监听器 + * @param listener 实现了OnTextViewChangeListener接口的监听器 + */ + public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { + mOnTextViewChangeListener = listener; + } + + /** + * 构造函数,用于在XML布局文件中创建NoteEditText实例 + * @param context 应用上下文 + * @param attrs 属性集合 + */ + public NoteEditText(Context context, AttributeSet attrs) { + super(context, attrs, android.R.attr.editTextStyle); + } + + /** + * 构造函数,使用指定的样式资源 + * @param context 应用上下文 + * @param attrs 属性集合 + * @param defStyle 样式资源ID + */ + public NoteEditText(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + // TODO Auto-generated constructor stub + } + + /** + * 处理触摸事件,确保点击位置能正确定位到文本 + * @param event 触摸事件 + * @return 是否处理了该事件 + */ + @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); + } + + /** + * 处理按键按下事件 + * @param keyCode 按键码 + * @param event 按键事件 + * @return 是否处理了该事件 + */ + @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); + } + + /** + * 处理按键释放事件 + * @param keyCode 按键码 + * @param event 按键事件 + * @return 是否处理了该事件 + */ + @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, "OnTextViewChangeListener was not seted"); + } + 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, "OnTextViewChangeListener was not seted"); + } + break; + default: + break; + } + return super.onKeyUp(keyCode, event); + } + + /** + * 焦点变化时的回调方法 + * @param focused 是否获得焦点 + * @param direction 焦点移动方向 + * @param previouslyFocusedRect 之前焦点所在的矩形区域 + */ + @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); + } + + /** + * 创建上下文菜单时的回调方法 + * @param menu 上下文菜单 + */ + @Override + protected void onCreateContextMenu(ContextMenu menu) { + // 检查选中文本中是否包含链接,如果包含则添加对应的菜单项 + 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); + if (urls.length == 1) { + int defaultResId = 0; + // 根据链接协议选择对应的菜单项文本 + for(String schema: sSchemaActionResMap.keySet()) { + if(urls[0].getURL().indexOf(schema) >= 0) { + defaultResId = sSchemaActionResMap.get(schema); + break; + } + } + + if (defaultResId == 0) { + 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); + } + } \ No newline at end of file