基于RecyclerView实现的课程表View(ScheduleView)
zjz668
8年前
<p>最近打算依托学校教务系统做一个Android平台的客户端,需要一个课程表控件,在网上找了几个都不是很满意,感觉用起来灵活性不是很好,于是自己动手码了一个,贴出来记录一下,有问题欢迎Issue</p> <h3><strong>效果图</strong></h3> <p style="text-align: center;"><img src="https://simg.open-open.com/show/ac24efd95e0c21aab0887fdbab15317e.png"></p> <p> </p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/70bd50b5c78b5ffeffa28553cdfb4de6.png"></p> <p> </p> <h2><strong>自定义View——ScheduleView</strong></h2> <h3><strong>结构</strong></h3> <p>LinearLayout</p> <p>-- LinearLayout</p> <p>-- RecyclerView</p> <p>层次很简单,最外层是一个LinearLayout,星期栏(header)是一个LinearLayout,下面整个是一个RecyclerView</p> <h3><strong>思路:</strong></h3> <ul> <li>利用 GridLayoutManager.setSpanSizeLookUp(SpanSizeLookup) 方法,在Adapter中通过 getItemViewType() 区分两种不同类型item,实现不同的item(index和course)</li> <li>从而控制最左侧的item(index)的宽度和正常的item(course)的宽度比例为1:TIMES(其中TIMES在Constant中定义)</li> <li>同样的,在Header(星期栏)中使用 addView(View child, ViewGroup.LayoutParams params) ,通过设置weight来控制每个item的大小和位置,从而保证,header中的item和下面RecyclerView中的item位置上是对应的</li> <li>将Header中一周显示几天交给调用者决定(大多数学校的周末是没有课的,所以可以一周只显示5天)</li> <li>调用者传入的是课程信息,但是RecyclerView的item中包含了index类型的item,所以需要将传入的 List<CourseBean> 做处理成 List<Data4RvItem> ,将index信息插入到合适位置,具体实现往下看</li> <li>RecyclerView在获取到数据之后才开始初始化加载,因此等待用户调用 fillData(...) 方法后才开始进行初始化操作</li> <li>在CourseViewHolder中对itemView添加点击事件</li> </ul> <h3><strong>实现</strong></h3> <p>说了这么多,不如直接撸代码:</p> <pre> <code class="language-java">public ScheduleView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.context = context; root = inflate(context, R.layout.schedule_view, this); scheduleHeadLL = (LinearLayout) root.findViewById(R.id.ll_schedule_head); scheduleContentRV = (RecyclerView) root.findViewById(R.id.rv_schedule_content); } ... 在获取到用户数据后调用者调用fillData(...)之后对RecyclerView的初始化: private void initRecyclerView(ScheduleBean mScheduleBean, int dayInWeek) { final List<Data4RvItem> datas = mScheduleBean.getDatas4show(); //+1加上最左边的课程号 GridLayoutManager manager = new GridLayoutManager(context, TIMES * dayInWeek + 1); manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { Data4RvItem data = datas.get(position); switch (data.getType()) { case TYPE_COURSE_INDEX: return 1;//index这个item的宽度的整个宽度的1/TIMES*daysInWeek+1 case TYPE_COURSE: return TIMES; } return 0; } }); scheduleContentRV.setLayoutManager(manager); scheduleContentRV.addItemDecoration(new RecyclerView.ItemDecoration() { @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { outRect.set(1, 1, 1, 1); } }); scheduleContentRV.setAdapter(new ScheduleContentRvAdapter(context , mScheduleBean, dayInWeek)); }</code></pre> <p>fillData(...)方法中的实现:</p> <pre> <code class="language-java">public void fillData(int[] startYMD, int daysInWeek, List<CourseBean> courses) { if (startYMD.length != 3) throw new RuntimeException("参数startYMD: 本周起始年月日-->数组长度为3,分别为year,month,day,传入参数异常"); if (daysInWeek < 0 || daysInWeek > 7) throw new RuntimeException("daysInWeek参数异常\nFunction fillData(List,int) got an incorrect param(daysInWeek)"); this.daysInWeek = daysInWeek; * ScheduleBean mScheduleBean = new ScheduleBean(courses, daysInWeek); initHeader(startYMD, daysInWeek); initRecyclerView(mScheduleBean, daysInWeek); }</code></pre> <p>在ScheduleBean的构造函数中对List<CourseBean>进行了操作,转换成了List<Data4RvItem></p> <pre> <code class="language-java">/** * Created by GG on 2016/11/22. * Email:gu.yuepeng@foxmail.com * <p> * 课程表数据bean */ public class ScheduleBean { /** * 传进来的本周课程列表,要求横向添加CourseBean * 即周一的1,2节课,周二的1,2节……周五的1,2节; * 周一的3,4节,周二的3,4节…… */ private List<CourseBean> courses; /** * 课程表每周显示几天 */ private int dayInWeek; /** * 实际填充在recyclerview中的数据源 * 包含已经处理好的index item */ private List<Data4RvItem> datas4show; public ScheduleBean(List<CourseBean> courses, int dayInWeek) { this.courses = courses; this.dayInWeek = dayInWeek; prepareData4show(); } public List<Data4RvItem> getDatas4show() { if (datas4show == null) Log.e(TAG, "getDatas4show: datas4show==null"); return datas4show; } /** * 将index和课程整合到一起 * 准备用于填充布局的数据源 */ private void prepareData4show() { datas4show = new ArrayList(); for (int i = 0; i < courses.size(); i++) { //每次应该添加周一的课程之前先将view左侧的课程节数添加进去 if (i % dayInWeek == 0) { datas4show.add(new Data4RvItem(i / dayInWeek)); } datas4show.add(new Data4RvItem(courses.get(i))); } } }</code></pre> <p>Adapter中的通过处理好的List<Data4RvItem>中的数据type来区分item的类型:</p> <pre> <code class="language-java">@Override public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View mView; switch (viewType) { case TYPE_COURSE_INDEX: mView = LayoutInflater.from(context) .inflate(R.layout.item_schedule_rv_course_index, null); mView.setClickable(false);//作为index的item不可接受点击事件 return new IndexViewHolder(context,mView); case TYPE_COURSE: mView = LayoutInflater.from(context) .inflate(R.layout.item_schedule_rv_course, null); return new CourseViewHolder(context,mView); } return null; } @Override public void onBindViewHolder(BaseViewHolder holder, int position) { Data4RvItem mData = datas4show.get(position); //此处不做处理,交给各自的Holder做处理 holder.fillData(mData); } @Override public int getItemViewType(int position) { return datas4show.get(position).getType(); } @Override public int getItemCount() { return datas4show.size(); }</code></pre> <p>BaseViewHolder:</p> <pre> <code class="language-java">public abstract class BaseViewHolder extends RecyclerView.ViewHolder { protected View root; protected Context context; public BaseViewHolder(Context context,View itemView) { super(itemView); this.context=context; root=itemView; } /** * 在此方法中将根据各自类型将传入的data填充到布局中 * @param data4RvItem */ public abstract void fillData(Data4RvItem data4RvItem); }</code></pre> <h3><strong>参数说明</strong></h3> <pre> <code class="language-java">public void fillData(int[] startYMD, int daysInWeek, List<CourseBean> courses)</code></pre> <ul> <li>startYMD:一个length为3的int数组,用来指定课程表的周一对应的日期,数组中分别应该是year,month,day.</li> <li>daysInWeek:Header中预期显示到周几,即一周有几天</li> <li>courses:课程表信息,即使没有课也需要占位,courses应和daysInWeek对应,以便后续操作转换成Data4RvItem</li> </ul> <h3><strong>总结</strong></h3> <ul> <li>中间曾想过使用StaggeredGridLayoutManager实现一个横向的瀑布流效果,最终还是用GridLayoutManager来做的,写完之后感觉还不错,不过bug应该不少,待发掘</li> <li>感觉使用RecyclerView来做整个层次结构简单清晰了很多</li> </ul> <p> </p> <p>来自:http://www.jianshu.com/p/ca7d97c6e074</p> <p> </p>