85.[HarmonyOS NEXT 实战案例十七] 设置选项列表网格布局(下)

项目已开源,开源地址: gitcode.com/nutpi/Harmo... , 欢迎fork & star

效果演示

1. 概述

在上一篇教程中,我们学习了如何使用HarmonyOS NEXT的GridRow和GridCol组件实现基础的设置选项列表网格布局。本篇教程将在此基础上,深入探讨如何优化和扩展设置选项列表,实现更加灵活、美观和功能丰富的界面。

本教程将涵盖以下内容:

  • 设置选项分组
  • 响应式布局设计
  • 设置选项的交互设计
  • 动画效果实现
  • 主题与样式定制
  • GridRow和GridCol的高级配置

2. 设置选项分组

2.1 分组数据结构设计

首先,我们需要扩展数据结构,支持设置选项的分组:

typescript 复制代码
interface SettingsType {
    title: string;
    icon: Resource;
    description?: string; // 可选的描述文本
    hasSwitch?: boolean; // 是否显示开关
    isOn?: boolean;      // 开关状态
}

interface SettingsGroupType {
    groupTitle: string;   // 分组标题
    items: SettingsType[]; // 分组内的设置项
}

这个扩展的数据结构增加了以下特性:

  • 为设置项添加了可选的描述文本
  • 为设置项添加了开关控制选项
  • 创建了分组结构,每个分组包含一个标题和多个设置项

2.2 分组数据准备

typescript 复制代码
private settingsGroups: SettingsGroupType[] = [
    {
        groupTitle: '账号',
        items: [
            { title: '账号与安全', icon: $r('app.media.01'), description: '管理您的账号信息和安全设置' },
            { title: '隐私设置', icon: $r('app.media.03'), description: '管理您的隐私和数据' }
        ]
    },
    {
        groupTitle: '通知与声音',
        items: [
            { title: '通知设置', icon: $r('app.media.02'), hasSwitch: true, isOn: true },
            { title: '声音设置', icon: $r('app.media.04'), hasSwitch: true, isOn: true }
        ]
    },
    {
        groupTitle: '通用',
        items: [
            { title: '通用设置', icon: $r('app.media.04') },
            { title: '语言', icon: $r('app.media.05'), description: '简体中文' },
            { title: '深色模式', icon: $r('app.media.01'), hasSwitch: true, isOn: false }
        ]
    },
    {
        groupTitle: '关于',
        items: [
            { title: '帮助与反馈', icon: $r('app.media.05') },
            { title: '关于我们', icon: $r('app.media.01'), description: '版本 1.0.0' }
        ]
    }
]

2.3 分组布局实现

typescript 复制代码
build() {
    Column() {
        Text('设置')
            .fontSize(20)
            .fontWeight(FontWeight.Bold)
            .margin({ bottom: 16 })
            .width('100%')
            .textAlign(TextAlign.Start)

        ForEach(this.settingsGroups, (group: SettingsGroupType, groupIndex: number) => {
            // 分组标题
            Text(group.groupTitle)
                .fontSize(14)
                .fontColor('#666666')
                .margin({ top: groupIndex > 0 ? 24 : 0, bottom: 8, left: 8 })
                .width('100%')
                .textAlign(TextAlign.Start)

            // 分组内的设置项
            GridRow({ columns: 1 }) {
                ForEach(group.items, (setting: SettingsType) => {
                    GridCol({ span: 1 }) {
                        this.SettingItem(setting)
                    }
                })
            }
        })
    }
    .width('100%')
    .padding(16)
}

2.4 设置项Builder实现

