您的位置:首页 > 移动开发 > Android开发

Android-部分可编辑的EditText

2014-02-20 14:20 302 查看
有一个需求是这样的,页面上有一个输入框,供用户输入手机号码,如果通讯录里面存在这个号码,会自动把名字追加到号码后面。这个需求变态的地方在于,假如用一个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();
	}
}
combined_edit_text_view.xml

<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;
    }
getLayout可能会返回null,因此更靠谱的做法是测量输入文字的长度。

Paint paint = new Paint();
int size = res.getDimensionPixelSize(R.dimen.fontsize_18);
paint.setTextSize(size);
float textWidth = paint.measureText(mobile);
走的各种弯路啊!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: