EditText on demand小部件

我想要一个TextView显示文本,当你单击/长按它时,文本框应该“显示”并允许编辑所述文本。 当你完成编辑时(我猜想是onkey输入)它应该恢复为带有更新文本的textview …

我想知道实现这样的小部件是否可行,还是我应该修复一个变通方法? 非常欢迎提示和建议。

如果您需要进一步了解我的意思,只需转到您的eg(windows)skype配置文件并亲自查看。

编辑:澄清:我特别要求一个小部件或类似的文本视图,直到被点击,然后转换为包含相同文本的edittext; 完成编辑后,它会转换回表示新更改文本的textview。 这就是我所说的“edittext on demand widget”。

但我希望得到更好的东西

public class Widget { TextView text; EditText edit; String textToRepresent; //... } 

你有几个不同的选择。

首先,您必须将onClick或onLongClick注册到要进行交互的TextView。 只需确保用户知道它是可点击的

然后让你的onClick函数启动一个DialogFragment 。 我喜欢创建show函数。 请注意,您可以在此处使用支持库,使您的应用向后兼容。

 private void showDialog() { MyDialogFragment dialog = new MyDialogFragment(); dialog.show(getSupportFragmentManager(), "dialog"); } 

DialogFragment非常简单。 在onCreateView中,您将要向用户显示要显示的视图。 如果您不想自定义,也可以使用简单的AlertDialogBu​​ilder进行包装。

 @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.your_dialog_layout); mTitleEditText = (TextView) view.findViewById(R.id.title); mTitleEditText.setOnClickListener(this); return view; } 

在findViewByIds之后设置onClickListeners。 您必须要处理的最后一件事是将数据恢复到原始TextView中。 您可以通过在Activity中创建一个可以从DialogFragment内部调用的公共方法来完成此操作。 像这样的东西

  @Override public void onClick(View v) { int clickedId = v.getId(); if (clickedId == mDoneButton.getId()) { MyActivity activity = (MyActivity)getActivity(); mTitle = mTitleEditText.getText().toString(); activity.setText(mTitle); dismiss(); } } 

我建议使用DialogFragment,因为它可以很好地处理你的生命周期。 但是,另一种选择是创建一个主题为对话框的新Activity

  

然后你可以startActivityForResult来显示你的对话框,然后在onActivityResult中捕获你的结果

这是我的解决方案。 我只是给你一个基本的。 在EditText前面创建一个TextView ,然后在两个Button OKCancel (你可以像Skype一样更改为ImageButton )。 改变两个视图的可见性。 代码很简单,没有评论。 您可以根据逻辑添加一些空检查。

 public class CompoundTextView extends RelativeLayout implements OnClickListener { private EditText edt; private TextView txt; RelativeLayout layout; public SkypeTextView(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub } @Override protected void onFinishInflate() { super.onFinishInflate(); edt = (EditText) findViewById(R.id.edt); txt = (TextView) findViewById(R.id.txt_name); layout = (RelativeLayout) findViewById(R.id.layout); Button ok = (Button) findViewById(R.id.ok_btn); Button cancel = (Button) findViewById(R.id.cancel_btn); ok.setOnClickListener(this); cancel.setOnClickListener(this); txt.setOnClickListener(this); } public void onClick(View v) { // TODO Auto-generated method stub switch (v.getId()) { case R.id.ok_btn: String editString = edt.getText().toString(); txt.setText(editString); layout.setVisibility(View.INVISIBLE); txt.setVisibility(View.VISIBLE); break; case R.id.cancel_btn: layout.setVisibility(View.INVISIBLE); txt.setVisibility(View.VISIBLE); break; case R.id.txt_name: txt.setVisibility(View.INVISIBLE); layout.setVisibility(View.VISIBLE); break; } } 

}

创建XML skypetextview 。 您可以自定义字体和背景,使其更漂亮。

  

         

将此视图添加(或包含)到您想要的布局。 示例:

 public class TestActivity extends Activity { SkypeTextView test; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LayoutInflater inflate = getLayoutInflater(); test = (SkypeTextView ) inflate.inflate(R.layout.compound_text_view, null); setContentView(test); } 

PS:我忘了。 您应该为textview添加一些underline格式,以便让用户注意到它是可点击的

EditText根据其状态(可编辑或冻结)更改其背景。 设置执行此操作的背景选择器。

使用此选择器xml

      

就像我周四所说的那样…… Yul非常接近但不太接近。 他确实有一个相同的想法,但(理论上)过早地冲进代码;)