typescript 复制代码
@Builder
private SettingItem(setting: SettingsType) {
    Row() {
        Image(setting.icon)
            .width(24)
            .height(24)
            .margin({ right: 16 })

        Column() {
            Text(setting.title)
                .fontSize(16)

            if (setting.description) {
                Text(setting.description)
                    .fontSize(12)
                    .fontColor('#999999')
                    .margin({ top: 4 })
            }
        }
        .alignItems(HorizontalAlign.Start)

        Blank()

        if (setting.hasSwitch) {
            Toggle({ type: ToggleType.Switch, isOn: setting.isOn ?? false })
                .margin({ left: 8 })
                .onChange((isOn: boolean) => {
                    setting.isOn = isOn
                })
        } else {
            Image($r("app.media.arrowright"))
                .width(16)
                .height(16)
        }
    }
    .width('100%')
    .padding(16)
    .backgroundColor('#FFFFFF')
    .borderRadius(8)
    .margin({ bottom: 8 })
}

3. 响应式布局设计

3.1 断点配置

为了使设置选项列表能够适应不同屏幕尺寸的设备,我们需要配置断点和响应式列数:

typescript 复制代码
// 在build方法中
Column() {
    // 标题...

    ForEach(this.settingsGroups, (group: SettingsGroupType, groupIndex: number) => {
        // 分组标题...

        // 分组内的设置项
        GridRow({ 
            columns: { xs: 1, sm: 1, md: 2, lg: 3 },
            breakpoints: { value: ['320vp', '600vp', '840vp'], reference: BreakpointsReference.WindowSize }
        }) {
            ForEach(group.items, (setting: SettingsType) => {
                GridCol({ span: 1 }) {
                    this.SettingItem(setting)
                }
            })
        }
    })
}

在这个配置中:

  • 在小屏和中小屏设备(xs和sm断点)上,设置项保持单列布局
  • 在中等屏幕(md断点)上,设置项使用2列布局
  • 在大屏设备(lg断点)上,设置项使用3列布局

3.2 自定义断点

除了使用默认的断点配置外,我们还可以根据具体需求自定义断点:

typescript 复制代码
// 自定义断点配置
private customBreakpoints = {
    value: ['320vp', '520vp', '720vp', '960vp'],
    reference: BreakpointsReference.WindowSize
};

// 在build方法中使用自定义断点
GridRow({ 
    columns: { xs: 1, sm: 1, md: 2, lg: 2, xl: 3 },
    breakpoints: this.customBreakpoints
}) {
    // ...
}

3.3 设置项的响应式布局

我们可以根据屏幕尺寸调整设置项的布局和样式:

typescript 复制代码
@Builder
private SettingItem(setting: SettingsType, isSmallScreen: boolean = false) {
    Row() {
        Image(setting.icon)
            .width(isSmallScreen ? 20 : 24)
            .height(isSmallScreen ? 20 : 24)
            .margin({ right: isSmallScreen ? 12 : 16 })

        Column() {
            Text(setting.title)
                .fontSize(isSmallScreen ? 14 : 16)

            if (setting.description) {
                Text(setting.description)
                    .fontSize(isSmallScreen ? 10 : 12)
                    .fontColor('#999999')
                    .margin({ top: isSmallScreen ? 2 : 4 })
            }
        }
        .alignItems(HorizontalAlign.Start)

        Blank()

        if (setting.hasSwitch) {
            Toggle({ type: ToggleType.Switch, isOn: setting.isOn ?? false })
                .margin({ left: 8 })
                .onChange((isOn: boolean) => {
                    setting.isOn = isOn
                })
        } else {
            Image($r("app.media.arrowright"))
                .width(isSmallScreen ? 14 : 16)
                .height(isSmallScreen ? 14 : 16)
        }
    }
    .width('100%')
    .padding(isSmallScreen ? 12 : 16)
    .backgroundColor('#FFFFFF')
    .borderRadius(8)
    .margin({ bottom: 8 })
}

然后在build方法中使用媒体查询来判断当前屏幕尺寸:

typescript 复制代码
build() {
    Column() {
        // 标题...

        ForEach(this.settingsGroups, (group: SettingsGroupType, groupIndex: number) => {
            // 分组标题...

            // 使用媒体查询判断屏幕尺寸
            MediaQuery({ minWidth: 320, maxWidth: 520 }) {
                GridRow({ columns: 1 }) {
                    ForEach(group.items, (setting: SettingsType) => {
                        GridCol({ span: 1 }) {
                            this.SettingItem(setting, true) // 小屏版本
                        }
                    })
                }
            }
            
            MediaQuery({ minWidth: 521 }) {
                GridRow({ 
                    columns: { sm: 1, md: 2, lg: 3 },
                    breakpoints: { value: ['600vp', '840vp'], reference: BreakpointsReference.WindowSize }
                }) {
                    ForEach(group.items, (setting: SettingsType) => {
                        GridCol({ span: 1 }) {
                            this.SettingItem(setting, false) // 大屏版本
                        }
                    })
                }
            }
        })
    }
}

4. 设置选项的交互设计

4.1 点击效果

为设置项添加点击效果,使其更具交互性:

typescript 复制代码
@Builder
private SettingItem(setting: SettingsType) {
    Row() {
        // 原有内容...
    }
    .width('100%')
    .padding(16)
    .backgroundColor('#FFFFFF')
    .borderRadius(8)
    .margin({ bottom: 8 })
    .onClick(() => {
        if (!setting.hasSwitch) {
            // 处理点击事件
            console.info(`${setting.title} clicked`)
        }
    })
    .stateStyles({
        pressed: {
            .backgroundColor('#F0F0F0')
            .scale({ x: 0.98, y: 0.98 })
        }
    })
}

4.2 悬停效果

在桌面设备上,我们可以为设置项添加悬停效果:

typescript 复制代码
.stateStyles({
    pressed: {
        .backgroundColor('#F0F0F0')
        .scale({ x: 0.98, y: 0.98 })
    },
    hover: {
        .backgroundColor('#F8F8F8')
        .shadow({
            radius: 4,
            color: 'rgba(0, 0, 0, 0.1)',
            offsetX: 0,
            offsetY: 2
        })
    }
})

4.3 开关交互

为带开关的设置项添加交互逻辑:

typescript 复制代码
@State settingsGroups: SettingsGroupType[] = [ /* ... */ ]

// 在SettingItem中
if (setting.hasSwitch) {
    Toggle({ type: ToggleType.Switch, isOn: setting.isOn ?? false })
        .margin({ left: 8 })
        .onChange((isOn: boolean) => {
            // 更新开关状态
            setting.isOn = isOn
            
            // 处理特定设置项的逻辑
            if (setting.title === '深色模式') {
                this.toggleDarkMode(isOn)
            } else if (setting.title === '通知设置') {
                this.toggleNotifications(isOn)
            }
        })
}

// 处理深色模式切换
private toggleDarkMode(isOn: boolean) {
    // 实现深色模式切换逻辑
    console.info(`深色模式: ${isOn ? '开启' : '关闭'}`)
}

// 处理通知设置切换
private toggleNotifications(isOn: boolean) {
    // 实现通知设置切换逻辑
    console.info(`通知设置: ${isOn ? '开启' : '关闭'}`)
}

5. 动画效果实现

5.1 列表项进入动画

为设置选项列表添加进入动画,使界面更加生动:

typescript 复制代码
@State appearAnimation: boolean = false

aboutToAppear() {
    // 延迟执行动画,确保组件已经渲染
    setTimeout(() => {
        this.appearAnimation = true
    }, 100)
}

// 在SettingItem中添加动画
@Builder
private SettingItem(setting: SettingsType, index: number) {
    Row() {
        // 原有内容...
    }
    .width('100%')
    .padding(16)
    .backgroundColor('#FFFFFF')
    .borderRadius(8)
    .margin({ bottom: 8 })
    .opacity(this.appearAnimation ? 1 : 0)
    .animation({
        duration: 300,
        curve: Curve.EaseOut,
        delay: 50 * index, // 根据索引设置延迟,创建级联效果
        iterations: 1
    })
    // 其他属性...
}

