/* * 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; 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:" ; private static final String SCHEME_HTTP = "http:" ; private static final String SCHEME_EMAIL = "mailto:" ; //协议模式和资源ID映射,用于创建上下文菜单项 private static final Map sSchemaActionResMap = new HashMap(); static { //将电话、网页、邮箱对应的文本资源ID填充到映射 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; //构造函数初始化编辑框,设置索引值为0 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) { //检查文本内容是否为Spanned类型 if (getText() instanceof Spanned) { int selStart = getSelectionStart();//获取选择文本起始位置 int selEnd = getSelectionEnd();// 结束位置 int min = Math.min(selStart, selEnd); int max = Math.max(selStart, selEnd); //从选择文本中获取URLSpan对象数组 final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class); //如果只有一个URLSpan存在 if (urls.length == 1) { int defaultResId = 0; //遍历所有已定义的模式(schema) for(String schema: sSchemaActionResMap.keySet()) { //如果URLSpan的URL中包含该模式 if(urls[0].getURL().indexOf(schema) >= 0) { //获取该模式对应的资源ID defaultResId = sSchemaActionResMap.get(schema); break; } } //如果没有找到匹配的模式对应的资源ID,则使用R.string.note_link_other作为默认资源ID 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 执行URLSpan中定义的点击操作 urls[0].onClick(NoteEditText.this); return true; } }); } } super.onCreateContextMenu(menu); //调用父类的方法创建上下文菜单 } }