普通视图

发现新文章,点击刷新页面。
昨天 — 2025年4月3日首页

鸿蒙ArkUI框架中的状态管理

作者 90后晨仔
2025年4月3日 10:56

在ArkUI框架中,状态管理是构建动态应用的核心。以下是组件级别应用级别状态管理装饰器的分类、用途及区别的总结,结合了思考过程中的关键点:

一、组件级别状态管理

1. @State

  • 用途:组件内部私有状态,变化触发UI更新。
  • 示例:按钮的点击状态、计数器数值。
  • 特点:只能初始化一次,单向数据流(组件内修改)。

代码示例

@Component
struct CounterButton {
  @State count: number = 0; // 组件内部状态

  build() {
    Button(`点击次数:${this.count}`)
      .onClick(() => {
        this.count++; // 修改@State变量自动更新UI
      })
  }
}

2. @Prop

  • 用途:父组件向子组件传递数据(单向)。
  • 特点:需通过父组件回调更新数据。
  • 示例:显示父组件传递的文本,子组件不可直接修改。

代码示例

// 父组件
@Component
struct ParentComponent {
  @State parentCount: number = 0;

  build() {
    Column() {
      ChildComponent({ countProp: this.parentCount }) // 传递数据
      Button("父组件增加").onClick(() => this.parentCount++)
    }
  }
}

// 子组件
@Component
struct ChildComponent {
  @Prop countProp: number; // 单向接收父组件数据

  build() {
    Text(`来自父组件的值:${this.countProp}`)
  }
}

3. @Link

  • 用途:父子组件双向数据绑定。
  • 特点:双向同步,类似Vue的v-model
  • 示例:共享开关状态,子组件直接修改影响父组件。

代码示例

// 父组件
@Component
struct ParentComponent {
  @State sharedCount: number = 0;

  build() {
    Column() {
      ChildComponent({ countLink: $sharedCount }) // 双向绑定
      Text(`父组件值:${this.sharedCount}`)
    }
  }
}

// 子组件
@Component
struct ChildComponent {
  @Link countLink: number; // 双向绑定变量

  build() {
    Button("子组件修改").onClick(() => {
      this.countLink++; // 修改会同步到父组件
    })
  }
}

4. @Provide / @Consume

  • 用途:跨层级组件数据共享(祖先→后代)。
  • 示例:主题颜色全局设置。
  • 特点:避免逐层传递,类似React Context。

代码示例

// 祖先组件
@Component
struct AncestorComponent {
  @Provide themeColor: string = 'blue'; // 提供数据

  build() {
    Column() {
      ChildComponent()
    }
  }
}

// 后代组件
@Component
struct ChildComponent {
  @Consume themeColor: string; // 消费数据

  build() {
    Text(`当前主题色:${this.themeColor}`)
      .fontColor(this.themeColor)
  }
}

5. @Observed / @ObjectLink

  • 用途:观察嵌套对象属性变化。
  • 特点@Observed装饰类,@ObjectLink引用实例。
  • 示例:用户对象({name: string})属性更新。

代码示例

@Observed // 装饰类
class User {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

@Component
struct UserProfile {
  @ObjectLink user: User; // 引用被观察对象

  build() {
    Column() {
      Text(`姓名:${this.user.name}`)
      Button("修改年龄").onClick(() => {
        this.user.age++; // 修改会触发UI更新
      })
    }
  }
}

6. @Style

  • 用途:定义可复用的组件样式。
  • 示例:统一按钮样式(颜色、边距)。

代码示例

@Styles function customButtonStyle() {
  .width(120)
  .height(40)
  .backgroundColor(Color.Blue)
  .fontColor(Color.White)
}

@Component
struct StyledButton {
  build() {
    Button("样式按钮")
      .useStyle(customButtonStyle) // 应用样式
  }
}

7. @Builder / @BuilderParam

  • 用途:构建可复用的UI片段或动态插入布局。
  • 示例:自定义卡片布局,父组件传递头部Builder。
  • 区别@Builder定义结构,@BuilderParam接收结构作为参数。

代码示例

@Component
struct CustomCard {
  @BuilderParam header: () => void; // 接收Builder参数

  build() {
    Column() {
      this.header() // 插入自定义头部
      Text("卡片内容...")
    }
  }
}

// 使用组件时传递Builder
CustomCard({
  header: () => {
    Text("自定义标题")
      .fontSize(20)
      .fontColor(Color.Red)
  }
})

8. @Extend

  • 用途:扩展组件样式(如全局字体)。
  • 示例:统一所有文本的字体大小和颜色。

代码示例

@Extend(Text) function boldText() {
  .fontWeight(FontWeight.Bold)
  .fontColor('#333')
}

@Component
struct ExtendedText {
  build() {
    Column() {
      Text("普通文本")
      Text("加粗文本").useStyle(boldText) // 应用扩展样式
    }
  }
}

二、应用级别状态管理

1. @LocalStorage

  • 用途:页面级临时存储(页面关闭后可能保留)。
  • 示例:表单草稿保存。

补充知识点: @LocalStorageProp / @LocalStorageLink

区别:前者单向同步,后者双向绑定页面存储数据。

代码示例

// 页面A
@Entry
@Component
struct PageA {
  @LocalStorage('formData') formData: string = '';

  build() {
    TextInput(this.formData)
      .onChange((value) => {
        this.formData = value; // 数据保存到LocalStorage
      })
  }
}

// 页面B可读取同一LocalStorage
@Component
struct PageB {
  @LocalStorage('formData') formData: string;

  build() {
    Text(`保存的数据:${this.formData}`)
  }
}

2. @AppStorage

  • 用途:全局状态存储(应用生命周期内有效)。
  • 示例:用户登录Token。

补充知识点: @StorageProp / @StorageLink

用途:绑定到AppStorage中的具体键。

区别@StorageProp单向,@StorageLink双向。

代码示例

// 全局存储用户Token
@AppStorage.setOrCreate('userToken', '') // 初始化

@Component
struct LoginComponent {
  @StorageLink('userToken') token: string; // 双向绑定

  build() {
    Button("登录").onClick(() => {
      this.token = 'abc123'; // 修改全局状态
    })
  }
}

// 其他页面读取
@Component
struct ProfilePage {
  @StorageProp('userToken') token: string; // 单向读取

  build() {
    Text(`Token: ${this.token}`)
  }
}

3. @PersistentStorage

  • 用途:持久化存储(应用重启保留)。
  • 示例:用户偏好设置(语言、主题)。

代码示例

@PersistentStorage.setOrCreate('settings', { theme: 'light', fontSize: 16 })

@Component
struct SettingsPage {
  @StorageLink('theme') theme: string;
  @StorageLink('fontSize') fontSize: number;

  build() {
    Column() {
      Button("切换主题").onClick(() => {
        this.theme = this.theme === 'light' ? 'dark' : 'light';
      })
      Slider({ min: 12, max: 24 })
        .value(this.fontSize)
        .onChange((value) => {
          this.fontSize = value;
        })
    }
  }
}

4. @Environment

  • 用途:访问环境变量(如主题、语言)。
  • 示例:根据系统主题切换应用外观。

代码示例

@Component
struct ThemeAwareComponent {
  @Environment('currentTheme') theme: string;

  build() {
    Text("主题敏感文本")
      .fontColor(this.theme === 'dark' ? Color.White : Color.Black)
      .backgroundColor(this.theme === 'dark' ? Color.Black : Color.White)
  }
}

三、关键区别与选择

  • 作用域

    • 组件级:@State@Prop等用于组件或父子通信。
    • 应用级:@AppStorage@PersistentStorage等跨页面共享。
  • 数据流

    • 单向:@Prop@LocalStorageProp(父→子/存储→组件)。
    • 双向:@Link@LocalStorageLink(父子/组件与存储同步)。
  • 持久性

    • 临时:@State@LocalStorage(页面级)。
    • 持久:@PersistentStorage(设备存储)。
  • 使用场景

    • 简单组件状态 → @State
    • 跨组件共享 → @Provide/@Consume
    • 全局配置 → @AppStorage + @StorageLink
    • 复杂对象监听 → @Observed + @ObjectLink


四、最佳实践总结

  1. 简单交互优先使用@State:适合按钮状态、临时计数等
  2. 父子通信选择@Prop/@Link:单向传递用@Prop,双向同步用@Link
  3. 跨层级共享数据用@Provide/@Consume:避免多级传递的麻烦
  4. 全局状态使用@AppStorage:如用户登录状态、主题配置
  5. 复杂对象监听用@Observed+@ObjectLink:确保嵌套属性变化触发更新
  6. 持久化数据用@PersistentStorage:用户设置、历史记录等需要长期保存的数据

官方文档是更全面的参考:
HarmonyOS ArkUI 文档

🔥《爆肝整理》保姆级系列教程-玩转Charles抓包神器教程(15)-Charles如何配置反向代理

作者 北京_宏哥
2025年4月3日 09:10

1.简介

在App开发的过程当中,抓包是一个很常见的需求,而有些app的请求不会在网络设置代理时被抓到数据包,这里若是需要抓包就需要搭建反向代理。

2.什么是代理?

什么是代理,来一张图了解一下。

代理又分为正向代理和反向代理。

3.什么是正向代理?

先来看张图~

【再举个栗子】

某同学喜欢面向搜索引擎编程,想通过 百度 搜索引擎查找一些学习资料,但是有些网站直接访问可能不太安全,会暴露自己的IP,同学比较苦恼,想着怎样才能使用百度 搜索自己想要的学习资料,又不会暴露自己的IP在网站上呢?

这时我告诉该同学,我呢手上刚好有一台代理服务器,这台代理服务器通过nginx配置了正向代理转发http和https请求,你呢,只需要在自己的Windows本地电脑的网关配置一下这台代理服务器的IP和端口号,就能正常通过代理服务器访问到百度 并搜索相关的学习资料了,还不会暴露自己真实的IP~

4.什么是反向代理?

先来一张图了解下~

和正向代理相应的,正向代理代理客户端,反向代理代理服务端。

反向代理(reverse proxy):是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。

我们在租房子的过程中,除了有些房源需要通过中介以外,还有一些是可以直接通过房东来租的。用户直接找到房东租房的这种情况就是我们不使用代理直接访问国内的网站的情况。

还有一种情况,就是我们以为我们接触的是房东,其实有时候也有可能并非房主本人,有可能是他的亲戚、朋友,甚至是二房东。但是我们并不知道和我们沟通的并不是真正的房东。这种帮助真正的房主租房的二房东其实就是反向代理服务器。这个过程就是反向代理。

对于常用的场景,就是我们在Web开发中用到的负载均衡服务器(二房东),客户端(租客)发送请求到负载均衡服务器(二房东)上,负载均衡服务器(二房东)再把请求转发给一台真正的服务器(房东)来执行,再把执行结果返回给客户端(租客)。
反向代理,其实是"代理服务器"代理了"目标服务器",去和"客户端"进行交互。

通过反向代理服务器访问目标服务器时,客户端是不知道真正的目标服务器是谁的,甚至不知道自己访问的是一个代理。

5.正向代理和反向代理的区别

虽然正向代理服务器和反向代理服务器所处的位置都是客户端和真实服务器之间,所做的事情也都是把客户端的请求转发给服务器,再把服务器的响应转发给客户端,但是二者之间还是有一定的差异的。

1、正向代理其实是客户端的代理,帮助客户端访问其无法访问的服务器资源。反向代理则是服务器的代理,帮助服务器做负载均衡,安全防护等。

2、正向代理一般是客户端假设的,比如在自己的机器上安装一个代理软件。而反向代理一般是服务器假设的,比如在自己的机器集群中部署一个反向代理服务器。

3、正向代理中,服务器不知道真正的客户端到底是谁,以为访问自己的就是真实的客户端。而在反向代理中,客户端不知道真正的服务器是谁,以为自己访问的就是真实的服务器。

4、正向代理和反向代理的作用和目的不同。正向代理主要是用来解决访问限制问题。而反向代理则是提供负载均衡、安全防护等作用。二者均能提高访问速度。

6.须要准备的工做

