蒋义刚 發表於 2026-2-4 10:12:00

鸿蒙开发实战:玩转“智感握姿”——新闻列表左右手智能切换

<p>大家好,我是V哥。</p>
<p>你有没有遇到过这种情况:</p>
<blockquote>
<p>左手拿着奶茶,右手刷新闻,结果头图永远在右边,点都点不到?</p>
</blockquote>
<p>现在好了,系统能实时感知你是左手还是右手握持,UI 自动适配!这才是真正的“懂你”!</p>
<p>今天 V 哥就用一个新闻列表页面,带你 10 分钟搞定智感握姿的完整开发!能根据你拿手机的姿势,自动把图片和文字互换位置。代码全在一个页面,复制进去就能跑,绝对硬核!</p>
<h2 id="技术原理手机怎么知道那是你的左手">技术原理:手机怎么知道那是你的左手?</h2>
<p>其实很简单。你想想,当你用<strong>右手</strong>单手握持手机时,为了让大拇指够到屏幕左侧,手机通常会不由自主地向<strong>左倾斜</strong>一点点(或者向右倾斜,看个人习惯,通常我们设定一个倾斜阈值)。</p>
<p>咱们利用鸿蒙的 <code>@ohos.sensor</code>(传感器能力),监听重力变化。</p>
<ul>
<li>当检测到手机向左倾斜(X轴重力分量变化),判定为左手或左侧模式。</li>
<li>当检测到手机向右倾斜,判定为右手或右侧模式。</li>
</ul>
<p>话不多说,直接上干货。</p>
<h2 id="实战代码智感握姿新闻列表">实战代码:智感握姿新闻列表</h2>
<p>先看一下 V 哥写的案例截图:</p>
<p>左手模式:</p>
<p><img src="https://img2024.cnblogs.com/blog/2860285/202602/2860285-20260204101043168-879542334.jpg"></p>
<p>右手模式:</p>
<p><img src="https://img2024.cnblogs.com/blog/2860285/202602/2860285-20260204101032750-655638683.jpg"></p>
<p>准备好你的 DevEco Studio,新建一个 ArkTS 页面,把下面的代码全选、复制、粘贴进去。</p>
<h3 id="完整代码案例">完整代码案例</h3>
<pre><code class="language-typescript">import sensor from '@ohos.sensor';
import promptAction from '@ohos.promptAction';

// 1. 定义新闻数据模型
class NewsItem {
id: number;
title: string;
summary: string;
imageColor: Color; // 用颜色块代替图片,方便测试,不用找资源

constructor(id: number, title: string, summary: string, color: Color) {
    this.id = id;
    this.title = title;
    this.summary = summary;
    this.imageColor = color;
}
}