// 在build方法中传递索引
ForEach(group.items, (setting: SettingsType, index: number) => {
    GridCol({ span: 1 }) {
        this.SettingItem(setting, index)
    }
})

5.2 开关动画

为开关添加自定义动画效果:

typescript 复制代码
@Builder
private AnimatedToggle(isOn: boolean, onChange: (isOn: boolean) => void) {
    Toggle({ type: ToggleType.Switch, isOn: isOn })
        .margin({ left: 8 })
        .onChange((value: boolean) => {
            onChange(value)
        })
        .animation({
            duration: 200,
            curve: Curve.EaseInOut,
            iterations: 1
        })
}

// 在SettingItem中使用AnimatedToggle
if (setting.hasSwitch) {
    this.AnimatedToggle(setting.isOn ?? false, (isOn: boolean) => {
        setting.isOn = isOn
        // 处理特定设置项的逻辑...
    })
}

6. 主题与样式定制

6.1 设置项样式变体

为设置项创建不同的样式变体:

typescript 复制代码
enum SettingItemStyle {
    Default,
    Highlighted,
    Outlined
}

@Builder
private SettingItem(setting: SettingsType, style: SettingItemStyle = SettingItemStyle.Default) {
    Row() {
        // 原有内容...
    }
    .width('100%')
    .padding(16)
    .backgroundColor(this.getItemBackground(style))
    .borderRadius(8)
    .border(style === SettingItemStyle.Outlined ? {
        width: 1,
        color: '#E0E0E0',
        style: BorderStyle.Solid
    } : null)
    .margin({ bottom: 8 })
    // 其他属性...
}

private getItemBackground(style: SettingItemStyle): string {
    switch (style) {
        case SettingItemStyle.Default:
            return '#FFFFFF';
        case SettingItemStyle.Highlighted:
            return '#E3F2FD';
        case SettingItemStyle.Outlined:
            return 'transparent';
        default:
            return '#FFFFFF';
    }
}

// 在build方法中使用不同的样式
ForEach(group.items, (setting: SettingsType, index: number) => {
    GridCol({ span: 1 }) {
        // 为特定设置项使用高亮样式
        if (setting.title === '深色模式') {
            this.SettingItem(setting, SettingItemStyle.Highlighted)
        } else if (setting.title === '关于我们') {
            this.SettingItem(setting, SettingItemStyle.Outlined)
        } else {
            this.SettingItem(setting)
        }
    }
})

6.2 自定义主题

创建自定义主题,使设置选项列表能够适应不同的应用风格:

typescript 复制代码
@Observed
class SettingsTheme {
    primaryColor: string = '#2196F3'
    backgroundColor: string = '#F5F5F5'
    cardBackgroundColor: string = '#FFFFFF'
    textPrimaryColor: string = '#000000'
    textSecondaryColor: string = '#666666'
    textTertiaryColor: string = '#999999'
    borderRadius: number = 8
    isDark: boolean = false
    
    // 切换暗色主题
    toggleDarkMode() {
        this.isDark = !this.isDark
        if (this.isDark) {
            this.backgroundColor = '#121212'
            this.cardBackgroundColor = '#1E1E1E'
            this.textPrimaryColor = '#FFFFFF'
            this.textSecondaryColor = 'rgba(255, 255, 255, 0.7)'
            this.textTertiaryColor = 'rgba(255, 255, 255, 0.5)'
        } else {
            this.backgroundColor = '#F5F5F5'
            this.cardBackgroundColor = '#FFFFFF'
            this.textPrimaryColor = '#000000'
            this.textSecondaryColor = '#666666'
            this.textTertiaryColor = '#999999'
        }
    }
}

@Component
export struct SettingsGrid {
    @Provide theme: SettingsTheme = new SettingsTheme()
    // 其他属性...
    