  • 在本身电脑上面搭建一个可用的Charles
  • 须要抓包的远端服务的端口号和Host地址
  • 在本身电脑上面搭建一个本地DNS解析服务

7.具体步骤 (Windows下的操作,Mac也同理)

1.确保手机可以连接上Charles,本身电脑上面可以看到正常请求出来的数据包(具体抓包可以看宏哥前边的教程)

2.打开Charles,勾选Proxy --> Reverse proxise...,进入反向代理设置界面。如下图所示:

3.进入Reverse Proxies Settings(反向代理设置)页面,勾选 Enable Reverse Proxies 。如下图所示:

4.在【Add】新增。如下图所示:

Edit Reverse Proxy 视图中的选项含义:

local port:本地端口
本地主机上的端口创建反向代理。该字段可能会自动填充一个可用的端口。如果有另一个应用程序使用该端口,则在反向代理启动时将收到一条警告消息

Remote host:远程主机
作为反向代理的目的地的远程主机的主机名或IP地址

Remote port:远程端口
远程端口默认为80,这是HTTP的默认端口。

Rewrite redirects:重写重定向
重定向远程服务器的响应将被重写与反向代理源地址相匹配,默认为开
远程服务器的重定向响应是完全限定的URL,即使它们在同一网站内
如果重定向到远程服务器地址,则需要将其重写为反向代理本地地址,否则客户端将使用重定向URL到远程主机,因此不再通过反向代理连接

Preserve host in header fields:保留主机头
Host HTTP标头从传入请求不变地传递,而不是正常重写主机头以匹配反向代理远程主机,默认为关闭
仅当您具有特定要求时,才需要保留主机头;普通使用的时候没有必要使用的

Listen on a specific address:监听特定地址
指定本地地址以侦听反向代理,可以启用此选项并在此处输入IP地址

8.Charles反向代理实战

Charles反向代理是提供一个端口转发的功能,用于除IE外发出的HTTP请求,例如需要跟踪Smartbi服务器与XMLA服务器之间的通信、Smartbi SDK与服务器之间通信等。

宏哥在Apache服务器安装在A计算机上,IP地址为:10.11.53.180,并开启服务,端口号为:80(默认)。然后宏哥简单部署一个HTML页面,在浏览器中访问服务。如下图所示:

现移动端访问A服务器部署的HTML页面出现错误,但是需要录制移动端的HTTP请求。这时候就需要Charles的反向代理帮助我们解决这个问题。具体操作步骤如下:

1.找一台其他计算机,如计算机B,其IP地址为10.11.53.193,宏哥这里演示的就是宏哥的本地计算机,如下图所示:

2.在计算机B上安装Charles,并启动,这里宏哥已经安装就不做演示了。

3.选中charles上的"Proxy"-》"Reverse Proxies",进入反向代理设置界面,如下图所示:

4.反向代理设置界面如下,点击"Add"按钮,新建反向代理设置,如下图所示:

5.设置反向代理的端口号,IP地址等信息。

其中 Local Port是指计算机B的一个空闲端口,如本例中使用8080;
Remote Host是指HTML页面服务的IP,即计算机A的IP: 10.11.55.182;
Remote Port是指HTML页面服务的端口号,在本例中访问HTML页面的端口号为80(Apache默认端口)
点击OK保存反向代理设置,如下图所示:

6.上一步点击OK之后会出现反向代理列表窗口,勾选我们上一步设置的反向代理,点击ok启用,如下图所示:

7.在任意一台计算机或者移动端上,通过**http://计算机B的IP:反向代理中设置的Loal Port端口/inde.html**,可以访问到HTML页面服务。本例中通过在浏览器或者移动端的服务器设置上输入**http://10.11.53.193:8080/index.html**访问,如下图所示:

*注:访问是需要写IP,不能写localhost。
*

8.在charles中会监测到反向代理访问,首次会弹出是否允许访问,选择'Allow'按钮,允许访问。没有设置代理之前是访问不到的,如下图所示:

9.在计算机B上的charles就可以录制到HTTP请求,如下图所示:

9.小结

反向代理位于用户和应用服务器之间,是连接用户和服务器的中介。

于是我们可以

1.缓存,将服务器的响应缓存在自己的内存中,减少服务器的压力。

2.负载均衡,将用户请求分配给多个服务器。

3.访问控制

4.加上一些特殊的东西做特殊的事情(如IPS—入侵防御系统、web应用防火墙等)

好了,今天时间也不早了,宏哥就讲解和分享到这里,感谢您耐心的阅读,希望对您有所帮助。

昨天以前首页

echarts 实现环形渐变

2025年4月2日 18:09

前言

最近产品在ecahrts官网上找到一个 饼图 想要实现这种从头到尾的渐变交互效果,一开始以为非常简单,echarts应该是提供了类似的配置项,知道做起来才发现,这其中没那么简单。

官网案例分析

官网例子中的渐变并不是echarts提供的配置项实现的,而是通过一张 图片 作为背景实现的渐变,所以一开始想着是先来实现一个渐变的饼图,然后通过多个饼图进行拼接来实现类似指针一样的效果,这样就能够实现自定义这个渐变的颜色,并且也很快就写出来一个demo

认识 Echarts 渐变色

在 echarts 的渐变色中,提供了三种类型,包括线性渐变(linear gradient)、径向渐变(radial gradient)和纹理填充(pattern)。

主要了解了一下 线性渐变 以及 径向渐变 的实现效果,在这之后,也意识到了一个严重的问题:通过echarts提供的颜色填充,貌似没办法实现案例里面这种从头到尾的渐变效果,通过线性渐变能够实现下面这种效果

image-20250402170925813.png

这种固定方向的渐变,但是并不符合我们的要求,

并且我也上网找了一些饼图渐变的案例,发现都是通过这种线性渐变来实现的,只不过会去计算这个渐变的角度,来实现类似从头到尾的渐变,但是一旦进度的幅度较大,就马上露馅了。

  • 例子

image-20250402171205454.png

image-20250402171307616.png

可以看到一旦我调大某一个区域的比例,就会发现最后的实现原理还是线性渐变,只不过动态的计算了角度,这种适合多个比例差不多的饼图,但是一旦有某个块比例过大,就还是会出现样式不够美观

奇思妙想

突然意识到,我们最终的目的是自定义这个圆环的起点和终点的颜色,这并不是非得用echarts提供的渐变功能,图片本身并没有问题,图片最大的限制就在于颜色是定好的,但是我们是不是可以让图片的颜色变成动态生成的?

当然可以!

与似乎,就有了下面的方案,通过 canvas 动态生成渐变背景,在讲这张背景图作为圆环的背景图,这样我们就能够实现自定义圆环的起点颜色和终点颜色了

canvas 生成渐变背景

canvas生成背景这个并不是什么难事,百度一下就能够找到类似的案例,然后丢给ai进行美化一下,修改参数变成自己想要的一个函数,我定义的是能够通过传入起点角度,起点颜色,终点颜色 图片大小 四个参数生成一张 base64 的图片

/**
 * 创建圆形渐变图片
 * @param startAngle 起始角度
 * @param startColor 起始颜色
 * @param endColor 结束颜色
 * @param size 大小
 * @returns
 */
export function createCircularGradientImage(startAngle = 0, startColor = '#fff', endColor = 'blue', size = 200) {
  // 创建一个canvas元素
  const canvas = document.createElement('canvas')
  // 设置canvas的宽度
  canvas.width = size
  // 设置canvas的高度
  canvas.height = size
  // 获取2D绘图上下文
  const ctx = canvas.getContext('2d')
  // 检查是否成功获取上下文
  if (!ctx) {
    throw new Error('ctx is null')
  }
  // 创建圆锥渐变
  // 参数:起始角度,圆心x坐标,圆心y坐标
  const gradient = ctx.createConicGradient(startAngle, size / 2, size / 2)
  // 添加渐变的起始颜色
  gradient.addColorStop(0, startColor)
  // 添加渐变的结束颜色
  gradient.addColorStop(1, endColor)
  // 设置填充样式并绘制矩形
  ctx.fillStyle = gradient
  ctx.fillRect(0, 0, size, size)
  // 将canvas转换为base64格式的图片数据
  const res = canvas.toDataURL('image/png')
  // 从DOM中移除canvas元素
  canvas.remove()
  // 返回生成的图片数据
  return res
}

最终我们能够得到一张类似这样的图片

image-20250402151912179.png

结果

接下来的步骤就简单了,参考官网的案例,我们只不过是替换了图片的来源,这样就能够通过传参获得一个自定义颜色的结果。

const _panelImageURL = createCircularGradientImage(0, '#E5E5FF', 'red')

最后的效果:

image-20250402155934106.png

至于文字颜色和阴影颜色,这些都有着很明显的配置项,这里就不做过多的赘述了,本文主要是分享一下通过canvas构造图片来实现渐变的这种思路

如果有大佬有更好的实现渐变的思路欢迎评论区留言!

远离 dismiss,拥抱状态驱动

作者 Fatbobman
2025年4月2日 22:12

在 SwiftUI 开发中,环境值 dismiss 因其灵活、自适应的特性备受开发者青睐。它能够根据当前视图的上下文智能执行关闭操作:在模态视图中关闭窗口、在导航堆栈中弹出视图,甚至在多列导航容器中自动关闭边栏。正是这种看似“万能”的便捷性,让许多开发者将它作为首选工具。然而,便捷的背后往往隐藏着风险。频繁使用 dismiss 可能在应用程序中埋下隐患,引发测试难题乃至难以追踪的稳定性问题。本文将分析我们为何应谨慎对待 dismiss,并介绍更加健壮可靠的状态管理方案。通过重新审视视图呈现与消失的逻辑,我们能够打造出更稳定、易维护且可预测的 SwiftUI 应用。

Meta 新款智能眼镜曝光!摄像头大升级支持隔空操作,价格比肩顶配 iPhone

作者 苏伟鸿
2025年4月2日 17:31

在去年展示了颇为科幻的「Orion」AR 眼镜的原型机后,外界都期待 Meta 何时能正式向市场推出首款 AR 眼镜产品。

彭博社今天带来最新消息:Meta 计划最早在今年年底向市场推出第一副带屏幕的眼镜,售价将超过 1000 美元,最高在 1300-1400 美元左右,也就是说人民币可能将突破万元。

作为对比,不带屏幕的智能眼镜 Ray-Ban Meta 售价 399 美元,折合人民币 3000 元左右。

需要指出的是,Meta 即将向市场推出的 AR 眼镜并不是去年展示的那个「Orion」,据悉这个产品的代号为「Hypernova」。

▲Meta Orion 原型机

和 Orion 的双镜片显示系统不同,第一代的 Hypernova 只在右镜片的右下象限有屏幕,这意味着内容只会显示在用户的右眼,并且显示效果在向下看时最清晰,至少避免了面对他人时「翻白眼」看眼镜屏幕的问题。

彭博社也表示,虽然 Hypernova 一代还没问世,Meta 已经在着手开发 Hypernova 2,将包含双目显示系统,预计在 2027 年推出。

Hypernova 预计将采用高度定制 Android 系统,而不是自家去年推出的 Meta Horizon OS 系统,Meta 也暂时不打算为 Hypernova 配备一个应用商店。

打开眼镜,显示完「启动屏幕」后,圆形应用图标将水平排布在 Hypernova 的主屏幕,类似 iPhone 和 Mac 的底部「dock」栏,接近当下 Meta Quest 的界面。

▲ Meta Quest 的主页,图源:Reedit@AdenInABlanket

眼镜的应用程序将包括相机、相册、地图等等,并且将非常依赖手机上的 Meta View 程序,配对后可以接听电话、播放音乐,还支持接收手机 App 的通知,例如 Meta 自家的社交媒体 Messenger 和 WhatsApp。

对于这副眼镜的交互方式,Meta 准备了两种方案:

  • 眼镜镜框侧面的电容式触摸,用户可以滑动、点击眼镜腿来滚动和选取应用或照片
  • 「神经腕带」,使用手势来控制眼镜,可以转手、捏合来滚动和选取项目,代号为「Ceres」的配件将在包装盒内随眼镜一起提供,这个方案也作为 Orion 的交互在去年进行过演示。

和 Ray-Ban Meta 类似,第一款 Hypernova 也专注于图像拍摄和语音 AI 功能。Meta 将升级智能眼镜的摄像头参数。目前的 Ray-Ban Meta 摄像头为 1200 万像素,Meta 将其视为「iPhone 11」级别,而对于 Hypernova,Meta 希望能配备一个「iPhone 13」级别的摄像头。

可以说,Hypernova 就是带有一个屏幕的 Ray-Ban Meta,定位类似 Ray-Ban Meta 和 Orion 之间的过渡产品。而迭代的 Hypernova 2 将会更加接近 Orion,采用更先进和昂贵的技术。

除了 Hypernova,Meta 还在打造「Supernova 2」的智能眼镜,这款产品将基于 Oakley 运动眼镜打造,不带任何显示功能,基本等同于 Ray-Ban Meta 的运动款,对自行车骑行等运动进行了优化。

▲ Oakley 运动眼镜

至于 Orion 原型机的商用计划,彭博社也报道了最新进展:产品名可能定为「Artemis」,目前 Meta 正在利用 Oriion 进行软件测试和应用开发, 最终将提供给开发人员,Artemis 的推出可能不会早于 2027 年。

知情人士表示,上面的消息并不意味着 Meta 最终都会推出这些产品,因为 Meta 经常在开发过程中更改和取消产品。就目前而言,Meta 的混合现实部门 Reality Labs 内部也对产品计划有不少顾虑,有可能将 Artemis 和 Hypernova 合二为一。

▲ Orion 眼镜的演示

Hypernova 不禁让人想起当年的 Google Glass,都类似地配备了单眼显示屏、镜腿滑动交互、相机拍摄,但它没有成功,除了隐私争议,还因为当时的技术难以支撑带来的产品硬伤,以及大众还难以接受超前的产品形态,特别是它这个看似科幻,实则怪异的外观。

▲ Google Glasses

沉寂 10 年后,在今年的 CES 大会上,一款「Halliday Glasses」引发了媒体的热议,它也是一款单眼显示屏眼镜,不是一款大而全的产品,却让试用过的媒体人都对其赞不绝口,因为它小巧轻便,能出色完成有限的功能,比那些野心勃勃但基本不太可用的产品靠谱多了。

▲ Halliday Glasses

10 年过去,Google Glass 当年描绘的蓝图,终于凭借日臻完善的技术渐渐实现,而像是 Ray-Ban Meta 这样的拍摄/音频眼镜,也让大众渐渐熟悉这种全新的设备形态。

除了 Halliday Glasses 今年国内的 Rokid Glasses 充当演讲「提词器」也出了一波圈,OPPO 在几年前也探索过单眼分目式的智能镜片,可以单挂在脸部一边显示内容,适用性更广。

▲ OPPO Air Glass

还有一点,不管是 Meta Orion、 Halliday Glasses 还是 Rokid Glasses,它们的外观都尽量贴近现实中的眼镜,戴在脸上不会吸引太多人异样的目光。

▲ Rokid Glasses

比起 Orion 这种全能型的 AR 眼镜,Hypernova 是一次相对「保守」的尝试,从 Ray-Ban Meta 往前迈出了一小步,保证这款消费级的产品,能真正获得更多用户。

在 Meta 眼中,这款还不能脱离 iPhone 使用的 Hypernova,恰恰是取代 iPhone 的关键一步,开始尝试从智能手机中抢夺用户的注意力,让更多消费者习惯去用眼镜解决一些简单的问题。

同时,Hypernova 也是一招先发制人,苹果、三星、Google 等竞争对手都已经传出打造更轻便 AR 眼镜的计划,完整的双目显示暂时出不来,今年能推出的初代 Hypernova,无疑能更快抢占市场。

虽然 Hypernova 的想法很美好,但超 1000 美元的售价或许是其能成功普及的最大挑战。Ray-Ban Meta 能大获成功的一个原因就是和普通雷朋眼镜相持的低廉售价。要说服消费者用能买 iPhone 的钱,买一副只能提供部分 Apple Watch 功能的眼镜,Meta 必须拿出更具吸引力的理由。

对于无法提供完整 AR 体验的 Hypernova 而言,多模态 AI 或许是一个突破口。借助语音 AI 和增强现实显示,Hypernova 也有望带来 Orion 已经演示过的现实交互能力。

比如,识别食材后生成相应的食谱,还能一步步进行烹饪教学;异国旅行,能实时翻译看到、听到的外语;出门在外,能根据现实世界的街道进行实时导航……

▲ 去年 Meta 展示的 Orion AR+AI 能力

Hypernova 或许难以和 Ray- Ban Meta 一样「卖爆」,也不是一款能淘汰智能手机的革命性产品,但就目前的信息,它确实有潜力成为我们迈向 AR 时代的坚实一步,虽然只是一小步。

#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。

爱范儿 | 原文链接 · 查看评论 · 新浪微博


echarts地图轮播markpoint-自用记录📝

作者 南茗啊
2025年4月1日 16:21

echarts地图轮播markpoint

正常添加echarts地图后,添加markpoint图层

 //引入echarts 注册地图 略过 只展示关键代码
option.series= [
            {
              // roam: true,
              name: '地图',
              type: 'map',
              id: 'mapData',
              map: 'china',
              data: this.outdata,  //无需在意 配合visualMap给地图区块添加颜色使用
              center: [95, 35],
              zoom: 1.5,
              z: 2,
              itemStyle: {
                borderColor: 'rgba(0,0,0,0.15)',
                borderWidth: 1.3,
                shadowColor: 'rgba(0,0,0,0.2)',
              },
              emphasis: {
                label: {
                  show: true,
                },
              },
            },
  //下方是markpoint代码 因为echarts官方已经不推荐使用markpoint 改为使用scatter
            {
              type: 'scatter',
              coordinateSystem: 'geo',
              //下方的symbol替换为需要展示的图标的base64编码即可
              symbol:
                'image://data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAAAXNSR0IArs4c6QAAA4VJREFUSEvNlV1oHUUUx/9nZnbv7s3dG2+akGoNGgp5UtDaEmqxNoIfXIUSYksrERP8Kj6KghSFvFQRTHzyIS0aWqQiIUUhHz4IxlDbBxUEK0UQCrYi9MamuTH3Y2d3jsy9DQZpbjcpBAeGhd2Z85v/mf85S9ikQZvEwf8P9ORXiy3kpnZp42yPockT8rcgLH0//lTztSRZuaWiA9NLbQtIH12uYlAzNRsGSAFCAY7Li55vxlKm8u7M3qDQCNgQ9NjE8o5q1Z+qaGytBZH1ycQgSYBCbfo+/sz6pfz0nsxPa8HWBO3/rNRxteydq4Z8tw1KBLC4Aas9GSxRB0qGH+DKFkc8/OWjdPlmsDVB+z7WZ/5eEr0kAbNyemK0NHFN1UKVwPa9hQoGK0ImMGfmnlB9iUH5U+HO60X1XVhhVyqCIaD9Do2hxwn3tdnIwC/zBkPnCH+UJOxhLFCkEbYF0Z7pHveH/8JuqqhnVB9dKsljiOp3oUSMsecMulodkM3hjfHrXxoDkwKhIJAgIAUEvn579pnUsUSgncPhaROpw2AAbLBjWwUn+n3ULmp16THj1UmNHwtO3SgKaAri8bn9zsFEoIc+NJMI8XQtZzrC3s4ihvtzIAtepYjBeHOqim9+dwFVd2GQNTPfPqvyiUC7hsPjcei8TMaAqxo5M4/Jd7Yi5cjVHFS1Qe/xCq6yB0gCKyCdNaNnD6kjiUDdH0TPx5E8aTQTRQZhoYBD3RHeOHwXXKduhlAbjEwtYPxCBm7WBQuCccDNW/TA1wfcU4lAu0cWW8Jy5gI03Qkw4uIywsI8HrhH45EHg1r6zl4M8fO1AE5rFtITsF4gj6+0txfv/6I3dz0RyC7qfi98TVfUR/buhTGIl8vQxSXEVQ0oCbc5A5FrAnkSwlpbMrxAH5l7yRtNXEd24b4hVmUnPhGxGEAEMDNgDGDqHUG4slakJGwJCPgZPSa7nFdmeyhaF6gOu+SV3Y4JE8u8sQBrOtt2VtqRdbuts3Q01dKp+mbyVF13r1vZcO/QJa9dbfskMvW6YgNQre3U+55sMqfT2csvzg52VjbcvVc2WmUVp+NkFMuDbJVZmENwgvDzXJf7QiMlKzFu+T/6F8Ze2eFPo4j6bOqUH014rap/dpAaKlk3yG7YPcJ+XNHvixQxkXrr/OtUbpSu1d8SK0oacMNmuF3AhlJ3O9BNS90/oxFMKoVN5+kAAAAASUVORK5CYII=',
              symbolSize: 30,
              symbolOffset: [0, '-50%'],
              label: {
                show: true,
                position: 'top',
                color: '#333333',
                fontSize: 14,
                fontWeight: 600,
                formatter: '{b}', //配合下方data中的name使用 展示name
                textBorderColor: '#ffffff',
                textBorderWidth: 2,
                textBorderType: 'solid',
              },
              emphasis: {
                scale: 1.2,
                label: {
                  fontSize: 18,
                },
              },
              select: {
                label: {
                  color: '#333333',
                  fontSize: 18,
                  fontWeight: 600,
                },
              },
              data: [
                {
                  name: '济南片区',
                  value: [117.2, 36.7],
                },
                {
                  name: '西南片区',
                  value: [104.065701, 30.659487],
                },
                {
                  name: '青岛片区',
                  value: [120.1, 36.4],
                },
                {
                  name: '华中片区',
                  value: [117.291321, 31.861125],
                },
                {
                  name: '华北片区',
                  value: [116.4, 39.9],
                },
                {
                  name: '华东片区',
                  value: [121.473701, 31.230393],
                },
                {
                  name: '华南片区',
                  value: [113.6, 23.1],
                },
                {
                  name: '东南片区',
                  value: [119.4543, 26.2875],
                },
                {
                  name: '中原片区',
                  value: [113.653427, 34.761125],
                },
              ],
            },
          ],

轮播代码如下:

思路是对当前高亮节点进行取消高亮操作,然后在高亮下一个节点

需要注意seriesIndex 可以通过在鼠标移入事件中添加打印信息获取

//该段代码在echarts初始化之后调用即可
startLunbo () {
        if (this.intervalId) {
          clearInterval(this.intervalId)
          this.intervalId = null
        }
        this.intervalId = setInterval(() => {
          this.myMapChart.dispatchAction({
            type: 'downplay',
            seriesIndex: 1,
            dataIndexInside: this.lunboId === 0 ? 8 : this.lunboId - 1, //和上面的代码片段中data的长度有关
          })
          this.myMapChart.dispatchAction({
            type: 'highlight',
            seriesIndex: 1,
            dataIndexInside: this.lunboId,
          })
          this.toolIdx = this.lunboId
          this.lunboId++
          if (this.lunboId > 8) { //和上面的代码片段中data的长度有关
            this.lunboId = 0
          }
        }, 2500)
      }

其中鼠标的移入移出事件 自行完成即可

宝马绝不背叛「驾驶」

作者 刘学文
2025年4月1日 16:20


在 2025 年的汽车新闻发布会上强调「驾驶」是一件有点反常识的事情,毕竟我们看到的汽车新闻发布会一般强调的是智能驾驶、智能座舱,以及舒适享受,驾驶仿佛是应该丢给司机,或者那二三十个传感器和几颗芯片去干的事儿。

当然,汽车新闻发布会上不再强调「驾驶」,好像也有点反常识。

一年一度的宝马创新日活动上,「驾驶」反而成为了所有讲解的关键词,真就是智驾时代的锐利异类。

看路还是看屏?宝马始终希望你看路

在聊智驾和人驾之前,其实还有一个比较隐性的矛盾摆在面前:汽车内外的显示设备越来越多,除了仪表屏和中控屏之外,可能还有 HUD 抬头显示、电子后视镜显示屏和贯穿远端屏。当然,后排娱乐屏,车外点阵屏等等也出现得越来越多。

也就是说,现在的司机位,不光是要耳听八方,眼观六路,还要眼观三四屏。

显然,这和专注驾驶安全驾驶是矛盾的。

虽然宝马在屏幕的使用上没有国产车那么激进,但显示设备越来越多,多媒体内容越来越丰富的趋势无法阻挡,实际上,宝马还是 HUD 抬头显示的普及者,早在 2003 年,宝马 5 系车型上大规模应用 HUD 抬头显示。

宝马给到的解决方案是 BMW 新时代操作系统 X 里的全景 iDRIVE 人机交互解决方案。

关注宝马新闻的朋友应该对操作系统 X 和全景 iDRIVE 人机交互解决方案不太陌生,它们首次亮相在 2023 秋季的德国国际汽车及智慧出行博览会(IAA MOBILITY)上,不过当时还属于概念设计,许诺在 2025 年真正上车。

这不,2025 年这就到了。

所以,这一套蕴含了大量人体工程学和人因研究的人机交互解决方案预计今年就能和消费者见面了。

首先最重要的一点,就是全景 iDRIVE 人机交互解决方案基于一个大前提:座舱「视觉锥」概念。

座舱「视觉锥」以驾驶者视线为轴心,将车内的交互信息分层防止,最重要的驾驶信息位于视线中央和焦点处,娱乐和舒适功能则位于视觉椎的底部周边视野,让合适的信息,以合适的界面,出现在合适的时间,尽量不干扰驾驶,最终让驾驶者减少视觉焦点和切换时间和频率。

因而,基于「视觉椎」概念,全景 iDRIVE 人机交互解决方案就被划分为了四大交互界面:

  • BMW 全景视域桥
  • 中央信息显示屏
  • 3D 视域前景显示
  • 全新多功能方向盘

如果不说 BMW 全景视域桥也是一种 HUD 抬头显示,想必很多人会以为这一条横亘在前挡风玻璃下方的长条显示区域是显示屏,但实际上,它还是投影,只是投影到了挡风玻璃下方的纳米涂层深色区域。如大家所见,投影效果有着堪比高分辨率显示屏的清晰度和色彩表现。

这里主要显示的是驾驶相关,比如当前时速,导航信息等等,还有就是在视线稍右方显示娱乐相关信息。

在全景视域桥更上方,则是 3D 视域前景显示,这也是一种 HUD 抬头显示,只不过信息呈现更立体,与显示世界的融合更贴合,主要是显示能够帮助驾驶员更专注于驾驶相关的信息, 比如关键的驾驶辅助、导航信息等。

最让人疑惑的,则是那个不规则的中央信息显示屏。

相比于普通的的长方形中控屏,宝马全景 iDRIVE 人机交互解决方案的这个中央信息显示屏有三点不同:

以平行四边形形状向驾驶侧靠拢,并进行了不规则的切割

屏幕向驾驶侧倾斜 17.5°,针对驾驶员视线进行了优化,并尽量缩短方向盘到显示屏的距离

中央信息显示屏带矩阵背光,保证了屏幕内容在任何光线下的可见度

另外,中央信息显示屏与全景视域桥可以双屏联动,用户可以在中央信息显示屏中选择天气、音乐等信息,往上滑动,流转到全景视域桥上进行显示。

不难看出,宝马之所以这么设计中央信息显示屏,还是基于视觉椎的概念,以及尽量保证驾驶者视线在视觉椎之内,保证驾驶的安全性。

最后的交互和屏幕无关,甚至和视觉的关系已经都不大了,宝马尽可能地减少了非方向盘区域之外的实体按键,并且在方向盘划出了专门交互区域。这里用到了「主动触觉反馈」和「分层显示」两项硬件技术,简单讲就是能够让用户实现「盲操」,不必看方向盘上的功能指示就能实现各种操作。

目的嘛,当然也是不让驾驶者分神,始终让视线集中在道路上。

在整套全景 iDRIVE 人机交互解决方案上,宝马还进行了为期 3 年的测试,通过行业独家眼动仪追踪测试,以避免系统中的微动画令驾驶者分心。

这是宝马给智能座舱时代看路还是看屏的一个回答,无论当下把智能驾驶和智能座舱描绘得多么吸引人,座舱内的生活多么精彩舒适,但宝马依旧想强调的是,驾驶者在汽车内的定位,依旧是专注于「驾驶」这件事。

智能驾驶,还是极限马力?宝马希望驾驶不要这么简单

与一些厂商喜欢宣传绝对意义上的马力大小不同,关于汽车驾驶参数层面,宝马倒是给出了两个数字:18000 牛·米扭矩,3G 横向加速度。

这是宝马新世代驾趣概念车的参数。

宝马表示:新世代驾趣概念车在 18000 牛·米轮上扭矩时,可以在连续弯道中实现超 3G 横向加速度。入弯时,系统会自动分配前后轴制动力,保证车身姿态平稳优雅;在冰雪路面紧急避障时,制动力还可以预判侧滑趋势,把失控苗头扼杀在没发生时。

之所以能够在如此极限的条件下,实现车辆的稳当控制,是因为宝马前不久发布的「驾控超级大脑 Heart of Joy」,这是宝马为新世代车型开发的智能驾控集成系统,通过高集成度和智能化设计,首次将动力及底盘控制二域合一,令动力传动、制动、充电、能量回收及转向等核心子功能进行高效集成,并通过全新线束系统和高速数据传输技术所构建的全新数字神经网络,最终,令决策链路延时小于 1 毫秒,信息处理速度较以往系统提升了 10 倍,实现了对车辆整体动态驾驶高效的全面思考与处理。

一般来说,我们会认为国内汽车厂商长于电子电器架构,智驾和智舱,机械层面的东西还是德国人更擅长,不过宝马还是希望用这个「驾控超级大脑 Heart of Joy」告诉大伙儿,机械层面的东西他们固然擅长,但是电子电气架构的创新,他们也能搞定,并且电子电气架构和机械素质,是没法分家的。

另外,宝马最近频繁对外释放了自己对于智能和驾驶的思考:

这(驾控超级大脑 Heart of Joy)不是让车变得更聪明,而是让它真正听懂驾驶者的每一寸肌肉记忆。(宝马工程师)

我们不生产车轮上的智能设备,而是将智能融入汽车的骨骼血脉。(宝马集团大中华区总裁高翔)

AI 不是替代人类,而是让我们找回最初爱上驾驶的理由。(华晨宝马 CEO 戴鹤轩)

之所以这么说,是因为宝马在「驾控超级大脑 Heart of Joy」之外,还发布了 BMW 动态性能控制系统(DPC)。历来宝马都以驾控见长,这不仅仅是工程师们的手工打磨调校,也是宝马的百年赛道经验,BMW 动态性能控制系统(DPC)可以理解为,宝马将这些过往的赛道经验,比如勒芒耐力赛的耐久,M 车型的狂暴运动等等变为了代码,写入了这个系统。

在此系统下,驾驶的动态控制理论上就可以 OTA 了,动能回收不仅仅是为了节能,也可以和制动、转向联动,最终让驾驶者能在几乎不使用制动踏板的情况下,凭借能量回收的精细力度就能实现汽车转向减速需要,达到车随心动,本能驾驶的结果。

过往我们老是用「开宝马,坐奔驰」来形容这两个德系豪华品牌的不同,当「开宝马」遇到了不少人所期盼的「智能驾驶,车坐不开」以及「马力不够,电机来凑」挑战时,宝马对于驾驶的思考就尤为值得玩味。

如上所说,宝马对于驾驶这件事,依旧审慎严肃,既不希望驾驶员在智能驾驶的帮助下分神于智能座舱,也不沉醉于尚不完善的智能驾驶愿景,或者大马力带来的加速快感,而是希望驾驶者专心开车,轻松开车,最后享受开车,从开车中获得乐趣。

这就是标题所说的,宝马绝不背叛「驾驶」。

稳中向好。

#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。

爱范儿 | 原文链接 · 查看评论 · 新浪微博


Harmonyos5应用开发实战——订单页面开发(part2)

作者 WOO
2025年3月31日 18:36
3. 页面索引变化处理

当页面索引发生变化时,调用 chooseRefresh 函数,确保必要信息的选择提示正常显示。

currentIndexChange() {
  if (this.currentIndex === TabBarType.HOMEPAGE) {
    this.chooseRefresh()
  }
}
4. 页面构建与组件使用

在页面构建过程中,使用了多个自定义组件,如 TitleCompContainerComp 等,并根据条件显示购物车、优惠活动等组件。

build() {
  NavDestination() {
    Stack() {
      Column() {
        TitleComp()
        ContainerComp().layoutWeight(1)
      }
      .height(Constants.FULL_SIZE)
      .width(Constants.FULL_SIZE)
      .bindSheet($$this.tableSheetShow,
        CustomSelectSheetBuilder($r('app.string.pick_table'), this.tableList.map((item) => {
          return item.name ?? '';
        }), (select: ResourceStr) => {
          this.confirmTable(select);
        }), {
          height: 408,
          blurStyle: BlurStyle.Thick,
          showClose: false,
        });

      MyCarListComp({
        close: () => {
          this.showCarList = false
        },
      }).visibility(this.showCarList ? Visibility.Visible : Visibility.Hidden)
      // 购物车
      MyCarComp({ showCarList: this.showCarList })
        .visibility(this.myCar.res?.length ? Visibility.Visible : Visibility.Hidden)
      // 优惠活动
      ShopDiscountComp().visibility(this.showShopDiscount ? Visibility.Visible : Visibility.Hidden)

    }.alignContent(Alignment.Bottom)
    .bindSheet($$this.dinerSheetShow,
      CustomSelectSheetBuilder($r('app.string.pick_dinner_num'), $r('app.strarray.diner_num'),
        (select: ResourceStr) => {
          this.confirmDiner(select);
        }), {
        height: 408,
        blurStyle: BlurStyle.Thick,
        showClose: false,
      });
  }
  .hideTitleBar(true)
}
5. 选择桌号和用餐人数的确认处理

当用户选择桌号或用餐人数后,进行相应的信息更新和处理。

confirmTable(select: ResourceStr) {
  if (select) {
    let table = this.tableList.find(item => item.name === select)
    this.tableId = table?.id ?? '';
    getTableInfoUtil(this.tableId).then((res) => {
      this.tableInfo = res;
    });
  }
  this.tableSheetShow = false
  if (!this.dinerNum) {
    this.dinerSheetShow = true
  }
}

confirmDiner(select: ResourceStr) {
  if (select) {
    this.dinerNum = select;
  } else {
    this.dinerNum = this.dinerNum || $r('app.string.diner_num_default');
  }
  this.dinerSheetShow = false
}

通过以上核心功能的实现,在HarmonyOS 5应用中成功开发了订单页面,为用户提供了便捷的商品选择和订单处理体验。

Harmonyos5应用开发实战——地图组件集成与定位功能实现(part2)

作者 WOO
2025年3月31日 18:18
3. 获取当前位置

getLocation方法用于获取用户的当前位置,并将位置信息设置到地图控制器中。

getLocation(mapController?: map.MapComponentController): Promise<geoLocationManager.Location> {
  let promise = geoLocationManager.getCurrentLocation();
  console.info('getCurrentLocation' + JSON.stringify(promise));
  promise.then(async (location: geoLocationManager.Location) => {
    mapController?.setMyLocation(location);
    console.info('getCurrentLocation' + JSON.stringify(location));
    return location;
  }).catch((error: Error) => {
    console.error('getCurrentLocation failed', 'getCurrentLocation error: ' + JSON.stringify(error));
  });
  return promise;
}
4. 跳转到花瓣地图导航

goPetalMap方法用于创建一个Want对象,指定花瓣地图的包名和URI,并传递目的地的经纬度和名称等信息,然后启动花瓣地图应用进行导航。

goPetalMap() {
  let petalMapWant: Want = {
    bundleName: 'com.huawei.hmos.maps.app',
    uri: 'maps://routes',
    parameters: {
      linkSource: AppStorage.get('packageName') as string,
      destinationLatitude: this.latitude,
      destinationLongitude: this.longitude,
      destinationPoiId: '',
      destinationName: this.storeInfo?.address ?? '',
    },
  }

  let context = getContext(this) as common.UIAbilityContext;
  context.startAbility(petalMapWant);
}
5. 界面布局

build方法中,使用NavDestinationStackRelativeContainer等组件进行界面布局,展示地图组件、返回按钮、定位按钮、店铺信息和导航按钮等。

build() {
  NavDestination() {
    Stack({ alignContent: Alignment.BottomStart }) {
      RelativeContainer() {
        MapComponent({ mapOptions: this.mapOption, mapCallback: this.callback })
        Row() {
          Row() {
            Image($r('app.media.back')).width(24).height(24)
          }
          .width(40)
          .height(40)
          .backgroundColor('#0C000000')
          .padding(8)
          .borderRadius(1000)
          .margin({ left: 13 })
          .onClick(() => {
            this.pageStack.pop()
          })

        }
        .width(Constants.FULL_SIZE)
        .margin({ top: this.windowTopHeight })
        .constraintSize({ maxWidth: Constants.FULL_SIZE })
        .padding({ bottom: 10, top: 16 })
        .justifyContent(FlexAlign.Start)
        .alignRules({
          'top': { 'anchor': '__container__', 'align': VerticalAlign.Top },
          'left': { 'anchor': '__container__', 'align': HorizontalAlign.Start },
          'right': { 'anchor': '__container__', 'align': HorizontalAlign.End },
        })


        Image($r('app.media.my_location'))
          .width(32)
          .height(32)
          .borderRadius(8)
          .margin({ bottom: 24, right: 16 })
          .alignRules({
            'bottom': { 'anchor': 'bottomCol', 'align': VerticalAlign.Top },
            'right': { 'anchor': '__container__', 'align': HorizontalAlign.End },
          })
          .onClick(() => {
            if (this.myLocation) {
              this.mapController?.animateCamera(map.newLatLng({
                latitude: this.myLocation?.latitude,
                longitude: this.myLocation?.longitude,
              }, 15), 200);
            }
          })
        Column() {
          Row() {
            Row() {
              Image(`${CommonUrl.CLOUD_STORAGE_URL}${this.storeInfo?.logo}`).width(40).height(40).borderRadius(8)
              Column() {
                Text(this.storeInfo?.name)
                  .fontSize($r('sys.float.Body_L'))
                  .fontWeight(FontWeight.Medium)
                  .fontColor($r('sys.color.font_primary'))
                  .maxLines(1)
                  .textOverflow({ overflow: TextOverflow.Ellipsis })
                Text(this.storeInfo?.address)
                  .fontSize($r('sys.float.Body_S'))
                  .fontColor($r('sys.color.font_secondary'))
                  .margin({ top: 2 })
                  .maxLines(2)
                  .textOverflow({ overflow: TextOverflow.Ellipsis })
              }.alignItems(HorizontalAlign.Start).margin({ left: 8 }).layoutWeight(1)
            }.layoutWeight(1)

            Image($r('app.media.navigation')).width(40).onClick(() => {
              this.goPetalMap()
            })
          }
          .width(Constants.FULL_SIZE)
          .justifyContent(FlexAlign.SpaceBetween)

        }
        .width(Constants.FULL_SIZE)
        .borderRadius({ topLeft: 20, topRight: 20 })
        .backgroundColor($r('sys.color.comp_background_list_card'))
        .padding({
          top: 30,
          left: 24,
          right: 24,
          bottom: 30,
        })
        .alignRules({
          'bottom': { 'anchor': '__container__', 'align': VerticalAlign.Bottom },
          'left': { 'anchor': '__container__', 'align': HorizontalAlign.Start },
          'right': { 'anchor': '__container__', 'align': HorizontalAlign.End },
        })
        .id('bottomCol')
      }.height(Constants.FULL_SIZE)

    }
  }.hideTitleBar(true)

}

通过以上步骤,在HarmonyOS 5应用中成功集成了地图组件,实现了定位功能和跳转到花瓣地图导航的功能。

Harmonyos5应用开发实战——地图组件集成与定位功能实现(part1)

作者 WOO
2025年3月31日 18:17

Harmonyos5应用开发实战——地图组件集成与定位功能实现

文章内容概要

本文聚焦于HarmonyOS 5应用开发中地图组件集成与定位功能的实现。详细介绍了如何在应用中集成地图组件,获取用户定位权限,展示商家位置,并实现跳转到花瓣地图导航的功能。

核心功能介绍

1. 组件状态与属性初始化

在组件中定义了多个状态变量,用于存储地图相关信息,如经纬度、店铺信息、地图控制器等,并设置了地图的样式。

@Component
export struct HwMap {
  @Consume('pageStack') pageStack: NavPathStack
  @State windowTopHeight: number = AppStorage.get('windowTopHeight') as number || 38.77
  @State latitude: number = 0
  @State longitude: number = 0
  @State storeInfo?: StoreInfo = undefined
  @State mapController?: map.MapComponentController | undefined = undefined;
  @State myLocation: geoLocationManager.Location | undefined = undefined;
  @State isShowMyLocation: boolean = false;
  private marker?: map.Marker;
  private mapOption?: mapCommon.MapOptions;
  private callback?: AsyncCallback<map.MapComponentController>;
  private style: mapCommon.MyLocationStyle = {
    anchorU: 0.5,
    anchorV: 0.5,
    radiusFillColor: 0xff00FFFFFF,
    displayType: mapCommon.MyLocationDisplayType.FOLLOW,
  };
}
2. 参数获取与权限请求

在组件即将显示时,通过getParams方法获取路由参数,得到店铺的经纬度和信息。同时,使用initPermission方法请求定位权限,若用户授权成功,则获取用户当前位置并在地图上显示。

aboutToAppear(): void {
  this.getParams()
  this.initPermission()
  this.mapOption = {
    position: {
      target: {
        latitude: this.latitude,
        longitude: this.longitude,
      },
      zoom: 15,
    },
  };

  this.callback = async (err, mapController) => {
    if (!err) {
      this.mapController = mapController;
      this.mapController.on('mapLoad', () => {
        console.info('mapLoad success');
      });
      this.abilityEnabled();
      mapController.setMyLocationStyle(this.style);
      if (this.isShowMyLocation) {
        this.getLocation(this.mapController).then((location: geoLocationManager.Location) => {
          console.info('my location' + JSON.stringify(location));
          this.myLocation = location;
        });
      }
      // Marker初始化参数
      let markerOptions: mapCommon.MarkerOptions = {
        position: {
          latitude: this.latitude,
          longitude: this.longitude,
        },
        rotation: 0,
        visible: true,
        zIndex: 0,
        alpha: 1,
        anchorU: 0.5,
        anchorV: 1,
        clickable: true,
        draggable: true,
        flat: false,
      };
      // 创建Marker
      this.marker = await this.mapController.addMarker(markerOptions);
      await this.marker.setIcon($r('app.media.store_location'));
      this.mapController?.animateCamera(map.newLatLng({
        latitude: this.latitude,
        longitude: this.longitude,
      }, 15), 200);
    }
  };
}

getParams() {
  let paramsArr: HwMapModel[] = this.pageStack.getParamByName('HwMap') as HwMapModel[]
  if (paramsArr.length) {
    let routerParam = paramsArr[paramsArr.length-1]
    this.latitude = routerParam?.latitude ?? 0
    this.longitude = routerParam?.longitude ?? 0
    this.storeInfo = routerParam?.storeInfo ?? undefined
  }
}

initPermission() {
  let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();

  atManager.requestPermissionsFromUser(getContext(this) as common.UIAbilityContext,
    ['ohos.permission.LOCATION', 'ohos.permission.APPROXIMATELY_LOCATION'])
    .then((data) => {
      let grantStatus: Array<number> = data.authResults;
      // 用户授权,可以继续访问目标操作
      this.isShowMyLocation = grantStatus.every(item => item === Constants.USER_GRANT_SUCCESS)
      if (this.isShowMyLocation) {
        // 授权成功
        this.getLocation(this.mapController).then((location: geoLocationManager.Location) => {
          console.info('my location' + JSON.stringify(location));
          this.myLocation = location;
          this.mapController?.setMyLocationEnabled(true);
          this.mapController?.setMyLocationControlsEnabled(true);
          this.mapController?.setZoomControlsEnabled(false);
        });
      }

    }).catch((err: BusinessError) => {
      console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
    });
}

HarmonyOS Next应用开发实战——多功能页面组件构建(part2)

2025年3月31日 17:44
3. 美图鉴赏模块

展示两组滚动的图片,实现美图鉴赏的功能。

Column(){
  Text('美图鉴赏').titleStyle()
  Column({ space: CommonConstants.PUBLIC_SPACE / 2 }){
    MarqueeImageComp({ imgList: [
      $r('app.media.back6'),
      $r('app.media.back7'),
      $r('app.media.back8'),
      $r('app.media.back9'),
      $r('app.media.back10')
    ] })
    MarqueeImageComp({ imgList: [
      $r('app.media.back5'),
      $r('app.media.back2'),
      $r('app.media.back7'),
      $r('app.media.back1'),
      $r('app.media.back4')
    ]})
  }
}
.columnStyle()
4. 了解更多信息展示

将“了解更多”的信息进行分组展示,并且可以水平滚动查看。

Column(){
  Text('了解更多').titleStyle()
  Scroll(){
    Row({ space: CommonConstants.PUBLIC_SPACE }){
      ForEach(this.modificationLearnMoreList.reverse(), ( item: LearnMoreModelInfo[])=>{
        Column({ space: CommonConstants.PUBLIC_SPACE }){
          ForEach(item, (itemData: LearnMoreModelInfo)=>{
            LearnMoreItemComp({ itemData })
          })
        }
      })
    }.justifyContent(FlexAlign.End).alignItems(VerticalAlign.Top)
  }.width(CommonConstants.COLUMN_WIDTH).scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off)
}
.columnStyle()
5. 车型活动展示