下面提供的TextBoxOnDemand代码是生产就绪的。 这个想法类似于我想要在OP中避免的以及Yul建议的内容,但是具有最佳实现(例如使用ViewSwitcher而不是RelativeLayout)

我在以下文章中收集了所需的资源:

从xml创建自定义视图

使用XML声明自定义android UI元素

定义定制的attrs

如何在java和xml中传递自定义组件参数

http://kevindion.com/2011/01/custom-xml-attributes-for-android-widgets/

并决定将它们发布在这里,因为官方的Google“培训”文档是无用的,要么已过时(已弃用),要么未涵盖我需要的内容。 我希望你不介意我声称我自己的赏金,但这是我想要的解决方案(并且期望,这是我的赏金)。 我猜代码必须要做;)

TextBoxOnDemand.java:

  package com.skype.widget; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.text.SpannableString; import android.text.style.UnderlineSpan; import android.text.util.Linkify; import android.util.AttributeSet; import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnFocusChangeListener; import android.view.View.OnHoverListener; import android.view.View.OnLongClickListener; import android.widget.EditText; import android.widget.ImageButton; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; import android.widget.ViewSwitcher; import com.skype.ref.R; import com.skype.ref.RemoteKeys; public class TextBoxOnDemand extends ViewSwitcher implements OnClickListener, OnLongClickListener, OnFocusChangeListener, OnHoverListener, OnEditorActionListener { public static final String LOGTAG = "TextBoxOnDemand"; private View btmGuard; private ImageButton cancel, accept; private EditText editor; private RelativeLayout editorLayout; private TextView face; private String hint = new String(); private boolean inEditMode = false; //normally this is in textview mode private boolean inputReady = false; private String ourData = new String(); private String prefillData = new String(); private String tag = new String(); //usually tag is empty. private View topGuard; private int autoLinkMask;// = Linkify.EMAIL_ADDRESSES; //Linkify.ALL; private ColorStateList textColor, hintColor = null; public TextBoxOnDemand(Context context) { super(context); build(context); setEditable(false); //init } public TextBoxOnDemand(Context context, AttributeSet attrs) { super(context, attrs); build(context); init(context, attrs); setEditable(false); //init } public String getPrefillData() { return prefillData; } public String getTag() { return tag; } public String getText() { Log.d(LOGTAG, "getText() returning '" + ourData + "'"); return ourData; } public boolean hasPrefillData() { return prefillData.isEmpty(); } public boolean isEditable() { Log.d(LOGTAG, "isEditable() returning " + inEditMode); return inEditMode; } @Override public void onClick(View v) { Log.d(LOGTAG, "onClick(" + v + ")"); if (inEditMode) { if (v.equals(accept)) { if (editor.getEditableText().length() == 0 || editor.getEditableText().length() > 5) ourData = editor.getEditableText().toString(); setEditable(false); } else if (v.equals(cancel)) { setEditable(false); } } } @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { // Log.d(LOGTAG, "onEditorAction(" + v + ", " + actionId + ", " + event + ") fired!"); Log.d(LOGTAG, "onEditorAction() fired, inputReady = " + inputReady); if (editor.getEditableText().length() > 0 && editor.getEditableText().length() < (prefillData.length() + 2)) return true; //the user needs to enter something if (inputReady && (event.getKeyCode() == RemoteKeys.ENTER.keycode() || event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) //always is { if (editor.getEditableText().length() > prefillData.length() || editor.getEditableText().length() == 0) ourData = editor.getEditableText().toString(); setEditable(false); return false; } if ((editor.getEditableText().toString().compareToIgnoreCase(ourData) == 0 || editor.getEditableText().toString() .compareToIgnoreCase(prefillData) == 0) && !inputReady) //means we didn't just keep on holding enter return true; else inputReady = true; return true; } @Override public void onFocusChange(View v, boolean hasFocus) { Log.d(LOGTAG, "onFocusChange(" + v + ", " + hasFocus + ")\tinEditMode = " + inEditMode); if (inEditMode) { if (hasFocus && (v.equals(topGuard) || v.equals(btmGuard))) { setEditable(false); requestFocus(); } if (hasFocus && (v.equals(editor) || v.equals(accept) || v.equals(cancel))) { //do nothing, you should be able to browse freely here if (ourData.isEmpty() && editor.getEditableText().length() < prefillData.length()) { Log.d(LOGTAG, "adding prefill, before = " + editor.getEditableText()); editor.setText(""); editor.append(prefillData); Log.d(LOGTAG, "now is = " + editor.getEditableText()); } } } else { String text = (ourData.isEmpty()) ? hint : ourData; ColorStateList color; if (hintColor != null && ourData.isEmpty()) color = hintColor; else color = textColor; face.setTextColor(color); if (hasFocus) { SpannableString ss = new SpannableString(text); ss.setSpan(new UnderlineSpan(), 0, text.length(), 0); face.setText(ss); } else face.setText(text); } } @Override public boolean onHover(View v, MotionEvent event) { // Log.d(LOGTAG, "onHover()"); String text = (ourData.isEmpty()) ? hint : ourData; ColorStateList color; if (hintColor != null && ourData.isEmpty()) color = hintColor; else color = textColor; face.setTextColor(color); switch (event.getAction()) { case MotionEvent.ACTION_HOVER_ENTER: SpannableString ss = new SpannableString(text); ss.setSpan(new UnderlineSpan(), 0, text.length(), 0); face.setText(ss); break; case MotionEvent.ACTION_HOVER_EXIT: face.setText(text); break; } return true; } @Override public boolean onLongClick(View v) { Log.d(LOGTAG, "onLongClick()\tinEditMode = " + inEditMode); if (!inEditMode) //implies that getDisplayedChild() == 0, meaning the textview { setEditable(true); return true; } else return false; } public void setEditable(boolean value) { Log.d(LOGTAG, "setEditable(" + value + ")"); inEditMode = value; if (inEditMode) { //display the editorLayout face.setOnLongClickListener(null); face.setOnHoverListener(null); face.setOnFocusChangeListener(null); //because of GC. face.setOnClickListener(null); face.setVisibility(View.GONE); setDisplayedChild(1); editorLayout.setVisibility(View.VISIBLE); editor.setOnFocusChangeListener(this); editor.setOnEditorActionListener(this); cancel.setOnClickListener(this); accept.setOnClickListener(this); accept.setOnFocusChangeListener(this); cancel.setOnFocusChangeListener(this); } else { editor.setOnFocusChangeListener(null); editor.setOnEditorActionListener(null); cancel.setOnClickListener(null); accept.setOnClickListener(null); accept.setOnFocusChangeListener(null); cancel.setOnFocusChangeListener(null); editorLayout.setVisibility(View.GONE); setDisplayedChild(0); face.setVisibility(View.VISIBLE); face.setOnLongClickListener(this); face.setOnHoverListener(this); face.setOnFocusChangeListener(this); face.setOnClickListener(this); face.setFocusable(true); face.setFocusableInTouchMode(true); } updateViews(); } @Override public void setNextFocusDownId(int nextFocusDownId) { super.setNextFocusDownId(nextFocusDownId); face.setNextFocusDownId(nextFocusDownId); // editor.setNextFocusDownId(nextFocusDownId); accept.setNextFocusDownId(nextFocusDownId); cancel.setNextFocusDownId(nextFocusDownId); } @Override public void setNextFocusForwardId(int nextFocusForwardId) { super.setNextFocusForwardId(nextFocusForwardId); face.setNextFocusForwardId(nextFocusForwardId); editor.setNextFocusForwardId(nextFocusForwardId); } @Override public void setNextFocusLeftId(int nextFocusLeftId) { super.setNextFocusLeftId(nextFocusLeftId); face.setNextFocusLeftId(nextFocusLeftId); editor.setNextFocusLeftId(nextFocusLeftId); } @Override public void setNextFocusRightId(int nextFocusRightId) { super.setNextFocusRightId(nextFocusRightId); face.setNextFocusRightId(nextFocusRightId); cancel.setNextFocusRightId(nextFocusRightId); } @Override public void setNextFocusUpId(int nextFocusUpId) { super.setNextFocusUpId(nextFocusUpId); face.setNextFocusUpId(nextFocusUpId); // editor.setNextFocusUpId(nextFocusUpId); accept.setNextFocusUpId(nextFocusUpId); cancel.setNextFocusUpId(nextFocusUpId); } public void setPrefillData(String prefillData) { this.prefillData = new String(prefillData); } public String setTag() { return tag; } public void setText(String text) { Log.d(LOGTAG, "setText(" + text + ")"); ourData = text; updateViews(); } private void build(Context context) { Log.d(LOGTAG, "build()"); addView(View.inflate(context, R.layout.textboxondemand, null)); setFocusable(true); setFocusableInTouchMode(true); setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); setOnFocusChangeListener(this); setOnLongClickListener(this); face = (TextView) findViewById(R.id.TBOD_textview); editorLayout = (RelativeLayout) findViewById(R.id.TBOD_layout); editor = (EditText) findViewById(R.id.TBOD_edittext); accept = (ImageButton) findViewById(R.id.TBOD_accept); cancel = (ImageButton) findViewById(R.id.TBOD_cancel); topGuard = (View) findViewById(R.id.TBOD_top); btmGuard = (View) findViewById(R.id.TBOD_bottom); face.setFocusable(true); face.setFocusableInTouchMode(true); face.setOnLongClickListener(this); face.setOnHoverListener(this); face.setOnFocusChangeListener(this); face.setOnClickListener(this); editor.setOnFocusChangeListener(this); editor.setOnEditorActionListener(this); editor.setHint(hint); editor.setFocusable(true); editor.setFocusableInTouchMode(true); accept.setOnClickListener(this); accept.setOnFocusChangeListener(this); accept.setFocusable(true); cancel.setFocusable(true); cancel.setOnFocusChangeListener(this); cancel.setOnClickListener(this); topGuard.setFocusable(true); topGuard.setOnFocusChangeListener(this); btmGuard.setFocusable(true); btmGuard.setOnFocusChangeListener(this); editor.setNextFocusRightId(R.id.TBOD_accept); editor.setNextFocusDownId(R.id.TBOD_bottom); editor.setNextFocusUpId(R.id.TBOD_top); accept.setNextFocusLeftId(R.id.TBOD_edittext); accept.setNextFocusRightId(R.id.TBOD_cancel); cancel.setNextFocusLeftId(R.id.TBOD_accept); } private void init(Context context, AttributeSet attrs) { TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TextBoxOnDemand); //Use a Log.d(LOGTAG, "init()"); if (a == null) Log.d(LOGTAG, "Did you include 'xmlns:app=\"http://schemas.android.com/apk/res-auto\"' in your root layout?"); final int N = a.getIndexCount(); for (int i = 0; i < N; ++i) { int attr = a.getIndex(i); switch (attr) { case R.styleable.TextBoxOnDemand_android_hint: hint = new String(a.getString(attr)); editor.setHint(a.getString(attr)); break; case R.styleable.TextBoxOnDemand_android_text: ourData = new String(a.getString(attr)); break; case R.styleable.TextBoxOnDemand_android_inputType: int inputType = a.getInt(attr, -1); if (inputType != -1) editor.setInputType(inputType); break; case R.styleable.TextBoxOnDemand_android_textColor: textColor = a.getColorStateList(attr); face.setTextColor(textColor); break; case R.styleable.TextBoxOnDemand_android_linksClickable: face.setLinksClickable(a.getBoolean(attr, true)); break; case R.styleable.TextBoxOnDemand_android_textColorHint: hintColor = a.getColorStateList(attr); break; case R.styleable.TextBoxOnDemand_android_autoLink: autoLinkMask = a.getInt(attr, 0); face.setAutoLinkMask(autoLinkMask); break; default: Log.d(LOGTAG, "Skipping attribute " + attr); } } //Don't forget this a.recycle(); } private void updateViews() { Log.d(LOGTAG, "updateViews()"); // if (getDisplayedChild() == 0) //first child - textview if (!inEditMode) //first child - textview { if (ourData.isEmpty()) { if (hintColor != null) face.setTextColor(hintColor); face.setText(hint); } else { face.setTextColor(textColor); face.setText(ourData); } face.setFocusable(true); face.setFocusableInTouchMode(true); face.setAutoLinkMask(autoLinkMask); } else { //second child - edittext editor.setFocusable(true); editor.setFocusableInTouchMode(true); if (ourData.startsWith(prefillData) || ourData.length() >= prefillData.length()) editor.setText(""); else editor.setText(prefillData); editor.append(ourData); inputReady = false; editor.requestFocus(); } } public void setAutoLinkMask(LinkifyEnum linkifyEnumConstant) { switch (linkifyEnumConstant) { case ALL: autoLinkMask = Linkify.ALL; break; case EMAIL_ADDRESSES: autoLinkMask = Linkify.EMAIL_ADDRESSES; break; case MAP_ADDRESSES: autoLinkMask = Linkify.MAP_ADDRESSES; break; case PHONE_NUMBERS: autoLinkMask = Linkify.PHONE_NUMBERS; break; case WEB_URLS: autoLinkMask = Linkify.WEB_URLS; break; case NONE: default: autoLinkMask = 0; break; } //set it now face.setAutoLinkMask(autoLinkMask); } public enum LinkifyEnum { ALL, EMAIL_ADDRESSES, MAP_ADDRESSES, PHONE_NUMBERS, WEB_URLS, NONE }; } 

我仍在研究一些与焦点相关的问题,但这是按预期工作的。 当我使用onFocuslistener 1时,你无法从一个TextBox聚焦到另一个TextBox; 当文本框本身是可聚焦的时候,我可以从一个聚焦到另一个就好了,但是我不能通过孩子进行相互聚焦,因此无法专注于要输入的edittext。

XML文件:

            

最后,attrs.xml文件:

              

这是我在我的主xml中使用它的方式(包括所需的命名空间添加后):

   

编辑:我调试了焦点问题。 事实certificate,除非你打电话,否则很难把重点放在孩子身上

 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); 

哪种方法可以解决问题,但仍然无法解决问题。 经过一段时间玩onFocusChange()听众仍然试图获得完美的行为,我扔了毛巾,并添加了两个焦点警卫。 我意识到我无法仅仅在我的容器上跟踪失去焦点(由于它从未接收到焦点)但我不妨跟踪想要离开编辑区域的想法…所以我走了肮脏的路线并添加了两个看不见的条状视图,以打开两者之间的编辑文本。 一旦他们获得了焦点,我就可以隐藏组件并确保它们正确转换。

它就是这样,现在它可以正常工作。 感谢所有参与了的人。

EDIT3:最终抛光版本,我倾销了自定义标签,因为它们根本无法正常工作。 需要学习的经验教训:如果某个东西有一个android标签,请不要打扰它。