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