@Entry
@Component
struct SmartGripNewsPage {
// 2. 状态变量
// isRightMode: true 代表右手模式(图在右),false 代表左手模式(图在左)
@State isRightMode: boolean = true;
// 记录当前的倾斜角度X值,用于显示调试信息
@State currentGravityX: number = 0;

// 模拟新闻数据
@State newsList: NewsItem[] = [
    new NewsItem(1, "鸿蒙Next正式发布", "纯血鸿蒙不再兼容安卓,开启移动操作系统新纪元。", Color.Blue),
    new NewsItem(2, "V哥聊技术", "深度解析ArkTS语言特性,带你弯道超车。", Color.Red),
    new NewsItem(3, "2026行业展望", "AI赛道爆发,普通程序员如何抓住最后的机会?", Color.Green),
    new NewsItem(4, "SpaceX星舰发射", "马斯克火星殖民计划又近了一步,震撼全人类。", Color.Orange),
    new NewsItem(5, "周末去哪儿玩", "发现城市周边的小众露营地,放松身心好去处。", Color.Pink),
];

// 3. 页面加载时开启传感器监听
aboutToAppear() {
    this.startSensor();
}

// 4. 页面销毁时关闭传感器,省电
aboutToDisappear() {
    this.stopSensor();
}

// 开启传感器逻辑
startSensor() {
    try {
      // 监听重力传感器,频率设置为 UI (适合UI交互的频率)
      sensor.on(sensor.SensorId.GRAVITY, (data) =&gt; {
      // data.x 代表 x 轴的重力分量
      // 当手机竖屏面对你:
      // 手机向右倾斜,x &gt; 0
      // 手机向左倾斜,x &lt; 0
      
      this.currentGravityX = data.x;

      // 设置一个阈值,防止轻微抖动就切换
      // 这里设置 1.5 为阈值,你可以根据手感调整
      if (data.x &gt; 1.5) {
          // 向右倾斜,认为是右手握持或者想看右边
          if (this.isRightMode === false) {
            this.isRightMode = true;
            this.showToast("智感切换:右手模式");
          }
      } else if (data.x &lt; -1.5) {
          // 向左倾斜,认为是左手握持
          if (this.isRightMode === true) {
            this.isRightMode = false;
            this.showToast("智感切换:左手模式");
          }
      }
      }, { interval: 100000000 }); // 100ms 一次回调
    } catch (err) {
      console.error("V哥提示:传感器启动失败,可能是模拟器不支持", err);
    }
}

// 关闭传感器
stopSensor() {
    try {
      sensor.off(sensor.SensorId.GRAVITY);
    } catch (err) {
      console.error("V哥提示:传感器关闭失败", err);
    }
}

// 小提示弹窗
showToast(msg: string) {
    promptAction.showToast({
      message: msg,
      duration: 1500,
      bottom: 100
    });
}

build() {
    Column() {
      // 顶部标题栏
      Row() {
      Text("智感新闻")
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
      Blank()
      // 显示当前模式状态
      Text(this.isRightMode ? "当前:右手模式" : "当前:左手模式")
          .fontSize(14)
          .fontColor(Color.Gray)
      }
      .width('100%')
      .padding(20)
      .height(60)
      .backgroundColor('#F1F3F5')

      // 调试信息(正式上线可以去掉)
      Text(`重力X轴感应值: ${this.currentGravityX.toFixed(2)}`)
      .fontSize(12)
      .fontColor(Color.Gray)
      .margin({ bottom: 10 })

      // 新闻列表
      List({ space: 15 }) {
      ForEach(this.newsList, (item: NewsItem) =&gt; {
          ListItem() {
            // 核心布局:根据 isRightMode 决定布局方向
            // Direction.Ltr (Left to Right) 或者是 Rtl
            // 这里我们用 Flex 或者 Row 手动控制顺序更稳
            this.NewsItemBuilder(item)
          }
      })
      }
      .width('100%')
      .layoutWeight(1) // 占满剩余空间
      .padding({ left: 15, right: 15 })
    }
    .width('100%')
    .height('100%')
}

// 自定义构建函数,处理单个新闻的布局
@Builder
NewsItemBuilder(item: NewsItem) {
    Row() {
      // 这里的逻辑:
      // 如果是左手模式(isRightMode=false),图片在左,文字在右
      // 如果是右手模式(isRightMode=true),文字在左,图片在右
      // 利用 Row 的 direction 属性或者简单的 if/else 渲染顺序

      if (!this.isRightMode) {
      // 左手模式:图 -&gt; 文
      this.ImageBlock(item.imageColor)
      this.TextBlock(item)
      } else {
      // 右手模式:文 -&gt; 图
      this.TextBlock(item)
      this.ImageBlock(item.imageColor)
      }
    }
    .width('100%')
    .height(100)
    .backgroundColor(Color.White)
    .borderRadius(10)
    .shadow({ radius: 5, color: 0x1F000000, offsetY: 2 })
    .padding(10)
    // 添加一个顺滑的动画效果
    .animation({
      duration: 300,
      curve: Curve.EaseInOut
    })
}

// 抽取图片组件
@Builder
ImageBlock(color: Color) {
    // 模拟图片
    Stack() {
      Text("头图")
      .fontColor(Color.White)
      .fontSize(12)
    }
    .width(100)
    .height('100%')
    .backgroundColor(color)
    .borderRadius(8)
    .margin(this.isRightMode ? { left: 10 } : { right: 10 }) // 根据位置给间距
}

// 抽取文字组件
@Builder
TextBlock(item: NewsItem) {
    Column() {
      Text(item.title)
      .fontSize(16)
      .fontWeight(FontWeight.Bold)
      .maxLines(1)
      .textOverflow({ overflow: TextOverflow.Ellipsis })
      .width('100%')
      
      Text(item.summary)
      .fontSize(14)
      .fontColor(Color.Gray)
      .maxLines(2)
      .textOverflow({ overflow: TextOverflow.Ellipsis })
      .margin({ top: 5 })
      .width('100%')
    }
    .layoutWeight(1) // 占满剩余宽度
    .height('100%')
    .justifyContent(FlexAlign.Start)
    .alignItems(HorizontalAlign.Start)
}
}
</code></pre>
<h3 id="代码深度解析v哥掰碎了讲">代码深度解析(V哥掰碎了讲)</h3>
<p>兄弟们,代码贴完了,V哥给你捋一捋这里的核心门道,面试或者做项目的时候都能吹一波。</p>
<p><strong>1. 传感器监听 (<code>sensor.on</code>)</strong><br>
这是整个功能的灵魂。我们用了 <code>sensor.SensorId.GRAVITY</code>。</p>
<ul>
<li><code>data.x</code> 是关键。当你拿着手机往左歪(像是左手拿着手机想看左边屏幕)时,X轴会变负数;往右歪时,X轴变正数。</li>
<li>这里我加了个<strong>阈值 1.5</strong>。为啥?如果不加阈值,你的手稍微抖一下,界面就左右乱跳,用户得气死。1.5 是个经验值,大约倾斜 15-20 度左右触发,既灵敏又不会误触。</li>
</ul>
<p><strong>2. 状态驱动 UI (<code>@State isRightMode</code>)</strong><br>
鸿蒙 ArkUI 的精髓就是<strong>状态驱动</strong>。</p>
<ul>
<li>我们不需要去手动搬运组件。只要改变 <code>isRightMode</code> 这个布尔值,UI 就会自动刷新。</li>
<li>配合 <code>.animation</code> 属性,当组件位置互换时,不会生硬地“闪现”,而是会有一个滑动的过渡效果,高级感立马就来了。</li>
</ul>
<p><strong>3. 条件渲染 (<code>if/else</code>)</strong><br>
在 <code>NewsItemBuilder</code> 里,V哥用了一个最笨但最有效的方法:</p>
<ul>
<li>如果是左手模式:先渲染图片组件,再渲染文字组件。</li>
<li>如果是右手模式:先渲染文字组件,再渲染图片组件。</li>
<li>因为是在 <code>Row</code> 容器里,渲染顺序直接决定了谁在左谁在右。</li>
</ul>
<h3 id="怎么测试">怎么测试?</h3>
<ol>
<li><strong>真机测试(推荐)</strong>:把代码烧录到鸿蒙手机上。拿着手机向左倾斜一下,你会发现图片“刷”一下跑到左边了;向右倾斜一下,图片又跑回右边了。</li>
<li><strong>模拟器测试</strong>:DevEco Studio 的模拟器通常有个“虚拟传感器”面板。你可以手动拖动重力传感器的 X 轴滑块,模拟手机倾斜,看界面会不会变。</li>
</ol>
<h3 id="v哥的最后唠叨">V哥的最后唠叨</h3>
<p>兄弟们,这个功能虽然代码不多,但体现的是<strong>以人为本</strong>的设计思维。</p>
<p>这就是鸿蒙 Next 开发好玩的地方,硬件能力调用极其简单。2026年,不管是做应用还是做系统,<strong>交互体验</strong>永远是核心竞争力。</p>
<p>赶紧把这代码跑起来,以后老板让你做“适老化”或者“单手模式”,你把这个 Demo 一亮,绝对惊艳全场!祝大家发码愉快,没有 Bug!</p>


</div>
<div id="MySignature" role="contentinfo">
    <p>本文来自博客园,作者:威哥爱编程,转载请注明原文链接:https://www.cnblogs.com/finally-vince/p/19572778</p><br><br>
来源:https://www.cnblogs.com/finally-vince/p/19572778
頁: [1]
查看完整版本: 鸿蒙开发实战:玩转“智感握姿”——新闻列表左右手智能切换