使用LazyForEach动态加载车型活动信息,并且可以水平滚动查看。

Column(){
  Text('车型活动').titleStyle()
  Scroll(){
    Row({ space: CommonConstants.PUBLIC_SPACE }){
      LazyForEach(this.vehicleModelActivitiesList,( item: VehicleModelActivitiesInfo)=>{
        VehicleModelActivitiesComp({ itemData: item })
      })
    }
  }.width(CommonConstants.COLUMN_WIDTH).scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off)
}
.columnStyle()
6. 金融计算器展示

展示金融计算器的计算结果,并设置背景图。

Column(){
  Text('金融计算器').titleStyle()
  Row(){
    Text('计算结果')
      .textStyle(Color.White)
      .backgroundColor(Color.Black)
      .borderColor(Color.Black)
      .width(CommonConstants.COLUMN_WIDTH)
  }
  .height(150)
  .width(CommonConstants.COLUMN_WIDTH)
  .backgroundImage($r('app.media.back9'))
  .backgroundImageSize(ImageSize.Cover)
  .alignItems(VerticalAlign.Bottom)
  .padding({
    left: CommonConstants.PUBLIC_SPACE * 2,
    right: CommonConstants.PUBLIC_SPACE * 2,
    bottom: CommonConstants.PUBLIC_SPACE * 2
  })
}
.columnStyle()
.margin({ bottom: CommonConstants.PUBLIC_SPACE })

