# TimetableView **Repository Path**: chinasoft_ohos/TimetableView ## Basic Information - **Project Name**: TimetableView - **Description**: 一款开源的、完善、高效的openharmony课程表控件 - **Primary Language**: Java - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 6 - **Forks**: 8 - **Created**: 2021-04-12 - **Last Updated**: 2024-12-31 ## Categories & Tags **Categories**: harmonyos-advanced **Tags**: None ## README # TimetableView #### 项目介绍 - 项目名称:TimetableView超级课程表 - 所属系列:openharmony的第三方组件适配移植 - 功能:是一款开源的、完善、高效的openharmony课程表控件 - 项目移植状态:主功能完成 - 调用差异:无 - 开发版本:sdk6,DevEco Studio 2.2 Beta1 - 基线版本:Release v2.0.7-beta #### 支持的功能 - 支持xml设置属性 - 业务逻辑自定义 - 课程颜色管理 - ScrollView可替换 - 可设置背景以及透明度 - 数据源可添加额外信息 - 空白格子可点击,课程项可长按 - 月份宽度可设置 - 周末可设置隐藏 - 课表具体逻辑可自定义 - 本地配置:配置隔离、加载、导出至文本 - 课程颜色可指定 #### 效果演示 ![演示图片](https://gitee.com/chinasoft_ohos/TimetableView/raw/master/image/demo.gif "demo.gif") #### 安装教程 1.在项目根目录下的build.gradle文件中, ```gradle allprojects { repositories { maven { url 'https://s01.oss.sonatype.org/content/repositories/releases/' } } } ``` 2.在entry模块的build.gradle文件中, ```gradle dependencies { implementation('com.gitee.chinasoft_ohos:timetable-view:1.0.0') ...... } ``` 在sdk6,DevEco Studio 2.2 Beta1下项目可直接运行 如无法运行,删除项目.gradle,.idea,build,gradle,build.gradle文件, 并依据自己的版本创建新项目,将新项目的对应文件复制到根目录下 #### 使用说明 ##### 添加控件 该控件包含的基础组件有日期栏、侧边栏、课表视图,在布局文件中加入如下代码后会包含这三个基础组件,注意要添加背景色,没有背景图片可以添加白色背景。 XML中添加控件: ```xml ``` ##### 数据源设置 数据源的设置方式有两种,以下分别来介绍: 方法1:使用指定的格式List,Schedule是控件提供的课程实体类,你可以将自己的数据封装为指定格式,然后进行如下配置即可 ```java mTimetableView.data(scheduleList) .curWeek(1) .showView(); ``` 方法2:方法1在很多场景下都满足不了需求,往往需要定义自己的课程实体类,你可以跟随以下几个步骤来使用它 - 创建自定义的实体类并实现ScheduleEnable接口 ```java public class MySubject implements ScheduleEnable { @Override public Schedule getSchedule() { Schedule schedule = new Schedule(); schedule.setDay(getDay()); schedule.setName(getName()); schedule.setRoom(getRoom()); schedule.setStart(getStart()); schedule.setStep(getStep()); schedule.setTeacher(getTeacher()); schedule.setWeekList(getWeekList()); schedule.setColorRandom(2); schedule.putExtras(EXTRAS_ID,getId()); schedule.putExtras(EXTRAS_AD_URL,getUrl()); return schedule; } } ``` ```java /** * SimpleSlice * * @since 2021-03-29 */ public class SimpleSlice extends AbilitySlice implements Component.ClickedListener { /** * 控件 */ TimetableView mTimetableView; WeekView mWeekView; DirectionalLayout layout; Text titleTextView; List mySubjects; /** * 记录切换的周次,不一定是当前周 */ int target = -1; @Override public void onStart(Intent intent) { super.onStart(intent); super.setUIContent(ResourceTable.Layout_ability_base_func); initViews(); } private void initViews() { titleTextView = (Text) findComponentById(ResourceTable.Id_id_title); layout = (DirectionalLayout) findComponentById(ResourceTable.Id_id_layout); layout.setClickedListener(this); initTimetableView(); requestData(); } @Override public void onActive() { super.onActive(); mTimetableView.onDateBuildListener() .onHighLight(); } EventHandler runner = new EventHandler(EventRunner.create()) { @Override protected void processEvent(InnerEvent event) { super.processEvent(event); mySubjects = SubjectRepertory.loadDefaultSubjects(); MySubject adSubject = new MySubject(); adSubject.setName("【广告】"); adSubject.setStart(1); adSubject.setStep(2); adSubject.setDay(7); List list = new ArrayList<>(); for (int i = 1; i <= 20; i++) { list.add(i); } adSubject.setWeekList(list); mySubjects.add(adSubject); SimpleSlice.this.getUITaskDispatcher().asyncDispatch(() -> { mWeekView.source(mySubjects).showView(); mTimetableView.source(mySubjects).showView(); }); } }; /** * 2秒后刷新界面,模拟网络请求 */ private void requestData() { new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { String errorString = e.toString(); } runner.sendEvent(InnerEvent.get()); } }).start(); } @Override public void onForeground(Intent intent) { super.onForeground(intent); } @Override public void onClick(Component component) { switch (component.getId()) { case ResourceTable.Id_id_layout: /** * 如果周次选择已经显示了,那么将它隐藏,更新课程、日期 */ if (mWeekView.isShowing()) { hideWeekView(); } else { showWeekView(); } break; default:break; } } /** * 初始化课程控件 */ private void initTimetableView() { mWeekView = (WeekView) findComponentById(ResourceTable.Id_id_weekview); mTimetableView = (TimetableView) findComponentById(ResourceTable.Id_id_timetableView); /** * 设置周次选择属性 */ mWeekView.curWeek(1) .callback(new IWeekView.OnWeekItemClickedListener() { @Override public void onWeekClicked(int week) { int cur = mTimetableView.curWeek(); /** * 更新切换后的日期,从当前周cur->切换的周week */ mTimetableView.onDateBuildListener() .onUpdateDate(cur, week); mTimetableView.changeWeekOnly(week); } }) .callback(new IWeekView.OnWeekLeftClickedListener() { @Override public void onWeekLeftClicked() { onWeekLeftLayoutClicked(); } }) /** * 设置隐藏,默认显示 */ .isShow(false) .showView(); mTimetableView.curWeek(1) .curTerm("大三下学期") .callback(new ISchedule.OnItemClickListener() { @Override public void onItemClick(Component v, List scheduleList) { display(scheduleList); } }) .callback(new ISchedule.OnItemLongClickListener() { @Override public void onLongClick(Component v, int day, int start) { ToastViewDialog.toast(SimpleSlice.this, "长按:周" + day + ",第" + start + "节"); } }) .callback(new ISchedule.OnWeekChangedListener() { @Override public void onWeekChanged(int curWeek) { titleTextView.setText("第" + curWeek + "周"); } }) .callback(new OnItemBuildAdapter() { @Override public void onItemUpdate(StackLayout layout, Text textView, Text countTextView, Schedule schedule, ShapeElement gd) { super.onItemUpdate(layout, textView, countTextView, schedule, gd); if (schedule.getName().equals("【广告】")) { layout.removeAllComponents(); } } }) .showView(); } /** * 周次选择布局的左侧被点击时回调
* 对话框修改当前周次 */ protected void onWeekLeftLayoutClicked() { final String items[] = new String[20]; int itemCount = mWeekView.itemCount(); for (int i = 0; i < itemCount; i++) { items[i] = "第" + (i + 1) + "周"; } target = -1; } /** * 显示内容 * * @param beans */ protected void display(List beans) { String str = ""; for (Schedule bean : beans) { str += bean.getName() + "," + bean.getWeekList().toString() + "," + bean.getStart() + "," + bean.getStep() + "\n"; } ToastViewDialog.toast(this, str); } /** * 隐藏周次选择,此时需要将课表的日期恢复到本周并将课表切换到当前周 */ public void hideWeekView() { mWeekView.isShow(false); titleTextView.setTextColor(Color.BLUE); int cur = mTimetableView.curWeek(); mTimetableView.onDateBuildListener() .onUpdateDate(cur, cur); mTimetableView.changeWeekOnly(cur); } public void showWeekView() { mWeekView.isShow(true); titleTextView.setTextColor(Color.RED); } } ``` ##### 基础能力 **配置属性** 我直接把这部分整个代码放出来了,分以下三步: 1. 获取控件 2. 设置WeekView属性 3. 设置TimetableView属性 使用如下方式获取到控件 ```java TimetableView mTimetableView; WeekView mWeekView; ``` ```java /** * 初始化课程控件 */ private void initTimetableView() { /** * 获取控件 */ mWeekView = (WeekView) findComponentById(ResourceTable.Id_id_weekview); mTimetableView = (TimetableView) findComponentById(ResourceTable.Id_id_timetableView); /** * 设置周次选择属性 */ mWeekView.source(mySubjects) .curWeek(1) .callback(new IWeekView.OnWeekItemClickedListener() { @Override public void onWeekClicked(int week) { int cur = mTimetableView.curWeek(); /** * 更新切换后的日期,从当前周cur->切换的周week */ mTimetableView.onDateBuildListener() .onUpdateDate(cur, week); mTimetableView.changeWeekOnly(week); } }) .callback(new IWeekView.OnWeekLeftClickedListener() { @Override public void onWeekLeftClicked() { onWeekLeftLayoutClicked(); } }) .isShow(false) .showView(); mTimetableView.source(mySubjects) .curWeek(1) .curTerm("大三下学期") .maxSlideItem(10) .monthWidthDp(50) .callback(new ISchedule.OnItemClickListener() { @Override public void onItemClick(Component v, List scheduleList) { display(scheduleList); } }) .callback(new ISchedule.OnItemLongClickListener() { @Override public void onLongClick(Component v, int day, int start) { ToastViewDialog.toast(BaseFuncSlice.this, "长按:周" + day + ",第" + start + "节"); } }) .callback(new ISchedule.OnWeekChangedListener() { @Override public void onWeekChanged(int curWeek) { titleTextView.setText("第" + curWeek + "周"); } }) /** * 旗标布局点击监听 */ .callback(new ISchedule.OnFlaglayoutClickListener() { @Override public void onFlaglayoutClick(int day, int start) { mTimetableView.hideFlaglayout(); ToastViewDialog.toast(BaseFuncSlice.this, "点击了旗标:周" + (day + 1) + ",第" + start + "节"); } }) .showView(); } ``` **删除课程** ```java /** * 删除课程 * 内部使用集合维护课程数据,操作集合的方法来操作它即可 * 最后更新一下视图(全局更新) */ protected void deleteSubject() { int size = mTimetableView.dataSource().size(); int pos = (int) (new SecureRandom().nextDouble() * size); if (size > 0) { mTimetableView.dataSource().remove(pos); mTimetableView.updateView(); } } ``` **添加课程** ```java /** * 添加课程 * 内部使用集合维护课程数据,操作集合的方法来操作它即可 * 最后更新一下视图(全局更新) */ protected void addSubject() { List dataSource = mTimetableView.dataSource(); int size = dataSource.size(); if (size > 0) { Schedule schedule = dataSource.get(0); dataSource.add(schedule); mTimetableView.updateView(); } } ``` **非本周课程显示与隐藏** ```java /** * 隐藏非本周课程 * 修改了内容的显示,所以必须更新全部(性能不高) * 建议:在初始化时设置该属性 *

* updateView()被调用后,会重新构建课程,课程会回到当前周 */ protected void hideNonThisWeek() { mTimetableView.isShowNotCurWeek(false).updateView(); } /** * 显示非本周课程 * 修改了内容的显示,所以必须更新全部(性能不高) * 建议:在初始化时设置该属性 */ protected void showNonThisWeek() { mTimetableView.isShowNotCurWeek(true).updateView(); } ``` **最大节次设置** ```java /** * 设置侧边栏最大节次,只影响侧边栏的绘制,对课程内容无影响 * @param num */ protected void setMaxItem(int num) { mTimetableView.maxSlideItem(num).updateSlideView(); } ``` **节次时间显示与隐藏** ```java /** * 显示时间 * 设置侧边栏构建监听,TimeSlideAdapter是控件实现的可显示时间的侧边栏 */ protected void showTime() { String[] times = new String[]{"8:00", "9:00", "10:10", "11:00", "15:00", "16:00", "17:00", "18:00", "19:30", "20:30", "21:30", "22:30"}; OnSlideBuildAdapter listener = (OnSlideBuildAdapter) mTimetableView.onSlideBuildListener(); listener.setTimes(times) .setTimeTextColor(Color.BLACK.getValue()); mTimetableView.updateSlideView(); } /** * 隐藏时间 * 将侧边栏监听置Null后,会默认使用默认的构建方法,即不显示时间 * 只修改了侧边栏的属性,所以只更新侧边栏即可(性能高),没有必要更新全部(性能低) */ protected void hideTime() { mTimetableView.callback((ISchedule.OnSlideBuildListener) null); mTimetableView.updateSlideView(); } ``` **WeekView显示与隐藏** ```java /** * 显示WeekView */ protected void showWeekView() { mWeekView.isShow(true); } /** * 隐藏WeekView */ protected void hideWeekView() { mWeekView.isShow(false); } ``` **月份宽度设置** ```java /** * 设置月份宽度 */ private void setMonthWidth() { mTimetableView.monthWidthDp(100).updateView(); } /** * 设置月份宽度,默认40dp */ private void resetMonthWidth() { mTimetableView.monthWidthDp(80).updateView(); } ``` **周末显示与隐藏** ```java /** * 隐藏周末 */ private void hideWeekends() { mTimetableView.isShowWeekends(false).updateView(); } /** * 显示周末 */ private void showWeekends() { mTimetableView.isShowWeekends(true).updateView(); } ``` ##### 周次选择控件 周次选择栏WeekView是控件实现的一个默认的周次选择控件,你可以使用它快速的拥有周次选择功能,TimetableView`本身是没有周次选择功能的,所以需要两者配合使用,完整代码参见 BaseFuncSlice **默认的周次选择栏** 1.添加控件 在布局文件中放一个TimetableView,然后在TimetableView的上边放一个WeekView ```xml ``` 2.初始化 我直接把这部分整个代码放出来了,分以下2步: 1. 获取控件 2. 设置WeekView属性 使用如下方式获取到控件 ```java WeekView weekView; ``` 周次选择控件应该与课程表控件结合使用,TimetableView的初始化和使用已省略,请参考其他章节 ```java //设置周次选择属性 mWeekView.source(mySubjects) .curWeek(1) .callback(new IWeekView.OnWeekItemClickedListener() { @Override public void onWeekClicked(int week) { int cur = mTimetableView.curWeek(); //更新切换后的日期,从当前周cur->切换的周week mTimetableView.onDateBuildListener() .onUpdateDate(cur, week); //课表切换周次 mTimetableView.changeWeekOnly(week); } }) .callback(new IWeekView.OnWeekLeftClickedListener() { @Override public void onWeekLeftClicked() { onWeekLeftLayoutClicked(); } }) .isShow(false)//设置隐藏,默认显示 .showView(); ``` **自定义周次选择栏** 你可以选择以下方法来实现周次选择栏的自定义: - 任意定制,因为周次选择栏和课表是没有关系的,所以你可以任意实现 - 遵循我定义的周次选择栏规范,实现 WeekViewEnable 接口 - 扩展默认的周次选择栏实现,继承自 WeekView ,重新方法 ##### 日期栏样式 本节主要演示如何对日期栏的属性设置以及自定义日期栏的步骤,通用步骤比如:添加控件、获取控件,设置数据源以及显示视图什么的都不再重复了,只讲解核心部分 完整代码参见 DateSlice **日期栏显示与隐藏** ```java /** * 隐藏日期栏 */ protected void hideDateView() { mTimetableView.hideDateView(); } /** * 显示日期栏 */ protected void showDateView() { mTimetableView.showDateView(); } ``` **恢复默认日期栏** ```java /** * 恢复默认日期栏 */ protected void cancelCustomDateView() { mTimetableView.callback((ISchedule.OnDateBuildListener) null) .updateDateView(); } ``` **自定义日期栏** Step1:自定义布局 需要定义两个布局,第一个xml定义的月份的样式 ```xml ``` 第二个xml定义的是星期一至星期日的每项的样式 ```xml ``` Step2:自定义实现类 注意:由于要修改默认的height,所以onBuildDayLayout onBuildMonthLayout都必须重写并设置为新的height,否则无效。 你也可以直接实现OnDateBuildListener接口,更灵活但是操作复杂。 ```java /** * 自定义日期栏 * 该段代码有点长,但是很好懂,仔细看看会有收获的,嘻嘻 */ protected void customDateView() { mTimetableView.callback( new OnDateBuildAapter() { @Override public Component onBuildDayLayout(LayoutScatter mInflate, int pos, int width, int height) { int newHeight = 50; Component view = mInflate.parse(ResourceTable.Layout_item_custom_dateview, null, true); Text dayTextView = (Text) view.findComponentById(ResourceTable.Id_id_week_day); dayTextView.setText(dateArray[pos]); layouts[pos] = (DirectionalLayout) view.findComponentById(ResourceTable.Id_id_week_layout); DirectionalLayout.LayoutConfig lp = new DirectionalLayout.LayoutConfig(width, newHeight); layouts[pos].setLayoutConfig(lp); return view; } @Override public Component onBuildMonthLayout(LayoutScatter mInflate, int width, int height) { int newHeight = 30; Component first = mInflate.parse(ResourceTable.Layout_item_custom_dateview_first, null, true); /** * 月份设置 */ textViews[0] = (Text) first.findComponentById(ResourceTable.Id_id_week_month); layouts[0] = null; DirectionalLayout.LayoutConfig lp = new DirectionalLayout.LayoutConfig(width, newHeight); int month = Integer.parseInt(weekDates.get(0)); first.setLayoutConfig(lp); textViews[0].setText(month + "\n月"); return first; } }) .updateDateView(); } ``` **日期延迟显示案例** 需求:设定一个开学时间,在开学时间到来之前,一直显示开学时的第一周的日期 我们使用自定义日期栏来实现这个需求 - 第一步:自定义日期栏 需要设置一个阈值以及一个默认的日期集合,如果当前时间戳小于阈值时,使用默认的日期集合,否则计算当前周的日期,所以这个就很简单了 ```java import com.zhuangfei.timetable.listener.OnDateBuildAapter; import com.zhuangfei.timetable.model.ScheduleSupport; import ohos.agp.components.DirectionalLayout; import java.text.SimpleDateFormat; import java.util.List; /** * 自定义日期栏 * Created by Liu ZhuangFei on 2018/8/24. */ public class OnDateDelayAdapter extends OnDateBuildAapter { /** * 阈值,即超过这个时间戳后开始更新日期 * 否则将一直显示initDates中的日期 */ protected long startTime; protected String startTimeStr; protected SimpleDateFormat sdf; /** * 日期集合,8个元素,当前时间小于等于阈值时使用 */ List initDates = null; public OnDateDelayAdapter() { sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm"); } /** * 设置日期集合 * * @param dates 元素个数必须大于等于8,第一个为月份数值,第2-8为周一至周日的日期数值(不带中文) */ public void setDateList(List dates) { if (dates.size() >= 8) { this.initDates = dates; } } public void setStartTime(long startTime) { this.startTime = startTime; this.startTimeStr = sdf.format(startTime); } @Override public void onInit(DirectionalLayout layout, float alpha) { super.onInit(layout, alpha); /** * 增加的 */ long curTime = System.currentTimeMillis(); if (curTime <= startTime) { weekDates = initDates; } } @Override public void onUpdateDate(int curWeek,int targetWeek) { if (textViews == null || textViews.length < 8) { return; } if (whenBeginSchool() <= 0) { weekDates = ScheduleSupport.getDateStringFromWeek(curWeek,targetWeek); } int month = Integer.parseInt(weekDates.get(0)); textViews[0].setText(month + "\n月"); for (int i = 1; i < 8; i++) { if (textViews[i] != null) { textViews[i].setText(weekDates.get(i) + "日"); } } } /** * 计算距离开学的天数 * * @return 返回值2种类型,-1:没有开学时间,无法计算;0:已经开学;>0:天数 */ public long whenBeginSchool() { if (!"".equals(startTimeStr)) { int calWeek = ScheduleSupport.timeTransfrom(startTimeStr); if (calWeek > 0) { return 0; } else { long seconds = (startTime - System.currentTimeMillis()) / 1000; long day = seconds / (24 * 3600); return day; } } return -1; } } ``` - 第二步:使用 大部分工作第一步已经做完了,剩下的事情就是如何去使用它,示例如下: 下面这个方法是获取一个OnDateDelayAdapter示例,并且对其初始化 ```java /** * 配置OnDateDelayAdapter */ public OnDateDelayAdapter getDateDelayAdapter() { OnDateDelayAdapter onDateDelayAdapter = new OnDateDelayAdapter(); /** * 计算开学时间戳 */ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm"); long startTime = 0; try { startTime = sdf.parse("2022-09-03 00:00").getTime(); } catch (ParseException e) { e.printStackTrace(); } /** * 计算开学时的一周日期,我这里模拟一下 */ List dateList = Arrays.asList("9", "03", "04", "05", "06", "07", "08", "09"); onDateDelayAdapter.setStartTime(startTime); onDateDelayAdapter.setDateList(dateList); return onDateDelayAdapter; } ``` 然后将OnDateDelayAdapter实例设置到TimetableView控件上 ```java mTimetableView.source(mySubjects) .curWeek(1) .curTerm("大三下学期") .maxSlideItem(10) .callback(getDateDelayAdapter())//这行要放在下行的前边 .callback(new ISchedule.OnWeekChangedListener() { @Override public void onWeekChanged(int curWeek) { OnDateDelayAdapter adapter= (OnDateDelayAdapter) mTimetableView.onDateBuildListener(); long when=adapter.whenBeginSchool(); if(when>0){ titleTextView.setText("距离开学还有"+when+"天"); }else{ titleTextView.setText("第" + curWeek + "周"); } } }) .showView(); ``` 其中这两句代码可以用来计算距离开学的日期 ```java OnDateDelayAdapter adapter= (OnDateDelayAdapter) mTimetableView.onDateBuildListener(); long when=adapter.whenBeginSchool(); ``` 为了保证日期能够更正,你还需要在onStart中对日期更正 ```java int cur = mTimetableView.curWeek(); mTimetableView.onDateBuildListener().onUpdateDate(cur, cur); OnDateDelayAdapter adapter = (OnDateDelayAdapter) mTimetableView.onDateBuildListener(); long when = adapter.whenBeginSchool(); if (when > 0) { titleTextView.setText("距离开学还有" + when + "天"); } ``` ##### 侧边栏样式 在课程视图的左侧有一列是侧边栏,本节演示如何对侧边栏的属性进行配置以及自定义侧边栏的步骤 完整代码参见 SlideSlice **准备** 添加控件 ```xml ``` 获取控件 ```java private void initViews() { mTimetableView = (TimetableView) findComponentById(ResourceTable.Id_id_timetableView); List mySubjects = SubjectRepertory.loadDefaultSubjects(); mTimetableView.source(mySubjects) .curWeek(1) .showView(); } ``` **节次时间显示与隐藏** ```java /** * 显示时间 * 设置侧边栏构建监听,TimeSlideAdapter是控件实现的可显示时间的侧边栏 * 只修改了侧边栏的属性,所以只更新侧边栏即可(性能高),没有必要更新全部(性能低) * * @see OnSlideBuildAdapter */ protected void showTime() { String[] times = new String[]{"8:00", "9:00", "10:10", "11:00","15:00", "16:00", "17:00", "18:00","19:30", "20:30", "21:30", "22:30"}; OnSlideBuildAdapter slideAdapter = new OnSlideBuildAdapter(); slideAdapter.setTimes(times); mTimetableView.callback(slideAdapter); mTimetableView.updateSlideView(); } /** * 隐藏时间 * 将侧边栏监听置Null后,会默认使用默认的构建方法,即不显示时间 * 只修改了侧边栏的属性,所以只更新侧边栏即可(性能高),没有必要更新全部(性能低) */ protected void hideTime() { mTimetableView.callback((ISchedule.OnSlideBuildListener) null) .updateSlideView(); } ``` **修改侧边栏背景** ```java /** * 修改侧边栏背景,默认的使用的是OnSlideBuildAdapter, * 所以可以强转类型 * * @param color */ protected void modifySlideBgColor(RgbColor color) { OnSlideBuildAdapter listener = (OnSlideBuildAdapter) mTimetableView.onSlideBuildListener(); listener.setBackground(color); mTimetableView.updateSlideView(); } ``` **修改节次文本颜色** ```java /** * 修改侧边栏节次文本的颜色值 * * @param color */ protected void modifyItemTextColor(int color) { OnSlideBuildAdapter listener = (OnSlideBuildAdapter) mTimetableView.onSlideBuildListener(); listener.setTextColor(color); mTimetableView.updateSlideView(); } ``` **修改时间文本颜色** ```java /** * 修改侧边栏时间文本的颜色值 * * @param color */ protected void modifyItemTimeColor(int color) { String[] times = new String[]{"8:00", "9:00", "10:10", "11:00", "15:00", "16:00", "17:00", "18:00", "19:30", "20:30", "21:30", "22:30"}; OnSlideBuildAdapter listener = (OnSlideBuildAdapter) mTimetableView.onSlideBuildListener(); listener.setTimes(times) .setTimeTextColor(color); mTimetableView.updateSlideView(); } ``` **侧边栏效果重置** ```java /** * 取消自定义的侧边栏,回到默认状态 * 只需要将监听器置空即可 */ protected void cancelCustomSlideView() { mTimetableView.callback((ISchedule.OnSlideBuildListener) null) .updateSlideView(); } ``` **自定义侧边栏** Step1:创建布局 创建一个XML文件,该文件的内容可完全自定义 ```xml ``` Step2:设置监听 为了简单起见,可以继承自OnSlideBuildAdapter,重写getView()方法,当然也可以直接实现ISchedule.OnSlideBuildListener接口 ```java /** * 自定义侧边栏效果 * 使用自定义的布局文件实现的文字居顶部的效果(默认居中) */ protected void customSlideView() { mTimetableView.callback( new OnSlideBuildAdapter() { @Override public Component getView(int pos, LayoutScatter inflater, int itemHeight, int marTop) { /** * 获取View并返回,注意设置marTop值 */ Component v = inflater.parse(ResourceTable.Layout_item_custom_slide, null, false); Text tv = (Text) v.findComponentById(ResourceTable.Id_item_slide_textview); DirectionalLayout.LayoutConfig lp = new DirectionalLayout.LayoutConfig(ComponentContainer.LayoutConfig.MATCH_PARENT, itemHeight); lp.setMargins(0, marTop, 0, 0); tv.setLayoutConfig(lp); tv.setText((pos + 1) + ""); return v; } }) .updateSlideView(); } ``` ##### 课程项样式 本节将演示如何配置课程项的样式,完整代码参见 ItemStyleSlice **非本周课程显示与隐藏** ```java /** * 隐藏非本周课程 * 修改了内容的显示,所以必须更新全部(性能不高) * 建议:在初始化时设置该属性 */ protected void hideNonThisWeek() { mTimetableView.isShowNotCurWeek(false).updateView(); } /** * 显示非本周课程 * 修改了内容的显示,所以必须更新全部(性能不高) * 建议:在初始化时设置该属性 */ protected void showNonThisWeek() { mTimetableView.isShowNotCurWeek(true).updateView(); } ``` **设置间距以及弧度** ```java /** * 设置间距以及弧度 * 该方法只能同时设置四个角的弧度,设置单个角的弧度可参考下文 */ protected void setMarginAndCorner() { mTimetableView.cornerAll(0) .marTop(0) .marLeft(0) .updateView(); } ``` **设置单个角弧度** ```java /** * 设置角度(四个角分别控制) * * @param leftTop * @param rightTop * @param rightBottom * @param leftBottom */ public void setCorner(final int leftTop, final int rightTop, final int rightBottom, final int leftBottom) { mTimetableView.callback(new OnItemBuildAdapter() { @Override public void onItemUpdate(StackLayout layout, Text textView, Text countTextView, Schedule schedule, ShapeElement gd) { super.onItemUpdate(layout, textView, countTextView, schedule, gd); /** * 数组8个元素,四个方向依次为左上、右上、右下、左下, * 每个方向在数组中占两个元素,值相同 */ gd.setCornerRadiiArray(new float[]{leftTop, leftTop, rightTop, rightTop, rightBottom, rightBottom, leftBottom, leftBottom}); } }); mTimetableView.updateView(); } ``` **修改显示的文本** ```java /** * 修改显示的文本 */ public void buildItemText() { mTimetableView.callback(new OnItemBuildAdapter() { @Override public String getItemText(Schedule schedule, boolean isThisWeek) { if (isThisWeek) { return "[本周]" + schedule.getName(); } else { return "[非本周]" + schedule.getName(); } } }) .updateView(); } ``` **设置非本周课的背景** ```java /** * 设置非本周课的背景 * * @param color */ public void setNonThisWeekBgcolor(RgbColor color) { mTimetableView.colorPool().setUselessColor(color); mTimetableView.updateView(); } ``` **课程重叠的样式** ```java /** * 修改课程重叠的样式,在该接口中,你可以自定义出很多的效果 */ protected void modifyOverlayStyle() { mTimetableView.callback(new OnItemBuildAdapter() { @Override public void onItemUpdate(StackLayout layout, Text textView, Text countTextView, Schedule schedule, ShapeElement gd) { super.onItemUpdate(layout, textView, countTextView, schedule, gd); /** * 可见说明重叠,取消角标,添加角度 */ if (countTextView.getVisibility() == Component.VISIBLE) { countTextView.setVisibility(Component.HIDE); gd.setCornerRadiiArray(new float[]{0, 0, 20, 20, 0, 0, 0, 0}); } } }); mTimetableView.updateView(); } ``` **添加广告** 广告一般是一张图片和一个链接,所以整体思路是这样的,首先向数据集里插入一条数据,数据的名字可以任意,但是必须和普通课程有所区分,根据这个名字可以判断出它是广告,然后给该数据设置上课周次、开始节次、持续节次;然后监听课程项的构建,如果监测到该课程是广告,那么将默认的课程布局remove掉,然后添加一个广告图片,再给它设置一个点击事件 ```java /** * 构造数据集 */ mySubjects = SubjectRepertory.loadDefaultSubjects(); MySubject adSubject = new MySubject(); adSubject.setName("【广告】"); adSubject.setStart(1); adSubject.setStep(2); adSubject.setDay(7); List list = new ArrayList<>(); for (int i = 1; i <= 20; i++) { list.add(i); } adSubject.setWeekList(list); adSubject.setUrl(AD_URL); mySubjects.add(adSubject); SimpleSlice.this.getUITaskDispatcher().asyncDispatch(() -> { mWeekView.source(mySubjects).showView(); mTimetableView.source(mySubjects).showView(); }); /** * 设置属性 */ mTimetableView.curWeek(1) .curTerm("大三下学期") .callback(new ISchedule.OnItemClickListener() { @Override public void onItemClick(Component v, List scheduleList) { display(scheduleList); } }) .callback(new ISchedule.OnItemLongClickListener() { @Override public void onLongClick(Component v, int day, int start) { ToastViewDialog.toast(SimpleSlice.this, "长按:周" + day + ",第" + start + "节"); } }) .callback(new ISchedule.OnWeekChangedListener() { @Override public void onWeekChanged(int curWeek) { titleTextView.setText("第" + curWeek + "周"); } }) .callback(new OnItemBuildAdapter() { @Override public void onItemUpdate(StackLayout layout, Text textView, Text countTextView, Schedule schedule, ShapeElement gd) { super.onItemUpdate(layout, textView, countTextView, schedule, gd); if (schedule.getName().equals("【广告】")) { layout.removeAllComponents(); Image imageView = new Image(SimpleSlice.this); imageView.setHeight(DependentLayout.LayoutConfig.MATCH_PARENT); imageView.setWidth(DependentLayout.LayoutConfig.MATCH_PARENT); layout.addComponent(imageView); String url = (String) schedule.getExtras().get(MySubject.EXTRAS_AD_URL); PixelMap pixelMap = loadImage(url); /** * 普通解码 */ getUITaskDispatcher().syncDispatch(() -> { imageView.setPixelMap(pixelMap); pixelMap.release(); }); imageView.setClickedListener(new Component.ClickedListener() { @Override public void onClick(Component component) { ToastViewDialog.toast(SimpleSlice.this, "进入广告网页链接"); } }); } } }) .showView(); ``` **中英文切换** step1:英文日期栏 ```java import com.zhuangfei.timetable.listener.OnDateBuildAapter; import com.zhuangfei.timetable.model.ScheduleSupport; /** * 英语日期栏 */ public class OnEnglishDateBuildAdapter extends OnDateBuildAapter { @Override public String[] getStringArray() { return new String[]{null, "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}; } @Override public void onUpdateDate(int curWeek, int targetWeek) { if (textViews == null || textViews.length < 8) { return; } String[] monthArray = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sept","Oct","Nov","Dec"}; weekDates = ScheduleSupport.getDateStringFromWeek(curWeek, targetWeek); int month = Integer.parseInt(weekDates.get(0)); textViews[0].setText(monthArray[month - 1]); for (int i = 1; i < 8; i++) { if (textViews[i] != null) { textViews[i].setText(weekDates.get(i)); } } } } ``` step2:英文课程项 ```java import com.zhuangfei.timetable.listener.OnItemBuildAdapter; import com.zhuangfei.timetable.model.Schedule; /** * 英文的课程文本设置 */ public class OnEnglishItemBuildAdapter extends OnItemBuildAdapter { @Override public String getItemText(Schedule schedule, boolean isThisWeek) { if (schedule == null || schedule.getName().equals("")) { return "Unknow"; } if (schedule.getRoom() == null) { if (!isThisWeek) { return "[Non]" + schedule.getName(); } return schedule.getName(); } String res = schedule.getName() + "@" + schedule.getRoom(); if (!isThisWeek) { res = "[Non]" + res; } return res; } } ``` step3:设置监听 ```java /** * 切换为英文 */ public void changeEnglishLanguage() { mTimetableView.callback(new OnEnglishDateBuildAdapter()) .callback(new OnEnglishItemBuildAdapter()) .updateView(); } /** * 切换为中文 */ public void changeChineseLanguage() { mTimetableView.callback((ISchedule.OnDateBuildListener) null) .callback((ISchedule.OnItemBuildListener) null) .updateView(); } ``` ##### 课程颜色管理 **颜色池** 颜色池ScheduleColorPool的内部实现是控件维护的一个集合,它管理着课程项的颜色,负责对颜色的存取 完整代码参见 ColorPoolSlice **获取颜色池** 以下代码的返回结果就是一个ScheduleColorPool实例对象 ```java mTimetableView.colorPool(); ``` **指定颜色** 如果需要让所有课程只在某几种颜色中分配颜色,只需要清空颜色池并加入特定的颜色,在分配颜色时会循环分配颜色池中的颜色 ```java /** * 设置指定的颜色,默认情况下颜色池中有一些颜色 * 所以这里需要先清空一下颜色池 * * @param colors */ public void setColor(RgbColor... colors) { mTimetableView.colorPool().clear().add(colors); mTimetableView.updateView(); } ``` **重置颜色池** 重置后,颜色池恢复到初始状态 ```java /** * 重置颜色池 */ public void resetColor() { mTimetableView.colorPool().reset(); mTimetableView.updateView(); } ``` **追加颜色** 向颜色池中追加颜色,在为课程项分配颜色时,会按照颜色池中的顺序依次取出颜色,所以并不保证追加的颜色一定会被用到 ```java /** * 追加颜色 * * @param colors */ public void addColor(RgbColor... colors) { mTimetableView.colorPool().add(colors); mTimetableView.updateView(); } ``` **设置非本周课程颜色** 向颜色池中追加颜色,在为课程项分配颜色时,会按照颜色池中的顺序依次取出颜色,所以并不保证追加的颜色一定会被用到 ```java /** * 设置非本周课的背景 * * @param color */ public void setNonThisWeekBgcolor(int color) { mTimetableView.colorPool().setUselessColor(color); mTimetableView.updateView(); } ``` **指定课程的颜色** setIgnoreUserlessColor 用来设置颜色映射是否忽略非本周的课程颜色,如果设置为true,表示该课程在颜色映射中能够查找到,那么不管是本周还是非本周它都将显示映射中的颜色;如果设置为false,表示如果该课程在颜色映射中能够查找到,并且该课程是本周的,那么将该课程设置为映射中的颜色,如果非本周,则使用非本周颜色对其渲染 ```java /** * 指定课程的颜色,未指定的课程自动分配 */ public void forColor() { Map colorMap = new HashMap<>(); colorMap.put("数字图像处理", Color.RED.getValue()); colorMap.put("算法分析与设计", Color.BLUE.getValue()); mTimetableView.colorPool().setIgnoreUserlessColor(false).setColorMap(colorMap); mTimetableView.updateView(); } ``` ##### 替换滚动布局 为什么会有这个功能?想象这样几个场景:下拉反弹效果、下拉刷新效果、监听到滚动的位置、解决滚动布局嵌套导致的滑动冲突。这几个需求都可以使用自定义View的方式来解决。 替换滚动布局的意思是:你可以使用自定义的ScrollView来替换控件中的普通的ScrollView,可以想象它将有更好的扩展性。 本节将演示如何实现一个有下拉反弹效果的课表界面,自定义View的知识不在本节的范围之内,有兴趣可以百度。 完整代码参见 ElasticSlice **自定义View** 你需要先准备好一个自定义View,根据你的需求而定,可以参考以下的弹性滚动布局ElasticScrollView ```java import ohos.agp.animation.Animator; import ohos.agp.animation.AnimatorProperty; import ohos.agp.components.AttrSet; import ohos.agp.components.Component; import ohos.agp.components.ScrollView; import ohos.agp.utils.Rect; import ohos.app.Context; import ohos.multimodalinput.event.MmiPoint; import ohos.multimodalinput.event.TouchEvent; /** * ElasticScrollView *

