Compose自定义布局的使用

发表于 5月以前  | 总阅读数:245 次

Compose自定义布局的使用

我们知道,在Android View体系下,自定义布局需要继承ViewGroup重写onMeasure、onLayout方法,那么在Compose UI框架中该如何实现自定义布局呢?

今天我们就来学习下Compose UI中自定义布局的具体使用。

实现目标

项目中有一个房源展示页面,用来展示一栋楼的所有房间信息,布局要求如下:

  1. 每个房间的宽高尺寸固定,水平方向需要动态计算可以显示的房间数量,内容水平方向居中;
  2. 房源数量较少,无法充满屏幕时,房源在屏幕中竖直方向居中显示;
  3. 房源数量超过屏幕时,从上向下布局,竖直方向可以滑动查看。

我们以此页面为目标,学习Compose自定义布局的使用。

效果图

效果一:上下、左右居中

效果二:上下滑动

Compose 自定义布局实现方式在编写代码前,我们先来了解下Compose 中自定义布局的实现方式。

Compose中使用Layout 可组合项来实现自定义布局,在Layout函数中完成子元素的测量和放置。以下是 Layout 可组合项的函数签名:

@Composable inline fun Layout(
    content: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    measurePolicy: MeasurePolicy
) {}

一个自定义布局的Layout代码结构通常如下代码所示:

@Composable
fun MyBasicColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->

        // 1. 使用给定的约束条件constraints测量children
        val placeables = measurables.map { measurable ->
            measurable.measure(constraints)
        }

        // 2. 设置布局的尺寸,放置子元素
        layout(constraints.maxWidth, constraints.maxHeight) {
            var yPosition = 0
           //在父布局中放置children
            placeables.forEach { placeable ->
                placeable.placeRelative(x = 0, y = yPosition)
                yPosition += placeable.height
            }
        }
    }
}

从上面代码可以看到,Compose实现自定义布局,主要有两步:

  1. 测量每个子元素在父布局约束下的大小
  2. 确定布局尺寸,放置子元素

注意:Compose 界面不允许多遍测量。这意味着,布局元素不能为了尝试不同的测量配置而多次测量任何子元素。

主要参数介绍

Layout函数中,有三个主要参数:

1. modifer

由外部传入的修饰符,用来修饰我们自定义的这Layout 组件的一些属性或约束 Constraints;

2. content

自定义布局 Layout 组件中所包含的子元素 children;

3. measurePolicy

mearsurePolicy 参数是 MeasurePolicy 类型,它是一个函数式接口,指定了布局测量和放置项目的方式。我们通常在Layout函数中以尾随 Lambda 的形式提供 MeasurePolicy 作为参数,从而实现所需的 MeasureScope.measure 函数。

fun interface MeasurePolicy {
    fun MeasureScope.measure(
        measurables: List<Measurable>,
        constraints: Constraints
    ): MeasureResult
}

measure函数接受一个 Constraints 对象来告知 Layout 它的尺寸限制。Constraints 是一个简单类,用于限制 Layout 的最大和最小宽度与高度, constraints中提供的maxWidth和maxHeight是计算过modifier中padding之后的值, 所以布局中不需要再考虑padding:

class Constraints {
    val minWidth: Int
    val maxWidth: Int
    val minHeight: Int
    val maxHeight: Int
}

measure 函数还会接受 List 作为参数,表示的是传入的子元素, Mesurealbe中拥有测量元素尺寸的函数Mesurealbe.measure(constraints: Constraints),使用此函数完成子元素的测量工作,获取子元素的布局尺寸。

在MeasurePolicy的measure函数中,完成测量和放置子元素的过程。

1)测量

遍历measureables, 调用measure(constrains:Constrains)方法进行测量。获取子元素的测量结果Placeable,Placeable包含测 量的宽度和高度

2)放置

调用layout(width: Int,height: Int,alignmentLines: Map<AlignmentLine, Int> = emptyMap(),placementBlock: Placeable.PlacementScope.() -> Unit)方法对子元素进行布局。

