亲测实现:RecycleView健康问卷布局(题目、选项动态)、逻辑(单选、多选功能、提交答案、显示上次选择的答案)

描述:健康问卷的题目、题目对应的选项都是由后台配置的,所以界面的布局是要根据数据来决定,使用了RecycleView实现二级列表的效果来动态布局问卷,效果图如下(录了视频,因为转GIF麻烦,所以就不上了):

  

拿到这个问卷,想到的问题:

  1. 标题中,(单选)、(多选)紧随标题的右边,两个控件要怎么布局?
  2. JSON数据及模型、在正常的项目中,提交数据后再次进来会显示之前的选中答案,要怎么显示呢?
  3. RecycleView实现二级列表呢?(注:我是用了https://blog.csdn.net/fan7983377/article/details/77970063博客的ClassAdapter实现的,在此特别感谢!)
  4. 如何实现单选多选的功能? 

带着上诉问题,在代码中逐渐解释如何解决它们。 

 代码安排如下(有用到DataBinding数据绑定):

一.试卷的数据实体

/**
 * 试卷实体
 * Created by Administrator on 2020/3/11
 *
 * @author mcl
 */
public class QuestionEntity {

    /**
     * num : 0
     * title : string
     * type : 0
     * option : [{"num":0,"title":"string"}]
     * answer : [0]
     */

    private int num;//题号
    private String title;//题目
    private int type;//类型单多选(2单选、1多选)
    private List<OptionBean> option;//选项
    private List<Integer> answer;//这题的选择答案

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    public List<OptionBean> getOption() {
        return option;
    }

    public void setOption(List<OptionBean> option) {
        this.option = option;
    }

    public List<Integer> getAnswer() {
        return answer;
    }

    public void setAnswer(List<Integer> answer) {
        this.answer = answer;
    }

    public static class OptionBean {
        /**
         * num : 0
         * title : string
         */

        private int num;
        private String title;

        public int getNum() {
            return num;
        }

        public void setNum(int num) {
            this.num = num;
        }

        public String getTitle() {
            return title;
        }

        public void setTitle(String title) {
            this.title = title;
        }
    }
}

二. QuestionnaireActivity(activity_questionnaire.xml布局,一个RecyclerView不贴出)

 数据源是后台传过来的,一般会按照题号排好序,里面的选项也排好序的,为了测试,这里的数据我动态添加。涉及到json转换成对象,adapter中自定义点击事件接口。BaseActivity是为了DataBinding中使用统一标题栏,换成AppCompatActivity但ToolBar不要。

public class QuestionnaireActivity extends BaseActivity {
    private ActivityQuestionnaireBinding mBinding;
    private List<QuestionEntity> mList = new ArrayList<>();//试卷题目
    private Gson gson = new Gson();
    private QuestionAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getToolbar().setTvTitle("健康问卷");//换成AppCompatActivity不要
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_questionnaire);
        mBinding.setToolBar(getToolbar());//换成AppCompatActivity不要
        mBinding.setActivity(this);

        initData();
    }

    //试卷内容string(JSON)转成实体
    private void initData() {
        //收到添加数据源,且按题号顺序添加
        String zm1 = "{\"num\":\"1\",\"title\":\"是否接触过韩国、伊朗、日本人员\",\"type\":\"2\",\"option\":[{\"num\":\"1\",\"title\":\"是\",\"isAbnormal\":\"1\"},{\"num\":\"2\",\"title\":\"否\",\"isAbnormal\":\"0\"},{\"num\":\"3\",\"title\":\"去过其他国家\",\"isAbnormal\":\"0\"}],\"answer\":null}";
        String zm2 = "{\"num\":\"2\",\"title\":\"是否有以下症状\",\"type\":\"1\",\"option\":[{\"num\":\"1\",\"title\":\"肠胃不适\",\"isAbnormal\":\"1\"},{\"num\":\"2\",\"title\":\"结膜炎不适\",\"isAbnormal\":\"1\"},{\"num\":\"3\",\"title\":\"感冒、发烧\",\"isAbnormal\":\"1\"},{\"num\":\"4\",\"title\":\"暂无\",\"isAbnormal\":\"0\"},{\"num\":\"5\",\"title\":\"均有以上症状\",\"isAbnormal\":\"1\"}],\"answer\":null}";
        QuestionEntity entity1 = gson.fromJson(zm1, QuestionEntity.class);
        mList.add(entity1);
        QuestionEntity entity2 = gson.fromJson(zm2, QuestionEntity.class);
        mList.add(entity2);

        initAdapter();

    }

    private void initAdapter() {
        if (null != mList && mList.size() > 0) {
            adapter = new QuestionAdapter(this, mList);
            mBinding.rvQuestionnaire.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
            mBinding.rvQuestionnaire.setAdapter(adapter);
            initClick();
        }

    }

    private void initClick() {
    //自定义点击事件的监听,这里处理单选还是多选、以及保存选中的答案到mList中,提交数据时,从mList中拿自己要的
        adapter.setOnItemClickListener(new QuestionAdapter.onItemClickListener() {
            /**
             * @param HeaderPosition 题号 
             * @param ContentPositionForHeader 选择的选项号  List<Integer> answer
             * @param type  该题号是否是单选 单选有就移没有就留,多选有就移没有就加
             */
            @Override
            public void onClick(int HeaderPosition, int ContentPositionForHeader, int type) {
                QuestionEntity entity = mList.get(HeaderPosition);
                QuestionEntity.OptionBean bean = entity.getOption().get(ContentPositionForHeader);
                if (2 == type) {//单选
                    List list = new ArrayList<>();
                    if (null != entity.getAnswer()) {
                        list.addAll(entity.getAnswer());
                    }
                    if (list.contains(bean.getNum())) {//答案已经存在
                        list.remove(0);//单选答案只有一个的
                    } else {//不存在
                        list.add(bean.getNum());
                    }
                    entity.setAnswer(list);//更新答案

                } else {//多选
                    List list = new ArrayList();
                    if (null != entity.getAnswer()) {
                        list.addAll(entity.getAnswer());//把原有的答案全部加入
                    }
                    int zm = bean.getNum();//点击的选项
                    if (list.contains(zm)) {//存在,找出来把它移走
                        for (int i = 0; i < list.size(); i++) {
                            int mm = (Integer) list.get(i);
                            if (zm == mm) {
                                list.remove(i);
                                break;
                            }
                        }
                    } else {//不存在就添加
                        list.add(zm);
                    }
                    entity.setAnswer(list);//更新答案
                }
                adapter.notifyDataSetChanged();//刷新界面,因为数据源mList里面的答案有改变
            }
        });

    }
}

