Android使用代碼實現一個選詞(拖拽)填空題

學習一些基礎知識
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);// 目標區(qū)域設置拖拽事件監(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進入目標區(qū)域Log.i("拖拽事件", "被拖拽View進入目標區(qū)域");return true;case DragEvent.ACTION_DRAG_LOCATION: // 被拖拽View在目標區(qū)域移動Log.i("拖拽事件", "被拖拽View在目標區(qū)域移動___X:" + event.getX() + "___Y:" + event.getY());tvTip.setText("X:" + event.getX() + " Y:" + event.getY());return true;case DragEvent.ACTION_DRAG_EXITED: // 被拖拽View離開目標區(qū)域Log.i("拖拽事件", "被拖拽View離開目標區(qū)域");return true;case DragEvent.ACTION_DROP: // 放開被拖拽ViewLog.i("拖拽事件", "放開被拖拽View");// 釋放拖放陰影,并獲取移動數據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;}}
看下效果:


實現
首先初始化一些數據
public class DragFillBlankView extends RelativeLayout implements View.OnDragListener,View.OnLongClickListener {private TextView tvContent;private LinearLayout llOption;// 初始數據private String originContent;// 初始答案范圍集合private List<AnswerRange> originAnswerRangeList;// 填空題內容private SpannableStringBuilder content;// 選項列表private List<String> optionList;// 答案范圍集合private List<AnswerRange> answerRangeList;// 答案集合private List<String> answerList;// 選項位置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);}...}
定義一個設置數據的方法,供外部調用
/*** 設置數據** @param originContent 源數據* @param optionList 選項列表* @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;}// 初始數據this.originContent = originContent;// 初始答案范圍集合this.originAnswerRangeList = new ArrayList<>();this.originAnswerRangeList.addAll(answerRangeList);// 獲取課文內容this.content = new SpannableStringBuilder(originContent);// 選項列表this.optionList = optionList;// 答案范圍集合this.answerRangeList = answerRangeList;// 避免重復創(chuàng)建拖拽選項if (llOption.getChildCount() < 1) {// 拖拽選項列表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);}// 顯示拖拽選項for (int i = 0; i < itemList.size(); i++) {llOption.addView(itemList.get(i));}} else {// 不顯示已經填空的選項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);}}}// 設置下劃線顏色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("");}// 設置填空處點擊事件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);}// 填空處設置觸摸事件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 當前對象*/private void startDrag(View v) {// 選項內容String optionContent = ((Button) v).getText().toString();// 記錄當前答案選項的位置optionPosition = getOptionPosition(optionContent);// 開始拖拽后在列表中隱藏答案選項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);}/*** 獲取選項位置** @param option 選項內容* @return 選項位置*/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進入目標區(qū)域return true;case DragEvent.ACTION_DRAG_LOCATION: // 被拖拽View在目標區(qū)域移動return true;case DragEvent.ACTION_DRAG_EXITED: // 被拖拽View離開目標區(qū)域return true;case DragEvent.ACTION_DROP: // 放開被拖拽Viewint position = 0;// 獲取TextView的Layout對象Layout layout = tvContent.getLayout();// 當前x、y坐標float currentX = event.getX();float currentY = event.getY();// 如果拖拽答案沒有進行填空則returnboolean isContinue = false;for (int i = 0; i < answerRangeList.size(); i++) {AnswerRange range = answerRangeList.get(i);// 獲取TextView中字符坐標Rect bound = new Rect();int line = layout.getLineForOffset(range.start);layout.getLineBounds(line, bound);// 字符頂部y坐標int yAxisTop = bound.top - dp2px(10);// 字符底部y坐標int yAxisBottom = bound.bottom + dp2px(5);// 字符左邊x坐標float xAxisLeft = layout.getPrimaryHorizontal(range.start) - dp2px(10);// 字符右邊x坐標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;}// 釋放拖放陰影,并獲取移動數據ClipData.Item item = event.getClipData().getItemAt(0);String answer = item.getText().toString();// 重復拖拽,在答案列表中顯示原答案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 當前填空處答案* @param position 填空位置*/private void fillAnswer(String answer, int position) {answer = " " + answer + " ";// 替換答案AnswerRange range = answerRangeList.get(position);content.replace(range.start, range.end, answer);// 更新當前的答案范圍AnswerRange currentRange = new AnswerRange(range.start, range.start + answer.length());answerRangeList.set(position, currentRange);// 答案設置下劃線content.setSpan(new UnderlineSpan(),currentRange.start, currentRange.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);// 將答案添加到集合中answerList.set(position, answer.replace(" ", ""));// 更新內容tvContent.setText(content);for (int i = 0; i < answerRangeList.size(); i++) {if (i > position) {// 獲取下一個答案原來的范圍AnswerRange oldNextRange = answerRangeList.get(i);int oldNextAmount = oldNextRange.end - oldNextRange.start;// 計算新舊答案字數的差值int difference = currentRange.end - range.end;// 更新下一個答案的范圍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) {// 重新初始化數據setData(originContent, optionList, originAnswerRangeList);// 重新填寫已經存在的答案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);}}}}
最后看下如何設置數據
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 = "紛紛揚揚的________下了半尺多厚。天地間________的一片。我順著________工地走了四十多公里," +"只聽見各種機器的吼聲,可是看不見人影,也看不見工點。一進靈官峽,我就心里發(fā)慌。";// 選項集合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
評論
圖片
表情