width, height指定可组合项的布局尺寸, placementBlock是具体的布局流程。

测量后的Placeable表示为可布局对象。通过placeable.placeRelative(x:Int,y:Int)方法对其进行摆放。x,y表示其距当前组件左上角的偏移量。另外还有一个place(x: Int, y: Int)方法,两个方法的区别是:placeRelative方法支持RTL布局,也就是从右向左的布局,place方法只支持LTR布局。

代码实现

接下来,正式开始编写代码。

我们预先定义一些布局条件:

  1. 自定义布局默认占满屏幕,宽高即为屏幕的宽高;
  2. 子元素尺寸固定,宽高为67dp * 36.dp;
  3. 子元素间的横向和竖向间距固定为5dp;

构建基本布局

在构建基本布局这一部分,我们先完成子元素在父布局中的基本展示,左右居中效果,后续步骤再分别添加上下居中效果和竖直滑动能力。

我们先向布局中添加一些子元素,子元素的宽高尺寸为固定值,如下:

CustomLayout() { //CustomLayout为我们要实现的自定义布局
  for (i in 1..100) {
    Box(
      modifier = Modifier
      .size(67.dp, 36.dp)
      .background(
        color = Color(0xFFFF6633),
        shape = RoundedCornerShape(2.dp)
      ), contentAlignment = Alignment.Center
    ) {
      Text(text = "10${i}", fontSize = 16.sp, color = Color.White)
    }
  }
}
1. 测量子元素的尺寸

在Layout函数中,根据父布局提供的Constraints约束条件,测量子元素,生成placeable,获取子元素的尺寸

@Composable
fun CustomLayout(modifier: Modifier = Modifier,content: @Composable () -> Unit) {
    Layout(content = content, modifier = modifier) { measurables, constraints ->

    val placeables = measurables.mapIndexed { index, measurable ->
        // 测量子元素的尺寸
        val placeable = measurable.measure(
            constraints
        )

        placeable
    }
}
2. 计算

接下来,我们需要通过获取到的子元素尺寸,计算每行子元素的总宽度、左右两侧边距、子元素的总高度、每个子元素在父布局中的位置。

由于子元素尺寸、间距固定,我们可以先计算出每行可以容纳的子元素个数,然后根据子元素宽度、元素间距计算得到每行子元素的总宽度;再通过子元素的总数量计算出子元素的总行数,通过子元素总行数、子元素高度,竖直方向元素间距可计算得到子元素内容的总高度。

布局示意图,水平居中即左右两侧边距相等

1)计算每行子元素数量

用变量columns记录每行可以容纳的子元素个数,rowWidth初始为布局的最大宽度,当rowWidth大于等于子元素的宽度childWidth时,说明当前行还可继续容纳子元素,columns加1,rowWidth减去( childWidth + 间距space ),即为剩余的可用宽度,通过while循环计算,直到rowWidth小于childWidth,说明此时宽度已不够放下子元素。

var columns = 0

var rowWidth = constraints.maxWidth
val childWidth = placeable.width
val childHeight = placeable.height

// 根据父元素、子元素的宽度以及子元素间的间距,计算每行可以显示的子元素数量
while (rowWidth >= childWidth) {
  rowWidth -= (childWidth + space)
  columns++
}
2) 计算每行子元素总宽度、左右间距

有了每行子元素的数量后,就可以根据子元素宽度,间距计算出子元素的总宽度,然后再计算出左右两侧的边距,水平方向居中即左右边距相等,为父布局最大宽度减去子元素总宽度后剩余宽度的一半:

//每行子元素占据的总宽度,包括子元素间间距
val lineWidth = columns * childWidth + (columns - 1) * space
//计算左右两侧边距,为最大宽度减去子元素总宽度剩余宽度的一半
edgeStart = (constraints.maxWidth - lineWidth) / 2
3)然后,再通过子元素的总数量、每行的子元素数量,计算出总行数rows,有了总行数后即可计算出子元素的总高度。
//计算总行数
rows = (measurables.size + columns - 1) / columns
// 计算子元素的总高度
contentHeight = rows * childHeight + (rows - 1) * space
4)拿到行数、列数后,就可以计算出每个元素的在父布局中的坐标位置
//两个二维数组,分别存放 * 行 * 列元素的坐标位置
var childX = Array(rows) { IntArray(columns) } //子元素 X 方向的位置
var childY = Array(rows) { IntArray(columns) } //子元素 Y 方向的位置

//当前元素是第几行,第几列,index为元素索引
val row = index / columns
val column = index % columns

//计算元素位置坐标
childX[row][column] = column * (placeable.width + space) + edgeStart
childY[row][column] = row * (placeable.height + space)
3. 设置布局尺寸,放置子元素

最后,调用layout方法,设置布局尺寸,我们这里使用布局的最大宽度和最大高度,然后遍历测量生成的placeables来放置子元素。

//布局的宽高默认为约束的最大宽高,这里即为屏幕的宽高
val layoutWidth = constraints.maxWidth
val layoutHeight = constraints.maxHeight

layout(layoutWidth, layoutHeight) {
    placeables.forEachIndexed { index, placeable ->
        //当前是第几行,第几列
        val row = index / columns
        val column = index % columns
        //放置元素
        placeable.placeRelative(
            x = childX[row][column],
            y = childY[row][column],
        )
    }
}

到这里,基本的布局就完成了,效果如下

实现竖直方向内容居中效果

完成了基本布局后,我们再来实现竖直方向内容居中的效果。和水平居中一样,竖直居中即上下两侧边距相等。

在上面计算过程中,我们获取了子元素内容的总高度,结合父布局的最大高度即可计算出上下侧的边距。

需要注意的是:只有当布局内容的高度小于布局的最大高度时,我们才来设置竖直居中。

 //top 上侧的边距, 当子元素的高度超过父容器高度时为0;子元素的高度小于父布局高度时进行计算(父容器高度-子元素总高度)/ 2
 var edgeTop = 0
if (contentHeight < layoutHeight) {
    edgeTop = (layoutHeight - contentHeight) / 2
}

有了上侧边距后,在对子元素布局时,y坐标加上侧的间距,即可实现内容上下居中

layout(layoutWidth, layoutHeight) {
    placeables.forEachIndexed { index, placeable ->
        //当前是第几行,第几列
        val row = index / columns
        val column = index % columns
        placeable.place(
            x = childX[row][column],
            y = childY[row][column] + edgeTop,
        )
    }
}

效果如图

这里我们需要注意,当我们给父元素添加height()或fillMaxSize(), fillMaxHeight()尺寸修饰符后,子元素的尺寸测量会出现异常。

CustomLayout(modifier = Modifier
    .background(Color.Yellow)
    .padding(12.dp)
    .fillMaxSize() //设置布局尺寸,占满所有可用空间,即和屏幕宽高一致
) {
    for (i in 1..20) {
        Box(
            modifier = Modifier
                .size(67.dp, 36.dp)
                .background(
                    color = Color.Green,
                    shape = RoundedCornerShape(2.dp)
                ), contentAlignment = Alignment.Center
        ) {
            Text(text = "10${i}", fontSize = 16.sp, color = Color.Black)
        }
    }
}

添加fillMaxSize修饰符后的效果是这样的,单个子元素占满了整个布局。

出现上述问题的原因是:父布局传入的Constraints约束条件发生了变化。

打印日志,可以看到,在添加尺寸修饰符后,约束条件为:

//添加尺寸条件后的约束
Constraints(minWidth = 1146, maxWidth = 1146, minHeight = 1620, maxHeight = 1620)

而无尺寸修饰符时的约束为:

//没有尺寸条件时的约束
Constraints(minWidth = 0, maxWidth = 1146, minHeight = 0, maxHeight = 2381)

对比发现,设置尺寸修饰后,约束条件发生了变化,minWidth和maxWidth、minHeight和maxHeight分别为同一个值。