三.QuestionAdapter适配器,实现二级列表、item自定义点击事件,已经选中的选项要显示成选中(圆圈是绿色,其实是一个绿色、白色的图片切换),根据当前选项是否在答案中进行确定是否选中。ClassAdapter的由来请回看问题3。

/**
 * 试卷adapter,是二级列表(TopicHolder题目,OptionHolder是选项)
 * Created by Administrator on 2020/3/11
 *
 * @author mcl
 */
public class QuestionAdapter extends ClassAdapter<QuestionAdapter.TopicHolder, QuestionAdapter.OptionHolder> {
    private List<QuestionEntity> mList;
    private Context mContext;
    private LayoutInflater mInflater;
    private onItemClickListener onItemClickListener;//自定义点击监听事件

    public QuestionAdapter(Context context, List<QuestionEntity> list) {
        this.mContext = context;
        this.mList = list;
        this.mInflater = LayoutInflater.from(context);
    }

    public interface onItemClickListener {
        void onClick(int HeaderPosition, int ContentPositionForHeader, int type);//点击的题目、选项、类型(单多选)
    }

    public void setOnItemClickListener(QuestionAdapter.onItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }

    /**
     * @return 头布局的个数
     */
    @Override
    public int getHeadersCount() {
        if (null != mList && mList.size() > 0) {
            return mList.size();
        } else {
            return 0;
        }
    }

    /**
     * @param headerPosition 第几个头布局
     * @return 头布局对应下的子布局个数
     */
    @Override
    public int getContentCountForHeader(int headerPosition) {
        if (null != mList && mList.size() > 0 && null != mList.get(headerPosition).getOption() && mList.get(headerPosition).getOption().size() > 0) {
            return mList.get(headerPosition).getOption().size();
        } else {
            return 0;
        }
    }

    /**
     * 创建头布局
     *
     * @param parent
     * @param viewType
     * @return
     */
    @Override
    public QuestionAdapter.TopicHolder onCreateHeaderViewHolder(ViewGroup parent, int viewType) {
        return new TopicHolder(mInflater.inflate(R.layout.item_topic, parent, false));
    }

    /**
     * 创建子布局
     *
     * @param parent
     * @param viewType
     * @return
     */
    @Override
    public QuestionAdapter.OptionHolder onCreateContentViewHolder(ViewGroup parent, int viewType) {
        return new OptionHolder(mInflater.inflate(R.layout.item_option, parent, false));
    }