    // 在SettingItem中使用主题属性
    @Builder
    private SettingItem(setting: SettingsType) {
        Row() {
            Image(setting.icon)
                .width(24)
                .height(24)
                .margin({ right: 16 })

            Column() {
                Text(setting.title)
                    .fontSize(16)
                    .fontColor(this.theme.textPrimaryColor)

                if (setting.description) {
                    Text(setting.description)
                        .fontSize(12)
                        .fontColor(this.theme.textTertiaryColor)
                        .margin({ top: 4 })
                }
            }
            .alignItems(HorizontalAlign.Start)

            Blank()

            // 其他内容...
        }
        .width('100%')
        .padding(16)
        .backgroundColor(this.theme.cardBackgroundColor)
        .borderRadius(this.theme.borderRadius)
        .margin({ bottom: 8 })
        // 其他属性...
    }
    
    // 在build方法中使用主题属性
    build() {
        Column() {
            Text('设置')
                .fontSize(20)
                .fontWeight(FontWeight.Bold)
                .fontColor(this.theme.textPrimaryColor)
                .margin({ bottom: 16 })
                .width('100%')
                .textAlign(TextAlign.Start)

            // 查找深色模式设置项并绑定到主题
            ForEach(this.settingsGroups, (group: SettingsGroupType) => {
                Text(group.groupTitle)
                    .fontSize(14)
                    .fontColor(this.theme.textSecondaryColor)
                    // 其他属性...
                
                // 设置项...
            })
        }
        .width('100%')
        .padding(16)
        .backgroundColor(this.theme.backgroundColor)
    }
}

7. GridRow和GridCol的高级配置

7.1 嵌套网格

使用嵌套的GridRow和GridCol实现更复杂的布局:

typescript 复制代码
// 在特定分组中使用嵌套网格
if (group.groupTitle === '通用') {
    GridRow({ columns: 1 }) {
        GridCol({ span: 1 }) {
            GridRow({ columns: { xs: 1, sm: 2, md: 3 } }) {
                ForEach(group.items, (setting: SettingsType) => {
                    GridCol({ span: 1 }) {
                        this.SettingItem(setting)
                    }
                })
            }
        }
    }
} else {
    // 其他分组使用普通网格
    GridRow({ columns: 1 }) {
        ForEach(group.items, (setting: SettingsType) => {
            GridCol({ span: 1 }) {
                this.SettingItem(setting)
            }
        })
    }
}

7.2 列偏移

使用offset属性实现列偏移,创建不对称的布局:

typescript 复制代码
// 在特定分组中使用列偏移
if (group.groupTitle === '关于') {
    GridRow({ columns: 12 }) {
        GridCol({ span: 5 }) {
            this.SettingItem(group.items[0])
        }
        
        GridCol({ span: 5, offset: 2 }) {
            this.SettingItem(group.items[1])
        }
    }
} else {
    // 其他分组使用普通网格
    // ...
}

7.3 列顺序调整

使用order属性调整列的顺序:

typescript 复制代码
// 在特定分组中调整列顺序
if (group.groupTitle === '账号') {
    GridRow({ columns: 2 }) {
        GridCol({ span: 1, order: 2 }) {
            this.SettingItem(group.items[0])
        }
        
        GridCol({ span: 1, order: 1 }) {
            this.SettingItem(group.items[1])
        }
    }
} else {
    // 其他分组使用普通网格
    // ...
}

8. 完整优化代码

以下是结合了分组、响应式布局、交互设计、动画效果和主题定制的完整优化代码:

typescript 复制代码
// 设置选项列表网格布局(优化版)
@Observed
class SettingsTheme {
    primaryColor: string = '#2196F3'
    backgroundColor: string = '#F5F5F5'
    cardBackgroundColor: string = '#FFFFFF'
    textPrimaryColor: string = '#000000'
    textSecondaryColor: string = '#666666'
    textTertiaryColor: string = '#999999'
    borderRadius: number = 8
    isDark: boolean = false
    
