我想使用像TextView
这样的东西来做串行/ TTY /文本终端,同步用户交互。我的意思是我需要以下操作:
Span
s样式)Span
s样式),在虚拟键盘上用换行符/一些“提交”式按钮完成/等等。没有其他部分文本应该是可选择的,可编辑的等等。当然第二个操作在UI线程上没有任何意义,所以我想象的方式,我有一个单独的线程,具有表单的同步代码
// NOT on the UI thread here
textui.addText("What's your name? ",
new StyleSpan(Typeface.BOLD),
new ForegroundColorSpan(Color.RED));
String name = textui.getLine(
new BackgroundColorSpan(Color.GREEN),
new StyleSpan(Typeface.ITALIC));
textui.addText("Hello, ");
textui.addText(name,
new StyleSpan(Typeface.BOLD));
textui.addText("!");
请注意,getLine
应该等待用户输入,用户应该无法编辑What's your name?
部分,并且当用户输入时,他的输入应该是绿色斜体。这是一些模型:
添加第一行后:
用户开始输入
用户提交了他的输入
我尝试过的一件事是使用带有EditText
的{{1}}拒绝用户修改,直到调用InputFilter
并禁止编辑任何以前的输出,但这仍然允许用户移动光标并选择输出,就像输入一样。
编辑添加:由于我的问题“不清楚”,我得到了一个接近的投票,所以让我试着用另一种方式来解释。想象一下,你有一个使用getLine
和System.out.println
的控制台程序,你希望在Android控件中复制那个确切的用户体验(可能被其他Android控件包围),并增加了样式。
答案 0 :(得分:1)
更新:添加了允许/禁止编辑的标志,并指定字段是单行还是多行。已将自定义EditText
移至单独的类文件。固定光标移动问题。
这是一种可能适合您的方法。我知道您正在寻找TTY类型的功能。我不确定同步部分是关于什么的,但是这里有。
当禁止编辑时,方法是使用自定义EditText
来消除用户在字段中移动的能力,并从硬件键盘中检测输入密钥(字段终止)和禁止删除键。这完全由EditText
中定义的自定义TtyEditText.java
完成。
对屏幕键盘的控制工作方式略有不同,并使用TextWatcher
来处理事情。
示例应用程序只需循环显示三个提示,并使用您在问题中提到的Spans
类型显示用户输入屏幕的信息。
这是一个不允许编辑的快速视频。
使用左右箭头键在一个字段中移动有一个怪癖。右箭头键工作正常,但左箭头键似乎想要在字段之间移动而不是在字段内返回。我已明确禁用两者,直到找到解决方案,即使这是一个问题。
根据this page:
用户还可以使用键盘上的箭头键导航您的应用程序(行为与使用D-pad或轨迹球导航时的行为相同)。系统根据屏幕上视图的布局,提供关于哪个视图应该在给定方向上聚焦的最佳猜测。但是,有时系统可能猜错了。
那么,Android猜错了吗?这已通过使完成的字段不可聚焦来解决。代码已更正为处理光标键。
<强> MainActivity.java 强>
public class MainActivity extends AppCompatActivity
implements TextWatcher, TtyEditText.FieldAction {
private String mPrompts[] = new String[]{
"What is your name? ",
"What is your quest? ",
"What is the air-speed velocity of an unladen swallow? "
};
private int mPromptIndex = -1;
private LinearLayout mLinearLayout;
private TtyEditText mCurrentField;
// true-current field can be edited (backspace/selection); false-disallow field editing
private final boolean mAllowEditing = true;
// true input is single line EditText else multi-line
private final boolean mSingleLine = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
final ScrollView scrollView;
super.onCreate(savedInstanceState);
scrollView = createViewGroup(); // Simple LinearLayout within ScrollView
mLinearLayout = (LinearLayout) scrollView.getChildAt(0);
setContentView(scrollView);
// Get the ball rolling with first prompt.
mCurrentField = generatePrompt(mLinearLayout);
}
// Create the next prompt field and place it at the bottom of the layout.
private TtyEditText generatePrompt(LinearLayout layout) {
final TtyEditText editText;
final SpannableString s;
if (++mPromptIndex >= mPrompts.length) {
mPromptIndex = 0;
}
editText = createTtyEditText(mAllowEditing, mPrompts[mPromptIndex].length());
s = styleText(mPrompts[mPromptIndex], new StyleSpan(Typeface.BOLD),
new ForegroundColorSpan(Color.RED));
editText.setText(s);
layout.addView(editText);
editText.setSingleLine(mSingleLine);
if (!mSingleLine) { // multi-line gets a "done" key.
editText.setImeOptions(EditorInfo.IME_ACTION_DONE);
}
editText.addTextChangedListener(MainActivity.this);
editText.requestFocus();
InputMethodManager imm =
(InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT);
return editText;
}
// Echo the user's input to the bottom of the layout.
private void echoResponse(LinearLayout layout, String enteredText) {
final TextView textView = new TextView(this);
final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams
(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
SpannableString prefix;
SpannableString input;
prefix = styleText("You entered: ", new StyleSpan(Typeface.NORMAL),
new ForegroundColorSpan(Color.BLACK));
input = styleText(enteredText, new StyleSpan(Typeface.BOLD), new ForegroundColorSpan(Color.BLACK));
textView.setLayoutParams(params);
textView.setTextSize(DEFAULT_TEXT_SIZE);
textView.setText(TextUtils.concat(prefix, input));
layout.addView(textView);
}
// Invoked from our EditText subclass when the enter key is seen on single line EditText and
// from this Activity on multiline EditText.
@Override
public void onEnter(TtyEditText editText) {
final String s = editText.getText().toString();
final String entry = s.substring(editText.getPromptLength(), s.length());
editText.removeTextChangedListener(this);
editText.setFocusable(false);
editText.setFocusableInTouchMode(false);
editText.setEnabled(false);
echoResponse(mLinearLayout, entry);
mCurrentField = generatePrompt(mLinearLayout);
}
@Override
public void afterTextChanged(Editable s) {
// Do nothing
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
if (mAllowEditing) {
if (start < mPrompts[mPromptIndex].length()) {
mCurrentField.removeTextChangedListener(this); // prevent infinite looping
mCurrentField.setText(s);
mCurrentField.addTextChangedListener(this);
}
} else if (after < count) { // Trying to delete, so don't allow it.
mCurrentField.removeTextChangedListener(this); // prevent infinite looping
mCurrentField.setText(s);
mCurrentField.addTextChangedListener(this);
}
}
// This method only does anything if the input field is multi-line.
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (mSingleLine) {
return;
}
// Multi-line fields get a newline character inserted. Look for the newline, strip it
// and that will complete field entry.
int newlinePos = TextUtils.indexOf(s, "\n");
if (newlinePos != -1) {
mCurrentField.removeTextChangedListener(this); // prevent infinite looping
s = TextUtils.concat(s.subSequence(0, newlinePos),
s.subSequence(newlinePos + 1, s.length()));
mCurrentField.setText(s);
onEnter(mCurrentField);
}
}
// Add text to a TextView (could be an EditText) and apply spans for styling.
private SpannableString styleText(String text, StyleSpan style,
ForegroundColorSpan foregroundColor) {
final SpannableString s = new SpannableString(text);
s.setSpan(style, 0, text.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
s.setSpan(foregroundColor, 0, text.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
s.setSpan(new ForegroundColorSpan(Color.GREEN), s.length(), s.length(),
Spanned.SPAN_INCLUSIVE_INCLUSIVE);
s.setSpan(new StyleSpan(Typeface.ITALIC), s.length(), s.length(),
Spanned.SPAN_INCLUSIVE_INCLUSIVE);
return s;
}
// Create an empty LinearLayout within ScrollView.
private ScrollView createViewGroup() {
final ScrollView scroll = new ScrollView(this);
final LinearLayout linearLayout = new LinearLayout(this);
final FrameLayout.LayoutParams params =
new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT);
final int margins = convertDpToPixels(16f, this);
params.setMargins(margins, margins, margins, margins);
scroll.setLayoutParams(params);
linearLayout.setLayoutParams(new FrameLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
linearLayout.setOrientation(LinearLayout.VERTICAL);
/* Bottom padding is set here due to a very strange interaction between the
EditText, the soft keyboard, and the ScrollView. With zero padding, when
input field abuts the top of the soft keyboard and the user anter a space,
things get a little wacky with how things are handled. User input freezes and
there are bad calls made to this app. The bottom padding fixes it, though.
*/
linearLayout.setPadding(0, 0, 0, convertDpToPixels(16f, this));
scroll.addView(linearLayout);
return scroll;
}
// Create an instance of our subclass of EditText.
private TtyEditText createTtyEditText(final boolean allowEditing, final int promptLength) {
final TtyEditText editText;
final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams
(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
editText = new TtyEditText(this);
editText.setEditParams(allowEditing, promptLength);
editText.setLayoutParams(params);
editText.setTextSize(DEFAULT_TEXT_SIZE);
editText.setBackgroundResource(android.R.color.transparent);
editText.setInputType(InputType.TYPE_CLASS_TEXT
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
| InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
editText.setPadding(0, 0, 0, 0);
editText.setFocusable(true);
editText.setFocusableInTouchMode(true);
return editText;
}
private static int convertDpToPixels(float dp, Context context) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
context.getResources().getDisplayMetrics());
}
private static final float DEFAULT_TEXT_SIZE = 18f;
}
<强> TtyEditText.java 强>
public class TtyEditText extends AppCompatEditText {
boolean mAllowEditing = true;
int mPromptLength = 0;
// Interface to activity for the enter key. This can be expanded to accommodate other keys.
FieldAction mFieldAction;
public TtyEditText(Context context) {
this(context, null);
}
public TtyEditText(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TtyEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mFieldAction = (FieldAction) context;
}
// Handle the "enter" and "delete" keys from hardware keyboards.
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER:
mFieldAction.onEnter(this);
return true;
}
super.onKeyDown(keyCode, event);
return false;
}
// Don't let the user move around in the field. If they try, put them back to the end.
@Override
protected void onSelectionChanged(int selStart, int selEnd) {
//on selection move cursor to end of text
if (!mAllowEditing) { // No backspace/no cursor movement except to add to end
setSelection(this.length());
} else if (selStart < mPromptLength && this.getText().length() > 0) { // can't move into prompt area
setSelection(mPromptLength);
} else {
super.onSelectionChanged(selStart, selEnd);
}
}
public void setEditParams(boolean allowEditing, int promptLength) {
mAllowEditing = allowEditing;
mPromptLength = promptLength;
}
public int getPromptLength() {
return mPromptLength;
}
interface FieldAction {
public void onEnter(TtyEditText ttyEditText);
}
}