子元素在测量时,由于约束条件中宽、高最小值,最大值为固定值,限定了子元素的宽高为约束的宽高,测量后的尺寸不再是我们期望的值。

此时,我们需要重新创建子元素的约束条件:

//设置约束最小宽度和最小高度为0
val childConstraints = Constraints(0, constraints.maxWidth, 0, constraints.maxWidth)

使用重新定义的约束条件进行测量,子元素的测量尺寸恢复正常。

// 使用新的约束条件测量子元素的尺寸
val placeable = measurable.measure(childConstraints)

添加竖直方向滚动能力

最后,给布局添加竖直方向的滚动能力,需要明确的是:竖直方向可滑动的前提条件是布局内容的高度大于布局的最大高度。

Compose给我们提供的verticalScrollhorizontalScroll 修饰符提供了一种最简单的方法,可让用户在元素内容边界大于最大尺寸约束时滚动元素。

我们使用verticalScroll修饰符来给布局添加竖直滚动能力。

CustomLayout(modifier = Modifier
    .background(Color.Gray)
    .fillMaxSize()
    .padding(12.dp)
    .verticalScroll(rememberScrollState()) //添加滚动修饰符
) {
    for (i in 1..100) { // 增加子元素的数量,使内容高度超过布局的高度
        Box(
            modifier = Modifier
                .size(67.dp, 36.dp)
                .background(
                    color = Color.Green,
                    shape = RoundedCornerShape(2.dp)
                ), contentAlignment = Alignment.Center
        ) {
            Text(text = "10${i}", fontSize = 16.sp, color = Color.Black)
        }
    }
}

然而,添加verticalScroll后,屏幕中没有展示出内容。

给父布局添加背景色后,可以看到屏幕中显示的仍然是布局的一部分。

我们来看下此时的约束条件,最大高度maxHeight为Infinity(无限大),我们上边距的计算方式为(maxHeight - contentHeight)/2,此时计算得到的上边距近似为Infinity/2(无限大),且我们在layout布局方法中传入的高度参数也为maxHeight,即我们给布局设置的布局内容高度为Infinity(无限大),此时屏幕中显示的是布局的上边距部分。

//最大高度约束为Infinity(无限大)
Constraints(minWidth = 1146, maxWidth = 1146, minHeight = 2381, maxHeight = Infinity)

val layoutWidth = constraints.maxWidth
val layoutHeight = constraints.maxHeight

//上边距为无限大的一半,即无限大
if (contentHeight < layoutHeight) {
    edgeTop = (layoutHeight - contentHeight) / 2
}

//layout布局高度为无限大
layout(layoutWidth, layoutHeight);

重新确定布局高度计算方式,布局高度取子元素总高度与约束最小高度的最大值

 val layoutHeight = max(contentHeight, constraints.minHeight)

最终效果如图

到这里,我们已经完成了一个简单自定义布局的实现。当然,后续我们可以继续来优化布局方式,如:通过定义参数来控制子元素的对齐方式,子元素的水平、竖直方向间距等。

小结

Compose使用Layout可组合函数实现自定义布局,整体流程和View中自定义布局大致相同,都需要两个主要步骤:

  1. 测量子元素在父布局约束下的大小;
  2. 在父布局中放置子元素;

需要注意的是:

  • Compose只允许测量一次,对某个元素进行多次测量,会抛出异常;
  • 父布局的约束条件会随修饰符的不同而变化,测量子元素时根据需要创建合适的约束条件;
  • layout(小写开头)方法设置布局尺寸时要选用正确的宽度和高度;

完整代码如下:

@Composable
fun CustomScreen() {
    Surface(
        color = MaterialTheme.colors.background
    ) {
        CustomLayout(modifier = Modifier
            .background(Color.Gray)
            .fillMaxSize()
            .padding(12.dp)
            .verticalScroll(rememberScrollState())
        ) {
            for (i in 1..100) {
                Box(
                    modifier = Modifier
                        .size(67.dp, 36.dp)
                        .background(
                            color = Color(0xFFFF6633),
                            shape = RoundedCornerShape(2.dp)
                        ), contentAlignment = Alignment.Center
                ) {
                    Text(text = "10${i}", fontSize = 16.sp, color = Color.White)
                }
            }
        }
    }
}


@Composable
fun CustomLayout(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
    Layout(content = content, modifier = modifier) { measurables, constraints ->
        //每行的高度是固定的
        //每个块的宽、高是固定的
        //内容总高度小于容器高度时居中,大于总高度时,可以向下滑动
        //从上向下布局

        println("约束条件:$constraints")

        //总行数
        var rows = 0
        //总列数-每行最多显示的子元素数量
        var columns = 0
        //start方向的padding
        var edgeStart = 0

        //top 方向的间距, 当子元素的高度超过父容器高度时为0;子元素的高度小于父容器高度时进行计算(父容器高度-子元素总高度)/ 2
        var edgeTop = 0

        // 子元素的总高度,包括子元素自身高度和子元素之间的间距
        var contentHeight = 0

        var isCalculated = false

        val space = 5.dp.roundToPx() // 水平和竖直方向间距固定为5dp

        var childX = Array(rows) { IntArray(columns) } //子元素 X 方向的位置
        var childY = Array(rows) { IntArray(columns) } //子元素 Y 方向的位置

        //重新创建子控件的约束
        //当父布局设置高度时,默认约束最小高度和最大高度相同,为设置的高度
        //直接使用默认约束条件会导致测量出来的子控件高度与父控件一样
        val childConstraints = Constraints(0, constraints.maxWidth, 0, constraints.maxWidth)
        val placeables = measurables.mapIndexed { index, measurable ->
            // 测量子元素的尺寸
            val placeable = measurable.measure(
                childConstraints
            )

            if (!isCalculated) {
                isCalculated = true

                var rowWidth = constraints.maxWidth
                val childWidth = placeable.width
                val childHeight = placeable.height

                // 根据父元素、子元素的宽度以及子元素间的间距,计算每行可以显示的子元素数量
                while (rowWidth > childWidth) {
                    rowWidth -= (childWidth + space)
                    columns++
                }
                //一行子元素占据的总宽度,包括子元素间间距
                val lineWidth = columns * childWidth + (columns - 1) * space
                //计算左右两侧的间距
                edgeStart =
                    (constraints.maxWidth - lineWidth) / 2

                rows = (measurables.size + columns - 1) / columns


                // 计算子元素的总高度
                contentHeight = rows * childHeight + (rows - 1) * space

                //有了行数,列数后重新构造二维数组
                childX = Array(rows) { IntArray(columns) { 0 } }
                childY = Array(rows) { IntArray(columns) { 0 } }
            }

            //当前是第几行,第几列
            val row = index / columns
            val column = index % columns

            childX[row][column] = column * (placeable.width + space) + edgeStart
            childY[row][column] = row * (placeable.height + space)

            placeable
        }

        val layoutWidth = constraints.maxWidth
        var layoutHeight = max(contentHeight, constraints.minHeight)
//         layoutHeight = constraints.maxHeight

        if (contentHeight < layoutHeight) {
            edgeTop = (layoutHeight - contentHeight) / 2
        }

        layout(layoutWidth, layoutHeight) {
            placeables.forEachIndexed { index, placeable ->
                //当前是第几行,第几列
                val row = index / columns
                val column = index % columns
                val x = childX[row][column]
                val y = childY[row][column] + edgeTop

                placeable.place(
                    x,
                    y,
                )
            }
        }
    }
}

https://mp.weixin.qq.com/s/_K5y35bq2aNEfTocE5ZnLg

 相关推荐

集体大降薪?有员工吐槽:再降要去公园卖鱼