    /**
     * 绑定头布局数据
     *
     * @param holder
     * @param position 第几个头布局
     */
    @Override
    public void onBindHeaderViewHolder(QuestionAdapter.TopicHolder holder, int position) {
        /*
        为了使题目右边紧随单双选,且字体颜色是不一样的;一开始是想用ZZ控件(显示单双选)紧随MM(显示题目)右边,
        *可是当题目多行时,处理不了,所以用同一个TextView显示,只需处理特定位置的字体颜色即可
        * */
        String title = (position + 1) + "." + mList.get(position).getTitle();
        if (2 == mList.get(position).getType()) {//(单选)、(多选)长度都是4,所以start=title.length() - 4
            title = title + "(单选)";
        } else {
            title = title + "(多选)";
        }
        SpannableStringBuilder stringBuilder = new SpannableStringBuilder(title);
        stringBuilder.setSpan(new ForegroundColorSpan(mContext.getResources().getColor(R.color.viewfinder_mask)), title.length() - 4, title.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
        holder.tvTitle.setText(stringBuilder);
        if (0==position){//第一条线不要显示
            holder.view.setVisibility(View.INVISIBLE);
        }

    }

    /**
     * 绑定对应头布局下的子布局数据
     *
     * @param holder
     * @param HeaderPosition           第几个头布局
     * @param ContentPositionForHeader 对应HeaderPosition下的第几个子布局
     */
    @Override
    public void onBindContentViewHolder(QuestionAdapter.OptionHolder holder, int HeaderPosition, int ContentPositionForHeader) {
        holder.tvOption.setText(mList.get(HeaderPosition).getOption().get(ContentPositionForHeader).getTitle());
        List<Integer> aList = mList.get(HeaderPosition).getAnswer();
        if (2 == mList.get(HeaderPosition).getType()) {//单选,把答案(只有一个)给显示出来
            if (null != aList && aList.size() > 0) {
                if (aList.get(0) == mList.get(HeaderPosition).getOption().get(ContentPositionForHeader).getNum()) {//答案跟选项匹配
                    holder.checkBox.setChecked(true);
                } else {
                    holder.checkBox.setChecked(false);
                }
            } else {
                holder.checkBox.setChecked(false);
            }

        } else {//多选,拿选项跟答案对比
            if (null != aList && aList.size() > 0) {
                boolean isChecked = false;//默认是不选中
                for (int i = 0; i < aList.size(); i++) {
                    if (mList.get(HeaderPosition).getOption().get(ContentPositionForHeader).getNum() == aList.get(i)) {
                        isChecked = true;
                        break;
                    }
                }
                holder.checkBox.setChecked(isChecked);

            } else {
                holder.checkBox.setChecked(false);
            }

        }

        //子布局的点击事件
        holder.cl.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onItemClickListener.onClick(HeaderPosition, ContentPositionForHeader, mList.get(HeaderPosition).getType());//自定义点击事件
            }
        });

    }

    public class TopicHolder extends RecyclerView.ViewHolder {
        private TextView tvTitle;//题目
        private View view;//下划线

        public TopicHolder(@NonNull View itemView) {
            super(itemView);
            tvTitle = itemView.findViewById(R.id.tv_item_subject_title);
            view=itemView.findViewById(R.id.view_item_subject);
        }
    }

    public class OptionHolder extends RecyclerView.ViewHolder {
        private ConstraintLayout cl;//整个item布局
        private RoundCheckBox checkBox;//选中与不选中状态
        private TextView tvOption;//选项

        public OptionHolder(@NonNull View itemView) {
            super(itemView);
            cl = itemView.findViewById(R.id.item_cl);
            checkBox = itemView.findViewById(R.id.item_option_checkBox);
            tvOption = itemView.findViewById(R.id.item_option_name);
        }
    }
}

 四.头布局item_topic.xml(一个View、一个TextView不贴了) 、子布局item_option.xml(RoundCheckBox控件只是为了让CheckBox显示圆的好看一些,不贴)代码已贴出

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/item_cl"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="11dp">

    <com.example.module_login.ui.wigdet.RoundCheckBox
        android:id="@+id/item_option_checkBox"
        android:layout_width="10dp"
        android:layout_height="10dp"
        android:layout_marginLeft="27dp"
        android:button="@drawable/checkbox_bg"
        android:clickable="false"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/item_option_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:textColor="@color/black"
        android:textSize="11sp"
        app:layout_constraintBottom_toBottomOf="@id/item_option_checkBox"
        app:layout_constraintLeft_toRightOf="@id/item_option_checkBox"
        app:layout_constraintTop_toTopOf="@id/item_option_checkBox" />

</android.support.constraint.ConstraintLayout>

 补:drawable里面的checkbox_bg.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/report_checked_icon" android:state_checked="true" />

    <item android:drawable="@drawable/report_uncheck_icon" android:state_checked="false" /><!--未选中时效果-->

</selector>

一开头提出的四个问题已经在代码中解决了,如果你没有发现,那么你得再细品了!

到此已经完成了,有什么建议,评论区见! 

 

2020.5.9补充(因为回过头发现单选时不太流畅,需要优化,所以写了扩展)

扩展(写完博客后实现了扩展,且点击什么的都很流程,代码暂时不提供):

1.提交问卷时需要全部题目已选择,单选、多选要流畅 ;

2.再次进来时显示上次选择的答案(样式灰色不可点击);

3.标题栏右上角有"修改"(或其它的样式)点击时,答案灰色变色(eg:灰色变绿色,因为前面我给定的选中样式是绿色)且可修改答案,再次提交

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值