Android使用代碼實(shí)現(xiàn)一個(gè)選詞(拖拽)填空題

學(xué)習(xí)一些基礎(chǔ)知識(shí)
public class DragActivity extends BaseActivity implements View.OnDragListener {@Bind(R.id.tv_tip)TextView tvTip;@Bind(R.id.rl_container)RelativeLayout rlContainer;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_drag);ButterKnife.bind(this);// 目標(biāo)區(qū)域設(shè)置拖拽事件監(jiān)聽rlContainer.setOnDragListener(this);}@OnTouch(R.id.iv_icon)public boolean onTouch(View v) {ClipData.Item item = new ClipData.Item("我來了");ClipData data = new ClipData(null, new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}, item);v.startDrag(data, new View.DragShadowBuilder(v), null, 0);return true;}@Overridepublic boolean onDrag(View v, DragEvent event) {final int action = event.getAction();switch (action) {case DragEvent.ACTION_DRAG_STARTED: // 拖拽開始Log.i("拖拽事件", "拖拽開始");return event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);case DragEvent.ACTION_DRAG_ENTERED: // 被拖拽View進(jìn)入目標(biāo)區(qū)域Log.i("拖拽事件", "被拖拽View進(jìn)入目標(biāo)區(qū)域");return true;case DragEvent.ACTION_DRAG_LOCATION: // 被拖拽View在目標(biāo)區(qū)域移動(dòng)Log.i("拖拽事件", "被拖拽View在目標(biāo)區(qū)域移動(dòng)___X:" + event.getX() + "___Y:" + event.getY());tvTip.setText("X:" + event.getX() + " Y:" + event.getY());return true;case DragEvent.ACTION_DRAG_EXITED: // 被拖拽View離開目標(biāo)區(qū)域Log.i("拖拽事件", "被拖拽View離開目標(biāo)區(qū)域");return true;case DragEvent.ACTION_DROP: // 放開被拖拽ViewLog.i("拖拽事件", "放開被拖拽View");// 釋放拖放陰影,并獲取移動(dòng)數(shù)據(jù)ClipData.Item item = event.getClipData().getItemAt(0);String content = item.getText().toString();Toast.makeText(this, content, Toast.LENGTH_SHORT).show();return true;case DragEvent.ACTION_DRAG_ENDED: // 拖拽完成Log.i("拖拽事件", "拖拽完成");return true;default:break;}return false;}}
看下效果:


