3 Star 4 Fork 7

CHINASOFT_OHOS / TimetableView

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MIT

TimetableView

项目介绍

  • 项目名称:TimetableView超级课程表
  • 所属系列:openharmony的第三方组件适配移植
  • 功能:是一款开源的、完善、高效的openharmony课程表控件
  • 项目移植状态:主功能完成
  • 调用差异:无
  • 开发版本:sdk6,DevEco Studio 2.2 Beta1
  • 基线版本:Release v2.0.7-beta

支持的功能

  • 支持xml设置属性
  • 业务逻辑自定义
  • 课程颜色管理
  • ScrollView可替换
  • 可设置背景以及透明度
  • 数据源可添加额外信息
  • 空白格子可点击,课程项可长按
  • 月份宽度可设置
  • 周末可设置隐藏
  • 课表具体逻辑可自定义
  • 本地配置:配置隔离、加载、导出至文本
  • 课程颜色可指定

效果演示

演示图片

安装教程

1.在项目根目录下的build.gradle文件中,

allprojects {
   repositories {
       maven {
           url 'https://s01.oss.sonatype.org/content/repositories/releases/'
       }
   }
}

2.在entry模块的build.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中添加控件:


	<com.zhuangfei.timetable.view.WeekView
        ohos:id="$+id:id_weekview"
        ohos:width="match_parent"
        ohos:height="match_content"/>

    <com.zhuangfei.timetable.TimetableView
        ohos:id="$+id:id_timetableView"
        ohos:width="match_parent"
        ohos:height="match_parent"
        ohos:background_element="$color:app_white"/>
数据源设置

数据源的设置方式有两种,以下分别来介绍:

方法1:使用指定的格式List,Schedule是控件提供的课程实体类,你可以将自己的数据封装为指定格式,然后进行如下配置即可


	mTimetableView.data(scheduleList)
                .curWeek(1)
                .showView();

方法2:方法1在很多场景下都满足不了需求,往往需要定义自己的课程实体类,你可以跟随以下几个步骤来使用它

  • 创建自定义的实体类并实现ScheduleEnable接口

	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;
	}
}

	/**
	 * SimpleSlice
	 *
	 * @since 2021-03-29
	 */
	public class SimpleSlice extends AbilitySlice implements Component.ClickedListener {
	    /**
    	 * 控件
    	 */
    	TimetableView mTimetableView;
    	WeekView mWeekView;
	
    	DirectionalLayout layout;
    	Text titleTextView;
    	List<MySubject> 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<Integer> 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<Schedule> 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();
	    }
	
	    /**
	     * 周次选择布局的左侧被点击时回调<br/>
	     * 对话框修改当前周次
	     */
	    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<Schedule> 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属性 使用如下方式获取到控件

	TimetableView mTimetableView;
	WeekView mWeekView;

	/**
     * 初始化课程控件
     */
    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<Schedule> 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();
    }

删除课程


	/**
     * 删除课程
     * 内部使用集合维护课程数据,操作集合的方法来操作它即可
     * 最后更新一下视图(全局更新)
     */
    protected void deleteSubject() {
        int size = mTimetableView.dataSource().size();
        int pos = (int) (new SecureRandom().nextDouble() * size);
        if (size > 0) {
            mTimetableView.dataSource().remove(pos);
            mTimetableView.updateView();
        }
    }

添加课程


	/**
     * 添加课程
     * 内部使用集合维护课程数据,操作集合的方法来操作它即可
     * 最后更新一下视图(全局更新)
     */
    protected void addSubject() {
        List<Schedule> dataSource = mTimetableView.dataSource();
        int size = dataSource.size();
        if (size > 0) {
            Schedule schedule = dataSource.get(0);
            dataSource.add(schedule);
            mTimetableView.updateView();
        }
    }

非本周课程显示与隐藏


	/**
     * 隐藏非本周课程
     * 修改了内容的显示,所以必须更新全部(性能不高)
     * 建议:在初始化时设置该属性
     * <p>
     * updateView()被调用后,会重新构建课程,课程会回到当前周
     */
    protected void hideNonThisWeek() {
        mTimetableView.isShowNotCurWeek(false).updateView();
    }

    /**
     * 显示非本周课程
     * 修改了内容的显示,所以必须更新全部(性能不高)
     * 建议:在初始化时设置该属性
     */
    protected void showNonThisWeek() {
        mTimetableView.isShowNotCurWeek(true).updateView();
    }