6月5日,一张券商降薪截图在社交媒体疯传。截图提到,当日上午,某中字头头部券商召开大会,除了MD外全员降薪,且降薪不只是降奖金,而是直接降底薪。按照职级不同,SA1降6K,SA3降8K,VP降8K—10K。据了解,降薪大概率整体属实,但具体幅度有所差异,且不同区域、不同业务条线目前掌握的降薪情况也不尽相同。

发布于:3天以前  |  1674次阅读  |  详细内容 »

或搭载骁龙 8 Gen2,李斌透露蔚来手机新进展

今日,蔚来 CEO 李斌在 2023 高通汽车技术与合作峰会上爆料,蔚来第二代技术平台的全系车型已标配第三代骁龙座舱平台。

发布于:14天以前  |  679次阅读  |  详细内容 »

Meta AI大模型能识别4000多种语言,称错误率仅有OpenAI产品的一半

Meta公司周一(5月22日)推出了一个开源AI语言模型——大规模多语言语音(Massively Multilingual Speech, MMS)模型,可以识别和产生1000多种语言的语音——比目前可用的模型增加了10倍。研究人员表示,他们的模型可以转换1000多种语言,但能识别4000多种语言。

发布于:15天以前  |  627次阅读  |  详细内容 »

“AI孙燕姿”火遍全网!孙燕姿发文回应:人类无法超越AI,你是可定制的

歌手孙燕姿在更新动态中回应了近日引发争议的“顶流AI歌手孙燕姿”,笑称粉丝已经接受她是“冷门”歌手,而AI成为了目前的顶流。

发布于:15天以前  |  596次阅读  |  详细内容 »

荣耀回应新设公司自研芯片传言:重点在终端侧核心软件、图形算法等研发

5月31日晚,荣耀方面对澎湃新闻记者表示,上海荣耀智能科技开发有限公司是荣耀位于上海的研究所,是荣耀在中国的5个研究中心之一,重点方向在终端侧核心软件、图形算法、通信、拍照等方面研究开发工作。荣耀强调,坚持以用户为中心,开放创新,与全球合作伙伴一起为用户提供最佳产品解决方案。

发布于:9天以前  |  320次阅读  |  详细内容 »

宣称“M1芯片速度最快”,苹果被罚20万元:M1 Pro和Max更快

据北京市市场监督管理局公示信息,5月24日,苹果电子产品商贸(北京)有限公司因发布虚假广告被北京市东城区市场监督管理局处以20万元的行政处罚。

发布于:8天以前  |  235次阅读  |  详细内容 »

因PC销售不景气,联想Q1裁员约5%

据外媒5月24日消息,全球最大的个人电脑制造商联想表示,在2023年1-3月期间,该公司裁员了约5%,这是由于PC市场不景气导致的。

发布于:13天以前  |  217次阅读  |  详细内容 »

博主发布“史上最清晰”小米汽车谍照:猎跑风格,体积“特别大”

日前,有网络博主号称拍摄到了小米首款汽车MS11的高清视频。从视频中可以看出,新车依旧包裹大面积的伪装,据该博主称,他之所以确定这是小米汽车,是因为靠近观察之后,发现它的三角形大灯轮廓和其最初手绘的小米汽车假想图几乎一模一样。

发布于:8天以前  |  215次阅读  |  详细内容 »

ChatGPT 之父警告:AI 可能灭绝人类,350 名 AI 权威签署联名公开信

超过 350 名从事人工智能工作的高管、研究人员和工程师签署了这份由非盈利组织人工智能安全中心发布的公开信,认为人工智能具备可能导致人类灭绝的风险,应当将其视为与流行病和核战争同等的社会风险。

发布于:10天以前  |  207次阅读  |  详细内容 »

错失英伟达后,木头姐预测:AI的下一波机会在软件

日前,以押注“颠覆性创新”著称的ARK Invest创始人Cathie Wood在接受媒体采访时表示,软件提供商将是人工智能狂潮的下一个受益板块。英伟达每卖出1美元的硬件,软件供应商SaaS供应商就会产生8美元的收入。

