前言;

各位同学大家好之前写了一些基于 OpenHarmony 系统写arkui的项目。所以移植到arkui-x上面来

效果图

  • OpenHarmony os 设备效果图 :

ArkUI-x 视频播放App初出茅庐
ArkUI-x 视频播放App初出茅庐

ArkUI-x 视频播放App初出茅庐

ArkUI-x 视频播放App初出茅庐

ArkUI-x 视频播放App初出茅庐

  • 安卓设备效果图

ArkUI-x 视频播放App初出茅庐
ArkUI-x 视频播放App初出茅庐

ArkUI-x 视频播放App初出茅庐

ArkUI-x 视频播放App初出茅庐

我们可以看到arkui-x的项目可以直接在OpenHarmony 的设备上面直接运行而且我们对比开发者选项的显示布局边界.akrui-x的项目使用arkts 开发对于OpenHarmony 来说就是原生开发的 但是在安卓 和iOS 上面来说就是类似flutter一样是一个跨平台框架那么我们在使用此框架的时候 只需要编写一次代码就可以在 安卓iOS OpenHarmony 上面直接运行

具体实现:

  • 顶部导航

// @ts-nocheck
import { TopBar } from '../view/common/TopBar';
import { PageAll } from '../view/tabcontent/PageAll';
import { CommonConstants } from '../common/constants/CommonConstant';
import { PageEntertainment } from '../view/tabcontent/PageEntertainment';
import { PageMovie } from '../view/tabcontent/PageMovie';
import PageAnime, {PageAnime} from '../view/tabcontent/PageAnime';
import  PageTV, { PageTV } from '../view/tabcontent/PageTV';
/**
 * 创建人:xuqing
 * 创建时间:2023年8月6日14:13:33
 * 类说明: 顶部导航
 */
@Entry
@Component
struct SwiperIndex {
  @State index: number = 0;
  build() {
    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Start }) {
      TopBar({ index: $index })
      Swiper() {
        PageAll()
        PageMovie()
        PageTV()
        PageEntertainment()
        PageAnime()
      }
      .index(this.index)
      .indicator(false)
      .loop(false)
      .duration(CommonConstants.DURATION_PAGE)
      .onChange((index: number) => {
        this.index = index;
      })
    }
    .backgroundColor($r('app.color.start_window_background'))
  }
}

导航器布局


import { TopBarItem } from '../../common/bean/TopBarItem';
import { initializeOnStartup } from '../../viewmodel/TopBarViewModel';
import { CommonConstants } from '../../common/constants/CommonConstant';
/**
 * TopBar component.
 */
@Component
export struct TopBar {
  @Link index: number;
  private tabArray: Array<TopBarItem> = initializeOnStartup();
  build() {
    Row({ space: CommonConstants.SPACE_TOP_BAR }) {
      ForEach(this.tabArray,
        (item: TopBarItem) => {
          Text(item.name)
            .fontSize(this.index === item.id ? CommonConstants.FONT_SIZE_CHECKED : CommonConstants.FONT_SIZE_UNCHECKED)
            .fontColor(Color.Black)
            .textAlign(TextAlign.Center)
            .fontWeight(this.index === item.id ? FontWeight.Bold : FontWeight.Regular)
            .onClick(() => {
              this.index = item.id;
            })
        }, item => JSON.stringify(item))
    }
    .margin({ left: CommonConstants.ADS_LEFT })
    .width(CommonConstants.FULL_WIDTH)
    .height(CommonConstants.TOP_BAR_HEIGHT)
  }
}

顶部导航我们使用 TopBar 配合轮播图组建 Swiper 来实现

电视剧页面实现


