我表示我真的只有几个月安卓经验,这两天搞TextView和EditText的问题搞得头大,研究了一下他们的实现
TextView
TextView在安卓UI中主要用来显示纯文本,他继承自View,实现了ViewTreeObserver.OnPreDrawListen接口
public class TextView extends View implements ViewTreeObserver . OnPreDrawListener {}
触发触摸事件
简单的问题就不说了,故事从手指触碰到TextView开始
手指的触摸事件触发了ViewTreeObserver的OnTouchModeChangeListener
从No Touch变成了Touch
OnTouchModeChangeListener可以添加并实现新的接口来触发OnTouchEvent函数,用于拦截并实现自定义事件
比如可以实现触发其他事件监听器
可选中/可编辑的接口
这里首先有几个比较重要的方法
/**
* Subclasses override this to specify that they have a KeyListener
* by default even if not specifically called for in the XML options.
*/
protected boolean getDefaultEditable ();
/**
* Subclasses override this to specify a default movement method.
*/
protected MovementMethod getDefaultMovementMethod ();
/**
* Return the text the TextView is displaying as an Editable object. If
* the text is not editable, null is returned.
*
* @see #getText
*/
public Editable getEditableText ();
/**
*
* Returns the state of the {@code textIsSelectable} flag (See
* {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
* to allow users to select and copy text in a non-editable TextView, the content of an
* {@link EditText} can always be selected, independently of the value of this flag.
* <p>
*
* @return True if the text displayed in this TextView can be selected by the user.
*
* @attr ref android.R.styleable#TextView_textIsSelectable
*/
public boolean isTextSelectable ();
这几个属性决定了这个View能不能被点中,或者被编辑
内部类SelectionCursorController与CursorHandle
https://github.com/zhouray/SelectableTextView/blob/master/src/com/zyz/mobile/example/SelectableTextView.java
SelectionCursorController是一个内部类,实现了OnTouchModeChange接口,用于响应TouchMode改变的事件
CursorHandle是一个View,用于实现被选中文字两边显示的箭头,但作者写的是有问题的,箭头会飘
这里作者的控件并没有Override父类的isTextSelectable和getDefaultEditable方法,所以不会把上线文菜单托管给系统
这里把事件的处理交给了内部类,从而对可选区域进行选中操作
而选择的操作则是由SelectionInfo来完成的,这是一个集合类,用于操作Selection
EditText
实现
EditText重载的方法非常少,主要是setSelection之类的方法,还有就是上面提到的isTextSelectable和getDefaultEditable方法
多行文本的实现
EditText的多行文本主要由这几个参数决定
inputType
maxLines
singleLine
如果把singleLine设置成false会把所有的相关的参数都重置,参考谷歌的文档
setSingleLine(boolean singleLine)
If true, sets the properties of this field (number of lines, horizontally scrolling, transformation method) to be for a single-line input; if false, restores these to the default conditions.
所以在写自定义控件的时候必须把setSingleLine写在maxLines之前,否则该属性会被重置
...
valueEditText . setSingleLine ( isSingleLine );
valueEditText . setMaxLines ( array . getInt ( R . styleable . WhosvWidget_android_maxLines , 1 ));
valueEditText . setInputType ( array . getInt ( R . styleable . WhosvWidget_android_inputType , InputType . TYPE_CLASS_TEXT ));
...
并且,只用maxLines是无法控制最大行数的,它只能够确定显示的最大行数
所以必须要自己添加逻辑,这里有两个选择,一个是重载OnKeyListener方法,在响应键盘事件的时候做拦截
另一种,可以重载OnTextChange方法,在afterTextChange中写逻辑
下面提供第一种方法的实现
valueEditText . setOnKeyListener ( new View . OnKeyListener (){
@Override
public boolean onKey ( View v , int keyCode , KeyEvent event ) {
int MaxLines = (( EditText ) v ). getMaxLines ();
if (! isSingleLine && MaxLines > 1 && keyCode == KeyEvent . KEYCODE_ENTER && event . getAction () == KeyEvent . ACTION_UP ) {
// 获取光标所在位置
int index = (( EditText ) v ). getSelectionStart ();
// 获取EditText文本内容
String text = (( EditText ) v ). getText (). toString ();
// 获取行数
if ( text . substring ( index - 1 , index ). equals ( "\n" ))
index = index - 1 ;
String singleEnterText = text . replaceAll ( "[\\n]+" , "\n" );
(( EditText ) v ). setText ( singleEnterText );
int editTextRowCount = (( EditText ) v ). getLineCount ();
if ( index > singleEnterText . length ())
index = singleEnterText . length ();
(( EditText ) v ). setSelection ( index );
Timber . d ( "text:" + text + ",rows:" + editTextRowCount + ",maxLines:" + MaxLines );
if ( editTextRowCount > MaxLines ) {
int lastBreakIndex = text . lastIndexOf ( "\n" );
String newText = text . substring ( 0 , lastBreakIndex );
(( EditText ) v ). setText ( "" );
(( EditText ) v ). append ( newText );
}
}
return false ;
}
});
ActionMode的用法
当然在复制的时候,普遍有两种方法,一种是以Context Menu的方式,另一种是ActionMode
ActionMode的方法比较简单,setActionMode(ActionMode.Callback callback)
private ActionMode . Callback mActionModeCallback = new ActionMode . Callback () {
// Called when the action mode is created; startActionMode() was called
@Override
public boolean onCreateActionMode ( ActionMode mode , Menu menu ) {
// Inflate a menu resource providing context menu items
MenuInflater inflater = mode . getMenuInflater ();
inflater . inflate ( R . menu . context_menu , menu );
return true ;
}
// Called each time the action mode is shown. Always called after onCreateActionMode, but
// may be called multiple times if the mode is invalidated.
@Override
public boolean onPrepareActionMode ( ActionMode mode , Menu menu ) {
return false ; // Return false if nothing is done
}
// Called when the user selects a contextual menu item
@Override
public boolean onActionItemClicked ( ActionMode mode , MenuItem item ) {
switch ( item . getItemId ()) {
case R . id . menu_share :
shareCurrentItem ();
mode . finish (); // Action picked, so close the CAB
return true ;
default :
return false ;
}
}
// Called when the user exits the action mode
@Override
public void onDestroyActionMode ( ActionMode mode ) {
mActionMode = null ;
}
};
someView . setOnLongClickListener ( new View . OnLongClickListener () {
// Called when the user long-clicks on someView
public boolean onLongClick ( View view ) {
if ( mActionMode != null ) {
return false ;
}
// Start the CAB using the ActionMode.Callback defined above
mActionMode = getActivity (). startActionMode ( mActionModeCallback );
view . setSelected ( true );
return true ;
}
});
其他参考
Android自由选择TextView的文字
这个文章写的比较简单,但是的确可以实现,网上其他的大部分文章写的都做不到