1. Write before
Recently, the project is busy. I haven't updated my blog for more than a month. Use my spare time to summarize the problems encountered in the project and share them with you!
When I first saw the need to fill in the blanks, the first response was to go to Baidu, ah... No, Google search for similar Demo, but all of them were Android interview questions. Alas, let's do it honestly, first see the effect:
2. Learn some basics
Let's start by learning how to locally set colors and click events for TextView, using an important class, SpannableString.
Talk is cheap. Show me the code.
public class SpannableStringActivity extends BaseActivity { @Bind(R.id.tv_content) TextView tvContent; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_spannable_string); ButterKnife.bind(this); initData(); } private void initData() { String originContent = "You see I can not only change color, but also click."; SpannableString content = new SpannableString(originContent); // Set Colors ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.parseColor("#4DB6AC")); content.setSpan(colorSpan, 7, 9, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // Set Click Events MyClickableSpan myClickableSpan = new MyClickableSpan(); content.setSpan(myClickableSpan, 12, 14, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // Click events will not take effect until this method is set tvContent.setMovementMethod(LinkMovementMethod.getInstance()); tvContent.setText(content); } class MyClickableSpan extends ClickableSpan { @Override public void onClick(View widget) { Toast.makeText(SpannableStringActivity.this, "I was clicked on", Toast.LENGTH_SHORT).show(); } } }
See the effect:
Simply put, first convert the content you want to display into a SpannableString object, then set the color through ForegroundColorSpace, ClickableSpan set the click event, SpannableString applies the color and click event to the content you display by calling the setSpan method. The setSpan method needs to pass in the starting coordinates from which the formatting takes effect (for example, the starting coordinates of the color are 7, 8, respectively).Then pass in 7,8+1), and finally notice the flag Spanned.SPAN_EXCLUSIVE_EXCLUSIVE, which has four flags to choose from:
Spanned.SPAN_INCLUSIVE_INCLUSIVE: both before and after include
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE: Neither before nor after
Spanned.SPAN_INCLUSIVE_EXCLUSIVE: Included before, excluded after
Spanned.SPAN_EXCLUSIVE_INCLUSIVE: not included before, but included after
This flag is used to identify the text within the Span range and whether it is also used when entering new characters back and forth.Masked face, what, what did you say?Let's look at the picture:
We set flag to Spanned.SPAN_INCLUSIVE_EXCLUSIVE (both before and after).
Is it much clearer, if you still can't read the picture, walk slowly without sending it!?
3. Implementation
Initialize some data first
public class FillBlankView extends RelativeLayout { private TextView tvContent; private Context context; // Answer Set private List<String> answerList; // Answer Range Collection private List<AnswerRange> rangeList; // Fill in the blanks private SpannableStringBuilder content; public FillBlankView(Context context) { this(context, null); } public FillBlankView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public FillBlankView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.context = context; initView(); } private void initView() { LayoutInflater inflater = LayoutInflater.from(context); inflater.inflate(R.layout.layout_fill_blank, this); tvContent = (TextView) findViewById(R.id.tv_content); } ... }
Define a method to set data for external calls
/** * Set up data * * @param originContent source data * @param answerRangeList Answer Range Collection */ public void setData(String originContent, List<AnswerRange> answerRangeList) { if (TextUtils.isEmpty(originContent) || answerRangeList == null || answerRangeList.isEmpty()) { return; } // Get Text Content content = new SpannableStringBuilder(originContent); // Answer Range Collection rangeList = answerRangeList; // Set underline color for (AnswerRange range : rangeList) { ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.parseColor("#4DB6AC")); content.setSpan(colorSpan, range.start, range.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } // Answer Set answerList = new ArrayList<>(); for (int i = 0; i < rangeList.size(); i++) { answerList.add(""); } // Set Fill-in Click Event for (int i = 0; i < rangeList.size(); i++) { AnswerRange range = rangeList.get(i); BlankClickableSpan blankClickableSpan = new BlankClickableSpan(i); content.setSpan(blankClickableSpan, range.start, range.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } // Click events will not take effect until this method is set tvContent.setMovementMethod(LinkMovementMethod.getInstance()); tvContent.setText(content); }
There are full comments in the code, mainly setting the color of the filling space and clicking events.
Click Event
/** * Click Event */ class BlankClickableSpan extends ClickableSpan { private int position; public BlankClickableSpan(int position) { this.position = position; } @Override public void onClick(final View widget) { View view = LayoutInflater.from(context).inflate(R.layout.layout_input, null); final EditText etInput = (EditText) view.findViewById(R.id.et_answer); Button btnFillBlank = (Button) view.findViewById(R.id.btn_fill_blank); // Show original answers String oldAnswer = answerList.get(position); if (!TextUtils.isEmpty(oldAnswer)) { etInput.setText(oldAnswer); etInput.setSelection(oldAnswer.length()); } final PopupWindow popupWindow = new PopupWindow(view, LayoutParams.MATCH_PARENT, dp2px(40)); // Get Focus popupWindow.setFocusable(true); // To prevent the pop-up menu from gaining focus, the other components that click on the Activity do not respond popupWindow.setBackgroundDrawable(new PaintDrawable()); // Set PopupWindow on top of the soft keyboard popupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); // PopupWindow Popup popupWindow.showAtLocation(tvContent, Gravity.BOTTOM, 0, 0); btnFillBlank.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // Fill in the answer String answer = etInput.getText().toString(); fillAnswer(answer, position); popupWindow.dismiss(); } }); // Show Soft Keyboard InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); inputMethodManager.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS); } @Override public void updateDrawState(TextPaint ds) { // Do not show underline ds.setUnderlineText(false); } }
Click on the fill-in to pop up a PopupWindow input box, type the answer, click OK, and call the fillAnswer method to set the answer to the fill-in.
Fill in the answer
High energy ahead, slow down!
/** * Fill in the answer * * @param answer Current Fill-in Answer * @param position Fill-in position */ private void fillAnswer(String answer, int position) { answer = " " + answer + " "; // Replace Answer AnswerRange range = rangeList.get(position); content.replace(range.start, range.end, answer); // Update the current answer range AnswerRange currentRange = new AnswerRange(range.start, range.start + answer.length()); rangeList.set(position, currentRange); // Underline Answer Settings content.setSpan(new UnderlineSpan(), currentRange.start, currentRange.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // Add the answer to the set answerList.set(position, answer.replace(" ", "")); // Update Content tvContent.setText(content); for (int i = 0; i < rangeList.size(); i++) { if (i > position) { // Get the original range for the next answer AnswerRange oldNextRange = rangeList.get(i); int oldNextAmount = oldNextRange.end - oldNextRange.start; // Calculate the difference between old and new answer words int difference = currentRange.end - range.end; // Update the scope of the next answer AnswerRange nextRange = new AnswerRange(oldNextRange.start + difference, oldNextRange.start + difference + oldNextAmount); rangeList.set(i, nextRange); } } }
First replace the underline in the blank or the old answer with the new answer, then update the current answer range. Since the underline has been replaced by the answer, you need to underline the answer. Finally, update the answer to the set, and this filling is complete.But, when the answer range in one blank changes, the answer range in all subsequent blanks changes, so you need to update the answer range in the subsequent blanks.First get the original range of the next answer, calculate the distance you need to move forward or backward, and then update the range.
4. Write at the end
The source code is hosted on GitHub. Welcome Fork. Start now if you think it's good.
Welcome to your classmates'comments. If you think this blog is useful for you, leave a message or click on your favorite blog.
Tomorrow is National Day. Happy National Day to all of you!
In the next article, we will learn how to achieve a drag-and-drop fill-in-the-blank topic (word filling-in-the-blank). Please look forward to it!