* 弹性滚动布局,下拉时会反弹 * * @since 2021-03-29 */ public class ElasticScrollView extends ScrollView implements Component.TouchEventListener { private Component inner; private float yy; private Rect normal = new Rect(); private boolean animationFinish = true; public ElasticScrollView(Context context) { super(context); } public ElasticScrollView(Context context, AttrSet attrs) { super(context, attrs); } public void commOnTouchEvent(TouchEvent ev) { if (animationFinish) { int action = ev.getAction(); MmiPoint point = ev.getPointerPosition(ev.getIndex()); switch (action) { case TouchEvent.PRIMARY_POINT_DOWN: yy = point.getY(); break; case TouchEvent.PRIMARY_POINT_UP: yy = 0; if (isNeedAnimation()) { animation(); } break; case TouchEvent.POINT_MOVE: final float preY = yy == 0 ? point.getY() : yy; float nowY = point.getY(); int deltaY = (int) (preY - nowY); yy = nowY; /** * 当滚动到最上或者最下时就不会再滚动,这时移动布局 */ if (isNeedMove()) { if (normal.isEmpty()) { /** * 保存正常的布局位置 */ normal.set(inner.getLeft(), inner.getTop(), inner.getRight(), inner.getBottom()); } /** * 移动布局 */ inner.setComponentPosition(inner.getLeft(), inner.getTop() - deltaY / 2, inner.getRight(), inner.getBottom() - deltaY / 2); } break; default: break; } } } /** * 开启动画移动 */ public void animation() { AnimatorProperty ta = new AnimatorProperty(inner); ta.moveFromX(0).moveToX(0).moveFromY(0).moveToY(normal.top - inner.getTop()).setDelay(200); ta.setStateChangedListener(new Animator.StateChangedListener() { @Override public void onStart(Animator animator) { } @Override public void onStop(Animator animator) { } @Override public void onCancel(Animator animator) { } @Override public void onEnd(Animator animator) { inner.setComponentPosition(normal.left, normal.top, normal.right, normal.bottom); normal.set(0, 0, 0, 0); animationFinish = true; } @Override public void onPause(Animator animator) { } @Override public void onResume(Animator animator) { } }); ta.start(); } /** * 是否需要开启动画 * * @return 是否需要开启动画 */ public boolean isNeedAnimation() { return !normal.isEmpty(); } /** * 是否需要移动布局 * * @return 是否需要移动布局 */ public boolean isNeedMove() { int offset = inner.getEstimatedHeight() - getHeight(); int scrollY = getScrollValue(0); if (scrollY == 0 || scrollY == offset) { return true; } return false; } @Override public boolean onTouchEvent(Component component, TouchEvent touchEvent) { if (inner != null) { commOnTouchEvent(touchEvent); } return false; } } ``` **布局文件** 备一个布局文件,命名任意,将以下内容复制到布局文件中,然后将根控件换成自定义控件,注意ID不能改变 ```xml ``` **设置监听** ```java private void initViews() { mTimetableView = (TimetableView) findComponentById(ResourceTable.Id_id_timetableView); List mySubjects = SubjectRepertory.loadDefaultSubjects(); /** * 过程很简单,步骤如下: * 1.创建一个xml文件,命名为custom_myscrollview.xml * 2.拷贝一段代码至该文件中,具体内容可以参见custom_myscrollview.xml * 3.将根布局控件修改为自定义的控件,其他内容无需修改 * 4.设置滚动布局构建监听并实现其方法,将自定义的xml转换为View返回即可 * */ mTimetableView.source(mySubjects) .callback(new ISchedule.OnScrollViewBuildListener() { @Override public Component getScrollView(LayoutScatter mInflate) { return mInflate.parse(ResourceTable.Layout_custom_myscrollview, null, true); } }) .showView(); } ``` ##### 旗标布局 什么是旗标布局?我自己随意起的名,不要当真,嘻嘻。 旗标布局是指当我们点击空白格子的时候出现一个布局,该布局占据一个格子的位置,它所在的位置就是我们所点击的格子的位置,该布局可以响应事件并回调处理 完整代码参见 FlaglayoutSlice 默认情况下,旗标布局是开启状态的,即不需要任何配置,在空白格子处点击会出现旗标布局,当然,它也可以关闭。 **事件监听** OnSpaceItemClickListener是空白格子点击监听器,当用户点击空白格子时,会回调该接口中的方法并传入点击的格子的位置,然后需要将旗标布局移动到指定格子位置,OnSpaceItemClickAdapter是这个接口的默认实现,这部分一般不用开发者关心 现在的效果是点击空白格子后,在点击的格子位置会出现一个旗标布局,开发者只需要使用使用OnFlaglayoutClickListener来监听旗标布局的点击事件即可,无需关心其他 以下代码中监听了三个事件: - 课程项的单击 - 课程项的长按 - 空白格子的单击(默认)以及旗标布局的单击 ```java private void initViews() { mTimetableView = (TimetableView) findComponentById(ResourceTable.Id_id_timetableView); List mySubjects = SubjectRepertory.loadDefaultSubjects(); mTimetableView.source(mySubjects) .curWeek(1) .maxSlideItem(10) .callback(new ISchedule.OnItemClickListener() { @Override public void onItemClick(Component v, List scheduleList) { display(scheduleList); } }) .callback(new ISchedule.OnItemLongClickListener() { @Override public void onLongClick(Component v, int day, int start) { ToastViewDialog.toast(FlaglayoutSlice.this, "长按:周" + day + ",第" + start + "节"); } }) .callback(new ISchedule.OnFlaglayoutClickListener() { @Override public void onFlaglayoutClick(int day, int start) { mTimetableView.hideFlaglayout(); ToastViewDialog.toast(FlaglayoutSlice.this, "点击了旗标:周" + (day + 1) + ",第" + start + "节"); } }) .showView(); } ``` **背景修改与重置** ```java /** * 修改旗标布局的背景色 * * @param color */ private void modifyFlagBgcolor(RgbColor color) { mTimetableView.flagBgcolor(color).updateFlaglayout(); } /** * 重置旗标布局的背景色 */ private void resetFlagBgcolor() { mTimetableView.resetFlagBgcolor().updateFlaglayout(); } ``` **开启与关闭** ```java /** * 取消旗标布局 */ private void cancelFlagBgcolor() { mTimetableView.isShowFlaglayout(false).updateFlaglayout(); } /** * 显示旗标布局 */ private void resetFlaglayout() { mTimetableView.isShowFlaglayout(true).updateFlaglayout(); } ``` **显示与隐藏** ```java /** * 显示旗标布局 */ mTimetableView.showFlaglayout(); /** * 隐藏旗标布局 */ mTimetableView.hideFlaglayout(); ``` ##### 额外的数据 设置数据源时可以使用自定义的数据类型,但是必须实现ScheduleEnable接口,为什么呢?因为在TimetableView内部保存的数据格式是List的,接口只是起到一个转换的作用,点击事件会回调,会获取到点击位置的一个List集合,此时我们已经拿不到自定义的数据类型了,那么此时我想拿到这门课程的ID怎么办? 完整代码参见 ExtrasSlice **存入额外数据** Schedule中没有这个字段,自定义类型中有,所以在Schedule新增了一个extras字段,它是一个Map。所以现在这个问题可以这样解决: ```java import com.zhuangfei.timetable.model.Schedule; import com.zhuangfei.timetable.model.ScheduleEnable; import java.util.List; /** * 自定义实体类需要实现ScheduleEnable接口并实现getSchedule() * * @see ScheduleEnable#getSchedule() */ public class MySubject implements ScheduleEnable { public static final String EXTRAS_ID = "extras_id"; public static final String EXTRAS_AD_URL = "extras_ad_url"; private int id = 0; /** * 课程名 */ private String name; private String time; /** * 教室 */ private String room; /** * 教师 */ private String teacher; /** * 第几周至第几周上 */ private List weekList; /** * 开始上课的节次 */ private int start; /** * 上课节数 */ private int step; /** * 周几上 */ private int day; private String term; /** * 一个随机数,用于对应课程的颜色 */ private int colorRandom = 0; private String url; public void setUrl(String url) { this.url = url; } public String getUrl() { return url; } public MySubject() { } public void setTime(String time) { this.time = time; } public String getTime() { return time; } public void setTerm(String term) { this.term = term; } public String getTerm() { return term; } public MySubject(String term,String name, String room, String teacher, List weekList , int start, int step, int day, int colorRandom, String time) { super(); this.term = term; this.name = name; this.room = room; this.teacher = teacher; this.weekList = weekList; this.start = start; this.step = step; this.day = day; this.colorRandom = colorRandom; this.time = time; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getRoom() { return room; } public void setRoom(String room) { this.room = room; } public String getTeacher() { return teacher; } public void setTeacher(String teacher) { this.teacher = teacher; } public void setWeekList(List weekList) { this.weekList = weekList; } public List getWeekList() { return weekList; } public int getStart() { return start; } public void setStart(int start) { this.start = start; } public int getStep() { return step; } public void setStep(int step) { this.step = step; } public int getDay() { return day; } public void setDay(int day) { this.day = day; } public int getColorRandom() { return colorRandom; } public void setColorRandom(int colorRandom) { this.colorRandom = colorRandom; } @Override public Schedule getSchedule() { Schedule schedule = new Schedule(); schedule.setDay(getDay()); schedule.setName(getName()); schedule.setRoom(getRoom()); schedule.setStart(getStart()); schedule.setStep(getStep()); schedule.setTeacher(getTeacher()); schedule.setWeekList(getWeekList()); schedule.setColorRandom(2); schedule.putExtras(EXTRAS_ID,getId()); schedule.putExtras(EXTRAS_AD_URL,getUrl()); return schedule; } public void setId(int id) { this.id = id; } public int getId() { return id; } } ``` **读出额外数据** ```java private void initViews() { mTimetableView = (TimetableView) findComponentById(ResourceTable.Id_id_timetableView); mTimetableView.source(mySubjects) .curWeek(1) .maxSlideItem(10) .callback(new ISchedule.OnItemClickListener() { @Override public void onItemClick(Component v, List scheduleList) { display(scheduleList); } }) .showView(); } protected void display(List beans) { String str = ""; for (Schedule bean : beans) { str += "[" + bean.getName() + "]的id:" + bean.getExtras().get(MySubject.EXTRAS_ID) + "\n"; } ToastViewDialog.toast(this, str); } ``` 它是通过这样的方式拿到的额外数据: ```java String id=bean.getExtras().get(MySubject.EXTRAS_ID); ``` ##### 工具类 控件提供了一个工具类,可以方便的以无界面的方式操作课程数据,本节演示如何使用工具类实现对课程颜色的可视化展示 完整代码参见 NonViewSlice **列表与适配器** 在Slice的布局中放一个ListContainer ```xml ``` item_nonview.xml是ListView中每一项的布局,它的内容如下: ```xml ``` 布局文件准备好之后,需要一个适配器 ```xml import com.chinasoft.ohos.ResourceTable; import com.zhuangfei.timetable.model.Schedule; import com.zhuangfei.timetable.model.ScheduleColorPool; import ohos.agp.colors.RgbColor; import ohos.agp.components.*; import ohos.agp.components.element.ShapeElement; import ohos.app.Context; import java.util.List; /** * Created by Liu ZhuangFei on 2018/6/18. BaseAdapter */ public class NonViewAdapter extends BaseItemProvider { List schedules; Context context; LayoutScatter inflater; public NonViewAdapter(Context context, List schedules) { this.context = context; this.schedules = schedules; inflater = LayoutScatter.getInstance(context); } @Override public int getCount() { return schedules.size(); } @Override public Object getItem(int i) { return schedules.get(i); } @Override public long getItemId(int i) { return i; } @Override public Component getComponent(int i, Component convertView, ComponentContainer viewGroup) { Component mView = null; ViewHolder holder; if (null == convertView) { holder = new ViewHolder(); convertView = inflater.parse(ResourceTable.Layout_item_nonview,null,false); holder.nameTextView = (Text) convertView.findComponentById(ResourceTable.Id_id_nonview_name); holder.colorTextView = (Text) convertView.findComponentById(ResourceTable.Id_id_nonview_color); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } Schedule schedule = (Schedule) getItem(i); ScheduleColorPool colorPool = new ScheduleColorPool(context); holder.nameTextView.setText(schedule.getName()); RgbColor colorAuto = colorPool.getColorAuto(schedule.getColorRandom()); ShapeElement shapeElement = new ShapeElement(); shapeElement.setRgbColor(colorAuto); holder.colorTextView.setBackground(shapeElement); return convertView; } /** * ViewHolder * * @since 2021-03-29 */ class ViewHolder { Text nameTextView; Text colorTextView; } } ``` 它是如何将课程的颜色找出来的?看下段代码: ```xml Schedule schedule = (Schedule) getItem(i); ScheduleColorPool colorPool = new ScheduleColorPool(context); holder.nameTextView.setText(schedule.getName()); RgbColor colorAuto = colorPool.getColorAuto(schedule.getColorRandom()); ShapeElement shapeElement = new ShapeElement(); shapeElement.setRgbColor(colorAuto); holder.colorTextView.setBackground(shapeElement); ``` 继续看,核心就是这一行,适配器中的数据都是被分配过颜色了(怎么分配的见下文),所谓分配颜色就是给它一个编号,然后拿着编号到颜色池中取颜色,getColorAuto()不会产生数组越界问题,内部使用模运算来循环的在颜色池中取值 ```java RgbColor colorAuto = colorPool.getColorAuto(schedule.getColorRandom()); ``` 设置适配器 ```java schedules = new ArrayList<>(); listView = (ListContainer) findComponentById(ResourceTable.Id_id_listview); adapter = new NonViewAdapter(this, schedules); listView.setItemProvider(adapter); ``` 获取数据 ```java public List getData() { List list = ScheduleSupport.transform(SubjectRepertory.loadDefaultSubjects()); list = ScheduleSupport.getColorReflect(list); return list; } ``` **显示所有课程** ```java /** * 获取所有课程 */ protected void all() { schedules.clear(); schedules.addAll(getData()); adapter.notifyDataChanged(); } ``` **第一周有课的课程** ```java /** * 获取第一周有课的课程并显示出来 */ protected void haveTime() { List result = new ArrayList<>(); List[] arr = ScheduleSupport.splitSubjectWithDay(getData()); for (int i = 0; i < arr.length; i++) { List tmpList = arr[i]; for (Schedule schedule : tmpList) { if (ScheduleSupport.isThisWeek(schedule, 1)) { result.add(schedule); } } } schedules.clear(); schedules.addAll(result); adapter.notifyDataChanged(); } ``` **周一有课的课程** ```java /** * 显示第一周周一有课的课程 */ protected void haveTimeWithMonday() { List tmpList = ScheduleSupport.getHaveSubjectsWithDay( getData(), 1, 0); schedules.clear(); schedules.addAll(tmpList); adapter.notifyDataChanged(); } ``` #### 测试信息 CodeCheck代码测试无异常 CloudTest代码测试无异常 病毒安全检测通过 当前版本demo功能与原组件基本无差异 #### 版本迭代 - 1.0.0