import { CommonConstants } from '../../common/constants/CommonConstant';
import { PositionDataSource } from '../../viewmodel/PositionDataSource';
@Component
export default struct PageTV {
  @Provide positionListData: PositionDataSource = new PositionDataSource();
  private startTouchOffsetY: number = 0;
  private endTouchOffsetY: number = 0;
  private username:String="";
  private title:String="";
  build() {
    Row() {
      List({ space: CommonConstants.LIST_ITEM_SPACE }) {
        LazyForEach(this.positionListData, (item) => {
          ListItem() {
            Row() {
              Column() {
                Image(item?.logo)
                  .width(CommonConstants.LAYOUT_WIDTH_OR_HEIGHT)
                  .height(CommonConstants.LAYOUT_WIDTH_OR_HEIGHT)
              }
              .width(CommonConstants.GOODS_IMAGE_WIDTH)
              .height(CommonConstants.LAYOUT_WIDTH_OR_HEIGHT)
              Column() {
                Row() {
                  Text(item?.name)
                    .fontSize(CommonConstants.NORMAL_FONT_SIZE)
                    .margin({ top: CommonConstants.BIGGER_FONT_SIZE })
                }
                .justifyContent(FlexAlign.Start)
                .width(CommonConstants.GOODS_LIST_WIDTH)
                Row() {
                  Text(item?.cname)
                    .fontColor($r('app.color.black_text_color'))
                    .fontSize(CommonConstants.GOODS_EVALUATE_FONT_SIZE)
                  Text(item?.size).fontSize(CommonConstants.NORMAL_FONT_SIZE).fontColor($r('app.color.login_blue_text_color'))
                    .margin({ left: 10 })
                } .justifyContent(FlexAlign.Start)
                .width(CommonConstants.GOODS_LIST_WIDTH)
                .margin({ top: 20 })
                Row() {
                  Text('时间').fontSize(CommonConstants.NORMAL_FONT_SIZE).fontColor($r('app.color.red'))
                  Text(item?.salary).fontSize(CommonConstants.GOODS_EVALUATE_FONT_SIZE).fontColor($r('app.color.login_blue_text_color'))
                    .margin({ left: 10 })
                }
                .justifyContent(FlexAlign.Start)
                .width(CommonConstants.GOODS_LIST_WIDTH)
                .margin({ top: 20 })
              }
              .padding(CommonConstants.GOODS_LIST_PADDING)
              .width(CommonConstants.GOODS_FONT_WIDTH)
              .height(CommonConstants.LAYOUT_WIDTH_OR_HEIGHT)
            }
            .justifyContent(FlexAlign.SpaceBetween)
            .height(CommonConstants.GOODS_LIST_HEIGHT)
            .width(CommonConstants.LAYOUT_WIDTH_OR_HEIGHT)
          }
          .onTouch((event: TouchEvent) => {
            switch (event.type) {
              case TouchType.Down:
                this.startTouchOffsetY = event.touches[0].y;
                break;
              case TouchType.Up:
                this.startTouchOffsetY = event.touches[0].y;
                break;
              case TouchType.Move:
                if(this.startTouchOffsetY - this.endTouchOffsetY > 0) {
                  this.positionListData.pushData();
                }
                break;
            }
          })
        })
      }
      .width(CommonConstants.GOODS_LIST_WIDTH).onClick(()=>{
      })
    }
    .justifyContent(FlexAlign.Center)
    .width(CommonConstants.LAYOUT_WIDTH_OR_HEIGHT)
  }
}

模拟器数据

  • 数据模型


export interface PositionListItemType {
  logo:Resource;
  name: Resource;
  cname: Resource;
  size: Resource;
  salary: Resource;
}
/**
 *
 * 电视剧
 *
 */
export const positionInitialList: PositionListItemType[] = [
  {
    logo: $r('app.media.fangyandexingxing'),
    name: $r('app.string.Entertainmentname1'),
    cname: $r('app.string.typesof'),
    size: $r('app.string.typesof1'),
    salary: $r('app.string.times1'),
  },
  {
    logo: $r('app.media.wohejiangshiyuehui'),
    name: $r('app.string.Entertainmentname2'),
    cname: $r('app.string.typesof'),
    size: $r('app.string.typesof2'),
    salary: $r('app.string.times2'),
  },
  {
    logo: $r('app.media.yitiantulongji'),
    name: $r('app.string.Entertainmentname3'),
    cname: $r('app.string.typesof'),
    size: $r('app.string.typesof3'),
    salary: $r('app.string.times3'),
  },
  {
    logo: $r('app.media.getoutianwang'),
    name: $r('app.string.Entertainmentname4'),
    cname: $r('app.string.typesof'),
    size: $r('app.string.typesof4'),
    salary: $r('app.string.times4'),
  },
  {
  logo: $r('app.media.xianjianqixiazhuna'),
  name: $r('app.string.Entertainmentname5'),
  cname: $r('app.string.typesof'),
  size: $r('app.string.typesof5'),
  salary: $r('app.string.times5'),
},
]