    // 切换暗色主题
    toggleDarkMode() {
        this.isDark = !this.isDark
        if (this.isDark) {
            this.backgroundColor = '#121212'
            this.cardBackgroundColor = '#1E1E1E'
            this.textPrimaryColor = '#FFFFFF'
            this.textSecondaryColor = 'rgba(255, 255, 255, 0.7)'
            this.textTertiaryColor = 'rgba(255, 255, 255, 0.5)'
        } else {
            this.backgroundColor = '#F5F5F5'
            this.cardBackgroundColor = '#FFFFFF'
            this.textPrimaryColor = '#000000'
            this.textSecondaryColor = '#666666'
            this.textTertiaryColor = '#999999'
        }
    }
}

interface SettingsType {
    title: string;
    icon: Resource;
    description?: string;
    hasSwitch?: boolean;
    isOn?: boolean;
}

interface SettingsGroupType {
    groupTitle: string;
    items: SettingsType[];
}

enum SettingItemStyle {
    Default,
    Highlighted,
    Outlined
}

@Component
export struct SettingsGrid {
    @Provide theme: SettingsTheme = new SettingsTheme()
    @State appearAnimation: boolean = false
    
    @State settingsGroups: SettingsGroupType[] = [
        {
            groupTitle: '账号',
            items: [
                { title: '账号与安全', icon: $r('app.media.01'), description: '管理您的账号信息和安全设置' },
                { title: '隐私设置', icon: $r('app.media.03'), description: '管理您的隐私和数据' }
            ]
        },
        {
            groupTitle: '通知与声音',
            items: [
                { title: '通知设置', icon: $r('app.media.02'), hasSwitch: true, isOn: true },
                { title: '声音设置', icon: $r('app.media.04'), hasSwitch: true, isOn: true }
            ]
        },
        {
            groupTitle: '通用',
            items: [
                { title: '通用设置', icon: $r('app.media.04') },
                { title: '语言', icon: $r('app.media.05'), description: '简体中文' },
                { title: '深色模式', icon: $r('app.media.01'), hasSwitch: true, isOn: false }
            ]
        },
        {
            groupTitle: '关于',
            items: [
                { title: '帮助与反馈', icon: $r('app.media.05') },
                { title: '关于我们', icon: $r('app.media.01'), description: '版本 1.0.0' }
            ]
        }
    ]
    
    private customBreakpoints = {
        value: ['320vp', '520vp', '720vp', '960vp'],
        reference: BreakpointsReference.WindowSize
    };
    
    aboutToAppear() {
        // 延迟执行动画,确保组件已经渲染
        setTimeout(() => {
            this.appearAnimation = true
        }, 100)
        
        // 查找深色模式设置项并绑定到主题
        this.findAndBindDarkModeToggle()
    }
    
    private findAndBindDarkModeToggle() {
        for (let group of this.settingsGroups) {
            for (let item of group.items) {
                if (item.title === '深色模式') {
                    item.isOn = this.theme.isDark
                    break
                }
            }
        }
    }
    
