DistributedVideoPlayer 分布式视频播放器(一) 原创 精华
@toc
DistributedVideoPlayer 分布式视频播放器(一)
介绍
本示例是在官方Video Play Ability 模板基础上做了扩展开发,官方模板提供基本的视频播放功能,并允许您在手机和电视之间传输视频.
应用分为手机端(entry)和TV端(entrytv),以及一个依赖模块(commonlib).
在示例的基础之上,手机端增加了视频播放列表功能,以及播放详情页和评论功能;手机端播放的视频可以流转到TV端,并实现远端遥控的功能。
内容比较多,会分两期给大家讲解,本期文章主要讲解的内容是手机端部分:
1.实现一个视频播放器 2.实现一个播放列表 3.实现一个评论功能.
[本文正在参与优质创作者激励]
效果展示
搭建环境
安装DevEco Studio,详情请参考DevEco Studio下载。
设置DevEco Studio开发环境,DevEco Studio开发环境需要依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:
如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作。
如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考配置开发环境。
下载源码后,使用DevEco 打开项目。
代码结构
Java代码
│ config.json
│
├─java
│ └─com
│ └─buty
│ └─distributedvideoplayer
│ │ MainAbility.java
│ │ MyApplication.java
│ │
│ ├─ability
│ │ DevicesSelectAbility.java
│ │ MainAbilitySlice.java #视频播放列表页
│ │ SyncControlServiceAbility.java
│ │ VideoPlayAbility.java #视频播放Ability
│ │ VideoPlayAbilitySlice.java #视频播放详情和评论页
│ │
│ ├─components
│ │ EpisodesSelectionDialog.java
│ │ RemoteController.java
│ │ VideoPlayerPlaybackButton.java #播放按钮组件
│ │ VideoPlayerSlider.java #播放时间进度条
│ │
│ ├─constant
│ │ Constants.java #常量
│ │ ResolutionEnum.java #分辨率枚举
│ │ RouteRegister.java #自定义路由
│ │
│ ├─data
│ │ VideoInfo.java #视频基础信息
│ │ VideoInfoService.java #视频信息服务,用于模拟数据
│ │ Videos.java #视频列表
│ │
│ ├─model
│ │ CommentModel.java #评论模型
│ │ DeviceModel.java
│ │ ResolutionModel.java #解析度模型
│ │ VideoModel.java #视频模型
│ │
│ ├─provider
│ │ CommentItemProvider.java #评论数据提供程序
│ │ DeviceItemProvider.java
│ │ ResolutionItemProvider.java #解析度数据提供程序
│ │ VideoItemProvider.java #视频数据提供程序
│ │
│ └─utils
│ AppUtil.java #工具类
│ DateUtils.java
资源文件
└─resources
├─base
│ ├─element
│ │ color.json
│ │ float.json
│ │ strarray.json
│ │ string.json
│ │
│ ├─graphic
│ │ background_ability_control_bg.xml
│ │ background_ability_control_middle.xml
│ │ background_ability_control_ok.xml
│ │ background_ability_devices.xml
│ │ background_ability_episodes.xml
│ │ background_button_click.xml
│ │ background_button_clicked.xml
│ │ background_episodes_item.xml
│ │ background_episodes_quality.xml
│ │ background_episodes_trailer.xml
│ │ background_slide_thumb.xml
│ │ background_switch_checked.xml
│ │ background_switch_empty.xml
│ │ background_switch_thumb.xml
│ │ background_switch_track.xml
│ │ list_divider.xml
│ │ shape_slider_thumb.xml
│ │
│ ├─layout
│ │ ability_main.xml #播放列表布局
│ │ comments_item.xml #单条评论布局
│ │ dialog_playlist.xml
│ │ dialog_resolution_list.xml
│ │ dialog_table_layout.xml
│ │ hm_sample_ability_video_box.xml #视频播放组件页
│ │ hm_sample_ability_video_comments.xml #播放详情布局页
│ │ hm_sample_view_video_box_seek_bar_style1.xml #播放进度条布局
│ │ hm_sample_view_video_box_seek_bar_style2.xml
│ │ remote_ability_control.xml
│ │ remote_ability_episodes.xml
│ │ remote_ability_select_devices.xml
│ │ remote_ability_sound_equipment.xml
│ │ remote_device_item.xml
│ │ remote_episodes_item.xml
│ │ remote_video_quality_item.xml
│ │
│ ├─media
│ │ comments.png
│ │ great.png
│ │ icon.png
│ │ ic_anthology.png
│ │
│ └─profile
├─en
│ └─element
│ string.json
│
├─rawfile
│ videos.json #模拟数据JSON文件
│
└─zh
└─element
string.json
实现步骤
1.实现一个视频播放器
引入对commonlib的依赖后,实现一个视频播放器就很容易了.
entry的build.gradle 增加对commonlib的依赖
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.har'])
testImplementation 'junit:junit:4.13'
ohosTestImplementation 'com.huawei.ohos.testkit:runner:1.0.0.200'
//引用commonlib 依赖
implementation project(path: ':commonlib')
}
1.1.页面布局 hm_sample_ability_video_comments.xml
添加一个VideoPlayerView组件
<?xml version="1.0" encoding="utf-8"?>
<StackLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:id="$+id:root_layout"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:background_element="#FFFFFFFF"
ohos:orientation="vertical">
<com.buty.distributedvideoplayer.player.ui.widget.media.VideoPlayerView
ohos:id="$+id:video_view"
ohos:height="250vp"
ohos:width="match_parent"/>
...
</StackLayout>
1.2.Java代码
获取视频组件对象后,只需要2步就可以了: 1.设置视频资源路径和描述; 2.设置播放器核心组件和自定义组件.几行关键代码 VideoPlayAbilitySlice.java
/**
* 初始化播放器
*/
private void initPlayer() {
HiLog.debug(LABEL, "initPlayer");
//自定义视频播放视图组件
player = (VideoPlayerView) findComponentById(ResourceTable.Id_video_view);
if (player != null) {
//获取视频信息服务
videoService = new VideoInfoService(getContext());
//获取当前播放视频的路径
String path = videoService
.getVideoInfoByIndex(currentPlayingIndex)
.getResolutions()
.get(currentPlayingResolutionIndex)
.getUrl();
//视频描述
String videoDesc = videoService.getVideoInfoByIndex(currentPlayingIndex).getVideoDesc();
HiLog.debug(LABEL, "videoDesc = " + videoDesc + " path = " + path);
if (path != null) {
//设置路径和描述
player.setVideoPathAndTitle(path, videoDesc);
//视频准备完毕就播放并设置播放进度条
player.setPlayerOnPreparedListener(
() -> {
player.start();
//设置播放位置
player.seekTo(currentPlayingPosition);
});
//双击播放或暂停
player.setDoubleClickedListener(
component -> {
//是否在控制TV端
if (remoteController != null && remoteController.isShown()) {
return;
}
HiLog.debug(LABEL, "VideoPlayView double-click event");
if (player.isPlaying()) {
player.pause();
} else {
player.start();
}
});
//监听播放错误
player.setErrorListener(
(errorType, errorCode) -> {
ToastDialog toast = new ToastDialog(getContext());
switch (errorType) {
case HmPlayerAdapter.ERROR_LOADING_RESOURCE:
toast.setText(
AppUtil.getStringResource(
getContext(), ResourceTable.String_media_file_loading_error));
break;
case HmPlayerAdapter.ERROR_INVALID_OPERATION:
toast.setText(
AppUtil.getStringResource(
getContext(), ResourceTable.String_invalid_operation));
break;
default:
toast.setText(
AppUtil.getStringResource(
getContext(), ResourceTable.String_undefined_error_type));
break;
}
getUITaskDispatcher().asyncDispatch(toast::show);
});
}
//添加核心组件,播放时间进度滑块
addCoreComponent();
//添加自定义组件
addCustomComponent();
HiLog.debug(LABEL, "initPlayer finish");
}
}
2.实现一个视频播放列表功能
2.1.页面布局 ability_main.xml
使用了DirectionalLayout布局组件,还有ScrollView和ListContainer组件
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:id="$+id:ability_main_root"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:orientation="vertical">
<DirectionalLayout
ohos:id="$+id:ability_main_titlebar"
ohos:height="match_content"
ohos:width="match_parent"
ohos:background_element="#B0B0B0"
ohos:orientation="horizontal"
ohos:padding="10vp">
<DirectionalLayout
ohos:height="match_parent"
ohos:width="match_content"
ohos:weight="1">
<Text
ohos:id="$+id:tag_favorite"
ohos:height="match_content"
ohos:width="match_parent"
ohos:text="关注"
ohos:text_size="$float:normal_text_size_15"
/>
</DirectionalLayout>
<DirectionalLayout
ohos:height="match_content"
ohos:width="match_content"
ohos:weight="1">
<Text
ohos:id="$+id:tag_support"
ohos:height="match_content"
ohos:width="match_content"
ohos:text="推荐"
ohos:text_size="$float:normal_text_size_15"
/>
</DirectionalLayout>
<DirectionalLayout
ohos:height="match_content"
ohos:width="match_content"
ohos:weight="1">
<Text
ohos:id="$+id:tag_movie"
ohos:height="match_content"
ohos:width="match_content"
ohos:element_end="$id:favorite"
ohos:text="电影"
ohos:text_color="#1C6AE9"
ohos:text_size="$float:normal_text_size_15"
ohos:text_weight="600"/>
<Component
ohos:id="$+id:device_item_divider"
ohos:height="2vp"
ohos:width="30vp"
ohos:background_element="$graphic:list_divider"/>
</DirectionalLayout>
<DirectionalLayout
ohos:height="match_content"
ohos:width="match_content"
ohos:weight="1">
<Text
ohos:id="$+id:tag_live"
ohos:height="match_content"
ohos:width="match_parent"
ohos:text="直播"
ohos:text_size="$float:normal_text_size_15"
/>
</DirectionalLayout>
<DirectionalLayout
ohos:height="match_content"
ohos:width="match_content"
ohos:weight="1">
<Text
ohos:id="$+id:tag_tv"
ohos:height="match_content"
ohos:width="match_parent"
ohos:text="电视"
ohos:text_size="$float:normal_text_size_15"
/>
</DirectionalLayout>
</DirectionalLayout>
<ScrollView
ohos:height="match_parent"
ohos:width="match_parent"
ohos:id="$+id:video_list_scroll" >
<ListContainer
ohos:id="$+id:videos_container"
ohos:height="match_parent"
ohos:width="match_parent">
</ListContainer>
</ScrollView>
</DirectionalLayout>
2.2.Java代码
申请用户敏感权限授权 MainAbility.java
/**
* Entry to the main interface of the program
*/
public class MainAbility extends Ability {
private static final int REQUEST_CODE = 1;
//读写媒体权限
private final String[] permissionLists
= new String[]{"ohos.permission.READ_MEDIA", "ohos.permission.WRITE_MEDIA"};
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_main);
super.setMainRoute(MainAbilitySlice.class.getName());
//申请授权
verifyPermissions();
}
private void verifyPermissions() {
for (String permissionList : permissionLists) {
int result = verifySelfPermission(permissionList);
if (result != IBundleManager.PERMISSION_GRANTED) {
requestPermissionsFromUser(permissionLists, REQUEST_CODE);
}
}
}
...
通过VideoInfoService读取videos.json文件初始化视频容器列表MainAbilitySlice.java
public class MainAbilitySlice extends AbilitySlice {
public static final HiLogLabel LABEL = new HiLogLabel(0, 0, "=>MainAbilitySlice");
private VideoInfoService videoService;
@Override
protected void onStart(Intent intent) {
super.onStart(intent);
//加载视频播放器页面
super.setUIContent(ResourceTable.Layout_ability_main);
initVideoContainer();
}
/**
* 模拟数据
* 初始化视频容器列表
*/
private void initVideoContainer() {
HiLog.debug(LABEL, "initVideoContainer");
List<VideoModel> videos = new ArrayList<>();
//获取视频信息服务
videoService = new VideoInfoService(getContext());
for (int i = 0; i < 7; i++) {
VideoModel video = new VideoModel();
video.setComments(new Random().nextInt(1000));
video.setFavorites(new Random().nextInt(1000));
video.setGreats(new Random().nextInt(10000));
VideoInfo videoInfo = videoService.getVideoInfoByIndex(i);
videoInfo.setIndex(i);
video.setVideoInfo(videoInfo);
videos.add(video);
}
ListContainer listContainer = (ListContainer) findComponentById(ResourceTable.Id_videos_container);
//容器绑定数据提供程序
VideoItemProvider provider = new VideoItemProvider(this, videos, this);
listContainer.setItemProvider(provider);
}
}
视频容器列表数据提供程序 VideoItemProvider.java
public class VideoItemProvider extends BaseItemProvider {
private static final HiLogLabel LABEL = new HiLogLabel(0, 0, "=>VideoItemProvider");
private final Context context;
private final List<VideoModel> list;
private AbilitySlice abilitySlice;
//当前播放视频分辨率索引
private int currentPlayingResolutionIndex = 0;
/**
* Initialization
*/
public VideoItemProvider(Context context, List<VideoModel> list, AbilitySlice abilitySlice) {
HiLog.debug(LABEL, "VideoItemProvider");
this.context = context;
this.list = list;
this.abilitySlice = abilitySlice;
}
public Context getContext() {
return context;
}
@Override
public int getCount() {
return list == null ? 0 : list.size();
}
@Override
public Object getItem(int position) {
if (list != null && position >= 0 && position < list.size()) {
return list.get(position);
}
return new VideoModel();
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public Component getComponent(int position, Component convertComponent, ComponentContainer componentContainer) {
HiLog.debug(LABEL, "getComponent:" + convertComponent + "," + componentContainer);
final Component cpt;
final VideoPlayerView player;
VideoModel item = list.get(position);
//初次获取组件
if (convertComponent == null) {
cpt = LayoutScatter.getInstance(context).parse(ResourceTable.Layout_hm_sample_ability_video_box, componentContainer, false);
player = (VideoPlayerView) cpt.findComponentById(ResourceTable.Id_video_view);
//初始化播放器组件
initPlayer(player,item.getVideoInfo());
} else {
//ScrollView滑动时也会调用getComponent方法,
cpt = convertComponent;
player = (VideoPlayerView) cpt.findComponentById(ResourceTable.Id_video_view);
}
//设置其他组件的值
Text text_favorites = (Text) cpt.findComponentById(ResourceTable.Id_text_favorites);
Text text_comments = (Text) cpt.findComponentById(ResourceTable.Id_text_comments);
Text text_greats = (Text) cpt.findComponentById(ResourceTable.Id_text_greats);
//注意setText的参数类型是字符串
text_favorites.setText(item.getFavorites() + "");
text_comments.setText(item.getComments() + "");
text_greats.setText(item.getGreats() + "");
//点击评论图标,停止当前播放器,打开VideoPlayAbility的Slice页面
Image commentImage = (Image) cpt.findComponentById(ResourceTable.Id_image_comment);
commentImage.setClickedListener(component -> {
//停止播放,列表页面的播放器
player.stopPlayback();
//打开带有评论的播放页面
startVideoPlayDetail(item,player);
});
return cpt;
}
/**
* 初始化播放器
*/
private void initPlayer(VideoPlayerView player, VideoInfo videoInfo) {
HiLog.debug(LABEL, "initPlayer");
if (player != null) {
//视频路径
String path = videoInfo
.getResolutions()
.get(currentPlayingResolutionIndex)
.getUrl();
HiLog.debug(LABEL, "path:" + path);
//视频描述
String videoDesc = videoInfo.getVideoDesc();
HiLog.debug(LABEL, "videoDesc = " + videoDesc + " path = " + path);
if(path!=null) {
//设置路径和名称
player.setVideoPathAndTitle(path, videoDesc);
//双击播放或暂停
player.setDoubleClickedListener(
component -> {
HiLog.debug(LABEL, "VideoPlayView double-click event");
if (player.isPlaying()) {
player.pause();
} else {
player.start();
}
});
//监听播放错误
player.setErrorListener(
(errorType, errorCode) -> {
ToastDialog toast = new ToastDialog(getContext());
switch (errorType) {
case HmPlayerAdapter.ERROR_LOADING_RESOURCE:
toast.setText(
AppUtil.getStringResource(
getContext(), ResourceTable.String_media_file_loading_error));
break;
case HmPlayerAdapter.ERROR_INVALID_OPERATION:
toast.setText(
AppUtil.getStringResource(
getContext(), ResourceTable.String_invalid_operation));
break;
default:
toast.setText(
AppUtil.getStringResource(
getContext(), ResourceTable.String_undefined_error_type));
break;
}
abilitySlice.getUITaskDispatcher().asyncDispatch(toast::show);
});
}
//添加核心组件,播放时间进度滑块
addCoreComponent(player);
//添加自定义组件
HiLog.debug(LABEL, "initPlayer finish");
}
}
/**
* 添加播放按钮,剧集,进度条等核心组件
* Adding core component like playback button and seek bar
*/
private void addCoreComponent(VideoPlayerView player) {
HiLog.debug(LABEL, "addCoreComponent");
//添加播放按钮组件
player.addPlaybackButton(new VideoPlayerPlaybackButton(getContext()), VideoBoxArea.BOTTOM);
//添加播放进度条组件
player.addSeekBar(
new VideoPlayerSlider(getContext()),
VideoBoxArea.BOTTOM,
(int) AppUtil.getFloatResource(getContext(), ResourceTable.Float_normal_margin_24));
}
/**
* 打开视频播放详情页
*/
private void startVideoPlayDetail(VideoModel item,VideoPlayerView player) {
HiLog.debug(LABEL, "startVideoPlayDetail ");
//启动评论播放页面
Intent intentService = new Intent();
//播放视频评论数
intentService.setParam("comments",String.valueOf(item.getComments()));
VideoInfo videoInfo=item.getVideoInfo();
if(videoInfo!=null) {
//播放视频在播放列表中的索引
intentService.setParam("currentPlayingIndex", videoInfo.getIndex());
//播放视频的分辨率索引
intentService.setParam("currentPlayingResolutionIndex", currentPlayingResolutionIndex);
//当前视频播放的位置
intentService.setParam(RemoteConstant.INTENT_PARAM_REMOTE_START_POSITION,(int)player.getCurrentPosition());
HiLog.debug(LABEL,RemoteConstant.INTENT_PARAM_REMOTE_START_POSITION+":"+player.getCurrentPosition());
}
Operation operation =
new Intent.OperationBuilder()
.withDeviceId("")
.withBundleName(abilitySlice.getBundleName())
.withAbilityName(VideoPlayAbility.class)
.build();
intentService.setOperation(operation);
abilitySlice.startAbility(intentService);
}
}
3.实现一个评论功能
3.1.页面布局,评论列表布局页 hm_sample_ability_video_comments.xml
使用了StackLayout,DependentLayout布局组件和ListContainer,TextField,Text 组件
...
<!-- 工具栏 -->
<DependentLayout
ohos:id="$+id:comments_bar"
ohos:height="40vp"
ohos:width="match_parent"
ohos:background_element="#FFDDDADA"
ohos:layout_alignment="top"
ohos:top_margin="250vp">
<DirectionalLayout
ohos:id="$+id:favorite"
ohos:height="match_parent"
ohos:width="match_content"
ohos:align_parent_left="true"
ohos:left_margin="10vp"
ohos:orientation="horizontal"
ohos:padding="4vp">
<Text
ohos:id="$+id:text_favorites"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:left_padding="5vp"
ohos:text="简介"
ohos:text_size="16fp"
ohos:text_weight="600">
</Text>
</DirectionalLayout>
<DirectionalLayout
ohos:id="$+id:comment"
ohos:height="match_parent"
ohos:width="match_content"
ohos:end_of="$id:favorite"
ohos:left_margin="20vp"
ohos:orientation="horizontal"
ohos:padding="4vp">
<Text
ohos:id="$+id:text_comment"
ohos:height="match_parent"
ohos:width="match_content"
ohos:left_padding="5vp"
ohos:text="评论"
ohos:text_weight="600"
ohos:text_size="16fp">
</Text>
<Text
ohos:id="$+id:text_comments"
ohos:height="match_parent"
ohos:width="match_content"
ohos:left_padding="5vp"
ohos:text="1161"
ohos:text_weight="600"
ohos:text_size="16fp">
</Text>
</DirectionalLayout>
</DependentLayout>
<!-- 评论列表 -->
<DependentLayout
ohos:id="$+id:comments_view"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:background_element="#FFFDFFFF"
ohos:top_margin="290vp">
<ListContainer
ohos:id="$+id:comments_container"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:layout_alignment="horizontal_center"
ohos:orientation="vertical"/>
</DependentLayout>
<!-- 评论对话框 -->
<DependentLayout
ohos:id="$+id:comments_dialog"
ohos:height="70vp"
ohos:width="match_parent"
ohos:background_element="#FFF4F4F8"
ohos:layout_alignment="bottom"
ohos:padding="5vp">
<TextField
ohos:id="$+id:comment_tf"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:hint="发一条友好的评论吧"
ohos:right_padding="60vp"
ohos:text_size="16vp"></TextField>
<Text
ohos:id="$+id:sent_comment"
ohos:height="match_parent"
ohos:width="60vp"
ohos:align_parent_right="true"
ohos:background_element="#FF7ECCCF"
ohos:text="发送"
ohos:input_enter_key_type="enter_key_type_send"
ohos:text_alignment="center"
ohos:text_size="16vp">
</Text>
</DependentLayout>
3.2.页面布局,单条评论组件布局 comments_item.xml
使用了DependentLayout,DirectionalLayout布局组件和Image,Component,Text 组件
<?xml version="1.0" encoding="utf-8"?>
<DependentLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:height="match_content"
ohos:width="match_parent">
<Image
ohos:id="$+id:header_item_icon"
ohos:height="50vp"
ohos:width="50vp"
ohos:end_margin="13vp"
ohos:left_margin="5vp"
ohos:image_src="$media:ic_header"
ohos:scale_mode="stretch"
ohos:top_margin="13vp"/>
<DirectionalLayout
ohos:id="$+id:comment_item_content"
ohos:height="match_content"
ohos:width="250vp"
ohos:bottom_padding="5vp"
ohos:orientation="vertical"
ohos:right_of="$id:header_item_icon">
<Text
ohos:id="$+id:comment_uname"
ohos:height="match_content"
ohos:width="160vp"
ohos:layout_alignment="vertical_center"
ohos:padding="5vp"
ohos:text="我是一只鱼"
ohos:text_color="#FFA09E9E"
ohos:text_size="16vp"/>
<Text
ohos:id="$+id:comment_content"
ohos:height="match_content"
ohos:width="match_parent"
ohos:layout_alignment="vertical_center"
ohos:multiple_lines="true"
ohos:padding="5vp"
ohos:text="这是一条评论"
ohos:text_color="$color:default_black_color"
ohos:text_size="16vp"/>
<Text
ohos:id="$+id:comment_date"
ohos:height="match_content"
ohos:width="match_content"
ohos:layout_alignment="vertical_center"
ohos:padding="5vp"
ohos:text="2天前"
ohos:text_color="#FFA09E9E"
ohos:text_size="16vp"/>
</DirectionalLayout>
<DirectionalLayout
ohos:height="match_content"
ohos:width="match_content"
ohos:id="$+id:goods_view"
ohos:align_parent_right="true"
ohos:padding="5vp"
ohos:right_margin="5vp"
ohos:right_of="$id:comment_item_content">
<Image
ohos:id="$+id:good_icon"
ohos:height="25vp"
ohos:width="match_parent"
ohos:image_src="$media:ic_great"
ohos:scale_mode="stretch"
ohos:top_margin="13vp"
/>
<Text
ohos:id="$+id:comment_goods"
ohos:height="match_content"
ohos:width="match_parent"
ohos:text="42"
ohos:text_alignment="center"
ohos:text_color="#FFA09E9E"
ohos:text_size="12vp"></Text>
</DirectionalLayout>
<DirectionalLayout
ohos:height="match_content"
ohos:width="match_parent"
ohos:align_bottom="$id:comment_item_content">
<Component
ohos:id="$+id:device_item_divider"
ohos:height="1vp"
ohos:width="match_parent"
ohos:background_element="$graphic:list_divider"/>
</DirectionalLayout>
</DependentLayout>
3.3.Java代码
创建评论列表提供程序类 CommentItemProvider.java
public class CommentItemProvider extends BaseItemProvider {
private static final HiLogLabel LABEL = new HiLogLabel(0, 0, "=>CommentsItemProvider");
private final Context context;
private final List<CommentModel> list;
private AbilitySlice abilitySlice;
/**
* Initialization
*/
public CommentItemProvider(Context context, List<CommentModel> list, AbilitySlice slice) {
this.context = context;
this.list = list;
this.abilitySlice = slice;
}
@Override
public int getCount() {
return list == null ? 0 : list.size();
}
@Override
public Object getItem(int position) {
if (list != null && position >= 0 && position < list.size()) {
return list.get(position);
}
return new CommentModel();
}
public void addComment(CommentModel comment) {
if (comment == null) return;
list.add(0, comment);
notifyDataSetItemInserted(0);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public Component getComponent(int position, Component convertComponent, ComponentContainer componentContainer) {
final Component cpt;
if (convertComponent == null) {
cpt = LayoutScatter.getInstance(context).parse(ResourceTable.Layout_comments_item, null, false);
} else {
cpt = convertComponent;
}
CommentModel item = list.get(position);
//评论人头像
Image headerIcon = (Image) cpt.findComponentById(ResourceTable.Id_header_item_icon);
headerIcon.setPixelMap(ResourceTable.Media_ic_header);
//评论人名称
Text uName = (Text) cpt.findComponentById(ResourceTable.Id_comment_uname);
uName.setText(item.getuName());
//评论内容
Text uContent = (Text) cpt.findComponentById(ResourceTable.Id_comment_content);
uContent.setText(item.getCommentContent());
//点击事件
uContent.setClickedListener(component -> {
DependentLayout commentDialog = (DependentLayout) abilitySlice.findComponentById(ResourceTable.Id_comments_dialog);
if (commentDialog.getVisibility() == Component.VISIBLE) {
commentDialog.setVisibility(Component.VISIBLE);
}
TextField comment = (TextField) abilitySlice.findComponentById(ResourceTable.Id_comment_tf);
comment.setText("");
comment.setHint("回复 " + item.getuName() + ":");
comment.requestFocus();
});
//评论日期
Text uDate = (Text) cpt.findComponentById(ResourceTable.Id_comment_date);
uDate.setText(item.getCommentDate());
//已赞数
Text goods = (Text) cpt.findComponentById(ResourceTable.Id_comment_goods);
HiLog.debug(LABEL, "goods:" + item.getCommentGoods());
goods.setText(item.getCommentGoods() + "");
//点赞
Image greatImage = (Image) cpt.findComponentById(ResourceTable.Id_good_icon);
//点赞加一
greatImage.setClickedListener(component1 -> {
greatImage.setPixelMap(ResourceTable.Media_ic_great_red);
goods.setTextColor(Color.RED);
goods.setText((item.getCommentGoods() + 1) + "");
});
if (position == list.size() - 1) {
Component divider = cpt.findComponentById(ResourceTable.Id_device_item_divider);
divider.setVisibility(Component.INVISIBLE);
}
return cpt;
}
}
初始化评论列表和评论功能 VideoPlayAbilitySlice.java
/**
* 初始化评论列表
*/
private void initComments(AbilitySlice slice) {
//评论列表,以及设置点击的监听事件、传递数据
ListContainer listContainer = (ListContainer) findComponentById(ResourceTable.Id_comments_container);
List<CommentModel> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
CommentModel obj = new CommentModel();
obj.setuId(i + "");
obj.setuName(getRandomUname());
obj.setCommentContent(getRandomText());
obj.setCommentDate("刚刚");
obj.setCommentGoods(new Random().nextInt(100));
list.add(obj);
}
//容器绑定数据提供程序
provider = new CommentItemProvider(this, list, slice);
listContainer.setItemProvider(provider);
Component sendComment = findComponentById(ResourceTable.Id_sent_comment);
TextField comment = (TextField) findComponentById(ResourceTable.Id_comment_tf);
//评论功能
sendComment.setClickedListener(component -> {
if (comment.getText().isEmpty()) {
new ToastDialog(getContext()).setText("评论不能为空").show();
return;
}
CommentModel model = new CommentModel();
model.setCommentContent(comment.getText());
model.setuId(UUID.randomUUID().toString());
model.setuName("robot");
model.setCommentDate("刚刚");
//添加到数据提供程序中
provider.addComment(model);
comment.setText("");
//评论数+1
commentsText.setText(String.valueOf(Integer.valueOf(comments) + 1));
new ToastDialog(getContext()).setText("评论成功").show();
});
}
问题总结
1.播放器列表滑动时,会重复添加播放组件的问题
2.评论功能,输入框跟随输入法调整位置的问题(待完善)
3.播放列表滑动时,根据ScrollView的位置控制视频播放(待完善)
4.播放列表中播放视频进度同步到播放详情页的问题(待完善)
完整代码
附件直接下载
[本文正在参与优质创作者激励]
期待大佬下期精彩内容
在 VideoPlayAbilitySlice.java的onStart方法中,增加如下代码,可以使TextField跟随输入法的键盘浮动,在输入的过程中看到输入的内容,感谢这个为网友的分享:https://harmonyos.51cto.com/posts/8674
楼主每一篇文章都是精品呀