发布于:8天以前  |  204次阅读  |  详细内容 »

小米投资恩井汽车科技公司

小米产投管理合伙人孙昌旭对此表示,小米产投将充分运用产业资源,与恩井科技形成高度业务协同,助力公司实现跨越式发展。

发布于:11天以前  |  192次阅读  |  详细内容 »

阿里云首席安全科学家吴翰清离职,投身AI短视频创业

据报道,阿里巴巴研究员吴翰清已于近期离职,钉钉显示其离职时间是5月19日。在阿里内部,研究员的职级为P10。据消息人士透露,吴翰清离职后,选择AI短视频赛道创业,已经close一轮融资。对于上述消息,截至发稿,阿里尚未回应。

发布于:13天以前  |  190次阅读  |  详细内容 »

阿里巴巴否认裁员传言,今年预估新招15000人

阿里巴巴集团官微宣布,2023年六大业务集团总计需新招15000人,其中校招超过3000人。同时表示,“近日,关于淘宝天猫、阿里云、菜鸟、本地生活各个业务裁员谣言传得很厉害,但谣言就是谣言。我们的招聘正在紧锣密鼓的进行。”

发布于:13天以前  |  184次阅读  |  详细内容 »

李开复:AI2.0带来的市场机遇会比移动互联网大10倍

“现今每一个存在的应用都将被AI 2.0重构,我觉得整个AI大模型带来的机遇和技术浪潮,会比过去Windows和安卓大10倍。”李开复表示。

发布于:10天以前  |  177次阅读  |  详细内容 »

一文读懂苹果WWDC大会:头显Vision Pro正式发布,售价高达2.5万元

苹果发布Vision Pro头显,正式宣布开启空间计算时代;苹果还发布新款MacBook Air,新款Mac Studio,并展示了iOS17、iPadOS 17、macOS Sonoma和watchOS10等新系统;Vision Pro头显售价3499美元,将于2024年初正式在美国市场发售;华尔街并不看好Vision Pro,苹果股价周一创历史新高后由涨转跌。

发布于:3天以前  |  175次阅读  |  详细内容 »

车圈“地震”:长城汽车实名举报比亚迪,比亚迪强势回应

5月25日,长城汽车就比亚迪秦PLUS DM-i、宋PLUS DM-i采用常压油箱,涉嫌整车蒸发污染物排放不达标的问题进行举报。

发布于:14天以前  |  159次阅读  |  详细内容 »

贾跃亭开抖音号,IP 在美国,粉丝数量53.7万,关注前妻甘薇

近日,一个名为“贾跃亭”的抖音账号悄然出现,带有“FF创始人、合伙人、首席产品及用户生态官, LeEco 乐视创始人”等标签,IP 地址显示为美国。

发布于:10天以前  |  148次阅读  |  详细内容 »

英伟达史诗级暴涨后再放大招!推出E级AI超算,黄仁勋狂捧生成式AI

5月29日消息,继上周远超预期的财报业绩预测引得股价和市值史诗级暴涨后,今日,英伟达(NVIDIA)创始人兼CEO黄仁勋穿着标志性的皮衣,意气风发地出现在台北电脑展COMPUTEX 2023上,在主题演讲期间先是现场给自家显卡带货,然后一连公布涉及加速计算和人工智能(AI)的多项进展。

发布于:10天以前  |  146次阅读  |  详细内容 »

苹果官方:618将在天猫开启全球首次直播

近日,苹果位于天猫的Apple Store官方旗舰店挂出直播预告,表示将在5月31日晚19时开启官方直播,这也是苹果官方在电商平台的全球首次直播。

发布于:10天以前  |  145次阅读  |  详细内容 »

前京东集团副总裁梅涛成立生成式 AI 公司,投身多模态领域

前京东集团副总裁、京东探索研究院副院长梅涛自今年初离职后,确认在 AI 领域创业,成立生成式 AI 公司 HiDream.ai。

发布于:8天以前  |  144次阅读  |  详细内容 »
 相关文章
 目录