    @Builder
    private SettingItem(setting: SettingsType, index: number = 0, style: SettingItemStyle = SettingItemStyle.Default) {
        Row() {
            Image(setting.icon)
                .width(24)
                .height(24)
                .margin({ right: 16 })

            Column() {
                Text(setting.title)
                    .fontSize(16)
                    .fontColor(this.theme.textPrimaryColor)

                if (setting.description) {
                    Text(setting.description)
                        .fontSize(12)
                        .fontColor(this.theme.textTertiaryColor)
                        .margin({ top: 4 })
                }
            }
            .alignItems(HorizontalAlign.Start)

            Blank()

            if (setting.hasSwitch) {
                this.AnimatedToggle(setting.isOn ?? false, (isOn: boolean) => {
                    setting.isOn = isOn
                    
                    // 处理特定设置项的逻辑
                    if (setting.title === '深色模式') {
                        this.theme.toggleDarkMode()
                    } else if (setting.title === '通知设置') {
                        this.toggleNotifications(isOn)
                    }
                })
            } else {
                Image($r("app.media.arrowright"))
                    .width(16)
                    .height(16)
            }
        }
        .width('100%')
        .padding(16)
        .backgroundColor(this.getItemBackground(style))
        .borderRadius(this.theme.borderRadius)
        .border(style === SettingItemStyle.Outlined ? {
            width: 1,
            color: '#E0E0E0',
            style: BorderStyle.Solid
        } : null)
        .margin({ bottom: 8 })
        .opacity(this.appearAnimation ? 1 : 0)
        .animation({
            duration: 300,
            curve: Curve.EaseOut,
            delay: 50 * index,
            iterations: 1
        })
        .onClick(() => {
            if (!setting.hasSwitch) {
                // 处理点击事件
                console.info(`${setting.title} clicked`)
            }
        })
        .stateStyles({
            pressed: {
                .backgroundColor(style === SettingItemStyle.Default ? '#F0F0F0' : this.getItemBackground(style))
                .scale({ x: 0.98, y: 0.98 })
                .opacity(0.9)
            },
            hover: {
                .backgroundColor(style === SettingItemStyle.Default ? '#F8F8F8' : this.getItemBackground(style))
                .shadow({
                    radius: 4,
                    color: 'rgba(0, 0, 0, 0.1)',
                    offsetX: 0,
                    offsetY: 2
                })
            }
        })
    }
    
    @Builder
    private AnimatedToggle(isOn: boolean, onChange: (isOn: boolean) => void) {
        Toggle({ type: ToggleType.Switch, isOn: isOn })
            .margin({ left: 8 })
            .onChange((value: boolean) => {
                onChange(value)
            })
            .animation({
                duration: 200,
                curve: Curve.EaseInOut,
                iterations: 1
            })
    }
    
    private getItemBackground(style: SettingItemStyle): string {
        if (this.theme.isDark) {
            switch (style) {
                case SettingItemStyle.Default:
                    return this.theme.cardBackgroundColor;
                case SettingItemStyle.Highlighted:
                    return '#1A237E';
                case SettingItemStyle.Outlined:
                    return 'transparent';
                default:
                    return this.theme.cardBackgroundColor;
            }
        } else {
            switch (style) {
                case SettingItemStyle.Default:
                    return this.theme.cardBackgroundColor;
                case SettingItemStyle.Highlighted:
                    return '#E3F2FD';
                case SettingItemStyle.Outlined:
                    return 'transparent';
                default:
                    return this.theme.cardBackgroundColor;
            }
        }
    }
    
    // 处理通知设置切换
    private toggleNotifications(isOn: boolean) {
        // 实现通知设置切换逻辑
        console.info(`通知设置: ${isOn ? '开启' : '关闭'}`)
    }
    
