有一个需求是这样的,页面上有一个输入框,供用户输入手机号码,如果通讯录里面存在这个号码,会自动把名字追加到号码后面。这个需求变态的地方在于,假如用一个EditText+TextView,那么不好控制二者之间的距离,就算是做了各种适配,但是用户可以设置系统的字体,仍然显示很难看!没办法,之好在一个EditText里面来做,让号码是可编辑的,名字是自动追加上的。
MainActivity.java:
- public class MainActivity extends Activity {
- private FrontPartEditableText edittext;
- private CombinedEditTextView edittext2;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- //方法一:很挫,效率很低
- final View rootView = this.findViewById(R.id.rootview);
- edittext = (FrontPartEditableText) this.findViewById(R.id.edittext1);
- edittext.setMaxFrontPartLength(11);
- edittext.setBuildTextContentListener(new FrontPartEditableText.BuildTextContentListener(){
- @Override
- public String buildTextContent(String text) {
- if(text == null){
- return "";
- }
- String arr[] = text.split("\\s+");
- String mobile = arr[0];
- String name = getName(mobile);
- return mobile + (name == null ? "" : " " + name);
- }
- @Override
- public void afterTextChanged(String text) {
- Log.e("test",text);
- }
- });
- // 空白处点击,隐藏软键盘
- rootView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- edittext.hideSoftInput();
- }
- });
- //方法二:推荐
- edittext2 = (CombinedEditTextView) this.findViewById(R.id.edittext2);
- edittext2.setMaxLength(11);
- edittext2.setListener(new CombinedEditTextView.TextContentListener() {
- @Override
- public String getBackTextContent(String frontText) {
- return getName(frontText);
- }
- @Override
- public void afterFrontTextChanged(String text1, String text2) {
- Log.e("test", "text1:"+text1+",text2:"+text2);
- }
- });
- }
- // 业务方法,联系人数据
- private Map<String, String> data;
- private String getName(String mobile) {
- if (data == null) {
- data = contactData();
- }
- return data.get(mobile);
- }
- private Map<String, String> contactData() {
- Map<String, String> data = new HashMap<String, String>();
- data.put("15012341234", "张三");
- data.put("15112341234", "李四");
- data.put("15212341234", "王五");
- return data;
- }
- }
activity_main.xml:
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:id="@+id/rootview">
- <com.example.edittextdemo.FrontPartEditableText
- android:id="@+id/edittext1"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
- <com.example.edittextdemo.CombinedEditTextView
- android:id="@+id/edittext2"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="50dp"/>
- </LinearLayout>
FrontPartEditableText.java:
- public class FrontPartEditableText extends LinearLayout {
- private Button button;
- private int maxFrontPartLength;
- private BackDetectableEditText edittext;
- private BuildTextContentListener listener;
- public FrontPartEditableText(Context context) {
- super(context);
- init();
- }
- public FrontPartEditableText(Context context, AttributeSet attrs) {
- super(context, attrs);
- init();
- }
- public FrontPartEditableText(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- init();
- }
- private void init() {
- final LayoutInflater mLayoutInflater = LayoutInflater.from(getContext());
- View v = mLayoutInflater.inflate(R.layout.front_part_editable_text, null,false);
- addView(v);
- edittext = (BackDetectableEditText) v.findViewById(R.id.part_editable_text);
- // 限定只能输入数字
- edittext.setInputType(EditorInfo.TYPE_CLASS_NUMBER);
- // 可以获取焦点
- button = (Button) v.findViewById(R.id.part_editable_text_dummy);
- button.setFocusable(true);
- button.setFocusableInTouchMode(true);
- // http://stackoverflow.com/questions/3425932/detecting-when-user-has-dismissed-the-soft-keyboard
- // 拦截返回事件,可以去掉的
- edittext.setOnEditTextImeBackListener(new BackDetectableEditText.EditTextImeBackListener() {
- @Override
- public void onImeBack(BackDetectableEditText ctrl, String text) {
- }
- });
- // 一旦获取焦点,设置光标位置
- edittext.setOnFocusChangeListener(new OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- if (hasFocus) {
- String mobile = getFrontPart(edittext.getText().toString());
- setCursorPosition(mobile.length());
- }
- }
- });
- // 返回true,手动处理touch事件,即使edittext获取了焦点,也不会自动弹出软键盘,要手动弹出
- // http://stackoverflow.com/questions/10263384/android-how-to-get-text-position-from-touch-event
- edittext.setOnTouchListener(new View.OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- if (event.getAction() == MotionEvent.ACTION_DOWN) {
- Layout layout = ((EditText) v).getLayout();
- float x = event.getX() + edittext.getScrollX();
- int offset = layout.getOffsetForHorizontal(0, x);
- if (offset >= 0 && offset < maxFrontPartLength) {
- edittext.setSelection(offset);
- } else if (offset >= maxFrontPartLength) {
- edittext.setSelection(maxFrontPartLength);
- }
- showSoftInput();
- }
- return true;
- }
- });
- edittext.addTextChangedListener(new TextWatcher() {
- private String preText;
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count,
- int after) {
- }
- @Override
- public void onTextChanged(CharSequence s, int start, int before,
- int count) {
- }
- @Override
- public void afterTextChanged(Editable s) {
- if(listener == null){
- throw new RuntimeException("BuildTextContentListener can not be empty");
- }
- String nowtext = listener.buildTextContent(s.toString());
- if (nowtext.equals(preText)) {
- return;
- }
- String frontPart = getFrontPart(nowtext);
- if(frontPart.length() < maxFrontPartLength -1){
- return;
- }
- // 计算当前的光标位置
- int offset = calCursorOffset(preText, nowtext);
- // 一定要在setTest之前设置preText,否则会StackOverflow
- preText = nowtext;
- edittext.setText(nowtext);
- // 文字发生变化,重新设置光标,否则会跑到最前面
- setCursorPosition(offset);
- if (frontPart.length() == maxFrontPartLength) {
- hideSoftInput();
- }
- listener.afterTextChanged(nowtext);
- }
- });
- }
- public void setBuildTextContentListener(BuildTextContentListener listener){
- this.listener = listener;
- }
- public interface BuildTextContentListener{
- public String buildTextContent(String text);
- public void afterTextChanged(String text);
- }
- public void setMaxFrontPartLength(int frontPartLength) {
- this.maxFrontPartLength = frontPartLength;
- }
- public void hideSoftInput() {
- edittext.requestFocus();
- Util.hideKeyboard(edittext);
- button.requestFocus();
- }
- public void showSoftInput() {
- edittext.requestFocus();
- Util.showKeyboard(edittext);
- }
- private String getFrontPart(String text) {
- if (text == null || text.length() <= 0) {
- return "";
- }
- String arr[] = text.split("\\s");
- String mobile = arr[0];
- return mobile;
- }
- private void setCursorPosition(int offset) {
- edittext.setSelection(offset);
- }
- private int calCursorOffset(String pre, String now) {
- if (Util.isBlank(pre) && Util.isBlank(now)) {
- return 0;
- } else if (!Util.isBlank(pre) && !Util.isBlank(now)) {
- for (int i = 0; i < pre.length() && i < now.length(); i++) {
- int prechar = pre.charAt(i);
- int nowchar = now.charAt(i);
- if (prechar != nowchar) {
- return i;
- }
- }
- }
- return now.length() > maxFrontPartLength ? maxFrontPartLength : now.length();
- }
- }
front_part_editable_text.xml:
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:id="@+id/part_editable_layout">
- <com.example.edittextdemo.BackDetectableEditText
- android:id="@+id/part_editable_text"
- android:layout_width="600dp"
- android:layout_height="wrap_content"/>
- <Button
- android:id="@+id/part_editable_text_dummy"
- android:layout_width="0dp"
- android:layout_height="0dp"/>
- </LinearLayout>
BackDetectableEditText.java
- public class BackDetectableEditText extends EditText {
- public BackDetectableEditText(Context context) {
- super(context);
- }
- public BackDetectableEditText(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
- public BackDetectableEditText(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
- private EditTextImeBackListener mOnImeBack;
- @Override
- public boolean onKeyPreIme(int keyCode, KeyEvent event) {
- if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
- if (mOnImeBack != null){
- mOnImeBack.onImeBack(this, this.getText().toString());
- return true;
- }
- }
- return super.dispatchKeyEvent(event);
- }
- public void setOnEditTextImeBackListener(EditTextImeBackListener listener) {
- mOnImeBack = listener;
- }
- public interface EditTextImeBackListener {
- public abstract void onImeBack(BackDetectableEditText ctrl, String text);
- }
- }
Util.java:
- public class Util {
- /**
- * 多次调用不会报错
- *
- * */
- public static void showKeyboard(EditText edittext) {
- if(edittext == null){
- return;
- }
- try {
- InputMethodManager imm = (InputMethodManager) edittext.getContext()
- .getSystemService(Context.INPUT_METHOD_SERVICE);
- imm.showSoftInput(edittext, 0);
- } catch (Exception e) {
- Log.e("SoftInput:Showing had a wrong.", e.toString());
- }
- }
- public static void hideKeyboard(EditText edittext) {
- if (edittext == null) {
- return;
- }
- try {
- InputMethodManager imm = ((InputMethodManager) edittext
- .getContext().getSystemService(
- Activity.INPUT_METHOD_SERVICE));
- imm.hideSoftInputFromWindow(edittext.getWindowToken(),
- InputMethodManager.HIDE_NOT_ALWAYS);
- } catch (Exception e) {
- Log.e("SoftInput:Hiding had a wrong.", e.toString());
- }
- }
- public static boolean isBlank(String str) {
- if (str == null || str.length() <= 0) {
- return true;
- }
- return false;
- }
- }
后来又试出来另一种做法。用两个view。
CombinedEditTextView.java
- public class CombinedEditTextView extends LinearLayout {
- private TextView backTextView;
- private EditText frontEdittextView;
- private int frontMaxLength;
- private TextContentListener listener;
- public CombinedEditTextView(Context context) {
- super(context);
- init();
- }
- public CombinedEditTextView(Context context, AttributeSet attrs) {
- super(context, attrs);
- init();
- }
- public CombinedEditTextView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- init();
- }
- private void init() {
- final LayoutInflater mLayoutInflater = LayoutInflater.from(getContext());
- View v = mLayoutInflater.inflate(R.layout.combined_edit_text_view, null,false);
- addView(v);
- frontEdittextView = (EditText) v.findViewById(R.id.combined_edit_text);
- backTextView = (TextView) v.findViewById(R.id.combined_text_view);
- backTextView.setFocusable(true);
- backTextView.setFocusableInTouchMode(true);
- frontEdittextView.addTextChangedListener(new TextWatcher() {
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count,
- int after) {
- }
- @Override
- public void onTextChanged(CharSequence s, int start, int before,
- int count) {
- }
- @Override
- public void afterTextChanged(Editable s) {
- String frontText = s.toString();
- int frontTextLength = frontText.length();
- if(frontTextLength < frontMaxLength - 1){
- return;
- }
- String edittext = frontText.trim();
- if(edittext.length() >= frontMaxLength){
- setBackTextView(edittext);
- hideSoftInput();
- }else{
- clearBackTextView();
- }
- listener.afterFrontTextChanged(edittext, backTextView.getText().toString());
- }
- });
- }
- //http://stackoverflow.com/questions/5044342/how-to-get-cursor-position-x-y-in-edittext-android
- private void setBackTextView(String frontText){
- String backTextContent = listener.getBackTextContent(frontText);
- if(backTextContent != null && backTextContent.length() > 0){
- RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)backTextView.getLayoutParams();
- params.leftMargin = (int)getCursorX() + 20;
- backTextView.setText(backTextContent);
- backTextView.setVisibility(View.VISIBLE);
- }else{
- clearBackTextView();
- }
- }
- private float getCursorX(){
- String frontTextContent = frontEdittextView.getText().toString();
- frontEdittextView.setSelection(frontTextContent.length());
- int pos = frontEdittextView.getSelectionStart();
- Layout layout = frontEdittextView.getLayout();
- float x = layout.getPrimaryHorizontal(pos);
- //int line = layout.getLineForOffset(pos);
- //int baseline = layout.getLineBaseline(line);
- //int ascent = layout.getLineAscent(line);
- //float y = baseline + ascent;
- return x;
- }
- private void clearBackTextView(){
- backTextView.setText("");
- backTextView.setVisibility(View.GONE);
- }
- public int getMaxLength() {
- return frontMaxLength;
- }
- public void setMaxLength(int maxLength) {
- this.frontMaxLength = maxLength;
- }
- public TextContentListener getListener() {
- return listener;
- }
- public void setListener(TextContentListener listener) {
- this.listener = listener;
- }
- public interface TextContentListener{
- public String getBackTextContent(String frontText);
- public void afterFrontTextChanged(String frontText,String backText);
- }
- public void hideSoftInput() {
- frontEdittextView.requestFocus();
- Util.hideKeyboard(frontEdittextView);
- frontEdittextView.clearFocus();
- backTextView.requestFocus();
- }
- }
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
- <EditText
- android:id="@+id/combined_edit_text"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:inputType="number"
- android:singleLine="true"
- android:layout_centerVertical="true"
- android:layout_alignParentLeft="true"/>
- <TextView
- android:id="@+id/combined_text_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerVertical="true"
- android:visibility="gone"/>
- </RelativeLayout>
此外,为了防止在进入页面的时候自动弹出软键盘,可以在manifest的activity元素添加<activity android:windowSoftInputMode="stateAlwaysHidden|adjustPan">
源码:http://download.csdn.net/download/goldenfish1919/6968309
ps:后来做横竖屏装换,切换的时候,页面上的view会被删除然后重新添加上,导致getLayout()出了空指针!
- /**
- * @return the Layout that is currently being used to display the text.
- * This can be null if the text or width has recently changes.
- */
- public final Layout getLayout() {
- return mLayout;
- }
- Paint paint = new Paint();
- int size = res.getDimensionPixelSize(R.dimen.fontsize_18);
- paint.setTextSize(size);
- float textWidth = paint.measureText(mobile);