懒加载数据处理


import { positionInitialList, PositionListItemType } from './PositionData';
/**
 * create a List range
 */
const createListRange = () => {
  let result = [];
  for (let i = 0; i < 2; i++) {
    result = [...result, ...positionInitialList];
  }
  return result;
}
/**
 * LazyLoad Class
 */
class BasicDataSource implements IDataSource {
  private listeners: DataChangeListener[] = []
  public totalCount(): number {
    return 0;
  }
  public getData(index: number): PositionListItemType {
    return undefined;
  }
  public getPositionData(index: number): PositionListItemType {
    return undefined;
  }
  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.indexOf(listener) < 0) {
      this.listeners.push(listener);
    }
  }
  unregisterDataChangeListener(listener: DataChangeListener): void {
    const position = this.listeners.indexOf(listener);
    if (position >= 0) {
      this.listeners.splice(position, 1);
    }
  }
  notifyDataReload(): void {
    this.listeners.forEach(listener => {
      listener.onDataReloaded();
    })
  }
  notifyDataAdd(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataAdd(index);
    })
  }
  notifyDataChange(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataChange(index);
    })
  }
  notifyDataDelete(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataDelete(index);
    })
  }
  notifyDataMove(from: number, to: number): void {
    this.listeners.forEach(listener => {
      listener.onDataMove(from, to);
    })
  }
}
export class PositionDataSource extends BasicDataSource {
  private listData = createListRange();
  public totalCount(): number {
    return this.listData.length;
  }
  public getData(index: number): PositionListItemType {
    return this.listData[index];
  }
  public pushData(): void {
    if(this.listData.length <  12) {
      this.listData = [...this.listData, ...positionInitialList];
      this.notifyDataAdd(this.listData.length - 1);
    }
  }
}

效果图 :

ArkUI-x 视频播放App初出茅庐

视频播放我们使用的是video组件 之前有写 有兴趣的可以去看我之前的文章

鸿蒙 Ark ui 视频播放组件 我不允许你不会

轮播图 也可以去看之前文章

鸿蒙 ark ui 轮播图实现教程

list列表组件也可以去看之前文件

鸿蒙ark ui 列表组件各种用法教程

grid网格组件也可以去看之前文章

鸿蒙 Ark Ui 零基础教程第三集 grid 组件的使用

运行环境 :

  • ArKUI-X

  • OpenHarmony os

  • api 10 版本

ArkUI-x 视频播放App初出茅庐

打包出的apk 反编译查看

  • 反编译之后资源在 Android到处assets 目录下面

ArkUI-x 视频播放App初出茅庐

ArkUI-x 视频播放App初出茅庐

ArkUI-x 视频播放App初出茅庐

ArkUI-x 视频播放App初出茅庐

  • 原工程资源

ArkUI-x 视频播放App初出茅庐

  • 在 LibChecker 中也可以看到该 APP 使用了 ArkUI 的依赖:

ArkUI-x 视频播放App初出茅庐

最后总结

这个视频播放项目 我也是参考了官网的一些demo 所以有些同学见过也正常 我这里不多赘述 然后也是对比了在OpenHarmony 开发板和主流安卓设备上面的效果 UI 的一致性做得还算不错。 然后就是性能我感觉完成度也算是可以,希望大家能踊跃的参与进来 共同建设国产系统平台的生态 让国产自研技术达到了一个新高度,完成了从零到一的迈步,让自研不再是 PPT ,不再是“套壳”。 有兴趣的朋友可以关注我和我的鸿蒙专栏。我们下一期再见 最后呢 希望我都文章能帮助到各位同学工作和学习 如果你觉得文章还不错麻烦给我三连 关注点赞和转发 谢谢