    build() {
        Column() {
            Text('设置')
                .fontSize(20)
                .fontWeight(FontWeight.Bold)
                .fontColor(this.theme.textPrimaryColor)
                .margin({ bottom: 16 })
                .width('100%')
                .textAlign(TextAlign.Start)

            ForEach(this.settingsGroups, (group: SettingsGroupType, groupIndex: number) => {
                // 分组标题
                Text(group.groupTitle)
                    .fontSize(14)
                    .fontColor(this.theme.textSecondaryColor)
                    .margin({ top: groupIndex > 0 ? 24 : 0, bottom: 8, left: 8 })
                    .width('100%')
                    .textAlign(TextAlign.Start)
                    .opacity(this.appearAnimation ? 1 : 0)
                    .animation({
                        duration: 300,
                        curve: Curve.EaseOut,
                        delay: 50 * groupIndex,
                        iterations: 1
                    })

                // 使用媒体查询判断屏幕尺寸
                MediaQuery({ minWidth: 320, maxWidth: 520 }) {
                    // 小屏设备使用单列布局
                    GridRow({ columns: 1 }) {
                        ForEach(group.items, (setting: SettingsType, index: number) => {
                            GridCol({ span: 1 }) {
                                // 为特定设置项使用特殊样式
                                if (setting.title === '深色模式') {
                                    this.SettingItem(setting, index, SettingItemStyle.Highlighted)
                                } else if (setting.title === '关于我们') {
                                    this.SettingItem(setting, index, SettingItemStyle.Outlined)
                                } else {
                                    this.SettingItem(setting, index)
                                }
                            }
                        })
                    }
                }
                
                MediaQuery({ minWidth: 521 }) {
                    // 根据分组使用不同的布局
                    if (group.groupTitle === '通用') {
                        // 通用分组使用3列布局
                        GridRow({ 
                            columns: { sm: 1, md: 3, lg: 3 },
                            breakpoints: this.customBreakpoints
                        }) {
                            ForEach(group.items, (setting: SettingsType, index: number) => {
                                GridCol({ span: 1 }) {
                                    if (setting.title === '深色模式') {
                                        this.SettingItem(setting, index, SettingItemStyle.Highlighted)
                                    } else {
                                        this.SettingItem(setting, index)
                                    }
                                }
                            })
                        }
                    } else if (group.groupTitle === '关于') {
                        // 关于分组使用列偏移
                        GridRow({ columns: 12 }) {
                            GridCol({ span: 5 }) {
                                this.SettingItem(group.items[0], 0)
                            }
                            
                            GridCol({ span: 5, offset: 2 }) {
                                this.SettingItem(group.items[1], 1, SettingItemStyle.Outlined)
                            }
                        }
                    } else if (group.groupTitle === '账号') {
                        // 账号分组调整列顺序
                        GridRow({ columns: 2 }) {
                            GridCol({ span: 1, order: 2 }) {
                                this.SettingItem(group.items[0], 0)
                            }
                            
                            GridCol({ span: 1, order: 1 }) {
                                this.SettingItem(group.items[1], 1)
                            }
                        }
                    } else {
                        // 其他分组使用2列布局
                        GridRow({ 
                            columns: { sm: 1, md: 2, lg: 2 },
                            breakpoints: this.customBreakpoints
                        }) {
                            ForEach(group.items, (setting: SettingsType, index: number) => {
                                GridCol({ span: 1 }) {
                                    this.SettingItem(setting, index)
                                }
                            })
                        }
                    }
                }
            })
        }
        .width('100%')
        .padding(16)
        .backgroundColor(this.theme.backgroundColor)
    }
}

10. 总结

本教程详细讲解了如何优化设置选项列表网格布局,添加分组、响应式支持、交互设计、动画效果和主题定制。通过使用HarmonyOS NEXT的GridRow和GridCol组件的高级特性,我们实现了一个功能丰富、美观灵活的设置选项列表。

相关推荐
Georgewu1 小时前
【 HarmonyOS 5 入门系列 】鸿蒙HarmonyOS示例项目讲解
harmonyos
libo_20253 小时前
HarmonyOS5 元宇宙3D原子化服务开发实践
harmonyos
半路下车3 小时前
【Harmony OS 5】DevEco Testing重塑教育质量
harmonyos·arkts
90后的晨仔3 小时前
解析鸿蒙 ArkTS 中的 Union 类型与 TypeAliases类型
前端·harmonyos
风浅月明4 小时前
[Harmony]颜色初始化
harmonyos·color
风浅月明4 小时前
[Harmony]网络状态监听
harmonyos·网络状态
半路下车4 小时前
【Harmony OS 5】DevEco Testing在教育领域的应用与实践
harmonyos·产品
simple丶4 小时前
【HarmonyOS Relational Database】鸿蒙关系型数据库
harmonyos·arkts·arkui
哼唧唧_5 小时前
使用 React Native 开发鸿蒙(HarmonyOS)运动健康类应用的系统化准备工作
react native·react.js·harmonyos·harmony os5·运动健康
三掌柜6665 小时前
HarmonyOS开发:显示图片功能详解
华为·harmonyos