通过以上核心功能的实现,开发者可以在HarmonyOS Next应用中创建一个功能丰富的页面组件。##### 3. 美图鉴赏模块 展示两组滚动的图片,实现美图鉴赏的功能。

Column(){
  Text('美图鉴赏').titleStyle()
  Column({ space: CommonConstants.PUBLIC_SPACE / 2 }){
    MarqueeImageComp({ imgList: [
      $r('app.media.back6'),
      $r('app.media.back7'),
      $r('app.media.back8'),
      $r('app.media.back9'),
      $r('app.media.back10')
    ] })
    MarqueeImageComp({ imgList: [
      $r('app.media.back5'),
      $r('app.media.back2'),
      $r('app.media.back7'),
      $r('app.media.back1'),
      $r('app.media.back4')
    ]})
  }
}
.columnStyle()
4. 了解更多信息展示

将“了解更多”的信息进行分组展示,并且可以水平滚动查看。

Column(){
  Text('了解更多').titleStyle()
  Scroll(){
    Row({ space: CommonConstants.PUBLIC_SPACE }){
      ForEach(this.modificationLearnMoreList.reverse(), ( item: LearnMoreModelInfo[])=>{
        Column({ space: CommonConstants.PUBLIC_SPACE }){
          ForEach(item, (itemData: LearnMoreModelInfo)=>{
            LearnMoreItemComp({ itemData })
          })
        }
      })
    }.justifyContent(FlexAlign.End).alignItems(VerticalAlign.Top)
  }.width(CommonConstants.COLUMN_WIDTH).scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off)
}
.columnStyle()
5. 车型活动展示

使用LazyForEach动态加载车型活动信息,并且可以水平滚动查看。

Column(){
  Text('车型活动').titleStyle()
  Scroll(){
    Row({ space: CommonConstants.PUBLIC_SPACE }){
      LazyForEach(this.vehicleModelActivitiesList,( item: VehicleModelActivitiesInfo)=>{
        VehicleModelActivitiesComp({ itemData: item })
      })
    }
  }.width(CommonConstants.COLUMN_WIDTH).scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off)
}
.columnStyle()
6. 金融计算器展示

展示金融计算器的计算结果,并设置背景图。

Column(){
  Text('金融计算器').titleStyle()
  Row(){
    Text('计算结果')
      .textStyle(Color.White)
      .backgroundColor(Color.Black)
      .borderColor(Color.Black)
      .width(CommonConstants.COLUMN_WIDTH)
  }
  .height(150)
  .width(CommonConstants.COLUMN_WIDTH)
  .backgroundImage($r('app.media.back9'))
  .backgroundImageSize(ImageSize.Cover)
  .alignItems(VerticalAlign.Bottom)
  .padding({
    left: CommonConstants.PUBLIC_SPACE * 2,
    right: CommonConstants.PUBLIC_SPACE * 2,
    bottom: CommonConstants.PUBLIC_SPACE * 2
  })
}
.columnStyle()
.margin({ bottom: CommonConstants.PUBLIC_SPACE })

通过以上核心功能的实现,开发者可以在HarmonyOS Next应用中创建一个功能丰富的页面组件。### HarmonyOS Next应用开发实战——多功能页面组件构建

文章概要

本文聚焦于HarmonyOS Next应用开发中多功能页面组件的构建。详细介绍了一个具备多种功能模块的页面组件,包括背景图展示、预约试驾与订购按钮、美图鉴赏、了解更多信息、车型活动展示以及金融计算器展示等功能的实现方式。

核心功能介绍

1. 数据初始化

在组件即将显示时,进行数据的初始化操作,包括车型活动列表和“了解更多”信息列表的处理。

aboutToAppear(){
  this.vehicleModelActivitiesList.pushArrayData(VehicleModelActivities.getMainList())
  let tempArr: LearnMoreModelInfo[] = []
  for(let i = 0; i < this.learnMoreList.length; i++){
    tempArr.push(this.learnMoreList[i]);
    if(i % 3 === 0){  // 三个一组
      this.modificationLearnMoreList.push(tempArr);
      tempArr = [];
    }
  }
}
2. 背景图与操作按钮布局

构建一个带有背景图的页面,并且在底部添加“预约试驾”和“立即订购”按钮。

Stack({ alignContent: Alignment.Bottom }){
  Image($r(this.imgStr))
    .width(CommonConstants.COLUMN_WIDTH).height(CommonConstants.COLUMN_HEIGHT)
  Flex({ justifyContent : FlexAlign.SpaceAround }){
    Text('预约试驾').textStyle(Color.White)
    Blank()
    Text('立即订购').textStyle(Color.Black).backgroundColor(Color.White)
  }
  .padding({
    left: CommonConstants.PUBLIC_SPACE * 2,
    right: CommonConstants.PUBLIC_SPACE * 2,
    bottom: CommonConstants.PUBLIC_SPACE * 3
  })
  .width(CommonConstants.COLUMN_WIDTH)
}
.width(CommonConstants.COLUMN_WIDTH).height(CommonConstants.COLUMN_HEIGHT)

MCP 崛起与苹果的 AI 框架设想 - 肘子的 Swift 周报 #77

作者 Fatbobman
2025年3月31日 22:00

在最近一段时间,在社交网络上,越来越多的 Model Context Protocol(MCP)使用者展示了各种丰富多彩的应用场景,从操控 Blender 创建精美场景,到利用最新的 GPT-4o 图片构建完整的漫画故事。MCP 巧妙地打开了以文本为主要互动手段的大模型,与现实世界之间的大门。

鸿蒙开发中的常见关键字简单总结

作者 90后晨仔
2025年3月31日 17:10

如果总结的有问题,欢迎大家指出来!

在鸿蒙应用开发中(特别是使用 ArkUI 框架和 ArkTS 语言时),装饰器(Decorators) 是关键概念,用于声明组件、管理状态和定义交互逻辑。以下是常用关键字的详细介绍及代码示例:


1. @Entry

作用:标记应用的主入口组件,一个页面有且仅有一个 @Entry
示例

@Entry
@Component
struct Index {
  build() {
    Column() {
      Text('Hello HarmonyOS')
    }
  }
}

2. @Component

作用:定义自定义组件,必须与 struct 结构体一起使用。
示例

@Component
struct MyComponent {
  build() {
    Text('自定义组件')
  }
}

3. @State

作用:组件内部的状态变量,变化时触发 UI 更新。
示例

@Entry
@Component
struct Counter {
  @State count: number = 0

  build() {
    Column() {
      Text(`Count: ${this.count}`)
      Button('增加').onClick(() => {
        this.count++
      })
    }
  }
}

点击按钮时,count 变化会自动更新 Text 显示。


4. @Prop

作用:子组件接收父组件的单向数据,变化会更新子组件。
示例

// 父组件
@Entry
@Component
struct Parent {
  @State parentCount: number = 10

  build() {
    Column() {
      Child({ count: this.parentCount }) // 传递数据
      Button('父修改').onClick(() => {
        this.parentCount++
      })
    }
  }
}

// 子组件
@Component
struct Child {
  @Prop count: number

  build() {
    Text(`子组件收到: ${this.count}`)
  }
}

父组件的 parentCount 变化会同步到子组件,但子组件无法直接修改 count


5. @Link

作用:父子组件间的双向绑定数据,任何一方修改都会同步。
示例

// 父组件
@Entry
@Component
struct Parent {
  @State parentCount: number = 20

  build() {
    Column() {
      Child({ countLink: $parentCount }) // 使用 $ 传递引用
      Button('父修改').onClick(() => {
        this.parentCount++
      })
    }
  }
}

// 子组件
@Component
struct Child {
  @Link countLink: number

  build() {
    Button('子修改').onClick(() => {
      this.countLink++ // 修改会同步到父组件
    })
  }
}

父组件和子组件均可修改 countLink,数据双向同步。


6. @Provide 和 @Consume

作用:跨层级组件共享数据,祖先组件提供数据(@Provide),后代组件消费数据(@Consume)。
示例

// 祖先组件
@Entry
@Component
struct Grandparent {
  @Provide shareData: string = '全局数据'

  build() {
    Column() {
      Parent()
    }
  }
}

// 中间组件
@Component
struct Parent {
  build() {
    Child()
  }
}

// 后代组件
@Component
struct Child {
  @Consume shareData: string

  build() {
    Text(`收到数据: ${this.shareData}`)
  }
}

7. @Watch

作用:监听状态变量变化,执行回调函数。
示例

@Entry
@Component
struct WatchExample {
  @State count: number = 0
  @State log: string = ''

  @Watch('count') // 监听 count 变化
  watchCount() {
    this.log = `Count 变为 ${this.count}`
  }

  build() {
    Column() {
      Text(this.log)
      Button('增加').onClick(() => {
        this.count++
      })
    }
  }
}

8. @Builder

作用:创建可复用的 UI 构建函数。
示例

@Entry
@Component
struct BuilderExample {
  @Builder customButton(text: string) {
    Button(text)
      .width(100)
      .height(40)
      .backgroundColor(Color.Blue)
  }

  build() {
    Column() {
      this.customButton('按钮1')
      this.customButton('按钮2')
    }
  }
}

9. @Styles

作用:定义可复用的样式集合。
示例

@Entry
@Component
struct StyleExample {
  @Styles commonStyle() {
    .width(200)
    .height(50)
    .backgroundColor(Color.Green)
  }

  build() {
    Column() {
      Text('文本1').commonStyle()
      Button('按钮').commonStyle()
    }
  }
}

总结

  • 状态管理@State@Prop@Link@Provide/@Consume
  • UI 构建@Entry@Component@Builder@Styles
  • 副作用监听@Watch

合理使用这些关键字可以高效管理组件状态和数据流,构建响应式 UI。根据具体场景选择单向 (@Prop) 或双向 (@Link) 数据绑定,跨组件共享数据时考虑 @Provide/@Consume

拆掉索尼大楼 8 年后,我们在东京看到了 Sony Park 的完全体

作者 谢东成
2025年3月29日 16:59

三流企业卖产品,二流企业卖服务,一流卖企业概念,超级企业卖大楼。

这是一个早年间广泛流传于社交媒体之上的梗,前三句直观地概括了不同层级企业的生存逻辑,最后一句是网友戏谑诺基亚和索尼出售总部大楼以换取资金改善财务状况的实例。

