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.
note/src/NoteEditText.java

228 lines
9.8 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.

/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* 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; // 导入URL样式类
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:"; // HTTP协议
private static final String SCHEME_EMAIL = "mailto:"; // 邮件协议
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); // HTTP协议动作
sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email); // 邮件协议动作
}
/**
* 由{@link NoteEditActivity}调用以删除或添加编辑文本
*/
public interface OnTextViewChangeListener {
/**
* 当{@link KeyEvent#KEYCODE_DEL}发生且文本为空时删除当前编辑文本
*/
void onEditTextDelete(int index, String text);
/**
* 当{@link KeyEvent#KEYCODE_ENTER}发生时在当前编辑文本后添加编辑文本
*/
void onEditTextEnter(int index, String text);
/**
* 当文本变化时隐藏或显示项选项
*/
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:
// 获取触摸事件的X和Y坐标
int x = (int) event.getX();
int y = (int) event.getY();
// 调整坐标以适应总内边距和滚动
x -= getTotalPaddingLeft();
y -= getTotalPaddingTop();
x += getScrollX();
y += getScrollY();
// 获取文本布局
Layout layout = getLayout();
// 获取Y坐标对应的行
int line = layout.getLineForVertical(y);
// 获取X坐标在行中的偏移量
int off = layout.getOffsetForHorizontal(line, x);
// 设置选择位置
Selection.setSelection(getText(), off);
break;
}
return super.onTouchEvent(event); // 调用父类的onTouchEvent方法
}
@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); // 调用父类的onKeyDown方法
}
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
// 处理按键释放事件
switch(keyCode) {
case KeyEvent.KEYCODE_DEL: // 如果是删除键
if (mOnTextViewChangeListener != null) { // 如果设置了文本变化监听器
if (0 == mSelectionStartBeforeDelete && mIndex != 0) { // 如果删除前的选择起始位置为0且索引不为0
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString()); // 调用监听器的删除方法
return true; // 消费事件
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted"); // 日志记录未设置监听器
}
} 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); // 调用父类的onKeyUp方法
}
@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); // 调用父类的onFocusChanged方法
}
@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); // 获取URL样式
if (urls.length == 1) { // 如果有一个URL样式
int defaultResId = 0; // 默认资源ID
for(String schema: sSchemaActionResMap.keySet()) { // 遍历协议动作资源映射
if(urls[0].getURL().indexOf(schema) >= 0) { // 如果URL包含协议
defaultResId = sSchemaActionResMap.get(schema); // 获取对应的资源ID
break;
}
}
if (defaultResId == 0) { // 如果未找到对应的资源ID
defaultResId = R.string.note_link_other; // 使用其他链接的资源ID
}
// 添加菜单项
menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener(
new OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
// 点击菜单项时的处理
urls[0].onClick(NoteEditText.this); // 调用URL样式的点击事件
return true; // 消费事件
}
});
}
}
super.onCreateContextMenu(menu); // 调用父类的onCreateContextMenu方法
}