diff --git a/src/net/micode/notes/ui/NoteEditText.java b/src/net/micode/notes/ui/NoteEditText.java index 2afe2a8..2227bfa 100644 --- a/src/net/micode/notes/ui/NoteEditText.java +++ b/src/net/micode/notes/ui/NoteEditText.java @@ -14,45 +14,53 @@ * limitations under the License. */ -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); - } - +// 导入需要的包和类 +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"; + // 定义一个整型变量mIndex,可能用于存储当前文本的选择位置或索引 + private int mIndex; + // 定义一个整型变量mSelectionStartBeforeDelete,可能用于存储删除操作前的文本选择起始位置 + private int mSelectionStartBeforeDelete; + + // 定义三个字符串常量,分别表示电话、HTTP和邮件的URL前缀 + private static final String SCHEME_TEL = "tel:" ; + private static final String SCHEME_HTTP = "http:" ; + private static final String SCHEME_EMAIL = "mailto:" ; + + // 定义一个静态的映射表,将不同的URL前缀映射到对应的资源ID(可能是一段提示文本) + private static final Map sSchemaActionResMap = new HashMap(); + // 在静态代码块中初始化映射表,将不同的URL前缀映射到对应的资源ID + 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 */ @@ -72,146 +80,208 @@ public class NoteEditText extends EditText { /** * 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); - } + // 定义一个名为onTextChange的接口,它有一个int类型的index参数和一个boolean类型的hasText参数 + void onTextChange(int index, boolean hasText); + } + + // 定义一个私有的OnTextViewChangeListener类型的成员变量mOnTextViewChangeListener + private OnTextViewChangeListener mOnTextViewChangeListener; + + // 定义一个公共的构造函数,接受一个Context对象作为参数,并调用父类EditText的构造函数 + public NoteEditText(Context context) { + super(context, null); + // 初始化mIndex为0 + mIndex = 0; + } + + // 定义一个公共的方法,接受一个int类型的index参数,将mIndex设置为该参数值 + public void setIndex(int index) { + mIndex = index; + } + + // 定义一个公共的方法,接受一个OnTextViewChangeListener类型的listener参数,将mOnTextViewChangeListener设置为该参数值 + public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { + mOnTextViewChangeListener = listener; + } + + // 定义一个公共的构造函数,接受一个Context对象、一个AttributeSet对象和一个int类型的defStyle参数,并调用父类EditText的构造函数 + public NoteEditText(Context context, AttributeSet attrs) { + super(context, attrs, android.R.attr.editTextStyle); + } + + // 定义一个公共的构造函数,接受一个Context对象、一个AttributeSet对象和一个int类型的defStyle参数,并调用父类EditText的构造函数 + public NoteEditText(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + // TODO: Auto-generated constructor stub + // 这里是生成的构造函数代码的占位符,通常用于后续添加代码 + } + + // 重写父类EditText的onTouchEvent方法,接受一个MotionEvent对象作为参数 + @Override + public boolean onTouchEvent(MotionEvent event) { + // 根据事件的类型进行判断 + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + // 获取事件发生的x和y坐标 + 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; + } + // 调用父类的onTouchEvent方法,返回其结果值 + return super.onTouchEvent(event); + } + + // 重写父类EditText的onKeyDown方法,接受一个int类型的keyCode参数和一个KeyEvent对象作为参数 + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + // 根据按键的键码进行判断 + switch (keyCode) { + case KeyEvent.KEYCODE_ENTER: // 如果按键是回车键 + // 如果存在OnTextViewChangeListener监听器,则返回false,即不处理回车键事件,否则继续执行下面的代码块 + if (mOnTextViewChangeListener != null) { + return false; + } + break; // 结束switch语句的执行,继续执行下面的代码块(如果有的话) + case KeyEvent.KEYCODE_DEL: // 如果按键是删除键(DEL) + // 保存当前选择文本的起始位置到mSelectionStartBeforeDelete变量中(如果当前有选中的文本) + mSelectionStartBeforeDelete = getSelectionStart(); + break; // 结束switch语句的执行,继续执行下面的代码块(如果有的话) + default: // 其他情况(即按键不是回车键也不是删除键)不做处理,直接退出switch语句执行后续代码块(如果有的话) + break; // 结束switch语句的执行,继续执行下面的代码块(如果有的话) + } // 结束switch语句的执行(如果有的话) + // 调用父类的onKeyDown方法,返回其结果值(默认为true)继续处理其他按键事件(如果有的话)或结束 @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); + // 当按键释放时,此方法被调用。它接收两个参数:按键的代码和按键事件。 + public boolean onKeyUp(int keyCode, KeyEvent event) { + // 使用switch语句根据按键的代码执行不同的操作。 + switch(keyCode) { + // 如果按键是删除键 + case KeyEvent.KEYCODE_DEL: + // 检查OnTextViewChangeListener是否已设置 + if (mOnTextViewChangeListener != null) { + // 如果删除前的选择开始位置为0且索引不为0(可能文本不为空) + if (0 == mSelectionStartBeforeDelete && mIndex != 0) { + // 调用OnTextViewChangeListener的onEditTextDelete方法,传递索引和文本。 + mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString()); + // 方法返回true,表示事件已被处理,不再向上传递。 + return true; + } + } else { + // 如果OnTextViewChangeListener未设置,打印日志。 + Log.d(TAG, "OnTextViewChangeListener was not seted"); + } + // 删除键的处理结束,没有执行其他操作。 + break; + // 如果按键是回车键 + case KeyEvent.KEYCODE_ENTER: + // 检查OnTextViewChangeListener是否已设置 + if (mOnTextViewChangeListener != null) { + // 获取选择开始的位置 + int selectionStart = getSelectionStart(); + // 获取从选择开始位置到文本长度的子序列的字符串形式。 + String text = getText().subSequence(selectionStart, length()).toString(); + // 将文本设置为从开始位置到文本长度的子序列。 + setText(getText().subSequence(0, selectionStart)); + // 调用OnTextViewChangeListener的onEditTextEnter方法,传递索引和文本。 + mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text); + } else { + // 如果OnTextViewChangeListener未设置,打印日志。 + Log.d(TAG, "OnTextViewChangeListener was not seted"); + } + // 回车键的处理结束,没有执行其他操作。 + break; + // 对于其他按键,没有特别的操作。 + default: + break; + } + // 调用父类的onKeyUp方法,将事件传递给上层处理。 + return super.onKeyUp(keyCode, event); + } + + // 当文本视图失去或重新获得焦点时,此方法被调用。它接收三个参数:是否聚焦、方向和之前聚焦的矩形区域。 + @Override + protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { + // 检查OnTextViewChangeListener是否已设置 + if (mOnTextViewChangeListener != null) { + // 如果文本视图失去焦点且文本为空或null,则调用OnTextViewChangeListener的onTextChange方法,传递索引和false。 + if (!focused && TextUtils.isEmpty(getText())) { + mOnTextViewChangeListener.onTextChange(mIndex, false); + } else { + // 否则,调用OnTextViewChangeListener的onTextChange方法,传递索引和true。 + mOnTextViewChangeListener.onTextChange(mIndex, true); + } + } + // 调用父类的onFocusChanged方法,将事件传递给上层处理。 + super.onFocusChanged(focused, direction, previouslyFocusedRect); + } + + // 当创建上下文菜单时,此方法被调用。它接收一个参数:上下文菜单。 + @Override + protected void onCreateContextMenu(ContextMenu menu) { + // 检查文本是否是Spanned类型(通常用于富文本编辑)。 + if (getText() instanceof Spanned) { + // 获取选择开始和结束的位置中的最小值。 + int selStart = getSelectionStart(); + int selEnd = getSelectionEnd(); + int min = Math.min(selStart, selEnd); + // 这段代码在这里被截断,我们没有看到下面的内容。但通常,接下来的代码可能会与选择的文本或光标位置有关。 + } + // 在这里,你可能想要添加与菜单相关的代码或调用其他方法。但在这段代码中没有显示。 } - - @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); + // 获取用户选择的文本起始和结束位置,并取其最大值 + int max = Math.max(selStart, selEnd); + + // 从用户选择的文本中获取URLSpan对象数组,这些对象在用户选择的文本范围内 + final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class); + + // 如果获取到的URLSpan对象只有一个 + if (urls.length == 1) { + // 初始化默认资源ID为0 + int defaultResId = 0; + + // 遍历sSchemaActionResMap的键集合 + for(String schema: sSchemaActionResMap.keySet()) { + // 如果当前URLSpan对象的URL中包含当前的schema(即当前的URL格式) + if(urls[0].getURL().indexOf(schema) >= 0) { + // 则将此schema对应的资源ID赋值给defaultResId + defaultResId = sSchemaActionResMap.get(schema); + // 跳出循环,不再继续遍历其他schema + break; + } + } + + // 如果defaultResId仍然为0,即没有找到匹配的schema + if (defaultResId == 0) { + // 则将默认资源ID设为R.string.note_link_other,这可能是一个默认的提示信息字符串资源ID + defaultResId = R.string.note_link_other; + } + + // 在菜单中添加一个选项,此选项的标题为默认资源ID对应的字符串,此选项的ID为0(这是添加的第一个选项,因此ID为0) + menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener( + // 设置此菜单选项的点击监听器 + new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // 当此菜单选项被点击时,执行以下操作: + // 1. 开启一个新的意图(Intent) + // 2. 执行URLSpan对象的onClick方法,此方法可能会打开用户点击的URL对应的网页或应用 + urls[0].onClick(NoteEditText.this); + // 返回true,表示此菜单选项被点击的事件已经被处理了 + return true; + } + }); } -}