如果你对「索尼百叶窗」还有印象的话,那么你应该还会记得 2017 年的时候,索尼曾将位于日本东京银座的索尼大楼(Sony Building)进行了拆除。

在宣布拆除大楼的同时,索尼还宣布将这座有着 50 年历史大楼的外立面百叶窗,切割下来做成一份「特别」的纪念品——「索尼大厦百叶窗纪念品」并进行售卖。

每一块「索尼大厦纪念百叶窗」都是从索尼大楼的外墙上拆下来,再经由手工一个个地切割成纪念品的尺寸大小,然后镌刻上「Sony Building 1966-2017」的字样。售价 5000 日元,按照当时的汇率约合 300 元人民币一块。

看到这,你可能会想问,索尼拆了大楼还要卖建筑垃圾来赚钱?

但事实上,索尼将售卖百叶窗纪念品的收入,全额捐给了日本儿童救助(Save the Children)与索尼共同企划的「儿童灾害紧急复原项目」。

也与「卖大楼」的玩笑相反,索尼拆除索尼大楼的真实原因,并非源于资金周转需求,而是在索尼大楼建成的 50 年之际,索尼集团整体的业务已经步入了更多元化的层次。

于是在新时代谋求战略转型的过程中,建成于 1966 年的索尼大楼已经难以胜任「产品陈列室」的历史任务,索尼大楼亟需「重生」。

经过 8 年时间两个阶段的建设,银座索尼公园(Ginza Sony Park)在 2025 年 1 月 26 日正式面向公众开放。值得一提的是,这一天恰好就是索尼创始人盛田昭夫诞辰 104 周年的纪念日。

在索尼公园正式开放的前几天,爱范儿受邀前往日本东京,提前探访了这一座「索尼打造过最大的产品」。

拆除索尼大楼,索尼为什么要在寸土寸金的银座建造「公园」?

在到访银座索尼公园之前,这是我最好奇的一个问题。

根据日本国土交通省发布的数据显示,东京银座连续 19 年蝉联日本地价最贵的地段,用寸土寸金来形容银座地段也是一点也不为过。事实上,从 1966 年的日经新闻报道来看,索尼最初选址于此的时候,这块地皮已经是当时日本乃至全世界地价最贵的。

只不过索尼作为一家电子产品制造商,在银座建设 Sony Building 的初衷从来都不是要作为自己的总部办公楼,而是要成为索尼 1959 年在银座数寄屋桥开设的「产品陈列室」的延续,用一座前所未有的现代化设计建筑,来成为最能代表、展示索尼一切的综合展厅大楼(Showroom Building)。

▲ Sony Building(1966-2017). 图片来自:索尼官网

显然,无论是过往的 Sony Building 还是面前的 Sony Park,其实都是索尼面向全球「索粉」们所提供的一座独具索尼特色的开放空间。

所以,时任索尼总裁的平井一夫在 2016 年重建索尼大楼的项目演示概念书中也表示,「新的索尼大楼」应该是一个新的「索尼信息共享中心」,能够继续作为向全世界传播索尼品牌的枢纽而存在。

简言之,旧的索尼大楼,已经很难去回答「索尼是一家怎样的公司」这个问题,索尼需要「新的地标」来匹配索尼业务生态从硬件制造商向创意娱乐科技巨擘的转变。

▲ 银座索尼公园项目负责人 永野大辅(左);时任索尼总裁平井一夫(右)

尽管当时还未曾确立「新索尼大楼」是以公园的形式来建设,但平井一夫在考虑索尼大楼「重生」的过程里,反复提到的一个重要关键词就是「邀请」——让尽可能多的人能够进入到这个建筑之中,享受这片「很索尼」的公共空间。

当然,这个打造公共空间的概念其实也源自 Sony Building 本身。

在索尼大楼的设计过程中,索尼创始人盛田昭夫十分认可设计师芦原义信的想法,在面向十字路口的一角留出了 33 平米的开放空间打造为 Sony Square,作为银座花园(Garden of Ginza)对外开放。

在寸土寸金的银座地段留出如此大面积的公共空间,当时在许多人看来,这都是一种近乎疯狂的奢侈举动。

作为开放空间,索尼每年都会顺应时节在此处栽种不同的花(当然也是日本最名贵的花),在夏天还会在此处放置巨大的鱼缸,以「Sony Aquarium」的形式展示来自冲绳美丽海水族馆引进的海水鱼类,让此处变成一个流动开放式水族馆,为夏天的路人们带来一丝清凉。

所以,索尼选择打造「银座索尼公园」的初衷,就是想要延续 50 年来的「银座花园」概念,并更进一步将其演变为「银座公园」,更加大胆地持续为城市提供一个富有创造力的公共空间。

历经 8 年时间的,其实是一场「独一无二的实验」

如果说将一栋六层高的大楼拆除然后重建成一座公园,要耗费长达 8 年的时间,听起来效率并不是很高。但从不走寻常路的索尼,实际上是将重建索尼大楼这个项目看作是一场「独一无二的实验」。

整个重建过程分为了三个阶段,在花费一年时间拆除大楼之后,索尼 2018 年在原址的地皮表面建造了一个街心公众公园 Sony Park,并利用了原来大楼建筑地下四层的空间来设立了艺术展览、潮流商店、游戏厅、啤酒餐吧等多种娱乐空间。

索尼对这个 Sony Park 阶段的定义是一座「不断在变化的公园」,在密密麻麻的银座商都一角,设置了一片开放式的「绿洲」。官方数据显示,在开放运营的四年时间,Sony Park 接待了 854 万游客,还举办了无数的展览和活动。

于是当我来到全新的 Ginza Sony Park 之中,得以与该项目负责人永野大辅对话的时候,我特别好奇,这场实验到底给索尼带来了什么结论,这个过程让索尼从中得到了什么帮助或者启发?

永野大辅听完我的问题,脸上露出了自信的微笑并说这是一个很好的问题,看起来这个问题早已经过长足的思考,如今也得到了充分的答案。

▲ 索尼公园项目负责人永野大辅,他身后就是原索尼大楼楼顶的霓虹灯 logo

他指出了「三点启发」:不建高楼、开放空间安全性更高、开放场所应该怎样去吸引人。

首先是不建高楼的启发,相较于原来八层楼高的索尼大楼,开放式的街心公园 Sony Park 在地表只有一层,具有更强烈开放氛围的 Sony Park 在实际表现里,是远比以前的索尼大楼要更加吸引过往的游客和市民。

这样的启发也让索尼深刻地认识到,低层的建筑比起高层的建筑要更加吸引游客和市民。于是,当银座建筑物高度被限制在 56 米以内的同时,银座索尼公园更是刻意地将高度设置得更低,大概是这个规则的一半左右。

无论是从远处眺望,还是步入于其中,都让银座索尼公园看起来更加开阔,与周边的高密度建筑体系形成了鲜明的对比。

隐约觉得,这跟国画的「留白」意味,有着异曲同工之境。

其次,索尼的第二点启发是「开放空间的安全性更高」。这一点是我此前从未想到过的,永野大辅告诉爱范儿,将建筑物变成开放的形态会让周边街区都变得更加安全。

改建之初,我们担心如果将索尼大楼做成开放空间,安全性是否无法保证,我们该如何应对因开放空间带来的一些隐患,带着这样的忐忑,我们大胆迈出了这一步实验。经过三年的实验,这一举措拿到了满分的反馈,所以银座索尼公园的第一层,将以完全开放的空间面向大家。

▲ 银座索尼公园的第一步. 图片来自:索尼官网

第三点,是利用有主题、技术和艺术三元素叠加的创意活动来提高品牌效应。永野大辅认为,场所服务于人,所以过往 4 年的运营一直都在促使索尼去思考,场所活动要如何去吸引人?如何利用场所活动去提高品牌效应?

最终,索尼得出的成功经验,就是以「主题 × 技术 × 艺术」三个元素相乘,从而满足大家来到这个场所的各种需求,无论是短暂地休憩、周末的放松、恋人的漫步、亲子间的互动等等。

正是因为第一阶段的大胆尝试,且得到了较好的效果,所以大家看到的新银座索尼主题公园结合了此前的成功经验,开幕后,银座索尼公园将陆续开展展览活动,欢迎市民和游客前来体验。

索尼「最大的产品」,如何诠释索尼?

无论是我初次造访,还是朋友圈中在春节假期路过东京银座的朋友们,都很容易被这座 Ginza Sony Park 抓住眼球。

除了更加低矮开阔的建筑高度,它很直白地将主题混凝土结构展露在外的样子,也与周边临近的繁华大楼们形成了强烈的反差对比。用近乎原始的混凝土外墙,配合刻意降低的建筑物高度,的确能够体现出索尼公园想要作为「城市平台」的地位。

随着脚步走进,索尼公园建筑物的主题视野也拉近了不少,可以看到覆盖在混凝土建筑表面的不锈钢网格状框架,自然而然地成为了索尼公园与繁华闹市之间的松散边界。

索尼公园项目负责人永野大辅告诉我们:「当光线从框架的缝隙中射入,就像阳光透过树叶渗透进来一样,会不断地移动和变化。」

当然,除了可以作为功能性外立面,设置一些活动相关的海报或者装置,它也可以在对设施进行扩建的时候,充当类似「脚手架」一样的辅助角色。

在开业前夕,我们可以看到外墙上挂着一句日本人常在回家进门时,就对家人说的「我回来了」。

银座索尼公园还继承了索尼大厦所珍视的独特元素:提供公共空间的设计理念、「枢纽」建筑和垂直长廊风格。

正如前文所述,索尼公园的一楼是一个完全开放的空间设计,弱化了公园与城市的界限,内在空旷的中庭位置变成了数寄屋桥十字路口进入的人流的天然容器,可以让游客从不同的角度自然地进入,然后按照垂直长廊的引导,自由地前往公园建筑的上方或者下方。

作为一栋「面向城市开放」的建筑,索尼公园内部从下至上设置了一条「垂直长廊」,螺旋结构的楼梯和缓坡可以让观众从底部一直往上漫步,弱化了楼层之间的分隔,自然而然地去完成整栋公园的游览。

这条「垂直长廊」的灵感来自于索尼大楼的「花瓣结构」,设计师芦原义信为了有效利用索尼大楼原本不算宽敞的占地面积,绞尽脑汁地以一系列相连的楼层将整栋大楼连接起来,使其成为一条垂直的长廊。参观者可以毫不费力地乘坐电梯上升到建筑顶部,然后随着着螺旋走道逐渐下降到达其他楼层,在不知不觉间完成整栋建筑的浏览。

相比起只在地上六层建筑建设垂直长廊,全新的银座索尼公园则是从地下三层至地上五层(屋顶平台)都实现了垂直长廊的设计,让整个建筑物的每一个楼层能够通过一条垂直长廊来连接。

对我来说,此前探访过一些银座建筑都需要乘坐电梯前往,单个楼层的浏览范围其实有限,但频繁地转移楼层的话,要么排队乘坐狭窄的手扶电梯,要么花费更长的时间去等待直梯,体验都算不上很好。而索尼公园的楼梯设计,可以让我高效地往来不同的楼层,无形中也提升了在其中游览穿梭的兴致。

在看不见的地底,索尼公园保留了一些旧建筑的痕迹,甚至加固了原来属于索尼大楼的地下室外墙,创建了一个类似于浴桶一样的独立结构。

这种施工方法可以保护整栋建筑物免受银座地下流动的土壤或地下水的压力,同时也能保障重建项目可以获得和之前一样多的公共空间,无需新增更多的地下外墙。索尼也保留了原来的地下入口,与银座站的地下通道相连,也能直接连接到银座最大的地下停车场。

