前言
最近的项目中用到了,可以连续滚动的跑马灯,并且滚动的不仅仅是纯文字,还有图片等其他的组件,那么这种情况下,系统为我们提供的Text组件或者Marquee组件就无法来实现了,只能自己自定义了。
我们先看下自定义后的效果:




总体而言,自定义之后的跑马灯,无论是纵向滚动还是横向滚动,是连续滚动还是停顿滚动,基本上都实现了,可以说,满足了实际开发中的大多数场景,那么如何实现以上的效果呢?其实很简单,一个Swiper组件和一个属性动画就可以实现了。
那么在自定义实现之前,有两个问题询问大家,第一个问题是,系统的Text组件或者Marquee组件,支持纵向滚动吗?
第二个问题是,如果我想连续滚动图文结构的内容,或者自定义组件滚动,系统是否为我们提供了可用组件?
可以明确的是,以上两个问题的答案都是否定的,系统的Text组件或者Marquee组件,只支持文本滚动,并且也支持横向滚动,所以,如果你想实现非文本滚动,或者纵向滚动,只能自己动手来实现。
本篇文章的大致内容如下:
1、停顿跑马灯实现
2、连续跑马灯实现
3、marquee组件全实现
4、相关总结
停顿跑马灯实现
所谓的停顿,就是跑马灯数据,暂停一些时间,然后再去轮动下一个,周而复始的展示;这种方式实现起来最简单,相信聪明的你,一下就能想起来了如何实现,没错,就是使用Swiper组件。
系统的Swiper组件,一般用于轮播图的实现,但是可以发现,这个暂停的跑马灯,其实就是一个轮播图。
横向也好,纵向也好,通过vertical属性便可轻松搞定,为true就纵向,fasle就是横向;对于时间上的控制,可以使用interval属性来设置。
简单的案例代码如下:
TypeScript
Swiper() {
ForEach(this.marqueeData, (item: Object, index: number) => {
this.marqueeLayout(item, index)
})
}
.vertical(this.marqueeDirection == MarqueeDirection.vertical ? true : false)
.autoPlay(this.autoPlay) //是否是自动循环
.loop(this.isLoop)
.interval(this.interval) //间隔时间
.indicator(false)
.disableSwipe(true)
Swiper组件是支持任意的自定义组件的,所以,如果你的项目中有停顿跑马灯效果的话,完全可以进行使用。
连续跑马灯实现
连续的跑马灯,就是一直滚动,不存在中间停顿的时间,上一个滚动完成之后,接着就滚动下一个,纯文本的横向滚动,我们可以使用系统的Text组件或者Marquee组件来实现,如果滚动的是非文本,那么就需要自定义来实现了。
自定义起来也是非常的简单,只需要两个属性便可以搞定,一个是平移属性动画,一个是设置动画参数,如果是横向,那么就设置x坐标,如果是纵向,就设置y坐标。
TypeScript
.translate({ y: this.offsetY })
.animation({
duration: this.interval,
curve: Curve.Linear, // 线性速度
iterations: this.isLoop ? -1 : this.iterations, // 无限循环
playMode: this.marqueeFromStart ? PlayMode.Normal : PlayMode.Reverse, // 单向播放
})
无论是纵向移动还是横向移动,所移动的距离,一定是整个组件的距离,需要自己动态来计算,比如纵向滚动,单个组件高度为50,共有5个组件需要轮动,那就是5*5,也就是轮动的组件数量乘以单个组件的高度。
marquee组件全实现
无论连续还是停顿,文本还是非文本,目前这些功能我全部已经封装完毕,发布到了中心仓库中,大家可以直接依赖使用。
地址:ohpm.openharmony.cn/#/cn/detail...
快速使用
方式一:在Terminal窗口中,执行如下命令安装三方包,DevEco Studio会自动在工程的oh-package.json5中自动添加三方包依赖。
建议:在使用的模块路径下进行执行命令。
TypeScript
ohpm install @abner/marquee
方式二:在工程模块的oh-package.json5中设置三方包依赖,配置示例如下:
TypeScript
"dependencies": { "@abner/marquee": "^1.0.0"}
代码使用
1、纯文字跑马灯
TypeScript
MarqueeView({
marqueeText: "我是一段纯文本内容,主要用来展示跑马灯效果,来吧,我们一起看一下吧",
})
属性介绍
| 属性 | 类型 | 概述 |
|---|---|---|
| marqueeText | string/Resource | 跑马灯文本数据 |
| marqueeTextAttribute | MarqueeTextAttribute | 跑马灯文本属性,设置颜色大小等 |
| marqueeStart | boolean | 控制跑马灯播放和停止,true播放,false暂停 |
| marqueeTextStep | number | 滚动动画文本滚动步长。默认值:4.0vp |
| marqueeFromStart | boolean | 设置文本从头开始滚动或反向滚动。true表示从头开始滚动,false表示反向滚动。默认值:true |
| marqueeTextDelay | number | 设置每次滚动的时间间隔。默认值:0单位:毫秒 |
| marqueeTextLoop | number | 设置重复滚动的次数,小于等于零时无限循环。默认值:-1 |
| marqueeTextFadeout | boolean | 设置文字超长时的渐隐效果。true表示支持渐隐效果,false表示不支持渐隐效果。 |
MarqueeTextAttribute
| 属性 | 类型 | 概述 |
|---|---|---|
| fontColor | ResourceColor | 跑马灯文本颜色 |
| fontSize | number / string/ Resource | 跑马灯文本颜色 |
| fontFamily | string / Resource | 字体族。默认字体'HarmonyOS Sans'。 |
| fontStyle | FontStyle | 字体样式。默认值:FontStyle.Normal |
| fontWeight | number / FontWeight / ResourceStr | 文本的字体粗细 |
2、纵向停顿跑马灯
TypeScript
@Entry
@Component
struct VerticalPausePage {
private marqueeDataArray: string[] = [
"公告:我是测试消息第一条",
"公告:我是测试消息第二条",
"公告:我是测试消息第三条"
]
/**
*AUTHOR:AbnerMing
*INTRODUCE:可以是任意布局
*/
@Builder
marqueeLayout(item: string, _: number) {
Row() {
SymbolGlyph($r('sys.symbol.bell_fill'))
.fontSize(18)
.renderingStrategy(SymbolRenderingStrategy.SINGLE)
.fontColor([Color.Red])
.margin({ left: 5 })
Text(item)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.margin({ left: 5 })
}.width("100%")
.height(50)
}
build() {
RelativeContainer() {
ActionBar({ title: "纵向停顿跑马灯" })
Column() {
MarqueeView({
marqueeData: this.marqueeDataArray, //可以传递任意类型数据数组
marqueeType: MarqueeType.pause,
interval:1000,
marqueeLayout: (item: Object, index: number) => {
this.marqueeLayout(item as string, index)
}
})
}.border({ width: 1, color: Color.Red })
.margin({ left: 10, right: 10 })
.alignRules({
center: { anchor: '__container__', align: VerticalAlign.Center },
middle: { anchor: '__container__', align: HorizontalAlign.Center }
})
}
.height('100%')
.width('100%')
}
}
属性介绍
| 属性 | 类型 | 概述 |
|---|---|---|
| marqueeData | Object[] | 跑马灯数据数组 |
| marqueeLayout | @BuilderParam | 用于传递跑马灯自定义组件,可以自定义@Builder来实现 |
| isLoop | boolean | 是否是循环播放,默认true为循环 |
| interval | number | 跑马灯间隔时间,默认是3000毫秒 |
| autoPlay | boolean | 是否是自动播放,默认true,是 |
| marqueeType | MarqueeType | 跑马灯类型,默认为文本类型,MarqueeType.text,pause:停顿类型,continuous:连续类型 |
| iterations | number | 循环次数,默认无限次 |
| marqueeStart | boolean | true播放,false暂停 |
3、纵向连续跑马灯
TypeScript
@Entry
@Component
struct VerticalContinuousPage {
private marqueeDataArray: string[] = [
"公告:我是测试消息第一条",
"公告:我是测试消息第二条",
"公告:我是测试消息第三条",
"公告:我是测试消息第四条",
"公告:我是测试消息第五条"
]
/**
*AUTHOR:AbnerMing
*INTRODUCE:可以是任意布局
*/
@Builder
marqueeLayout(item: string, _: number) {
Row() {
SymbolGlyph($r('sys.symbol.bell_fill'))
.fontSize(18)
.renderingStrategy(SymbolRenderingStrategy.SINGLE)
.fontColor([Color.Red])
.margin({ left: 5 })
Text(item)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.margin({ left: 5 })
}.width("100%")
.height(50)
}
/**
*AUTHOR:AbnerMing
*INTRODUCE:可以是任意布局
*/
@Builder
marqueeText(item: string, _: number) {
Text(item)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.margin({ left: 5 })
.width("100%")
.height(50)
}
/**
*AUTHOR:AbnerMing
*INTRODUCE:可以是任意布局
*/
@Builder
marqueeLayout2(item: string, _: number) {
Column() {
Image("https://loveharmony.oss-cn-beijing.aliyuncs.com/banner/banner_01.jpg")
.width("100%")
.height(100)
Text(item)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.margin({ left: 5, top: 10 })
}.width("100%")
.padding(10)
}
build() {
Column() {
ActionBar({ title: "纵向连续跑马灯" })
//纯文本
MarqueeView({
marqueeData: this.marqueeDataArray, //可以传递任意类型数据数组
marqueeType: MarqueeType.continuous, //连续滚动
marqueeLayout: (item: Object, index: number) => {
this.marqueeText(item as string, index)
}
}).border({ width: 1, color: Color.Red })
//自定义布局1
MarqueeView({
marqueeData: this.marqueeDataArray, //可以传递任意类型数据数组
marqueeType: MarqueeType.continuous, //连续滚动
marqueeLayout: (item: Object, index: number) => {
this.marqueeLayout(item as string, index)
}
}).border({ width: 1, color: Color.Red })
.margin({ top: 20 })
//自定义布局2
MarqueeView({
marqueeData: this.marqueeDataArray, //可以传递任意类型数据数组
marqueeType: MarqueeType.continuous, //连续滚动
marqueeLayout: (item: Object, index: number) => {
this.marqueeLayout2(item as string, index)
}
}).border({ width: 1, color: Color.Red })
.margin({ top: 20 })
}.padding({ left: 20, right: 20 })
.height('100%')
.width('100%')
}
}
4、横向停顿跑马灯
TypeScript
@Entry
@Component
struct HorizontalPausePage {
private marqueeDataArray: string[] = [
"公告:我是测试消息第一条",
"公告:我是测试消息第二条",
"公告:我是测试消息第三条"
]
/**
*AUTHOR:AbnerMing
*INTRODUCE:可以是任意布局
*/
@Builder
marqueeLayout(item: string, index: number) {
Row() {
SymbolGlyph($r('sys.symbol.bell_fill'))
.fontSize(18)
.renderingStrategy(SymbolRenderingStrategy.SINGLE)
.fontColor([Color.Red])
.margin({ left: 5 })
Text(item)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.margin({ left: 5 })
}.width("100%")
.height(50)
.justifyContent(FlexAlign.Center) //内容居中
}
build() {
Column() {
ActionBar({ title: "横向停顿跑马灯" })
Column() {
MarqueeView({
marqueeData: this.marqueeDataArray, //可以传递任意类型数据数组
marqueeDirection: MarqueeDirection.horizontal, //横向滚动
marqueeType: MarqueeType.pause,
marqueeLayout: (item: Object, index: number) => {
this.marqueeLayout(item as string, index)
}
})
}.border({ width: 1, color: Color.Red })
.margin({ left: 10, right: 10, top: 50 })
}
.height('100%')
.width('100%')
}
}
5、横向连续跑马灯
TypeScript
@Entry
@Component
struct HorizontalContinuousPage {
private marqueeDataArray: string[] = [
"公告:我是测试消息第一条",
"公告:我是测试消息第二条",
"公告:我是测试消息第三条"
]
/**
*AUTHOR:AbnerMing
*INTRODUCE:可以是任意布局
*/
@Builder
marqueeText(item: string, _: number) {
Text(item)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.margin({ left: 5 })
.width("100%")
.height(50)
}
/**
*AUTHOR:AbnerMing
*INTRODUCE:可以是任意布局
*/
@Builder
marqueeLayout(item: string, _: number) {
Row() {
SymbolGlyph($r('sys.symbol.bell_fill'))
.fontSize(18)
.renderingStrategy(SymbolRenderingStrategy.SINGLE)
.fontColor([Color.Red])
.margin({ left: 5 })
Text(item)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.margin({ left: 5 })
}.width("100%")
.height(50)
}
/**
*AUTHOR:AbnerMing
*INTRODUCE:可以是任意布局
*/
@Builder
marqueeLayout2(item: string, _: number) {
Column() {
Image("https://loveharmony.oss-cn-beijing.aliyuncs.com/banner/banner_01.jpg")
.width("100%")
.height(100)
Text(item)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.margin({ left: 5, top: 10 })
}.width("100%")
.padding(10)
}
build() {
Column() {
ActionBar({ title: "横向连续跑马灯" })
//纯文本
MarqueeView({
marqueeData: this.marqueeDataArray, //可以传递任意类型数据数组
marqueeType: MarqueeType.continuous, //连续滚动
marqueeDirection: MarqueeDirection.horizontal, //横向
marqueeLayout: (item: Object, index: number) => {
this.marqueeText(item as string, index)
}
}).border({ width: 1, color: Color.Red })
//自定义布局1
MarqueeView({
marqueeData: this.marqueeDataArray, //可以传递任意类型数据数组
marqueeType: MarqueeType.continuous, //连续滚动
marqueeDirection: MarqueeDirection.horizontal, //横向
marqueeLayout: (item: Object, index: number) => {
this.marqueeLayout(item as string, index)
}
}).border({ width: 1, color: Color.Red })
.margin({ top: 20 })
//自定义布局2
MarqueeView({
marqueeData: this.marqueeDataArray, //可以传递任意类型数据数组
marqueeType: MarqueeType.continuous, //连续滚动
marqueeDirection: MarqueeDirection.horizontal, //横向
marqueeLayout: (item: Object, index: number) => {
this.marqueeLayout2(item as string, index)
}
}).border({ width: 1, color: Color.Red })
.margin({ top: 20 })
}
.height('100%')
.width('100%')
.padding({ left: 10, right: 10 })
}
}
相关总结
在实际的开发中,如果是横向的连续滚动,并且是文本类型的,那么建议直接使用系统的Text组件或者Marquee组件,当然了,也可以使用,我开发好的marquee组件,里面也实现了系统的能力,其实是一样的。如果是非文本,那没有别的办法,只能使用自定义的marquee组件。