问题现象
在HarmonyOS中,如何使用不同的滚动与滑动组件,实现标题吸顶效果?
背景知识
吸顶效果是网页开发中的一种常见交互设计,指当用户滚动页面时,某个元素(如导航栏、标题栏、工具栏等)会固定在浏览器窗口的顶部(或其他指定位置),保持始终可见,不会随着页面滚动而消失。在开发过程中,吸顶效果通常用于需要保持关键元素始终可见的场景,以提升用户体验和操作效率。
在HarmonyOS中有多种实现吸顶效果的方案,以及不同的吸顶效果,不同方案及其需要了解的知识如下:
Tabs:通过页签进行内容视图切换的容器组件,每个页签对应一个内容视图。
nestedScroll:设置前后两个方向的嵌套滚动模式,实现与父组件的滚动联动。
List:列表包含一系列相同宽度的列表项。适合连续、多行呈现同类数据,例如图片和文本。
sticky:配合ListItemGroup组件使用,设置ListItemGroup中header是否要吸顶或footer是否要吸底。sticky属性可以设置为StickyStyle.Header|StickyStyle.Footer以同时支持header吸顶和footer吸底。
解决方案
本文主要介绍实现普通吸顶效果、单标题滚动嵌套吸顶效果和多标题滚动嵌套吸顶效果的方案。
普通吸顶效果
方案一:通过Tabs组件的tabBar属性实现吸顶效果。可以自定义实现Tabs效果。
示例代码如下:
@Entry
@Component
export struct CommonSolutionOne {
subsController: TabsController = new TabsController();
@State arr: string[] = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15'];
build() {
Tabs({ barPosition: BarPosition.Start, controller: this.subsController }) {
TabContent() {
List({ space: 10 }) {
ForEach(this.arr, (item: number) => {
ListItem() {
Row() {
Text('item' + item)
.fontSize(16)
.height(72)
.fontColor(Color.Black);
}
.width('100%')
.height(72)
.justifyContent(FlexAlign.Center);
}
.borderRadius(15)
.backgroundColor('#F1F3F5');
}, (item: string) => item);
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.padding({ left: 10, right: 10 })
.width('100%')
.height('100%')
.scrollBar(BarState.Off);
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.tabBar('关注')
.backgroundColor('#ffffff');
TabContent() {
Text('推荐');
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.tabBar('推荐')
.backgroundColor('#ffffff');
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.backgroundColor('#ffffff');
}
}
实现效果如下:
点击放大
方案二:自定义标题的吸顶实现。若要实现Tabs页面切换效果,需要自定义页面切换逻辑。
参考上文中方案一,示例代码如下:
@Entry
@Component
export struct CommonSolutionTwo {
subsController: TabsController = new TabsController();
@State arr: string[] = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15'];
build() {
Column() {
Row() {
// 吸顶标题
Text('自定义占位标题')
.width('100%')
.height(60)
.textAlign(TextAlign.Center)
.backgroundColor('#66666666');
};
List({ space: 10 }) {
ForEach(this.arr, (item: number) => {
ListItem() {
Row() {
Text('item' + item)
.fontSize(16)
.height(72)
.fontColor(Color.Black);
}
.width('100%')
.height(72)
.justifyContent(FlexAlign.Center);
}
.borderRadius(15)
.backgroundColor('#F1F3F5');
}, (item: string) => item);
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.padding({ left: 10, right: 10 })
.width('100%')
.scrollBar(BarState.Off)
.layoutWeight(1); // 更换List组件height属性,高度限制以layoutWeight(1)的形式自动填充父组件高度
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.width('100%')
.height('100%') // 普通吸顶效果时,滚动组件的父组件可以不设置高度,但是滚动嵌套吸顶时一定要设置高度限制为100%
.backgroundColor('#ffffff');
}
}
实现效果如下:
点击放大
单标题滚动嵌套吸顶效果
方案一: 通过nestedScroll属性,实现吸顶效果。由于nestedScroll属性是滚动与滑动组件的通用属性,理论上在滚动组件嵌套的过程中,支持该属性的滚动组件都可以实现吸顶效果。以Tabs组件为例,示例代码如下:
@Entry
@Component
struct SingleTitleSolutionOne {
subsController: TabsController = new TabsController();
@State arr: string[] = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15'];
build() {
Scroll() {
Column() {
Column() {
Text('自定义占位标题')
.fontSize(30)
.width('100%')
.height(200)
.backgroundColor('#66666666')
.textAlign(TextAlign.Center);
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
Tabs({ barPosition: BarPosition.Start, controller: this.subsController }) {
TabContent() {
List({ space: 10 }) {
ForEach(this.arr, (item: number) => {
ListItem() {
Row() {
Text('item' + item)
.fontSize(16)
.height(72)
.fontColor(Color.Black);
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.width('100%')
.height(72)
.justifyContent(FlexAlign.Center);
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.borderRadius(15)
.backgroundColor('#F1F3F5');
}, (item: string) => item);
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.padding({ left: 10, right: 10 })
.width('100%')
.height('100%')
.scrollBar(BarState.Off)
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST, // 向上滚动PARENT_FIRST:父组件先滚动,父组件滚动到边缘以后自身滚动。
scrollBackward: NestedScrollMode.SELF_FIRST // 向下滚动SELF_FIRST:自身先滚动,自身滚动到边缘以后父组件滚动。
});
}
.clip(false)
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.tabBar('关注')
.backgroundColor('#ffffff');
TabContent() {
Text('推荐');
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.tabBar('推荐')
.backgroundColor('#ffffff');
}
.clip(false)
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.backgroundColor('#ffffff');
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.width('100%');
}
.clip(false)
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.edgeEffect(EdgeEffect.Spring)
.friction(0.6)
.backgroundColor('#ffffff')
.scrollBar(BarState.Off)
.width('100%')
.height('100%');
}
}
效果预览:
点击放大
说明
自定义标题的实现方式,此处主要讲解滚动组件嵌套实现方式。若是想要以自定义标题实现滚动嵌套吸顶效果,滚动组件的父组件一定要设置高度限制为100%,若不设置高度限制,父组件自适应高度会导致吸顶失效,可详见下文中“多标题滚动嵌套吸顶效果”的方案一,其内第一个标题为自定义标题,及其父组件高度注释。
方案二: 由于Waterflow布局本身不支持吸顶,如需使用Waterflow布局实现吸顶效果,可以在滚动组件上通过Stack叠加一个组件实现,示例代码如下:
// 瀑布流布局项数据模型(用于描述每个子项的布局属性)
export class WaterFlowItemModel {
fullWidth: boolean = false;
stickyTop: number | undefined = undefined;
height: number = 0;
width: number = 0;
bgColor: string = '#f0f0f0';
}
// 瀑布流数据源实现类
export class WaterFlowDataSource implements IDataSource {
private dataArray: WaterFlowItemModel[] = [];
private listeners: DataChangeListener[] = [];
constructor() {
this.dataArray = [];
// 生成10个瀑布流测试数据项
for (let i = 0; i < 10; i++) {
const model = new WaterFlowItemModel();
model.fullWidth = i % 5 === 4;
model.height = 120;
model.bgColor = '#f0f0f0';
this.dataArray.push(model);
}
}
// 获取数据总量
totalCount(): number {
return this.dataArray.length;
}
// 根据索引获取单个数据项
getData(index: number) {
return this.dataArray[index];
}
// 注册数据变更监听器
registerDataChangeListener(listener: DataChangeListener) {
if (this.listeners.indexOf(listener) < 0) {
this.listeners.push(listener);
}
}
// 移除已注册的数据变更监听器
unregisterDataChangeListener(listener: DataChangeListener) {
const pos = this.listeners.indexOf(listener);
if (pos >= 0) {
this.listeners.splice(pos, 1);
}
}
}
@Entry
@Component
struct SingleTitleSolutionTwo {
scroller: Scroller = new Scroller();
datasource: WaterFlowDataSource = new WaterFlowDataSource();
@State scrollOffset: number = 0;
build() {
Stack({ alignContent: Alignment.TopStart }) {
WaterFlow({ scroller: this.scroller }) {
LazyForEach(this.datasource, (item: WaterFlowItemModel, index) => {
FlowItem() {
// 当索引不为1,展示当前索引值
if (index !== 1) {
Text(${index});
}
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.margin({ right:20,bottom:12,left:19 })
.borderRadius(15)
.onClick(() => {
console.info('onClick ---- ', index);
})
.width('90%')
.height(item.height)
// .backgroundColor(index === 1 ? '#00000000' : Color.White);
.backgroundColor('#F1F3F5')
}, (key: string) => key);
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.edgeEffect(EdgeEffect.Spring)
.onWillScroll((offset: number) => {
this.scrollOffset = this.scroller.currentOffset().yOffset + offset;
})
.width('100%')
.height('100%')
.backgroundColor('#ffffff');
// 在索引为1的WaterFlowItem上方叠加一个组件
Stack() {
Text('自定义占位标题')
.width('100%')
.height(this.datasource.getData(1).height)
.fontColor(Color.White)
.backgroundColor('#66666666')
.fontSize(17)
.textAlign(TextAlign.Center);
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.hitTestBehavior(HitTestMode.Transparent)
.position({ x: 0, y: this.scrollOffset >= 120 ? 0 : 120 - this.scrollOffset });
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.width('100%')
.height('100%')
.clip(true);
}
}
实现效果如下:
点击放大
多标题滚动嵌套吸顶效果
方案一:通过nestedScroll属性对滚动组件多次滚动嵌套,参考单标题滚动嵌套吸顶效果,示例代码如下:
@Entry
@Component
struct MultipleTitleSolutionOne {
subsController: TabsController = new TabsController();
@State arr: string[] = ['1', '2', '3'];
@State arr1: string[] = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15'];
build() {
Scroll() {
Column() {
Column() {
Text('自定义占位标题')
.fontSize(30)
.width('100%')
.height(200)
.backgroundColor('#66666666')
.textAlign(TextAlign.Center);
};
Column() {
Row() {
Text('自定义占位标题') // 自定义实现标题吸顶,依旧采用Tabs组件也适用
.fontSize(16)
.height(60)
.width('100%')
.textAlign(TextAlign.Center);
}.backgroundColor('#6dbab8b8')
Scroll() {
Column() {
ForEach(this.arr, (item: number) => {
Row() {
Text('item' + item)
.fontSize(16)
.height(72)
.fontColor(Color.Black);
}
.width('100%')
.height(72)
.borderRadius(15)
.margin({
bottom: 10
})
.justifyContent(FlexAlign.Center);
}, (item: string) => item);
Tabs({ barPosition: BarPosition.Start, controller: this.subsController }) {
TabContent() {
List({ space: 10 }) {
ForEach(this.arr1, (item: number) => {
ListItem() {
Row() {
Text('item' + item)
.fontSize(16)
.height(72)
.fontColor(Color.Black);
}
.width('100%')
.height(72)
.justifyContent(FlexAlign.Center);
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.borderRadius(15)
.backgroundColor(Color.White);
}, (item: string) => item);
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.padding({ left: 10, right: 10 })
.width('100%')
.height('100%')
.scrollBar(BarState.Off)
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST, // 向上滚动PARENT_FIRST:父组件先滚动,父组件滚动到边缘以后自身滚动。
scrollBackward: NestedScrollMode.SELF_FIRST // 向下滚动SELF_FIRST:自身先滚动,自身滚动到边缘以后父组件滚动。
});
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.tabBar('关注')
.backgroundColor('#F1F3F5');
TabContent() {
Text('推荐');
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.tabBar('推荐')
.backgroundColor('#F1F3F5');
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.backgroundColor('#6dbab8b8');
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.backgroundColor(Color.White)
.width('100%');
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.width('100%')
.layoutWeight(1)
.scrollBar(BarState.Off)
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST, // 向上滚动PARENT_FIRST:父组件先滚动,父组件滚动到边缘以后自身滚动。
scrollBackward: NestedScrollMode.SELF_FIRST // 向下滚动SELF_FIRST:自身先滚动,自身滚动到边缘以后父组件滚动。
});
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.height('100%') // 由于是自定义的标题,所以容纳标题和列表的父组件一定要设置高度为100%
.backgroundColor('#6dbab8b8');
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.width('100%');
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.edgeEffect(EdgeEffect.Spring)
.friction(0.6)
.backgroundColor(Color.White)
.scrollBar(BarState.Off)
.width('100%')
.height('100%');
}
}
实现效果如下:
点击放大
方案二:通过List组件的sticky属性,实现吸顶效果。
常见场景一:header吸顶和footer吸底,该方案仅适用于List组件,具体实施方案参考官方示例:吸顶/吸底。
常见场景二:多标题多tab菜单吸顶时,实现滑动时自动切换tab栏。通过onDidScroll方法,监听滚动事件,根据滚动偏移量动态计算当前处于哪个区域,再根据当前区块切换标题内容。示例代码如下:
@Entry
@Component
struct MultipleTitleSolutionOnePlus {
@State data1: string[] = ['标题1', '标题2', '标题3', '标题4'];
@State data2: string[] = ['1', '2', '3', '4'];
@State selectIndex1: number = 0;
private listController: ListScroller = new ListScroller();
@Builder
topBuilder1() {
Row() {
ForEach(this.data1, (item: string, index: number) => {
Text(item)
.fontColor(index == this.selectIndex1 ? Color.Blue : '#000')
.fontSize(index == this.selectIndex1 ? 20 : 16)
.fontWeight(index == this.selectIndex1 ? 500 : 400)
.onClick(() => {
this.listController.scrollToItemInGroup(0, index, true);
});
});
}
.backgroundColor(0xAABBCC)
.height(60)
.width('100%')
.justifyContent(FlexAlign.SpaceAround);
}
@Builder
topBuilder2() {
Row() {
Text('第二组吸顶')
.padding(5)
.textAlign(TextAlign.Center);
}
.backgroundColor(0xAABBCC)
.justifyContent(FlexAlign.SpaceAround)
.height(60)
.width('100%');
}
build() {
Column() {
List({ space: 10, scroller: this.listController }) {
ListItemGroup({ header: this.topBuilder1, space: 10 }) {
ForEach(this.data1, (item: string) => {
ListItem() {
Text(item)
.width('90%')
.height(200)
.backgroundColor('#f1f3f5')
.borderRadius(10)
.textAlign(TextAlign.Center);
};
});
};
ListItemGroup() {
ListItem() {
Text('其他内容')
.width('90%')
.height(400)
.backgroundColor('#f1f3f5')
.borderRadius(10)
.fontSize(16)
.textAlign(TextAlign.Center);
};
};
ListItemGroup({ header: this.topBuilder2, space: 10 }) {
ForEach(this.data2, (item: string) => {
ListItem() {
Text(item)
.width('90%')
.height(200)
.backgroundColor('#f1f3f5')
.borderRadius(10)
.textAlign(TextAlign.Center);
};
});
};
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.onDidScroll(() => {
let currentOffset = this.listController.currentOffset().yOffset;
this.selectIndex1 = Math.floor(currentOffset / 200);
})
.scrollBar(BarState.Off)
.sticky(StickyStyle.Header)
.alignListItem(ListItemAlign.Center);
};
}
}
实现效果如下:
点击放大
二级标题滚动嵌套吸顶效果
第一层吸顶用Scroll嵌套List实现,第二层是利用ListItemGroup的sticky属性实现,示例代码如下:
@Entry
@Component
struct MultipleTitleSolutionTwo {
private timeTable: TimeTable[] = [
{
title: 'Header2-0',
projects: ['内容1']
},
{
title: 'Header2-1',
projects: ['内容2']
},
{
title: 'Header2-2',
projects: ['内容3']
}
];
@Builder
itemHead(text: string, index: number) {
if (index === 0) {
Text().height(0);
} else {
Text(text)
.width('100%')
.textAlign(TextAlign.Center)
.fontColor(Color.Black)
.height(index === 1 ? 100 : 200)
.backgroundColor('#d1d1d6');
}
}
build() {
Scroll() {
Column() {
Text('自定义占位标题')
.width('100%')
.height(200)
.backgroundColor('#66666666')
.textAlign(TextAlign.Center);
Column() {
// 第一层吸顶效果主要是通过设置nestedScroll属性以及父组件设置高度100%实现
Text('Header1')
.width('100%')
.height(100)
.backgroundColor('#4d666666')
.textAlign(TextAlign.Center)
.fontColor(Color.Black);
List() {
ForEach(this.timeTable, (item: TimeTable, index) => {
ListItemGroup({ header: this.itemHead(item.title, index) }) {
ForEach(item.projects, (project: string) => {
ListItem() {
Text(project)
.width('100%')
.height(800)
.fontSize(20)
.textAlign(TextAlign.Center)
.backgroundColor('#FFFFFF');
}
}, (item: string) => item);
}.clip(false);
});
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.sticky(StickyStyle.Header)
.scrollBar(BarState.Off)
.width('100%')
.height('calc(100% - 100vp)')
.backgroundColor('#ffffff')
.edgeEffect(EdgeEffect.Spring)
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST,
scrollBackward: NestedScrollMode.SELF_FIRST
});
}.clip(false).expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM]).height('100%')
}.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.clip(false)
.edgeEffect(EdgeEffect.Spring)
.friction(0.6)
.backgroundColor(Color.White)
.scrollBar(BarState.Off)
.width('100%')
.height('100%');
}
}
interface TimeTable {
title: string;
projects: string[];
}
实现效果如下:
点击放大
动态控制二级标题滚动嵌套吸顶效果:
在List组件onDidScroll方法里判断索引为2的组件滚动偏移量,动态调整Header1的position属性和List组件的contentStartOffset属性,示例代码如下:
@Entry
@Component
struct MultipleTitleSolutionTwoDynamic {
@State header1Position: number = 0;
@State listStartOffset: number = 100;
private scroller = new Scroller();
private timeTable: TimeTableDynamic[] = [
{
title: 'Header2-0',
projects: ['内容1']
},
{
title: 'Header2-1',
projects: ['内容2']
},
{
title: 'Header2-2',
projects: ['内容3']
}
];
@Builder
itemHead(text: string, index: number) {
if (index === 0) {
Text().height(0);
} else {
Text(text)
.width('100%')
.textAlign(TextAlign.Center)
.fontColor(Color.Black)
.height(index === 1 ? 100 : 200)
.backgroundColor('#d1d1d6');
}
}
build() {
Scroll() {
Column() {
Text('二级标题滚动嵌套吸顶')
.width('100%')
.height(200)
.backgroundColor('#66666666')
.textAlign(TextAlign.Center);
Column() {
// 第一层吸顶效果主要是通过设置nestedScroll属性以及父组件设置高度100%实现
Text('Header1')
.width('100%')
.height(100)
.backgroundColor('#4d666666')
.textAlign(TextAlign.Center)
.fontColor(Color.Black)
.position({ y: this.header1Position })
.zIndex(10);
List({ scroller: this.scroller }) {
ForEach(this.timeTable, (item: TimeTableDynamic, index) => {
ListItemGroup({ header: this.itemHead(item.title, index) }) {
ForEach(item.projects, (project: string) => {
ListItem() {
Text(project)
.width('100%')
.height(800)
.fontSize(20)
.textAlign(TextAlign.Center)
.backgroundColor('#ffffff');
}.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM]);
}, (item: string) => item);
};
});
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.sticky(StickyStyle.Header)
.scrollBar(BarState.Off)
.width('100%')
.height('100%')
.backgroundColor('#ffffff')
.edgeEffect(EdgeEffect.Spring)
.contentStartOffset(this.listStartOffset)
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST,
scrollBackward: NestedScrollMode.SELF_FIRST
})
.onDidScroll(() => {
let secondRect = this.scroller.getItemRect(2);
if (secondRect.x === 0 && secondRect.y === 0 && secondRect.width === 0 && secondRect.height === 0) {
console.info(索引为${2}的组件不在屏幕上);
return;
}
this.header1Position = Math.min(0, secondRect.y - 200);
this.listStartOffset = Math.max(0, 100 + this.header1Position);
});
}.height('100%');
};
}.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.clip(false)
.edgeEffect(EdgeEffect.Spring)
.friction(0.6)
.backgroundColor(Color.White)
.scrollBar(BarState.Off)
.width('100%')
.height('100%');
}
}
interface TimeTableDynamic {
title: string;
projects: string[];
}
https://coub.com/view/gylbvhqrkz
https://coub.com/view/gqrdnsqbsm
https://coub.com/view/0a3thuu2b4
https://coub.com/view/lg6lw08ivp
https://coub.com/view/qzxlw9ogx1
https://coub.com/view/68z9behrh7
https://coub.com/view/4iijkgxtaa
https://coub.com/view/xuelqryeob
https://coub.com/view/037iwxjpf9
https://coub.com/view/ydu5rfse9r
https://coub.com/view/g71sfv35n0
https://coub.com/view/hmkl7icf96
https://coub.com/view/7gf8jpjeqf
https://coub.com/view/6ar7vzg3jt
https://coub.com/view/x6brb1zhst
https://coub.com/view/esc2tpa50i
https://coub.com/view/ij4i54b4su
https://coub.com/view/6csj522lvg
https://coub.com/view/oaspyursw3
https://coub.com/view/gvem0zleaj
https://coub.com/view/xs8g570e03
https://coub.com/view/suwbkm015s
https://coub.com/view/74yubqnb80
https://coub.com/view/6wh2qfuaws
https://coub.com/view/2yv1gta5d2
https://coub.com/view/hopm4eqo5c
https://coub.com/view/q1tm3sdodd
https://coub.com/view/7udyb7fyhi
https://coub.com/view/1z3anr32zq
https://coub.com/view/jhh5mhj3y0
https://coub.com/view/dvq9c4eyfc
https://coub.com/view/wv83dp9qeo
https://coub.com/view/a9i5tkfl9w
https://coub.com/view/v372pqbiis
https://coub.com/view/ig9545no10
https://coub.com/view/w79ltp6ou9
https://coub.com/view/u9s0qk1akf
https://coub.com/view/damsu4njzt
https://coub.com/view/v2vriht1bd
https://coub.com/view/e93br3m9qa
https://coub.com/view/xd56t399ne
https://coub.com/view/dnvfpx6l5b
https://coub.com/view/pehxieghx6
https://coub.com/view/6w14zzs972
https://coub.com/view/46b628kwgi
https://coub.com/view/o5bg5eawhx
https://coub.com/view/rrinp7vw3c
https://coub.com/view/mamo602b5a
https://coub.com/view/hlvm2nw499
https://coub.com/view/jzu48fqzw5
https://coub.com/view/ncc5pejn2y
https://coub.com/view/o61yarijz2
https://coub.com/view/xt572wfwwl
https://coub.com/view/t4z6lnd7n8
https://coub.com/view/cxz5p1rtil
https://coub.com/view/a630gf5urg
https://coub.com/view/7khrln5czp
https://coub.com/view/u52ikkf9j8
https://coub.com/view/6ispqsd85w
https://coub.com/view/g5ndpkf32e
https://coub.com/view/nf11v6hl00
https://coub.com/view/g2va9aj5ue
https://coub.com/view/qe1rsfh7ey
https://coub.com/view/ldvx0v2kr2
https://coub.com/view/w3bct13r9i
https://coub.com/view/3zyw0cjxo1
https://coub.com/view/ny9foponj5
https://coub.com/view/g4q545h3da
https://coub.com/view/kekg8y04v7
https://coub.com/view/ms20cjnzki
https://coub.com/view/6hg6xx4f3u
https://coub.com/view/4gd6mjpi41
https://coub.com/view/91selywfey
https://coub.com/view/d9lltiovbh
https://coub.com/view/j6dhi1cqlu
https://coub.com/view/pupns1qvyy
https://coub.com/view/42eqvna2sj
https://coub.com/view/213yl91c0r
https://coub.com/view/ligxe8niat
https://sites.google.com/view/4npswg73/home
https://sites.google.com/view/1cqggg42/home
https://sites.google.com/view/7hwthv95/home
https://sites.google.com/view/8ftpag65/home
https://sites.google.com/view/9hupnv16/home
https://sites.google.com/view/9rcwcc44/home
https://sites.google.com/view/4ygxfw66/home
https://sites.google.com/view/7yjaoo71/home
https://sites.google.com/view/6euxnz02/home
https://sites.google.com/view/2xrcwv82/home
https://sites.google.com/view/3bruzf22/home
https://sites.google.com/view/1fampb85/home
https://sites.google.com/view/0uqjgm22/home
https://sites.google.com/view/8lytyx96/home
https://sites.google.com/view/2rcvcx10/home
https://sites.google.com/view/0wxard26/home
https://sites.google.com/view/0rhcrm43/home
https://sites.google.com/view/1cilam64/home
https://sites.google.com/view/2dsnqw73/home
https://sites.google.com/view/0vgsrq46/home
https://sites.google.com/view/8nqpzw07/home
https://sites.google.com/view/2zlysr71/home
https://sites.google.com/view/6krwym27/home
https://sites.google.com/view/6xuung21/home
https://sites.google.com/view/5irzjv73/home
https://sites.google.com/view/7lukby92/home
https://sites.google.com/view/3oyfhr95/home
https://sites.google.com/view/2srcwe43/home
https://sites.google.com/view/0setkv89/home
https://sites.google.com/view/5rvxgj88/home
https://sites.google.com/view/7tfahr42/home
https://sites.google.com/view/7yboep62/home
https://sites.google.com/view/4upmsh64/home
https://sites.google.com/view/0zwrrv97/home
https://sites.google.com/view/2gawzu13/home
https://sites.google.com/view/6qeyzl51/home
https://sites.google.com/view/3ievss43/home
https://sites.google.com/view/0ibpqm02/home
https://sites.google.com/view/3awrap71/home
https://sites.google.com/view/0tvqkg18/home
https://sites.google.com/view/5sbyjy90/home
https://sites.google.com/view/6ehwco13/home
https://sites.google.com/view/7tjzdg23/home
https://sites.google.com/view/6rakcq50/home
https://sites.google.com/view/7hexol86/home
https://sites.google.com/view/0cyonp67/home
https://sites.google.com/view/7cpmvf18/home
https://sites.google.com/view/7qroxh49/home
https://sites.google.com/view/2qshnp57/home
https://sites.google.com/view/6dwyim71/home
https://sites.google.com/view/4cegdc43/home
https://sites.google.com/view/9kbwox44/home
https://sites.google.com/view/6sciwd21/home
https://sites.google.com/view/6bjrsb10/home
https://sites.google.com/view/3qxmma28/home
https://sites.google.com/view/7cbftv08/home
https://sites.google.com/view/4kkccj88/home
https://sites.google.com/view/5pjonl99/home
https://sites.google.com/view/4aeacr58/home
https://sites.google.com/view/6fiuxa97/home
https://sites.google.com/view/5brhgy37/home
https://sites.google.com/view/1mzbfo35/home
https://sites.google.com/view/9kbckv34/home
https://sites.google.com/view/9cobir53/home
https://sites.google.com/view/6qgbcm33/home
https://sites.google.com/view/8lgcnx33/home
https://sites.google.com/view/9jzmzc49/home
https://sites.google.com/view/0bujrj34/home
https://sites.google.com/view/1heuxf15/home
https://sites.google.com/view/0przrr24/home
https://sites.google.com/view/8gmodp38/home
https://sites.google.com/view/1qgplx06/home
https://sites.google.com/view/1vbfom31/home
https://sites.google.com/view/4fgfqr84/home
https://sites.google.com/view/6oybbx22/home
https://sites.google.com/view/1knpbg15/home
https://sites.google.com/view/0opgke85/home
https://sites.google.com/view/7kirma83/home
https://sites.google.com/view/1zzbdj74/home
https://sites.google.com/view/8csmsa36/home
https://sites.google.com/view/1edswe69/home
https://sites.google.com/view/8kyxaz85/home
https://sites.google.com/view/8bplrw32/home
https://sites.google.com/view/7pdmfv03/home
https://sites.google.com/view/8qkoih30/home
https://sites.google.com/view/4noxpv38/home
https://sites.google.com/view/4itlbr61/home
https://sites.google.com/view/5rjefu09/home
https://sites.google.com/view/5ujnxe58/home
https://sites.google.com/view/3gqlqd93/home
https://sites.google.com/view/4ijybb89/home
https://sites.google.com/view/6ipevr32/home
https://sites.google.com/view/5mhxux08/home
https://sites.google.com/view/8wojcb88/home
https://sites.google.com/view/0riyrm78/home
0 个评论
要回复文章请先登录或注册