實(shí)現(xiàn)
首先初始化一些數(shù)據(jù)
public class DragFillBlankView extends RelativeLayout implements View.OnDragListener,View.OnLongClickListener {private TextView tvContent;private LinearLayout llOption;// 初始數(shù)據(jù)private String originContent;// 初始答案范圍集合private List<AnswerRange> originAnswerRangeList;// 填空題內(nèi)容private SpannableStringBuilder content;// 選項(xiàng)列表private List<String> optionList;// 答案范圍集合private List<AnswerRange> answerRangeList;// 答案集合private List<String> answerList;// 選項(xiàng)位置private int optionPosition;// 一次拖拽填空是否完成private boolean isFillBlank;public DragFillBlankView(Context context) {this(context, null);}public DragFillBlankView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public DragFillBlankView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initView();}private void initView() {LayoutInflater inflater = LayoutInflater.from(getContext());inflater.inflate(R.layout.layout_drag_fill_blank, this);tvContent = (TextView) findViewById(R.id.tv_content);llOption = (LinearLayout) findViewById(R.id.ll_option);}...}
定義一個(gè)設(shè)置數(shù)據(jù)的方法,供外部調(diào)用
/*** 設(shè)置數(shù)據(jù)** @param originContent 源數(shù)據(jù)* @param optionList 選項(xiàng)列表* @param answerRangeList 答案范圍集合*/public void setData(String originContent, List<String> optionList, List<AnswerRange> answerRangeList) {if (TextUtils.isEmpty(originContent) || optionList == null || optionList.isEmpty()|| answerRangeList == null || answerRangeList.isEmpty()) {return;}// 初始數(shù)據(jù)this.originContent = originContent;// 初始答案范圍集合this.originAnswerRangeList = new ArrayList<>();this.originAnswerRangeList.addAll(answerRangeList);// 獲取課文內(nèi)容this.content = new SpannableStringBuilder(originContent);// 選項(xiàng)列表this.optionList = optionList;// 答案范圍集合this.answerRangeList = answerRangeList;// 避免重復(fù)創(chuàng)建拖拽選項(xiàng)if (llOption.getChildCount() < 1) {// 拖拽選項(xiàng)列表List<Button> itemList = new ArrayList<>();for (String option : optionList) {Button btnAnswer = new Button(getContext());LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);params.setMargins(0, 0, dp2px(10), 0);btnAnswer.setLayoutParams(params);btnAnswer.setBackgroundColor(Color.parseColor("#4DB6AC"));btnAnswer.setTextColor(Color.WHITE);btnAnswer.setText(option);btnAnswer.setOnLongClickListener(this);itemList.add(btnAnswer);}// 顯示拖拽選項(xiàng)for (int i = 0; i < itemList.size(); i++) {llOption.addView(itemList.get(i));}} else {// 不顯示已經(jīng)填空的選項(xiàng)for (int i = 0; i < llOption.getChildCount(); i++) {Button button = (Button) llOption.getChildAt(i);String option = button.getText().toString();if (!answerList.isEmpty() && answerList.contains(option)) {button.setVisibility(INVISIBLE);} else {button.setVisibility(VISIBLE);}}}// 設(shè)置下劃線顏色for (AnswerRange range : this.answerRangeList) {ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.parseColor("#4DB6AC"));content.setSpan(colorSpan, range.start, range.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}// 答案集合answerList = new ArrayList<>();for (int i = 0; i < answerRangeList.size(); i++) {answerList.add("");}// 設(shè)置填空處點(diǎn)擊事件for (int i = 0; i < this.answerRangeList.size(); i++) {AnswerRange range = this.answerRangeList.get(i);BlankClickableSpan blankClickableSpan = new BlankClickableSpan(i);content.setSpan(blankClickableSpan, range.start, range.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}// 填空處設(shè)置觸摸事件tvContent.setMovementMethod(new TouchLinkMovementMethod());tvContent.setText(content);tvContent.setOnDragListener(this);}
public class TouchLinkMovementMethod extends LinkMovementMethod {@Overridepublic boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {int action = event.getAction();if (action == MotionEvent.ACTION_DOWN) {int x = (int) event.getX();int y = (int) event.getY();x -= widget.getTotalPaddingLeft();y -= widget.getTotalPaddingTop();x += widget.getScrollX();y += widget.getScrollY();Layout layout = widget.getLayout();int line = layout.getLineForVertical(y);int off = layout.getOffsetForHorizontal(line, x);ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);if (link.length != 0) {link[0].onClick(widget);return true;} else {Selection.removeSelection(buffer);}}return super.onTouchEvent(widget, buffer, event);}}
拖拽開始
@Overridepublic boolean onLongClick(View v) {startDrag(v);return true;}/*** 開始拖拽** @param v 當(dāng)前對(duì)象*/private void startDrag(View v) {// 選項(xiàng)內(nèi)容String optionContent = ((Button) v).getText().toString();// 記錄當(dāng)前答案選項(xiàng)的位置optionPosition = getOptionPosition(optionContent);// 開始拖拽后在列表中隱藏答案選項(xiàng)v.setVisibility(INVISIBLE);ClipData.Item item = new ClipData.Item(optionContent);ClipData data = new ClipData(null, new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}, item);v.startDrag(data, new DragShadowBuilder(v), null, 0);}/*** 獲取選項(xiàng)位置** @param option 選項(xiàng)內(nèi)容* @return 選項(xiàng)位置*/private int getOptionPosition(String option) {for (int i = 0; i < llOption.getChildCount(); i++) {Button btnOption = (Button) llOption.getChildAt(i);if (btnOption.getText().toString().equals(option)) {return i;}}return 0;}
@Overridepublic boolean onDrag(View v, DragEvent event) {final int action = event.getAction();switch (action) {case DragEvent.ACTION_DRAG_STARTED: // 拖拽開始return event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);case DragEvent.ACTION_DRAG_ENTERED: // 被拖拽View進(jìn)入目標(biāo)區(qū)域return true;case DragEvent.ACTION_DRAG_LOCATION: // 被拖拽View在目標(biāo)區(qū)域移動(dòng)return true;case DragEvent.ACTION_DRAG_EXITED: // 被拖拽View離開目標(biāo)區(qū)域return true;case DragEvent.ACTION_DROP: // 放開被拖拽Viewint position = 0;// 獲取TextView的Layout對(duì)象Layout layout = tvContent.getLayout();// 當(dāng)前x、y坐標(biāo)float currentX = event.getX();float currentY = event.getY();// 如果拖拽答案沒有進(jìn)行填空則returnboolean isContinue = false;for (int i = 0; i < answerRangeList.size(); i++) {AnswerRange range = answerRangeList.get(i);// 獲取TextView中字符坐標(biāo)Rect bound = new Rect();int line = layout.getLineForOffset(range.start);layout.getLineBounds(line, bound);// 字符頂部y坐標(biāo)int yAxisTop = bound.top - dp2px(10);// 字符底部y坐標(biāo)int yAxisBottom = bound.bottom + dp2px(5);// 字符左邊x坐標(biāo)float xAxisLeft = layout.getPrimaryHorizontal(range.start) - dp2px(10);// 字符右邊x坐標(biāo)float xAxisRight = layout.getSecondaryHorizontal(range.end) + dp2px(10);if (xAxisRight > xAxisLeft) { // 填空在一行if (currentX > xAxisLeft && currentX < xAxisRight &¤tY < yAxisBottom && currentY > yAxisTop) {position = i;isContinue = true;break;}} else { // 跨行填空if ((currentX > xAxisLeft || currentX < xAxisRight) &¤tY < yAxisBottom && currentY > yAxisTop) {position = i;isContinue = true;break;}}}if (!isContinue) {return true;}// 釋放拖放陰影,并獲取移動(dòng)數(shù)據(jù)ClipData.Item item = event.getClipData().getItemAt(0);String answer = item.getText().toString();// 重復(fù)拖拽,在答案列表中顯示原答案String oldAnswer = answerList.get(position);if (!TextUtils.isEmpty(oldAnswer)) {llOption.getChildAt(getOptionPosition(oldAnswer)).setVisibility(VISIBLE);}// 填寫答案fillAnswer(answer, position);isFillBlank = true;return true;case DragEvent.ACTION_DRAG_ENDED: // 拖拽完成if (!isFillBlank) {llOption.getChildAt(optionPosition).setVisibility(VISIBLE);} else {isFillBlank = false;}return true;default:break;}return false;}
/*** 填寫答案** @param answer 當(dāng)前填空處答案* @param position 填空位置*/private void fillAnswer(String answer, int position) {answer = " " + answer + " ";// 替換答案AnswerRange range = answerRangeList.get(position);content.replace(range.start, range.end, answer);// 更新當(dāng)前的答案范圍AnswerRange currentRange = new AnswerRange(range.start, range.start + answer.length());answerRangeList.set(position, currentRange);// 答案設(shè)置下劃線content.setSpan(new UnderlineSpan(),currentRange.start, currentRange.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);// 將答案添加到集合中answerList.set(position, answer.replace(" ", ""));// 更新內(nèi)容tvContent.setText(content);for (int i = 0; i < answerRangeList.size(); i++) {if (i > position) {// 獲取下一個(gè)答案原來的范圍AnswerRange oldNextRange = answerRangeList.get(i);int oldNextAmount = oldNextRange.end - oldNextRange.start;// 計(jì)算新舊答案字?jǐn)?shù)的差值int difference = currentRange.end - range.end;// 更新下一個(gè)答案的范圍AnswerRange nextRange = new AnswerRange(oldNextRange.start + difference,oldNextRange.start + difference + oldNextAmount);answerRangeList.set(i, nextRange);}}}

/*** 觸摸事件*/class BlankClickableSpan extends ClickableSpan {private int position;public BlankClickableSpan(int position) {this.position = position;}@Overridepublic void onClick(final View widget) {// 顯示原有答案String oldAnswer = answerList.get(position);if (!TextUtils.isEmpty(oldAnswer)) {answerList.set(position, "");updateAnswer(answerList);startDrag(llOption.getChildAt(getOptionPosition(oldAnswer)));}}@Overridepublic void updateDrawState(TextPaint ds) {// 不顯示下劃線ds.setUnderlineText(false);}}/*** 更新答案** @param answerList 答案列表*/public void updateAnswer(List<String> answerList) {// 重新初始化數(shù)據(jù)setData(originContent, optionList, originAnswerRangeList);// 重新填寫已經(jīng)存在的答案if (answerList != null && !answerList.isEmpty()) {for (int i = 0; i < answerList.size(); i++) {String answer = answerList.get(i);if (!TextUtils.isEmpty(answer)) {fillAnswer(answer, i);}}}}
最后看下如何設(shè)置數(shù)據(jù)
public class MainActivity extends AppCompatActivity {@BindView(R.id.dfbv_content)DragFillBlankView dfbvContent;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ButterKnife.bind(this);initData();}private void initData() {String content = "紛紛揚(yáng)揚(yáng)的________下了半尺多厚。天地間________的一片。我順著________工地走了四十多公里," +"只聽見各種機(jī)器的吼聲,可是看不見人影,也看不見工點(diǎn)。一進(jìn)靈官峽,我就心里發(fā)慌。";// 選項(xiàng)集合List<String> optionList = new ArrayList<>();optionList.add("白茫茫");optionList.add("霧蒙蒙");optionList.add("鐵路");optionList.add("公路");optionList.add("大雪");// 答案范圍集合List<AnswerRange> rangeList = new ArrayList<>();rangeList.add(new AnswerRange(5, 13));rangeList.add(new AnswerRange(23, 31));rangeList.add(new AnswerRange(38, 46));dfbvContent.setData(content, optionList, rangeList);}}
源碼地址:
https://github.com/alidili/Demos/tree/master/DragFillBlankQuestionDemo
評(píng)論
圖片
表情