最大节次设置


	/**
     * 设置侧边栏最大节次,只影响侧边栏的绘制,对课程内容无影响
     * @param num
     */
    protected void setMaxItem(int num) {
        mTimetableView.maxSlideItem(num).updateSlideView();
    }

节次时间显示与隐藏


    /**
     * 显示时间
     * 设置侧边栏构建监听,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显示与隐藏


    /**
     * 显示WeekView
     */
    protected void showWeekView() {
        mWeekView.isShow(true);
    }

    /**
     * 隐藏WeekView
     */
    protected void hideWeekView() {
        mWeekView.isShow(false);
    }

月份宽度设置


    /**
     * 设置月份宽度
     */
    private void setMonthWidth() {
        mTimetableView.monthWidthDp(100).updateView();
    }

    /**
     * 设置月份宽度,默认40dp
     */
    private void resetMonthWidth() {
        mTimetableView.monthWidthDp(80).updateView();
    }

周末显示与隐藏


    /**
     * 隐藏周末
     */
    private void hideWeekends() {
        mTimetableView.isShowWeekends(false).updateView();
    }

    /**
     * 显示周末
     */
    private void showWeekends() {
        mTimetableView.isShowWeekends(true).updateView();
    }
周次选择控件

周次选择栏WeekView是控件实现的一个默认的周次选择控件,你可以使用它快速的拥有周次选择功能,TimetableView`本身是没有周次选择功能的,所以需要两者配合使用,完整代码参见 BaseFuncSlice

默认的周次选择栏

1.添加控件

在布局文件中放一个TimetableView,然后在TimetableView的上边放一个WeekView


	<com.zhuangfei.timetable.view.WeekView
        ohos:id="$+id:id_weekview"
        ohos:width="match_parent"
        ohos:height="match_content"/>

    <com.zhuangfei.timetable.TimetableView
        ohos:id="$+id:id_timetableView"
        ohos:width="match_parent"
        ohos:height="match_parent"
        ohos:background_element="$color:app_white"/>

2.初始化 我直接把这部分整个代码放出来了,分以下2步:

  1. 获取控件
  2. 设置WeekView属性

使用如下方式获取到控件


	WeekView weekView;

周次选择控件应该与课程表控件结合使用,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();

自定义周次选择栏

你可以选择以下方法来实现周次选择栏的自定义:

  • 任意定制,因为周次选择栏和课表是没有关系的,所以你可以任意实现
  • 遵循我定义的周次选择栏规范,实现 WeekViewEnable 接口
  • 扩展默认的周次选择栏实现,继承自 WeekView ,重新方法
日期栏样式

本节主要演示如何对日期栏的属性设置以及自定义日期栏的步骤,通用步骤比如:添加控件、获取控件,设置数据源以及显示视图什么的都不再重复了,只讲解核心部分 完整代码参见 DateSlice

日期栏显示与隐藏


    /**
     * 隐藏日期栏
     */
    protected void hideDateView() {
        mTimetableView.hideDateView();
    }

    /**
     * 显示日期栏
     */
    protected void showDateView() {
        mTimetableView.showDateView();
    }

恢复默认日期栏


    /**
     * 恢复默认日期栏
     */
    protected void cancelCustomDateView() {
        mTimetableView.callback((ISchedule.OnDateBuildListener) null)
                .updateDateView();
    }

自定义日期栏

Step1:自定义布局

需要定义两个布局,第一个xml定义的月份的样式


	<?xml version="1.0" encoding="utf-8"?>
	<Text
	    ohos:id="$+id:id_week_month"
	    ohos:width="match_content"
	    ohos:height="match_content"
	    ohos:text_alignment="vertical_center|horizontal_center"
	    ohos:text_size="12fp"
	    xmlns:ohos="http://schemas.huawei.com/res/ohos"/>

第二个xml定义的是星期一至星期日的每项的样式


	<?xml version="1.0" encoding="utf-8"?>
	<DirectionalLayout
	
	    xmlns:ohos="http://schemas.huawei.com/res/ohos"
	    ohos:id="$+id:id_week_layout"
	    ohos:height="40vp"
	    ohos:width="match_content"
	    ohos:alignment="horizontal_center|vertical_center"
	    ohos:orientation="vertical"
	   >
	
	    <Text
	        ohos:id="$+id:id_week_day"
	        ohos:height="match_content"
	        ohos:width="match_content"
	        ohos:text_size="12vp"
	        ohos:text_font="$string:textfront_bold"/>
	</DirectionalLayout>

Step2:自定义实现类

注意:由于要修改默认的height,所以onBuildDayLayout onBuildMonthLayout都必须重写并设置为新的height,否则无效。

你也可以直接实现OnDateBuildListener接口,更灵活但是操作复杂。


	/**
     * 自定义日期栏
     * 该段代码有点长,但是很好懂,仔细看看会有收获的,嘻嘻
     */
    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();
    }

日期延迟显示案例

需求:设定一个开学时间,在开学时间到来之前,一直显示开学时的第一周的日期

我们使用自定义日期栏来实现这个需求

  • 第一步:自定义日期栏

需要设置一个阈值以及一个默认的日期集合,如果当前时间戳小于阈值时,使用默认的日期集合,否则计算当前周的日期,所以这个就很简单了


	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<String> initDates = null;
	
	    public OnDateDelayAdapter() {
	        sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
	    }
	
	    /**
	     * 设置日期集合
	     *
	     * @param dates 元素个数必须大于等于8,第一个为月份数值,第2-8为周一至周日的日期数值(不带中文)
	     */
	    public void setDateList(List<String> 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示例,并且对其初始化


	/**
     * 配置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<String> dateList = Arrays.asList("9", "03", "04", "05", "06", "07", "08", "09");

        onDateDelayAdapter.setStartTime(startTime);
        onDateDelayAdapter.setDateList(dateList);
        return onDateDelayAdapter;
    }

然后将OnDateDelayAdapter实例设置到TimetableView控件上


	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();

其中这两句代码可以用来计算距离开学的日期


    OnDateDelayAdapter adapter= (OnDateDelayAdapter) mTimetableView.onDateBuildListener();
    long when=adapter.whenBeginSchool();

为了保证日期能够更正,你还需要在onStart中对日期更正


	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

准备

添加控件


	<com.zhuangfei.timetable.view.WeekView
        ohos:id="$+id:id_weekview"
        ohos:width="match_parent"
        ohos:height="match_content"/>

    <com.zhuangfei.timetable.TimetableView
        ohos:id="$+id:id_timetableView"
        ohos:width="match_parent"
        ohos:height="match_parent"
        ohos:background_element="$color:app_white"/>

获取控件


    private void initViews() {
        mTimetableView = (TimetableView) findComponentById(ResourceTable.Id_id_timetableView);

        List<MySubject> mySubjects = SubjectRepertory.loadDefaultSubjects();
        mTimetableView.source(mySubjects)
                .curWeek(1)
                .showView();
    }

节次时间显示与隐藏


    /**
     * 显示时间
     * 设置侧边栏构建监听,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();
    }

修改侧边栏背景


    /**
     * 修改侧边栏背景,默认的使用的是OnSlideBuildAdapter,
     * 所以可以强转类型
     *
     * @param color
     */
    protected void modifySlideBgColor(RgbColor color) {
        OnSlideBuildAdapter listener = (OnSlideBuildAdapter) mTimetableView.onSlideBuildListener();
        listener.setBackground(color);
        mTimetableView.updateSlideView();
    }

修改节次文本颜色


    /**
     * 修改侧边栏节次文本的颜色值
     *
     * @param color
     */
    protected void modifyItemTextColor(int color) {
        OnSlideBuildAdapter listener = (OnSlideBuildAdapter) mTimetableView.onSlideBuildListener();
        listener.setTextColor(color);
        mTimetableView.updateSlideView();
    }

修改时间文本颜色


    /**
     * 修改侧边栏时间文本的颜色值
     *
     * @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();
    }

侧边栏效果重置


    /**
     * 取消自定义的侧边栏,回到默认状态
     * 只需要将监听器置空即可
     */
    protected void cancelCustomSlideView() {
        mTimetableView.callback((ISchedule.OnSlideBuildListener) null)
                .updateSlideView();
    }

自定义侧边栏

Step1:创建布局

创建一个XML文件,该文件的内容可完全自定义


	<?xml version="1.0" encoding="utf-8"?>
	<DirectionalLayout
	    ohos:width="match_parent"
	    ohos:height="match_content"
	    ohos:orientation="vertical"
	    xmlns:ohos="http://schemas.huawei.com/res/ohos">
	    <Text
	        ohos:text_color="$color:app_qing2"
	        ohos:width="match_parent"
	        ohos:height="match_content"
	        ohos:text_alignment="top|horizontal_center"
	        ohos:id="$+id:item_slide_textview"
	        ohos:text_size="14fp"/>
	</DirectionalLayout>

Step2:设置监听

为了简单起见,可以继承自OnSlideBuildAdapter,重写getView()方法,当然也可以直接实现ISchedule.OnSlideBuildListener接口


    /**
     * 自定义侧边栏效果
     * 使用自定义的布局文件实现的文字居顶部的效果(默认居中)
     */
    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

非本周课程显示与隐藏


    /**
     * 隐藏非本周课程
     * 修改了内容的显示,所以必须更新全部(性能不高)
     * 建议:在初始化时设置该属性
     */
    protected void hideNonThisWeek() {
        mTimetableView.isShowNotCurWeek(false).updateView();
    }

    /**
     * 显示非本周课程
     * 修改了内容的显示,所以必须更新全部(性能不高)
     * 建议:在初始化时设置该属性
     */
    protected void showNonThisWeek() {
        mTimetableView.isShowNotCurWeek(true).updateView();
    }

设置间距以及弧度


    /**
     * 设置间距以及弧度
     * 该方法只能同时设置四个角的弧度,设置单个角的弧度可参考下文
     */
    protected void setMarginAndCorner() {
        mTimetableView.cornerAll(0)
                .marTop(0)
                .marLeft(0)
                .updateView();
    }

设置单个角弧度


    /**
     * 设置角度(四个角分别控制)
     *
     * @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();
    }

修改显示的文本


    /**
     * 修改显示的文本
     */
    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();
    }

设置非本周课的背景


    /**
     * 设置非本周课的背景
     *
     * @param color
     */
    public void setNonThisWeekBgcolor(RgbColor color) {
        mTimetableView.colorPool().setUselessColor(color);
        mTimetableView.updateView();
    }

课程重叠的样式


    /**
     * 修改课程重叠的样式,在该接口中,你可以自定义出很多的效果
     */
    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掉,然后添加一个广告图片,再给它设置一个点击事件


	/**
     * 构造数据集
     */
    mySubjects = SubjectRepertory.loadDefaultSubjects();
    MySubject adSubject = new MySubject();
    adSubject.setName("【广告】");
    adSubject.setStart(1);
    adSubject.setStep(2);
    adSubject.setDay(7);
    List<Integer> 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<Schedule> 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:英文日期栏


	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:英文课程项


	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:设置监听


    /**
     * 切换为英文
     */
    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实例对象


	mTimetableView.colorPool();

指定颜色

如果需要让所有课程只在某几种颜色中分配颜色,只需要清空颜色池并加入特定的颜色,在分配颜色时会循环分配颜色池中的颜色


    /**
     * 设置指定的颜色,默认情况下颜色池中有一些颜色
     * 所以这里需要先清空一下颜色池
     *
     * @param colors
     */
    public void setColor(RgbColor... colors) {
        mTimetableView.colorPool().clear().add(colors);
        mTimetableView.updateView();
    }

重置颜色池

重置后,颜色池恢复到初始状态


    /**
     * 重置颜色池
     */
    public void resetColor() {
        mTimetableView.colorPool().reset();
        mTimetableView.updateView();
    }

追加颜色

向颜色池中追加颜色,在为课程项分配颜色时,会按照颜色池中的顺序依次取出颜色,所以并不保证追加的颜色一定会被用到


    /**
     * 追加颜色
     *
     * @param colors
     */
    public void addColor(RgbColor... colors) {
        mTimetableView.colorPool().add(colors);
        mTimetableView.updateView();
    }

设置非本周课程颜色

向颜色池中追加颜色,在为课程项分配颜色时,会按照颜色池中的顺序依次取出颜色,所以并不保证追加的颜色一定会被用到


    /**
     * 设置非本周课的背景
     *
     * @param color
     */
    public void setNonThisWeekBgcolor(int color) {
        mTimetableView.colorPool().setUselessColor(color);
        mTimetableView.updateView();
    }

指定课程的颜色

setIgnoreUserlessColor 用来设置颜色映射是否忽略非本周的课程颜色,如果设置为true,表示该课程在颜色映射中能够查找到,那么不管是本周还是非本周它都将显示映射中的颜色;如果设置为false,表示如果该课程在颜色映射中能够查找到,并且该课程是本周的,那么将该课程设置为映射中的颜色,如果非本周,则使用非本周颜色对其渲染


    /**
     * 指定课程的颜色,未指定的课程自动分配
     */
    public void forColor() {
        Map<String, Integer> 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


	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
	 * <p>
	 * 弹性滚动布局,下拉时会反弹
	 *
	 * @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 version="1.0" encoding="utf-8"?>
	<com.chinasoft.ohos.views.ElasticScrollView
	    xmlns:ohos="http://schemas.huawei.com/res/ohos"
	    ohos:id="$+id:id_scrollview"
	    ohos:width="match_parent"
	    ohos:height="match_parent">
	
	    <include ohos:layout="$layout:view_content"
	             ohos:height="match_content"
	             ohos:width="match_parent"/>
	
	</com.chinasoft.ohos.views.ElasticScrollView>

设置监听


    private void initViews() {
        mTimetableView = (TimetableView) findComponentById(ResourceTable.Id_id_timetableView);
        List<MySubject> 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来监听旗标布局的点击事件即可,无需关心其他

以下代码中监听了三个事件:

  • 课程项的单击
  • 课程项的长按
  • 空白格子的单击(默认)以及旗标布局的单击

    private void initViews() {
        mTimetableView = (TimetableView) findComponentById(ResourceTable.Id_id_timetableView);
        List<MySubject> mySubjects = SubjectRepertory.loadDefaultSubjects();
        mTimetableView.source(mySubjects)
                .curWeek(1)
                .maxSlideItem(10)
                .callback(new ISchedule.OnItemClickListener() {
                    @Override
                    public void onItemClick(Component v, List<Schedule> 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();
    }

背景修改与重置


    /**
     * 修改旗标布局的背景色
     *
     * @param color
     */
    private void modifyFlagBgcolor(RgbColor color) {
        mTimetableView.flagBgcolor(color).updateFlaglayout();
    }

    /**
     * 重置旗标布局的背景色
     */
    private void resetFlagBgcolor() {
        mTimetableView.resetFlagBgcolor().updateFlaglayout();
    }

开启与关闭


    /**
     * 取消旗标布局
     */
    private void cancelFlagBgcolor() {
        mTimetableView.isShowFlaglayout(false).updateFlaglayout();
    }

    /**
     * 显示旗标布局
     */
    private void resetFlaglayout() {
        mTimetableView.isShowFlaglayout(true).updateFlaglayout();
    }

显示与隐藏


	/**
     * 显示旗标布局
     */
	mTimetableView.showFlaglayout();
	
	/**
     * 隐藏旗标布局
     */
	mTimetableView.hideFlaglayout();
额外的数据

设置数据源时可以使用自定义的数据类型,但是必须实现ScheduleEnable接口,为什么呢?因为在TimetableView内部保存的数据格式是List的,接口只是起到一个转换的作用,点击事件会回调,会获取到点击位置的一个List集合,此时我们已经拿不到自定义的数据类型了,那么此时我想拿到这门课程的ID怎么办? 完整代码参见 ExtrasSlice

存入额外数据

Schedule中没有这个字段,自定义类型中有,所以在Schedule新增了一个extras字段,它是一个Map。所以现在这个问题可以这样解决:


	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<Integer> 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<Integer> 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<Integer> weekList) {
			this.weekList = weekList;
		}
		
		public List<Integer> 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;
		}
	}

读出额外数据


    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<Schedule> scheduleList) {
                        display(scheduleList);
                    }
                })
                .showView();
    }

    protected void display(List<Schedule> beans) {
        String str = "";
        for (Schedule bean : beans) {
            str += "[" + bean.getName() + "]的id:" + bean.getExtras().get(MySubject.EXTRAS_ID) + "\n";
        }
        ToastViewDialog.toast(this, str);
    }

它是通过这样的方式拿到的额外数据:


	String id=bean.getExtras().get(MySubject.EXTRAS_ID);
工具类

控件提供了一个工具类,可以方便的以无界面的方式操作课程数据,本节演示如何使用工具类实现对课程颜色的可视化展示 完整代码参见 NonViewSlice

列表与适配器

在Slice的布局中放一个ListContainer


    <ListContainer
        ohos:id="$+id:id_listview"
        ohos:width="match_parent"
        ohos:height="match_parent"
        ohos:orientation="vertical"></ListContainer>

item_nonview.xml是ListView中每一项的布局,它的内容如下:


	<?xml version="1.0" encoding="utf-8"?>
	<DependentLayout
	    xmlns:ohos="http://schemas.huawei.com/res/ohos"
	    ohos:height="match_content"
	    ohos:width="match_parent"
	    ohos:orientation="horizontal">
	
	    <Text
	        ohos:id="$+id:id_nonview_name"
	        ohos:height="50vp"
	        ohos:width="match_content"
	        ohos:align_baseline="end"
	        ohos:align_parent_left="true"
	        ohos:left_margin="15vp"
	        ohos:max_text_lines="1"
	        ohos:right_margin="15vp"
	        ohos:text_alignment="vertical_center"
	        ohos:text_size="12fp"
	        ohos:truncation_mode="ellipsis_at_end"
	        ohos:vertical_center="true"/>
	
	    <Text
	        ohos:id="$+id:id_nonview_color"
	        ohos:height="30vp"
	        ohos:width="30vp"
	        ohos:align_parent_right="true"
	        ohos:right_margin="15vp"
	        ohos:vertical_center="true"/>
	
	    <DirectionalLayout
	        ohos:height="1px"
	        ohos:width="match_parent"
	        ohos:alignment="bottom"
	        ohos:background_element="$color:colorCg"/>
	
	
	
	</DependentLayout>

布局文件准备好之后,需要一个适配器


	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<Schedule> schedules;
	    Context context;
	    LayoutScatter inflater;
	
	    public NonViewAdapter(Context context, List<Schedule> 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;
	    }
	}

它是如何将课程的颜色找出来的?看下段代码:


	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()不会产生数组越界问题,内部使用模运算来循环的在颜色池中取值


	RgbColor colorAuto = colorPool.getColorAuto(schedule.getColorRandom());

设置适配器


	schedules = new ArrayList<>();
    listView = (ListContainer) findComponentById(ResourceTable.Id_id_listview);
    adapter = new NonViewAdapter(this, schedules);
    listView.setItemProvider(adapter);

获取数据


    public List<Schedule> getData() {
        List<Schedule> list = ScheduleSupport.transform(SubjectRepertory.loadDefaultSubjects());
        list = ScheduleSupport.getColorReflect(list);
        return list;
    }

显示所有课程


    /**
     * 获取所有课程
     */
    protected void all() {
        schedules.clear();
        schedules.addAll(getData());
        adapter.notifyDataChanged();
    }

第一周有课的课程


    /**
     * 获取第一周有课的课程并显示出来
     */
    protected void haveTime() {
        List<Schedule> result = new ArrayList<>();
        List<Schedule>[] arr = ScheduleSupport.splitSubjectWithDay(getData());
        for (int i = 0; i < arr.length; i++) {
            List<Schedule> tmpList = arr[i];
            for (Schedule schedule : tmpList) {
                if (ScheduleSupport.isThisWeek(schedule, 1)) {
                    result.add(schedule);
                }
            }
        }
        schedules.clear();
        schedules.addAll(result);
        adapter.notifyDataChanged();
    }

周一有课的课程


    /**
     * 显示第一周周一有课的课程
     */
    protected void haveTimeWithMonday() {
        List<Schedule> tmpList = ScheduleSupport.getHaveSubjectsWithDay(
                getData(), 1, 0);
        schedules.clear();
        schedules.addAll(tmpList);
        adapter.notifyDataChanged();
    }

测试信息

CodeCheck代码测试无异常

CloudTest代码测试无异常

病毒安全检测通过

当前版本demo功能与原组件基本无差异

版本迭代

  • 1.0.0
MIT License Copyright (c) 2017 壮飞 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

简介

一款开源的、完善、高效的openharmony课程表控件 展开 收起
Java
MIT
取消

贡献者

全部

近期动态

加载更多
不能加载更多了
Java
1
https://gitee.com/chinasoft_ohos/TimetableView.git
git@gitee.com:chinasoft_ohos/TimetableView.git
chinasoft_ohos
TimetableView
TimetableView
master

搜索帮助