作为新时代索尼的「产品陈列室」,索尼并未打算在银座索尼公园之内塞满索尼的产品。为了吸引更多用户或者市民到来,索尼选择「主题 × 技术 × 艺术」的方式,在此处举办各种活动和展览,进而传递索尼的魅力。

随着银座索尼公园的开幕,索尼将同步设置开园以来的首个展会——「Sony Park 展 2025」。这是一个以索尼集团的六大业务为主题,与 6 组富有个性的艺术家共同创作的创意体验型活动。

受邀参展的 6 组艺术家的创意,将会与不同主题的索尼业务相结合,通过索尼的技术来还原艺术家的创意灵感。整个展会分为两个阶段,每个阶段将会有三位艺术家和展览分别展出。

首批开展的艺术家包括了 YOASOBI、羊文学和 Vaundy。爱范儿作为首批邀请到访银座索尼公园的中国媒体之一,也被特别安排分别体验了这三个展览。

首先是索尼金融与羊文学的展,是以「金融如诗」为题,聚焦在羊文学的两首歌里(其中一首是《More than words》),索尼为此重新打造了一套巨大的水盘光影装置,将歌词与水和光影一起交融。

我留意到在此处,索尼用上了他们引以为傲的 360 临场音效(360 Reality Audio)技术,声音效果非常震撼。

离场的时候,我们还能体验到索尼的触觉技术「Active Slate」,地板传来逼真的震动会让你觉得真的踩在了水道上。

YOASOBI 是和索尼半导体一起办展,以「心跳」为主题,来访者在入场之时会通过索尼的传感器设备,记录一段心率图谱,然后通过 AI 算法生成一个心跳图案。而后步入到投影互动装置之中,你就会发现,自己的心跳图案可以融入到 YOASOBI 《HEART BEAT》这首歌的声画当中。

最后我们走到了地下入口位置的旁边,看到了 Vaundy 与索尼音乐一起举办的「音乐如旅行」展。

他在 B2 区域设置了一个「音乐地层」,Vanudy 在这里放置了 200 首他自己珍藏的宝藏歌曲。观众可以在入场时,领取一个经典的索尼监听耳机(MDR-CD900ST)。

然后带着这个耳机走进展区之内,可以看到琳琅满目的歌曲名字以及对应的耳机接口,Vanudy 按照不同的音乐类型进行了区分,包括希望、爱情等等……

你只需要按照分类,看到自己感兴趣的曲名,就可以将耳机的 6.3mm 接口插进相应的歌曲接口,耳机就会自动播放相应的音乐了。个人感觉,这个形式还是相当有趣,可以发现一些在「猜你喜欢」的算法之外,也能引起情感共鸣的曲子。

源自创始人的精神:索尼要做别人未曾做过的事情

浏览完整座银座索尼公园之后,我对索尼的印象有了更加深刻的认知。

过去,我们时常会探讨「索尼究竟是一家怎样的公司?」——皆因索尼的业务在迈入二十一世纪之后,变得相当多元化,看似各行各业都有索尼的身影。

▲ 索尼品川总部大楼内的 Sony Square 一角

比如索尼 A9M3 相机拍下了可能是特朗普一生中最具张力的照片;索尼旗舰电视也是不少大户人家的首选;PlayStation 依然是备受追捧的游戏主机;知名动画作品《鬼灭之刃》是由索尼旗下的子公司 Aniplex 出品;与漫威联合制作的《蜘蛛侠》和《毒液》电影系列也在全球范围内热映……

但与此同时,索尼也这些年也推出过不少被认为是失败的产品,甚至在大好时势下眼睁睁地错失了移动互联网的最佳风口。在消费者眼中,曾经先进且独具个性的索尼 Xperia 手机,也黯然在中国大陆市场「断档」,更别说熟悉的 VAIO 电脑业务和元老级的锂电池业务也被索尼陆续出售。

但正因这些大刀阔斧的改革手段,聚焦更多元化的核心业务,剥离非核心资产,才让索尼从亏损的困境中突围而出。在 2024 财年,索尼预计将实现本世纪以来最亮眼的业绩表现,旗下的六大板块业务都分别实现了盈利,预计同比增长 11%。华尔街分析师认为,如今转型以创意娱乐为主导的索尼,有了更强的抗风险能力。

谈及现阶段的企业定位,索尼官方的定义是一家「建立在坚实技术基础上的创意娱乐公司」。

当我第一次听到这个略显抽象的企业定位时,就意识到要全面诠释索尼企业定位并非一件易事,只能通过一些具象的东西来加以理解。

在索尼位于东京品川的总部大楼,有一面索尼历史墙(History Wall),展示了索尼创立以来的重要产品和大事记。当中放置的一个用黄金铸造的小白鼠引起了我们的注意。

▲ 图片来自:kimoto-sbd

1955 年,索尼推出日本首台晶体管收音机 TR-55,虽开创了技术先河,但 3 年后随着晶体管成为主流,被大企业以规模优势超越。彼时评论家讥讽仍算是创业公司的索尼,是「大型企业的实验小白鼠」。

▲ 索尼晶体管收音机 TR-55,图片来自:索尼官网

虽然这个评价引起了当时索尼员工们的愤怒,但面对质疑,索尼创始人之一的井深大却将这一标签转化为精神动力:「开拓新产品若被视作『小白鼠精神』,何尝不是荣耀?」。后来,索尼更是用黄金铸造一个了小白鼠雕塑,既是对嘲讽的回应,亦是对索尼「敢为天下先」价值观的定格,并用以激励后来的索尼员工都要保持「创造未存在之物」的初心。

在这次索尼公园的采访过程中,项目负责人永野大辅告诉我们:

盛田昭夫作为创始人之一,他给我们的影响很大,所以我们想把他的一些想法或者精神世代的传承下去。不管是盛田昭夫先生,还是索尼,一个重要的 DNA 就是做别人没有做过的事情。

▲ 摆放在 Sony Park 顶层天台的 AFEELA 原型车

从这个角度来看,放弃可观的地产商业价值转而拥抱城市开放空间的银座索尼公园,其实是索尼「创造未存在之物」的空间载体,它理所应当成为了索尼迄今为止打造过的「最大的产品」——这栋独特的建筑物本身,也在诠释着索尼品牌创办时的初心:做别人不做的事情,做别人没有做过的事情。

从宣布拆除大楼,到出售大楼百叶窗周边,继而运营街心开放公园作为实验田,直至最终重建银座索尼公园,这长达 8 年的整个过程里,索尼都在践行「做别人没有做过的事情」这一点。显然对于索尼来说,坚持做别人从未做过的事情,就是一件最酷的事情。

总体看下来,无论是这个地段,还是这个建筑本身,以及未来这座公园的持续运营,索尼无疑都是需要去倾注大量的时间和金钱,才能一步一步地将银座索尼大楼,变成银座索尼花园,再变成一座银座索尼公园。

▲ 在 Sony Park 遇到了带着 AIBO 机器狗来观展的用户,它的名字是 さくら(樱花)

它与我们之前造访过的 Apple Park 等冠以「Park」之名的办公园区都不同,也超脱于常规意义的企业 Showroom。

从开放性来看,这座 Sony Park 的确是我们传统意义上的城市公园——它面向所有人都开放,至于索尼的产品与技术,都隐性地存在于这栋建筑之中,成为场所的一部分,不断更新且持续地服务到访的所有来客。

可以预见,全新的 Ginza Sony Park 能够为银座街区以及周边居民注入更多活力的同时,也能继续吸引全球各地的「索粉」们前来踊跃打卡。

#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。

爱范儿 | 原文链接 · 查看评论 · 新浪微博


超过 10 亿投入,理想宣布开源自研汽车操作系统

作者 芥末
2025年3月29日 09:00


理想汽车董事长李想在 2025 年中关村论坛年会上宣布理想汽车自研整车操作系统「理想星环 OS」进行开源。

「理想星环 OS」主要包括车控操作系统、智能驾驶操作系统、通信中间件、虚拟化平台等核心部分,预计将在 4 月底上线开源社区。

对于开源这件事,李想后来发了一条微博,更详细的讲了下。

其中表示自研 OS 是“逼上梁山”。首要原因是 autosar 的高使用成本和长芯片适配周期,其次是对 autosar 闭源的性能和安全性的担忧,毕竟看不见内部。

星环 OS 这次开源其实暗含着摆脱和替代 autosar 的意图。

要先解释一下 Autosar 是个啥,它究竟有多重要。

Autosar 是一种汽车软件架构,同时在他背后也有同名的一系列相关标准和组织。

它存在的意义其实在于作为一种标准化的方案,autosar 能够通过提供基本软件模块规范、定义应用接口以及构建基于标准化交换格式的通用开发方法,来提高汽车电子系统和软件的可扩展性、可靠性和互操作性。

发起者主要是德国公司,像宝马、博世、大陆集团、奔驰、西门子 VDO 和大众等都是其创立成员,之后福特、标致雪铁龙、丰田、通用等也相继加入。

但 Autosar 的标准和架构都是不要钱的,主要要钱的都是符合 Autosar 标准和架构的具体软件产品的一次性费用和授权费,以及配套工具链使用授权费。

大概了解了 autosar,我们回到理想这边。

为了解决刚才提到的两个缺点和 2021 年「芯片荒」的困境,理想开始自研操作系统,累计投入超过十亿元以及近 200 人的研发团队。

除此之外,理想汽车在智能化道路上还遇到了系统关键性能、安全性、可靠性等方面问题。因此,理想汽车开始一步步扩大车用操作系统自研领域,最终完成了整车操作系统的全栈自研。2024 年,理想自研的操作系统实现商用上车。

研发起来相当不容易,但却带来了十分显著的效果。

自研操作系统将新型号芯片的适配时间从 6 个月减少到 4 周,并且支持了车用芯片的各种架构,做到芯片选择自由,极大缓解了芯片荒对供应链的影响。

同时理想的操作系统做到了全链路融合,响应速度快了 1 倍,响应稳定性提高了 5 倍。反映到高速 AEB 上,时速 120 公里下的刹停距离可以减少7米。在信息安全上也具备了更好的能力。

当然,给 Autosar 的钱也不用交了,每年可节约数十亿的成本。

那投入这么大的项目,理想为何决定把它开源呢?

至少有两大好处。 一是智能汽车专用的操作系统技术门槛高,需要座舱、智驾、底盘等横向和应用、系统、硬件等纵向联合创新,开源能够让大家资源共享,合力研发,理想自己的成本压力也会小一些。

另外,现在的开源生态中并没有面向整车的操作系统,理想把已有的成果开源出来,可以让大家避免「军备竞赛」,更专注在其他的差异化功能上。

有业内人士评价理想开源时举了 Android 的例子,说通过开源理想汽车说不定能像 Google 一样,成为智能汽车领域的「规则制定者」。

然而就在同一天,Google 决定不再维护目前安卓 AOSP 的公开分支,逐渐关闭相关的的支持性资源,并可能停止更新有法定开源义务(GPL 等协议的代码)外的组件的源代码。

#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。

爱范儿 | 原文链接 · 查看评论 · 新浪微博


SwiftUI Environment:理念与实践

作者 Fatbobman
2025年3月26日 22:12

SwiftUI 的 Environment 是一个优雅且功能强大的依赖注入机制,几乎每个 SwiftUI 开发者都会在日常开发中接触和应用。这一机制不仅简化了视图间的数据传递,也为应用架构设计提供了更多的可能性。本文将暂且搁置具体的实现细节,转而聚焦于 Environment 在架构中的角色与边界,探讨那些常被忽视却至关重要的设计理念与实践经验。

❌
❌