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);// 目标区域设置拖拽事件监听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进入目标区域Log.i("拖拽事件", "被拖拽View进入目标区域");return true;case DragEvent.ACTION_DRAG_LOCATION: // 被拖拽View在目标区域移动Log.i("拖拽事件", "被拖拽View在目标区域移动___X:" + event.getX() + "___Y:" + event.getY());tvTip.setText("X:" + event.getX() + " Y:" + event.getY());return true;case DragEvent.ACTION_DRAG_EXITED: // 被拖拽View离开目标区域Log.i("拖拽事件", "被拖拽View离开目标区域");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;// 避免重复创建拖拽选项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进入目标区域return true;case DragEvent.ACTION_DRAG_LOCATION: // 被拖拽View在目标区域移动return true;case DragEvent.ACTION_DRAG_EXITED: // 被拖拽View离开目标区域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 = "纷纷扬扬的________下了半尺多厚。天地间________的一片。我顺着________工地走了四十多公里," +"只听见各种机器的吼声,可是看不见人影,也看不见工点。一进灵官峡,我就心里发慌。";// 选项集合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
评论
