1. 介绍
本篇codelab主要由2*2和2*4布局的电影卡片、主页(电影排行榜单)和电影详情页组成,功能包括卡片的定时刷新和相关卡片的创建,删除,主动刷新相关回调函数的使用以及事件的控制,还介绍了电影首页的布局以及如何携带参数跳转电影详情页,最后介绍了电影详情页的布局以及电影剧情介绍的展开和收起。效果如下图:
最终效果预览
我们最终会构建一个基于Java代码的电影卡片手机客户端。效果如下图所示。本篇Codelab我们将会一起完成这个客户端,其中包括:
- 页面未初始化布局
- 页面初始化布局
- 应用初始化
- 卡片的刷新
2. 搭建HarmonyOS环境
我们首先需要完成HarmonyOS开发环境搭建,可参照如下步骤进行。
- 安装DevEco Studio,详情请参考下载和安装软件。
- 设置DevEco Studio开发环境,DevEco Studio开发环境需要依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:
- 如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作。
- 如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考配置开发环境。
- 开发者可以参考以下链接,完成设备调试的相关配置:
3. 代码结构解读
本篇Codelab只对核心代码进行讲解,对于完整代码,我们会在9 参考中提供下载方式,接下来我们会用一小节来讲解整个工程的代码结构:
- annotation包:Bind是一个自定义注解,用来初始化页面中的组件。
- database包:存放对象关系数据库主要组件,具体使用方法可考官方文档,对象关系映射数据库
- provider包: 主要作用为高效传递和使用相关数据。
- slice包:MainAbilitySlice是主要代码逻辑实现的一个类,同时对应我们的电影首页,MoviesDetailAbilitySlice对应电影详情页。
- util包:存放所有封装好的公共方法,如LogUtils等。
- resources目录:存放工程使用到的资源文件,其中resourcesbaselayout下存放xml布局文件;resourcesbasemedia下存放图片资源,resources/rawfile存放电影说明文件资源。
- config.json:工程相关配置文件。
4. 页面布局
卡片布局
卡片布局分为2*2和2*4两种布局,由于2*4中包含2*2的元素,且包含更多的功能,下面将以2*4布局卡片来介绍。
2*4布局对应的xml文件名为form_list_pattern_form_card_2_4.xml,可分为上下两部分,其中上部分显示标题,下部分用于展示电影的图片及说明。当手机重启之后不点击应用和卡片,卡片为未初始化状态。效果图如下:
标题部分由两个Text组件组成,分别作为标题和完整榜单的点击事件,示例代码如下:
- <DependentLayout
- ohos:height="40vp"
- ohos:width="match_parent"
- ohos:orientation="horizontal"
- >
- <Text
- ohos:id="$+id:title"
- ohos:height="match_parent"
- ohos:width="100vp"
- ohos:end_margin="12vp"
- ohos:start_margin="44vp"
- ohos:text="$string:title"
- ohos:text_color="#E5000000"
- ohos:text_size="16fp"
- ohos:text_weight="500"
- ohos:bottom_margin="5vp"
- ohos:top_margin="10vp"/>
-
- <Text
- ohos:id="$+id:rankings"
- ohos:height="match_parent"
- ohos:width="100vp"
- ohos:align_parent_end="true"
- ohos:right_margin="35vp"
- ohos:text="$string:all_rankings"
- ohos:text_color="#E5000000"
- ohos:text_size="16fp"
- ohos:text_alignment="center"
- ohos:background_element="$graphic:background_button"
- ohos:bottom_margin="5vp"
- ohos:text_weight="500"
- ohos:top_margin="10vp"/>
- </DependentLayout>
复制代码 当手机重启,应用未启动时卡片给出提示用户点击卡片刷新数据,布局对应xml文件如下:
- <DirectionalLayout
- ohos:id="$+id:card4_hints"
- ohos:height="match_content"
- ohos:width="match_parent"
- ohos:center_in_parent="true"
- ohos:visibility="visible">
- <Text
- ohos:height="match_content"
- ohos:width="match_parent"
- ohos:text_alignment="center"
- ohos:text="$string:init_data">
- </Text>
- </DirectionalLayout>
复制代码 当卡片数据刷新后,布局对应xml文件如下:
- <DirectionalLayout
- ohos:id="$+id:card4_content"
- ohos:height="match_content"
- ohos:width="match_parent"
- ohos:align_parent_bottom="true"
- ohos:bottom_margin="12vp"
- ohos:end_margin="12vp"
- ohos:orientation="horizontal"
- ohos:start_margin="24vp"
- ohos:top_margin="12vp"
- ohos:visibility="invisible">
-
- ...
-
- </DirectionalLayout>
复制代码 卡片内容区域分为左右两个布局,用于展示电影信息,其中包含电影的图片,电影名以及介绍,对应xml文件如下:
- <DirectionalLayout
- ohos:id="$+id:movie_bottom_2x4"
- ohos:height="match_parent"
- ohos:width="0vp"
- ohos:left_margin="5vp"
- ohos:orientation="vertical"
- ohos:weight="4">
-
- <DependentLayout
- ohos:height="70vp"
- ohos:width="130vp">
-
- <Image
- ohos:id="$+id:movie_bottom_img_2x4"
- ohos:height="match_parent"
- ohos:width="match_parent"
- ohos:scale_mode="stretch"
- />
- </DependentLayout>
-
- <DirectionalLayout
- ohos:height="match_content"
- ohos:width="match_content"
- ohos:layout_alignment="vertical_center"
- ohos:left_margin="10vp"
- ohos:orientation="vertical">
-
- <Text
- ohos:id="$+id:movie_bottom_title_2x4"
- ohos:height="match_content"
- ohos:width="match_parent"
- ohos:text_color="#E5000000"
- ohos:text_size="14fp"
- ohos:text_weight="500"
- ohos:truncation_mode="ellipsis_at_end"/>
-
- <Text
- ohos:id="$+id:movie_bottom_introduction_2x4"
- ohos:height="match_content"
- ohos:width="match_parent"
- ohos:text_color="#99000000"
- ohos:text_size="10fp"
- ohos:text_weight="400"
- ohos:top_margin="2vp"
- ohos:truncation_mode="ellipsis_at_end"/>
- </DirectionalLayout>
- </DirectionalLayout>
复制代码 电影首页布局
对应的xml文件名为ability_main.xml,可分为上下2大块,其中上半部分显示标题及说明,由DirectionalLayout布局和两个Text组件组成;下半部分展示电影排行榜由ScrollView和ListContainer组件组成。
效果图如下:
上半部分示例代码如下:
- <DirectionalLayout
- ohos:height="200vp"
- ohos:width="match_parent"
- ohos:background_element="$media:movies_bg"
- ohos:scrollbar_background_color="#FF262525"
- ohos:orientation="vertical">
-
- <Text
- ohos:height="50vp"
- ohos:width="match_content"
- ohos:text_size="25fp"
- ohos:layout_alignment="center"
- ohos:text_alignment="center"
- ohos:text_color="#FF7E7A7A"
- ohos:text="$string:movie_rank"/>
-
- <Text
- ohos:height="match_content"
- ohos:width="match_content"
- ohos:text_size="15fp"
- ohos:layout_alignment="center"
- ohos:multiple_lines="true"
- ohos:max_text_lines="3"
- ohos:text_color="#FF0FE570"
- ohos:text="$string:movie_rank_detail"/>
- </DirectionalLayout>
复制代码 中部滑动区域对应的xml文件如下:
- <ScrollView
- ohos:height="match_parent"
- ohos:width="match_parent">
- <ListContainer
- ohos:id="$+id:movies_container"
- ohos:width="match_parent"
- ohos:height="match_parent"/>
- </ScrollView>
复制代码 用于ListContainer的item(item_movies_layout.xml)示例代码如下:
电影详情页布局
电影详情页主要展示电影的图片,演员表,剧照,介绍的信息效果图如下:
对应的xml文件名为movies_detail_layout.xml,其中包含xml布局文件如下:
5. 应用初始化
当我们首次安装应用打开时会进入到类MainAbility的onStart(Intent intent)方法,进入电影首页会调用MainAbilitySlice类的onStart()方法,进入电影详情页会进入MoviesDetailAbilitySlice类的onStart()方法,依次进行了如下流程:
MainAbility
初始化数据,从json文件中读取电影信息并写入对象关系映射数据库,示例代码如下:
- /**
- * initData
- */
- private void initData() {
- // 查询数据库是否有数据
- Long movieCount = DatabaseUtils.queryMovieCount(this, null);
- if (movieCount == 0) {
- saveMovieInfo(CommonUtils.getMoviesData(this));
- }
- }
-
- /**
- * insert movies
- * [url=home.php?mod=space&uid=3142012]@param[/url] movieInfoList movie list
- */
- private void saveMovieInfo(List<MovieInfo> movieInfoList) {
- if (movieInfoList.size() < 1 || movieInfoList == null) {
- return;
- }
- DatabaseUtils.deletMovie(this, null);
- for (int index = 0; index < movieInfoList.size(); index++) {
- MovieInfo movieInfo = movieInfoList.get(index);
- DatabaseUtils.insertMovieInfo(this, movieInfo);
- }
- }
复制代码 初始化InnerEvent对象用于定时刷新卡片,示例代码如下:
- private void initMyHandler() {
- myHandler = new MyEventHandler(EventRunner.current());
- long param = 0L;
- Object object = null;
- InnerEvent normalInnerEvent = InnerEvent.get(EVENT_MESSAGE_NORMAL, param, object);
- myHandler.sendEvent(normalInnerEvent, 0, EventHandler.Priority.IMMEDIATE);
- }
- /**
- * MyEventHandler
- */
- class MyEventHandler extends EventHandler {
- MyEventHandler(EventRunner runner) throws IllegalArgumentException {
- super(runner);
- }
-
- [url=home.php?mod=space&uid=2735960]@Override[/url]
- protected void processEvent(InnerEvent event) {
- super.processEvent(event);
- if (event == null) {
- return;
- }
- int eventId = event.eventId;
- switch (eventId) {
- case EVENT_MESSAGE_NORMAL:
- invokeScheduleMethod();
- break;
- default:
- break;
- }
- }
- }
复制代码 启动更新卡片的定时任务,示例代码如下:
- private void invokeScheduleMethod() {
- Timer timer = new Timer();
- timer.schedule(
- new TimerTask() {
- /**
- * 定时更新卡片
- */
- public void run() {
- getUITaskDispatcher().syncDispatch(() -> {
- refeshData();
- });
- }
- },
- 0, PERIOD
- );
- }
复制代码 更新卡片信息,示例代码如下:
- private void refeshData() {
- // 获取卡片集合
- List<FormInfo> formList = DatabaseUtils.queryForms(this, null);
- // 查询电影集合
- List<MovieInfo> movieInfoList = DatabaseUtils.queryMovieList(this, null);
- if (formList == null && formList.size() < 1) {
- return;
- }
- for (FormInfo formInfo : formList) {
- ComponentProvider componentProvider = getComponentProvider(movieInfoList, formInfo.getDimension());
- try {
- initCardContent(componentProvider); // 隐藏提示信息 显示卡片内容
- updateForm(formInfo.getFormId(), componentProvider);
- } catch (FormException e) {
- LogUtils.error("FormException", "FormException");
- }
- }
- }
复制代码 隐藏提示信息,显示卡片内容,示例代码如下:
- private void initCardContent (ComponentProvider componentProvider) {
- // 设置2*2卡片
- componentProvider.setVisibility(ResourceTable.Id_card2_hints, Component.INVISIBLE);
- componentProvider.setVisibility(ResourceTable.Id_card2_content, Component.VISIBLE);
- // 设置2*4卡片
- componentProvider.setVisibility(ResourceTable.Id_card4_hints, Component.INVISIBLE);
- componentProvider.setVisibility(ResourceTable.Id_card4_content, Component.VISIBLE);
- }
复制代码
MainAbilitySlice
主页面,主要展示电影排行榜(电影列表),其中电影列表的显示的示例代码如下:
- private void initMoviesContainer() {
- // 获取数据库连接
- helper = new DatabaseHelper(this);
- connect = helper.getOrmContext("MovieDatabase", "MovieDatabase.db", MovieDatabase.class);
- // 查询电影列表
- movieInfoList = DatabaseUtils.queryMovieList(this, null);
- // 创建MoviesListProvider对象,用于列表的展示
- moviesListProvider = new MoviesListProvider(movieInfoList, this);
- moviesListContainer.setItemProvider(moviesListProvider);
- }
复制代码 给列表item设置点击事件,即点击跳转到对应电影的详情页,示例代码如下:
- private void initListener() {
- moviesListContainer.setItemClickedListener((listContainer, component, position, id) -> {
- getUITaskDispatcher().asyncDispatch(new Runnable() {
- @Override
- public void run() {
- // 跳转到电影详情页,携带参数,电影id
- toAnotherPage(position);
- }
- });
- });
- }
复制代码
MoviesDetailAbilitySlice
电影详情页,用于显示电影的详细信息,如电影介绍,演员表,剧照等。获取进入页面携带的参数并查询电影信息,示例代码如下:
- public void initParam(Intent intent) {
- // 获取携带的电影id参数
- if (intent.hasParameter("movieId")) {
- Long movieId = intent.getLongParam("movieId", 0);
- if (movieId == 0) {
- return;
- }
- // 根据电影id查询电影信息
- List<MovieInfo> movies = DatabaseUtils.queryMovieList(this, movieId);
- if (movies.size() <= 0) {
- return;
- }
- // 给各组件赋值
- MovieInfo movieInfo = movies.get(0);
- title = movieInfo.getTitle();
- image = movieInfo.getImgUrl();
- rating = movieInfo.getRating();
- type = movieInfo.getType();
- introduction = movieInfo.getIntroduction();
- commentCount = movieInfo.getCommentcount();
- }
- }
复制代码 给各组件赋值,示例代码如下:
- private void initView() {
- initViewAnnotation();
- moviesRating.setText(rating);
- moviesCommentCount.setText("电影类型: " + type);
- moviesTitle.setText(title);
- moviesImage.setPixelMap(CommonUtils.getPixelMapFromPath(this, image, WIDTH, HIGHT).get());
- moviesImage.setCornerRadius(RADIUS);
- setText(introduction);
- mExpansionButton.setVisibility(VISIBLE);
- mExpansionButton.setClickedListener(va -> {
- toggleExpansionStatus();
- });
- }
复制代码 电影介绍的展开与收起,示例代码如下:
- private void toggleExpansionStatus() {
- mIsExpansion = !mIsExpansion;
- if (mIsExpansion) {
- mExpansionButton.setText("收起");
- moviesIntroduction.setMaxTextLines(Integer.MAX_VALUE);
- } else {
- mExpansionButton.setText("全文");
- moviesIntroduction.setMaxTextLines(MMAXLINE);
- }
- }
复制代码
6. 卡片的相关回调 创建卡片事件的回调,示例代码如下:
- @Override
- protected ProviderFormInfo onCreateForm(Intent intent) {
- // 卡片名称
- String formName = intent.getStringParam(AbilitySlice.PARAM_FORM_NAME_KEY);
- // 卡片id
- Long formId = intent.getLongParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY, 0);
- // 卡片规格
- int dimension = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, DEFAULT_DIMENSION_2X2);
- // 将卡片信息存入数据库
- saveFormInfo(formId, formName, dimension);
- // 创建卡片展示信息
- ProviderFormInfo formInfo = null;
- int layoutId = ResourceTable.Layout_form_list_pattern_form_card_2_2;
- if (dimension == DIMENSION_2X4) {
- layoutId = ResourceTable.Layout_form_list_pattern_form_card_2_4;
- }
- formInfo = new ProviderFormInfo(layoutId, this);
- List<MovieInfo> movieInfoList = DatabaseUtils.queryMovieList(this, null);
- ComponentProvider componentProvider = getComponentProvider(movieInfoList, dimension);
- initCardContent(componentProvider); // 隐藏提示信息 显示卡片内容
- formInfo.mergeActions(componentProvider);
- return formInfo;
- }
复制代码其中可通过componentProvider 的setIntentAgent方法给卡片上的组件设置点击事件,第一个参数为组件的资源id,第二个参数为IntentAgent对象,示例代码如下:
- componentProvider.setIntentAgent(resourceIds.get(RESOUECE_AGENT_ID),
-
- getIntentAgent(movieInfo));
复制代码获取IntentAgent对象,示例代码如下:
- private IntentAgent getIntentAgent(MovieInfo movie) {
- // 设置点击的跳转页面
- Intent intent = new Intent();
- Operation operation = new Intent.OperationBuilder()
- .withDeviceId("")
- .withBundleName(getBundleName())
- .withAbilityName(MoviesDetailAbility.class.getName())
- .build();
- // 携带参数,电影id
- intent.setParam("movieId", movie.getId());
- intent.setOperation(operation);
- List<Intent> intentList = new ArrayList<>();
- intentList.add(intent);
- // 对于不同的响应事件,第一个参数requestCode需要不同,此处用电影id设为requestCode
- IntentAgentInfo paramsInfo = new IntentAgentInfo(Math.toIntExact(movie.getId()),
- IntentAgentConstant.OperationType.START_ABILITY,
- IntentAgentConstant.Flags.UPDATE_PRESENT_FLAG, intentList, null);
- IntentAgent intentAgent = IntentAgentHelper.getIntentAgent(this, paramsInfo);
- return intentAgent;
- }
复制代码删除卡片事件的回调,示例代码如下:
- @Override
- protected void onDeleteForm(long formId) {
- super.onDeleteForm(formId);
- // 删除数据库中的卡片信息
- DatabaseUtils.deleteForm(this, formId);
- }
复制代码系统桌面使用方主动定时定点更新卡片的回调,由于本篇Codelab未在此回调中做其他操作,因此不做介绍。此回调需要配合config.json中的配置,config.json配置示例代码如下:
- "forms": [
- {
- "landscapeLayouts": [
- "$layout:form_list_pattern_form_card_2_2"
- ],
- "isDefault": true,
- "scheduledUpdateTime": "10:40",
- "defaultDimension": "2*2",
- "name": "form_card",
- "description": "This is a form card",
- "colorMode": "auto",
- "type": "Java",
- "supportDimensions": [
- "2*2"
- ],
- "portraitLayouts": [
- "$layout:form_list_pattern_form_card_2_2"
- ],
- "updateEnabled": true,
- "updateDuration": 1
- }
- ]
复制代码
说明
上述的"scheduledUpdateTime": "10:40","updateDuration": 1分别为桌面使用方主动更新卡片的时间和间隔(1表示30分钟更新一次),详细说明可参考Java卡片开发指导
7. 回顾和总结 本篇Codelab我们介绍了Java电影卡片的页面布局开发和卡片几个重要的回调函数的处理,在主页面可以通过向上滑动app图标进行卡片的创建,点击卡片或者app图标进入电影首页,点击首页中的每个电影进入电影详情页,在详情页中点击全部展开功能进行电影剧情内容的收缩。
8. 恭喜你
目前你已经成功完成了Codelab并且学到了:
- 如何进行卡片的创建
- 如何进行卡片的刷新
- 卡片的事件控制
- 如何进行电影首页和详情页的布局
- 如何开发电影剧情展开收起功能
9. 参考