多平台屏幕江湖生存指南

UniApp 屏幕适配大师:多平台屏幕江湖生存指南

屏幕江湖:尺寸混战

屏幕适配就像是应对不同体型的客人:从迷你的手机屏,到标准的平板,再到巨大的电视屏幕,你的应用必须有如武林高手般的适应力。

┌──────────────────────────────────────────────────────┐
│           【屏幕适配挑战的本质】                      │
├──────────────┬───────────────────────────────────────┤
│ 像素密度不同 │ iPhone 13 Pro: 460ppi vs 普通安卓: 300ppi │
├──────────────┼───────────────────────────────────────┤
│ 屏幕比例不同 │ 16:9, 18:9, 4:3, 甚至折叠屏的动态比例 │
├──────────────┼───────────────────────────────────────┤
│ 渲染差异    │ 小程序、H5、原生App的渲染机制各不相同 │
├──────────────┼───────────────────────────────────────┤
│ 操作模式不同 │ 触摸屏vs鼠标操作,交互尺寸需求不同    │
└──────────────┴───────────────────────────────────────┘

生活类比:多功能服装

想象UniApp的屏幕适配系统是一件神奇的”变形衣”:

基础款式(核心布局)保持不变
面对高个子(长屏幕)时,自动拉长
面对胖人(宽屏幕)时,自动变宽
面对特殊体型(奇怪比例)时,智能调整关键部位

UniApp的屏幕适配三大武功

一、【像素统一】:rpx单位

rpx是UniApp的独门武功,以750像素宽度为基准动态计算:

/* rpx将自动在不同屏幕上缩放 */
.container {
            
  width: 700rpx;    /* 接近全屏宽度 */
  height: 300rpx;   /* 高度也会等比例缩放 */
  padding: 20rpx;   /* 内边距同样会缩放 */
  border-radius: 10rpx; /* 圆角也自动适应 */
}
生活类比:魔法裁缝

rpx就像是一位魔法裁缝,给他一套按标准人体(750px宽)设计的服装样式:

对于”瘦小”的手机,他会等比例缩小每个尺寸
对于”高大”的平板,他会等比例放大每个尺寸
裁缝不需要知道具体尺寸,只需按比例工作

┌──────────────────────────────────────────────────────┐
│        【rpx单位计算流程图】                         │
│                                                      │
│  ┌───────────┐      ┌───────────────┐               │
│  │设计稿尺寸 │      │实际设备宽度   │               │
│  │(通常750px)│      │(如375px)      │               │
│  └─────┬─────┘      └───────┬───────┘               │
│        │                    │                        │
│        ▼                    ▼                        │
│  ┌───────────────────────────────────┐               │
│  │         计算比例因子              │               │
│  │   实际设备宽度/设计稿宽度=0.5     │               │
│  └──────────────────┬────────────────┘               │
│                     │                                │
│                     ▼                                │
│  ┌───────────────────────────────────┐               │
│  │          rpx到px的转换            │               │
│  │ 100rpx × 0.5 = 50px (在该设备上)  │               │
│  └───────────────────────────────────┘               │
│                                                      │
└──────────────────────────────────────────────────────┘

二、【弹性布局】:Flex布局

Flex布局是适配各种屏幕尺寸的绝世神功:

.adaptive-container {
            
  display: flex;
  flex-direction: column; /* 控制主轴方向 */
  justify-content: space-between; /* 主轴对齐方式 */
  align-items: center; /* 交叉轴对齐方式 */
}

.responsive-item {
            
  flex: 1; /* 可伸缩,占用所有可用空间 */
  width: 100%; /* 宽度100% */
  max-width: 600rpx; /* 最大宽度限制 */
}
生活类比:水流智能填充

Flex布局像水一样流动填满容器:

水会自动填满任何形状的容器 (flex元素填满可用空间)
可以控制水流方向 (flex-direction)
可以控制水量分配 (flex-grow, flex-shrink)
可以设置水坝限制 (max-width, min-height)

三、【比例适应】:媒体查询与条件编译

针对特定屏幕尺寸进行定制处理:

/* CSS媒体查询适应不同屏幕 */
.content {
            
  padding: 20rpx;
}

/* 窄屏手机特殊处理 */
@media screen and (max-width: 320px) {
            
  .content {
            
    padding: 10rpx;
  }
}

/* 平板及以上设备优化布局 */
@media screen and (min-width: 768px) {
            
  .content {
            
    padding: 40rpx;
    display: flex;
  }
}

条件编译处理平台差异:

<template>
  <view class="container">
    <!-- #ifdef H5 -->
    <view class="web-optimized-layout">网页优化布局</view>
    <!-- #endif -->
    
    <!-- #ifdef MP-WEIXIN -->
    <view class="wechat-optimized-layout">微信小程序优化布局</view>
    <!-- #endif -->
    
    <!-- #ifdef APP-PLUS -->
    <view class="app-optimized-layout">App优化布局</view>
    <!-- #endif -->
  </view>
</template>
生活类比:智能家具

媒体查询和条件编译像智能家具:

智能家具可以感知房间大小 (媒体查询检测屏幕尺寸)
根据房间类型变形 (条件编译针对不同平台)
小房间时收缩 (小屏幕优化)
大房间时展开更多功能 (大屏幕增强功能)

实战案例:全能型产品卡片

代码示例:自适应产品卡片

<template>
  <view class="product-card">
    <view class="card-media">
      <image class="product-image" :src="product.image">{ product.discount }}%</view>
    </view>
    
    <view class="card-content">
      <text class="product-title">{
           { product.title }}</text>
      
      <!-- 响应式布局:价格区块 -->
      <view class="price-container">
        <text class="current-price">¥{
           { product.price }}</text>
        <text class="original-price" v-if="product.originalPrice">¥{
           { product.originalPrice }}</text>
      </view>
      
      <!-- 平板及桌面端显示更多信息 -->
      <!-- #ifdef H5 || APP-PLUS -->
      <view class="extra-info" v-if="isWideScreen">
        <text class="description">{
           { product.description }}</text>
        <view class="specs">
          <text v-for="(spec, index) in product.specifications" :key="index" class="spec-item">{
           { spec }}</text>
        </view>
      </view>
      <!-- #endif -->
      
      <!-- 响应式操作按钮区 -->
      <view class="action-area">
        <button class="add-to-cart-btn">加入购物车</button>
        
        <!-- 宽屏幕显示更多按钮 -->
        <button class="buy-now-btn" v-if="isWideScreen">立即购买</button>
        
        <!-- 窄屏使用图标按钮节省空间 -->
        <button class="fav-btn" v-if="!isWideScreen">❤</button>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  props: {
    product: {
      type: Object,
      required: true
    }
  },
  data() {
    return {
      screenWidth: 0,
      isWideScreen: false
    }
  },
  created() {
    // 获取设备信息
    const info = uni.getSystemInfoSync();
    this.screenWidth = info.screenWidth;
    
    // 判断是否宽屏
    this.isWideScreen = this.screenWidth >= 480;
    
    // 监听屏幕旋转(主要用于App和H5)
    // #ifdef APP-PLUS || H5
    uni.onWindowResize((res) => {
      this.screenWidth = res.size.windowWidth;
      this.isWideScreen = this.screenWidth >= 480;
    });
    // #endif
  }
}
</script>

<style>
.product-card {
  /* 使用rpx单位自适应不同屏幕 */
  width: 710rpx;
  margin: 20rpx;
  border-radius: 16rpx;
  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
  background-color: #ffffff;
  overflow: hidden;
}

/* 媒体内容区 */
.card-media {
  position: relative;
  width: 100%;
  height: 350rpx;
}

.product-image {
  width: 100%;
  height: 100%;
}

.discount-badge {
  position: absolute;
  top: 20rpx;
  right: 20rpx;
  background-color: #ff4d4f;
  color: white;
  padding: 10rpx 16rpx;
  border-radius: 8rpx;
  font-size: 24rpx;
  font-weight: bold;
}

/* 内容区 */
.card-content {
  padding: 24rpx;
}

.product-title {
  font-size: 32rpx;
  font-weight: bold;
  margin-bottom: 16rpx;
  /* 文本溢出控制 */
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

/* 价格区域 */
.price-container {
  display: flex;
  align-items: baseline;
  margin-bottom: 20rpx;
}

.current-price {
  font-size: 36rpx;
  color: #ff4d4f;
  font-weight: bold;
  margin-right: 12rpx;
}

.original-price {
  font-size: 24rpx;
  color: #999;
  text-decoration: line-through;
}

/* 额外信息区域 - 仅宽屏显示 */
.extra-info {
  margin: 20rpx 0;
}

.description {
  font-size: 26rpx;
  color: #666;
  line-height: 1.5;
  margin-bottom: 12rpx;
}

.specs {
  display: flex;
  flex-wrap: wrap;
  gap: 8rpx;
}

.spec-item {
  font-size: 22rpx;
  color: #333;
  background-color: #f5f5f5;
  padding: 6rpx 12rpx;
  border-radius: 6rpx;
}

/* 操作区域 */
.action-area {
  display: flex;
  justify-content: space-between;
  margin-top: 24rpx;
}

.add-to-cart-btn {
  flex: 1;
  height: 80rpx;
  line-height: 80rpx;
  text-align: center;
  background-color: #1890ff;
  color: white;
  border-radius: 40rpx;
  font-size: 28rpx;
}

.buy-now-btn {
  flex: 1;
  height: 80rpx;
  line-height: 80rpx;
  text-align: center;
  background-color: #ff4d4f;
  color: white;
  border-radius: 40rpx;
  font-size: 28rpx;
  margin-left: 20rpx;
}

.fav-btn {
  width: 80rpx;
  height: 80rpx;
  line-height: 80rpx;
  text-align: center;
  background-color: #f5f5f5;
  color: #ff4d4f;
  border-radius: 40rpx;
  font-size: 32rpx;
  margin-left: 20rpx;
}

/* 媒体查询 - 窄屏优化 */
@media screen and (max-width: 320px) {
  .product-card {
    width: 280px; /* 固定宽度,防止溢出 */
    margin: 10rpx;
  }
  
  .card-media {
    height: 280rpx;
  }
  
  .card-content {
    padding: 16rpx;
  }
  
  .product-title {
    font-size: 28rpx;
  }
  
  .current-price {
    font-size: 32rpx;
  }
}

/* 媒体查询 - 平板及以上设备优化 */
@media screen and (min-width: 768px) {
  .product-card {
    display: flex;
    height: 300rpx;
  }
  
  .card-media {
    width: 300rpx;
    height: 100%;
  }
  
  .card-content {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
  }
  
  .action-area {
    width: 100%;
  }
}
</style>

适配流程图:全场景屏幕适配决策树

┌──────────────────────────────────────────────────────────────────┐
│            【UniApp屏幕适配决策流程图】                          │
│                                                                  │
│ ┌───────────────┐                                                │
│ │设计稿确定     │                                                │
│ │(通常750px宽)  │                                                │
│ └───────┬───────┘                                                │
│         │                                                        │
│         ▼                                                        │
│ ┌───────────────────────────┐                                    │
│ │选择基础适配策略           │                                    │
│ └───────┬───────────┬───────┘                                    │
│         │           │                                            │
│         ▼           ▼                                            │
│ ┌───────────┐  ┌──────────┐                                      │
│ │使用rpx单位│  │使用flex布局│                                   │
│ └───────────┘  └──────────┘                                      │
│                                                                  │
│         ▼                                                        │
│ ┌────────────────────────┐                                       │
│ │处理特殊屏幕和平台      │                                       │
│ └───────┬────────────────┘                                       │
│         │                                                        │
│         ├──────────────────┬──────────────────┐                  │
│         ▼                  ▼                  ▼                  │
│ ┌────────────┐    ┌────────────────┐  ┌─────────────┐           │
│ │媒体查询    │    │JS动态适配      │  │条件编译     │           │
│ │@media      │    │getSystemInfo   │  │平台特定代码  │           │
│ └────────────┘    └────────────────┘  └─────────────┘           │
│                                                                  │
│         ▼                                                        │
│ ┌────────────────────────┐                                       │
│ │测试与优化              │                                       │
│ └───────┬────────────────┘                                       │
│         │                                                        │
│         ├──────────────────┬──────────────────┐                  │
│         ▼                  ▼                  ▼                  │
│ ┌────────────┐    ┌────────────────┐  ┌─────────────┐           │
│ │真机预览    │    │模拟器多设备测试│  │性能优化     │           │
│ └────────────┘    └────────────────┘  └─────────────┘           │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

七大屏幕适配黄金法则

1. 移动优先设计

先适配移动设备,再逐步增强大屏体验。这就像先学会步行,再学会驾车。

// 示例:移动优先的JS适配逻辑
export default {
            
  data() {
            
    return {
            
      isMobile: true, // 默认假设为移动设备
      isTablet: false,
      isDesktop: false,
      layoutMode: 'compact' // 默认使用紧凑布局
    }
  },
  created() {
            
    this.detectDeviceType();
  },
  methods: {
            
    detectDeviceType() {
            
      const systemInfo = uni.getSystemInfoSync();
      const screenWidth = systemInfo.screenWidth;
      
      // 根据屏幕宽度判断设备类型
      if (screenWidth >= 992) {
            
        this.isMobile = false;
        this.isTablet = false;
        this.isDesktop = true;
        this.layoutMode = 'spacious';
      } else if (screenWidth >= 768) {
            
        this.isMobile = false;
        this.isTablet = true;
        this.isDesktop = false;
        this.layoutMode = 'balanced';
      }
      
      // 如果是pad,进一步检测横屏还是竖屏
      // #ifdef APP-PLUS || H5
      if (this.isTablet) {
            
        const isPortrait = systemInfo.screenHeight > systemInfo.screenWidth;
        this.layoutMode = isPortrait ? 'balanced' : 'horizontal';
      }
      // #endif
    }
  }
}

2. 内容优先级排序

小屏幕显示核心内容,大屏幕再显示次要内容,就像行李打包先装必需品。

<template>
  <view class="container">
    <!-- 核心内容:所有屏幕都显示 -->
    <view class="core-content">
      <text class="title">产品名称</text>
      <text class="price">¥199</text>
      <button class="primary-btn">购买</button>
    </view>
    
    <!-- 次要内容:只在中等及以上屏幕显示 -->
    <view class="secondary-content" v-if="screenWidth >= 375">
      <text class="description">产品详细描述文字...</text>
      <view class="rating">★★★★☆ (42条评价)</view>
    </view>
    
    <!-- 增强内容:只在大屏幕显示 -->
    <view class="enhanced-content" v-if="screenWidth >= 768">
      <view class="related-products">
        <text>相关推荐</text>
        <!-- 相关产品列表 -->
      </view>
      <view class="detailed-specs">
        <!-- 详细规格 -->
      </view>
    </view>
  </view>
</template>

3. 图片适配优化

针对不同屏幕提供不同分辨率的图片,就像不同尺寸的电视需要不同清晰度的视频。

<template>
  <view class="image-container">
    <!-- 通过mode属性控制图片缩放和裁剪方式 -->
    <image 
      :src="getImageByScreenDensity()">

4. 灵活的网格系统

使用动态列数的网格布局,像变形积木可以重组成不同形状。

<template>
  <view class="grid-container">
    <view 
      class="grid-item"
      v-for="(item, index) in items"
      :key="index"
      :style="{ width: gridItemWidth }"
    >
      <view class="item-content">
        <image :src="item.image">{ item.title }}</text>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  props: {
    items: {
      type: Array,
      default: () => []
    }
  },
  data() {
    return {
      screenWidth: 375,
      columns: 2 // 默认2列
    }
  },
  computed: {
    gridItemWidth() {
      // 计算每项的宽度(减去间距)
      const gap = 20; // 项目之间的间距
      const containerPadding = 40; // 容器左右内边距总和
      const availableWidth = this.screenWidth - containerPadding;
      const totalGapWidth = gap * (this.columns - 1); // 所有间隙的总宽度
      const itemWidth = (availableWidth - totalGapWidth) / this.columns;
      return itemWidth + 'px';
    }
  },
  created() {
    // 获取屏幕宽度
    const info = uni.getSystemInfoSync();
    this.screenWidth = info.screenWidth;
    
    // 根据屏幕宽度决定列数
    if (this.screenWidth >= 768) {
      this.columns = 4; // 平板显示4列
    } else if (this.screenWidth >= 480) {
      this.columns = 3; // 大手机显示3列
    } else {
      this.columns = 2; // 小手机显示2列
    }
    
    // 监听窗口大小变化(主要用于H5和App)
    // #ifdef H5 || APP-PLUS
    uni.onWindowResize((res) => {
      this.screenWidth = res.size.windowWidth;
      
      // 重新计算列数
      if (this.screenWidth >= 768) {
        this.columns = 4;
      } else if (this.screenWidth >= 480) {
        this.columns = 3;
      } else {
        this.columns = 2;
      }
    });
    // #endif
  }
}
</script>

<style>
.grid-container {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  padding: 20rpx;
}

.grid-item {
  margin-bottom: 20rpx;
}

.item-content {
  border-radius: 12rpx;
  overflow: hidden;
  background-color: #fff;
  box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.1);
}

.item-image {
  width: 100%;
  height: 200rpx;
}

.item-title {
  font-size: 28rpx;
  padding: 16rpx;
  display: block;
}
</style>

5. 横竖屏适应

针对屏幕旋转提供优化布局,就像书本可以竖着看也可以横着看。

<template>
  <view :class="['container', orientation]">
    <!-- 不同方向显示不同布局 -->
    <view v-if="orientation === 'portrait'" class="portrait-layout">
      <!-- 竖屏布局 - 垂直堆叠 -->
      <view class="header">
        <image class="banner" :src="banner">{ title }}</text>
        <text class="subtitle">{
           { subtitle }}</text>
        <view class="action-buttons">
          <button class="primary-btn">主操作</button>
          <button class="secondary-btn">次操作</button>
        </view>
      </view>
    </view>
    
    <view v-else class="landscape-layout">
      <!-- 横屏布局 - 左右布局 -->
      <view class="left-section">
        <image class="banner" :src="banner">{ title }}</text>
        <text class="subtitle">{
           { subtitle }}</text>
        <view class="action-buttons">
          <button class="primary-btn">主操作</button>
          <button class="secondary-btn">次操作</button>
        </view>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  props: {
    title: String,
    subtitle: String,
    banner: String
  },
  data() {
    return {
      orientation: 'portrait' // 默认竖屏
    }
  },
  created() {
    // 获取初始方向
    this.checkOrientation();
    
    // 监听屏幕旋转(App和H5支持)
    // #ifdef APP-PLUS || H5
    uni.onWindowResize(() => {
      this.checkOrientation();
    });
    // #endif
  },
  methods: {
    checkOrientation() {
      const systemInfo = uni.getSystemInfoSync();
      if (systemInfo.windowWidth > systemInfo.windowHeight) {
        this.orientation = 'landscape';
      } else {
        this.orientation = 'portrait';
      }
    }
  }
}
</script>

<style>
.container {
  width: 100%;
  height: 100%;
}

/* 竖屏布局样式 */
.portrait-layout {
  display: flex;
  flex-direction: column;
}

.portrait-layout .header {
  width: 100%;
  height: 500rpx;
}

.portrait-layout .banner {
  width: 100%;
  height: 100%;
}

.portrait-layout .content {
  padding: 40rpx;
}

.portrait-layout .title {
  font-size: 48rpx;
  font-weight: bold;
  margin-bottom: 20rpx;
}

.portrait-layout .subtitle {
  font-size: 32rpx;
  color: #666;
  margin-bottom: 40rpx;
}

.portrait-layout .action-buttons {
  display: flex;
  flex-direction: column;
  gap: 20rpx;
}

/* 横屏布局样式 */
.landscape-layout {
  display: flex;
  flex-direction: row;
  height: 100%;
}

.landscape-layout .left-section {
  flex: 1;
  height: 100%;
}

.landscape-layout .banner {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.landscape-layout .right-section {
  flex: 1;
  padding: 40rpx;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.landscape-layout .title {
  font-size: 48rpx;
  font-weight: bold;
  margin-bottom: 20rpx;
}

.landscape-layout .subtitle {
  font-size: 32rpx;
  color: #666;
  margin-bottom: 40rpx;
}

.landscape-layout .action-buttons {
  display: flex;
  flex-direction: row;
  gap: 20rpx;
}

/* 通用按钮样式 */
.primary-btn, .secondary-btn {
  height: 80rpx;
  line-height: 80rpx;
  text-align: center;
  border-radius: 40rpx;
  font-size: 32rpx;
}

.primary-btn {
  background-color: #1890ff;
  color: white;
}

.secondary-btn {
  background-color: #f5f5f5;
  color: #333;
}
</style>

6. 大屏特殊优化

平板和PC等大屏幕需要特殊照顾,像设计大房子和小房子的区别。

<template>
  <view :class="['page-container', deviceType]">
    <!-- 导航区:在大屏上显示为侧边栏,小屏上显示为顶部导航 -->
    <view :class="['nav-area', {'side-nav': isLargeScreen, 'top-nav': !isLargeScreen}]">
      <view class="logo">
        <image src="/static/logo.png">{ item.name }}
        </view>
      </view>
    </view>
    
    <!-- 主内容区 -->
    <view class="main-content">
      <!-- 大屏显示更丰富的列表项 -->
      <view class="item-list">
        <view 
          v-for="(item, index) in listItems"
          :key="index"
          :class="['list-item', {'detailed': isLargeScreen}]"
        >
          <image class="item-image" :src="item.image">{ item.title }}</text>
            
            <!-- 小屏只显示价格 -->
            <view v-if="!isLargeScreen" class="item-price">¥{
           { item.price }}</view>
            
            <!-- 大屏显示更多详情 -->
            <view v-else class="item-details">
              <text class="item-desc">{
           { item.description }}</text>
              <view class="item-meta">
                <text class="item-price">¥{
           { item.price }}</text>
                <text class="item-rating">评分: {
           { item.rating }}</text>
                <text class="item-sales">已售: {
           { item.sales }}</text>
              </view>
              <view class="action-row">
                <button class="action-btn">查看详情</button>
                <button class="action-btn primary">加入购物车</button>
              </view>
            </view>
          </view>
          
          <!-- 小屏显示简化操作按钮 -->
          <view v-if="!isLargeScreen" class="item-actions">
            <button class="mini-btn">+</button>
          </view>
        </view>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      deviceType: 'mobile', // 默认移动设备
      isLargeScreen: false, // 默认不是大屏
      menuVisible: false, // 菜单默认隐藏(小屏使用)
      navItems: [
        { name: '首页', url: '/pages/index/index' },
        { name: '分类', url: '/pages/category/category' },
        { name: '购物车', url: '/pages/cart/cart' },
        { name: '我的', url: '/pages/profile/profile' }
      ],
      listItems: [
        // 模拟数据
{ 
  id: 1, 
  title: '超级智能手机',
  image: '/static/products/phone1.jpg',
  price: 2999,
  description: '最新一代智能手机,搭载高通骁龙处理器,6.7英寸超清屏幕...',
  rating: 4.8,
  sales: 3240
},
{ 
  id: 2, 
  title: '无线蓝牙耳机',
  image: '/static/products/headphone.jpg',
  price: 499,
  description: '主动降噪,50小时超长续航,IPX7防水...',
  rating: 4.6,
  sales: 8752
},
// 更多商品...
]
    };
  },
  created() {
    this.detectScreenSize();
    
    // #ifdef H5 || APP-PLUS
    // 监听屏幕大小变化
    uni.onWindowResize(() => {
      this.detectScreenSize();
    });
    // #endif
  },
  methods: {
    detectScreenSize() {
      const sysInfo = uni.getSystemInfoSync();
      const screenWidth = sysInfo.screenWidth;
      
      // 判断设备类型
      if (screenWidth >= 768) {
        this.deviceType = 'tablet';
        this.isLargeScreen = true;
      } else if (screenWidth >= 1024) {
        this.deviceType = 'desktop';
        this.isLargeScreen = true;
      } else {
        this.deviceType = 'mobile';
        this.isLargeScreen = false;
      }
    },
    
    toggleMenu() {
      this.menuVisible = !this.menuVisible;
    },
    
    navigateTo(url) {
      uni.navigateTo({ url });
      // 小屏幕导航后自动隐藏菜单
      if (!this.isLargeScreen) {
        this.menuVisible = false;
      }
    }
  }
}
</script>

<style>
.page-container {
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100vh;
}

/* 平板和桌面端使用不同布局 */
.page-container.tablet,
.page-container.desktop {
  flex-direction: row;
}

/* 导航区域样式 */
.nav-area {
  background-color: #333;
  color: white;
}

/* 顶部导航样式(小屏) */
.top-nav {
  width: 100%;
  height: 100rpx;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 30rpx;
}

/* 侧边导航样式(大屏) */
.side-nav {
  width: 250rpx;
  height: 100vh;
  padding: 40rpx 20rpx;
}

.logo {
  height: 60rpx;
}

.menu-toggle {
  font-size: 48rpx;
}

/* 导航菜单 */
.nav-menu {
  display: flex;
}

/* 小屏菜单默认隐藏 */
.top-nav .nav-menu {
  position: absolute;
  top: 100rpx;
  left: 0;
  width: 100%;
  background-color: #333;
  flex-direction: column;
  transform: translateY(-100%);
  transition: transform 0.3s;
  z-index: 100;
}

.top-nav .nav-menu.active {
  transform: translateY(0);
}

/* 大屏菜单总是垂直显示 */
.side-nav .nav-menu {
  flex-direction: column;
  margin-top: 60rpx;
}

.nav-item {
  padding: 20rpx;
  font-size: 32rpx;
}

/* 主内容区 */
.main-content {
  flex: 1;
  padding: 20rpx;
  overflow-y: auto;
}

/* 列表项 */
.list-item {
  margin-bottom: 20rpx;
  background-color: white;
  border-radius: 16rpx;
  overflow: hidden;
  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
  display: flex;
}

/* 小屏列表项 */
.device-mobile .list-item {
  height: 180rpx;
}

/* 大屏列表项 */
.list-item.detailed {
  height: 300rpx;
}

.item-image {
  width: 180rpx;
  height: 100%;
  object-fit: cover;
}

.list-item.detailed .item-image {
  width: 300rpx;
}

.item-info {
  flex: 1;
  padding: 20rpx;
  display: flex;
  flex-direction: column;
}

.item-title {
  font-size: 32rpx;
  font-weight: bold;
  margin-bottom: 10rpx;
}

.item-price {
  color: #ff4d4f;
  font-size: 36rpx;
  font-weight: bold;
}

/* 大屏专属详情样式 */
.item-details {
  display: flex;
  flex-direction: column;
  flex: 1;
}

.item-desc {
  font-size: 26rpx;
  color: #666;
  margin: 10rpx 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.item-meta {
  display: flex;
  margin: 10rpx 0;
}

.item-rating, .item-sales {
  margin-left: 20rpx;
  font-size: 24rpx;
  color: #999;
}

.action-row {
  display: flex;
  margin-top: auto;
}

.action-btn {
  padding: 0 30rpx;
  height: 60rpx;
  line-height: 60rpx;
  font-size: 28rpx;
  margin-right: 20rpx;
  background-color: #f5f5f5;
  color: #333;
  border-radius: 30rpx;
}

.action-btn.primary {
  background-color: #1890ff;
  color: white;
}

/* 小屏专属操作按钮 */
.item-actions {
  display: flex;
  align-items: center;
  padding-right: 20rpx;
}

.mini-btn {
  width: 60rpx;
  height: 60rpx;
  line-height: 56rpx;
  text-align: center;
  background-color: #1890ff;
  color: white;
  border-radius: 30rpx;
  font-size: 36rpx;
}
</style>

7. 平台特异性处理:各平台的专属服装

每个平台都有自己的特殊需求,就像不同场合需要不同服装。

<template>
  <view class="container">
    <view class="title-area">
      <text class="page-title">{
           { title }}</text>
      
      <!-- #ifdef APP-PLUS -->
      <!-- 原生APP导航栏集成 -->
      <view class="status-bar-placeholder" :style="{ height: statusBarHeight + 'px' }"></view>
      <!-- #endif -->
      
      <!-- 微信小程序胶囊按钮适配 -->
      <!-- #ifdef MP-WEIXIN -->
      <view class="capsule-placeholder" :style="{ width: menuButtonWidth + 'px' }"></view>
      <!-- #endif -->
    </view>
    
    <view class="content-area">
      <!-- 平台特异性内容 -->
      
      <!-- #ifdef MP-WEIXIN -->
      <!-- 微信小程序特有UI -->
      <view class="wx-specific">
        <button open-type="share" class="share-btn">分享给朋友</button>
        <button open-type="contact" class="contact-btn">联系客服</button>
      </view>
      <!-- #endif -->
      
      <!-- #ifdef MP-ALIPAY -->
      <!-- 支付宝小程序特有UI -->
      <view class="ali-specific">
        <button class="ali-btn" @tap="aliShare">支付宝分享</button>
      </view>
      <!-- #endif -->
      
      <!-- #ifdef APP-PLUS -->
      <!-- APP特有功能 -->
      <view class="app-specific">
        <button class="app-btn" @tap="checkUpdate">检查更新</button>
        <button class="app-btn" @tap="downloadResource">下载资源</button>
      </view>
      <!-- #endif -->
      
      <!-- #ifdef H5 -->
      <!-- H5特有功能 -->
      <view class="h5-specific">
        <button class="h5-btn" @tap="copyLink">复制链接</button>
      </view>
      <!-- #endif -->
      
      <!-- 通用内容:所有平台共享 -->
      <view class="common-content">
        <text class="hint">以下是所有平台通用内容</text>
        <!-- 其他通用内容 -->
      </view>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      title: '平台适配示例',
      statusBarHeight: 20, // 默认状态栏高度
      menuButtonWidth: 87  // 默认胶囊按钮宽度
    }
  },
  created() {
    this.initPlatformSpecific();
  },
  methods: {
    initPlatformSpecific() {
      // 获取系统信息
      const sysInfo = uni.getSystemInfoSync();
      
      // #ifdef APP-PLUS
      // 设置APP状态栏高度
      this.statusBarHeight = sysInfo.statusBarHeight;
      // #endif
      
      // #ifdef MP-WEIXIN
      // 获取微信胶囊按钮信息
      const menuButton = wx.getMenuButtonBoundingClientRect();
      this.menuButtonWidth = menuButton.width + 10; // 加上右边距
      // #endif
    },
    
    // 平台特异性方法
    // #ifdef MP-ALIPAY
    aliShare() {
      my.showSharePanel();
    },
    // #endif
    
    // #ifdef APP-PLUS
    checkUpdate() {
      uni.showToast({
        title: '检查更新中...',
        icon: 'loading'
      });
      
      // 模拟检查更新过程
      setTimeout(() => {
        uni.showToast({
          title: '已是最新版本',
          icon: 'success'
        });
      }, 1500);
    },
    
    downloadResource() {
      uni.showLoading({
        title: '资源下载中...'
      });
      
      // 模拟下载过程
      setTimeout(() => {
        uni.hideLoading();
        uni.showToast({
          title: '下载完成',
          icon: 'success'
        });
      }, 2000);
    },
    // #endif
    
    // #ifdef H5
    copyLink() {
      const url = var test;
      // 创建临时输入框
      const input = document.createElement('input');
      input.setAttribute('readonly', 'readonly');
      input.value = url;
      document.body.appendChild(input);
      input.select();
      document.execCommand('copy');
      document.body.removeChild(input);
      
      uni.showToast({
        title: '链接已复制',
        icon: 'success'
      });
    }
    // #endif
  }
}
</script>

<style>
.container {
  width: 100%;
  min-height: 100vh;
}

.title-area {
  position: relative;
  height: 100rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #1890ff;
  color: white;
}

.page-title {
  font-size: 36rpx;
  font-weight: bold;
}

.status-bar-placeholder {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  z-index: 99;
}

.capsule-placeholder {
  position: absolute;
  top: 50%;
  right: 20rpx;
  transform: translateY(-50%);
  height: 60rpx;
}

.content-area {
  padding: 30rpx;
}

/* 平台特异性样式 */
.wx-specific, .ali-specific, .app-specific, .h5-specific {
  margin-bottom: 40rpx;
  padding: 20rpx;
  border-radius: 12rpx;
}

.wx-specific {
  background-color: #07c160;
}

.ali-specific {
  background-color: #00aaff;
}

.app-specific {
  background-color: #fa6400;
}

.h5-specific {
  background-color: #9c27b0;
}

.share-btn, .contact-btn, .ali-btn, .app-btn, .h5-btn {
  margin: 10rpx 0;
  color: white;
}

.share-btn, .contact-btn {
  background-color: #07c160;
}

.ali-btn {
  background-color: #00aaff;
}

.app-btn {
  background-color: #fa6400;
}

.h5-btn {
  background-color: #9c27b0;
}

.common-content {
  margin-top: 40rpx;
  padding: 30rpx;
  background-color: #f5f5f5;
  border-radius: 12rpx;
}

.hint {
  font-size: 28rpx;
  color: #666;
}
</style>

实战技巧:屏幕适配高级战略

1. 智能组件:适配的前线战士

打造自适应组件是屏幕适配的关键,就像训练能在任何环境中作战的特种兵。

<!-- AdaptiveCard.vue - 智能适应不同屏幕尺寸的卡片组件 -->
<template>
  <view 
    :class="['adaptive-card', orientation, layout]" 
    :style="customStyle"
  >
    <view class="card-media" :style="mediaStyle">
      <slot name="media">
        <image 
          v-if="media" 
          :src="media">{ title }}</text>
      </slot>
      
      <slot name="content">
        <text v-if="content" class="card-content">{
           { content }}</text>
      </slot>
      
      <view class="card-actions">
        <slot name="actions"></slot>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  name: 'AdaptiveCard',
  props: {
    // 卡片内容
    title: String,
    content: String,
    media: String,
    
    // 布局控制
    layout: {
      type: String,
      default: 'auto', // auto, horizontal, vertical
      validator: val => ['auto', 'horizontal', 'vertical'].includes(val)
    },
    
    // 自定义样式
    cardStyle: {
      type: Object,
      default: () => ({})
    },
    mediaStyle: {
      type: Object,
      default: () => ({})
    }
  },
  data() {
    return {
      screenWidth: 375,
      orientation: 'portrait', // portrait or landscape
      deviceType: 'mobile' // mobile, tablet, desktop
    }
  },
  computed: {
    // 合并自定义样式与默认样式
    customStyle() {
      return {
        ...this.getDeviceTypeStyle(),
        ...this.cardStyle
      }
    }
  },
  created() {
    this.updateScreenInfo();
    
    // #ifdef H5 || APP-PLUS
    uni.onWindowResize(() => {
      this.updateScreenInfo();
    });
    // #endif
  },
  methods: {
    updateScreenInfo() {
      const sysInfo = uni.getSystemInfoSync();
      this.screenWidth = sysInfo.screenWidth;
      
      // 判断横竖屏
      if (sysInfo.screenWidth > sysInfo.screenHeight) {
        this.orientation = 'landscape';
      } else {
        this.orientation = 'portrait';
      }
      
      // 判断设备类型
      if (this.screenWidth >= 768 && this.screenWidth < 1024) {
        this.deviceType = 'tablet';
      } else if (this.screenWidth >= 1024) {
        this.deviceType = 'desktop';
      } else {
        this.deviceType = 'mobile';
      }
    },
    
    // 根据设备类型返回默认样式
    getDeviceTypeStyle() {
      switch(this.deviceType) {
        case 'tablet':
          return {
            borderRadius: '16rpx',
            boxShadow: '0 4rpx 12rpx rgba(0, 0, 0, 0.1)'
          };
        case 'desktop':
          return {
            borderRadius: '20rpx',
            boxShadow: '0 8rpx 24rpx rgba(0, 0, 0, 0.15)'
          };
        default: // mobile
          return {
            borderRadius: '12rpx',
            boxShadow: '0 2rpx 8rpx rgba(0, 0, 0, 0.1)'
          };
      }
    }
  }
}
</script>

<style>
.adaptive-card {
  background-color: #ffffff;
  margin: 20rpx;
  overflow: hidden;
  transition: all 0.3s;
}

/* 垂直布局 */
.adaptive-card.vertical {
  display: flex;
  flex-direction: column;
}

.adaptive-card.vertical .card-media {
  width: 100%;
  height: 350rpx;
}

/* 水平布局 */
.adaptive-card.horizontal {
  display: flex;
  flex-direction: row;
  height: 250rpx;
}

.adaptive-card.horizontal .card-media {
  width: 250rpx;
  height: 100%;
}

/* 自动布局 - 基于屏幕尺寸自动选择 */
.adaptive-card.auto.portrait {
  display: flex;
  flex-direction: column;
}

.adaptive-card.auto.portrait .card-media {
  width: 100%;
  height: 350rpx;
}

.adaptive-card.auto.landscape {
  display: flex;
  flex-direction: row;
  height: 250rpx;
}

.adaptive-card.auto.landscape .card-media {
  width: 250rpx;
  height: 100%;
}

/* 内容区样式 */
.card-body {
  padding: 20rpx;
  display: flex;
  flex-direction: column;
  flex: 1;
}

.card-title {
  font-size: 32rpx;
  font-weight: bold;
  margin-bottom: 10rpx;
}

.card-content {
  font-size: 28rpx;
  color: #666;
  flex: 1;
}

.card-actions {
  margin-top: 16rpx;
  display: flex;
  justify-content: flex-end;
}

/* 媒体内容样式 */
.media-content {
  width: 100%;
  height: 100%;
}

/* 设备类型特异性样式 */
/* 平板样式 */
@media screen and (min-width: 768px) {
  .card-title {
    font-size: 36rpx;
  }
  
  .card-content {
    font-size: 30rpx;
  }
}

/* 桌面样式 */
@media screen and (min-width: 1024px) {
  .card-title {
    font-size: 40rpx;
  }
  
  .card-content {
    font-size: 32rpx;
  }
}
</style>

使用智能组件示例:

<template>
  <view class="page">
    <!-- 基本用法 -->
    <adaptive-card 
      title="智能适应卡片" 
      content="自动适应各种屏幕尺寸和方向" 
      media="/static/images/card-image.jpg" 
    />
    
    <!-- 强制水平布局 -->
    <adaptive-card 
      layout="horizontal"
      title="水平布局卡片" 
      content="始终使用水平布局,无论屏幕大小" 
      media="/static/images/card-image.jpg" 
    />
    
    <!-- 自定义样式 -->
    <adaptive-card 
      title="自定义样式卡片" 
      content="使用自定义样式的卡片" 
      media="/static/images/card-image.jpg" 
      :card-style="{ backgroundColor: '#f0f8ff' }"
      :media-style="{ borderRadius: '50%' }"
    />
    
    <!-- 使用插槽自定义内容 -->
    <adaptive-card>
      <template #title>
        <view class="custom-title">
          <text class="main-title">定制标题</text>
          <text class="sub-title">副标题文本</text>
        </view>
      </template>
      
      <template #content>
        <view class="custom-content">
          <text>完全自定义的内容区域</text>
          <progress :percent="80" stroke-width="4" />
        </view>
      </template>
      
      <template #actions>
        <button class="custom-btn">自定义按钮</button>
      </template>
      
      <template #media>
        <video 
          src="/static/videos/demo.mp4"
          class="media-content"
          controls
        ></video>
      </template>
    </adaptive-card>
  </view>
</template>

<script>
import AdaptiveCard from '@/components/AdaptiveCard.vue';

export default {
  components: {
    AdaptiveCard
  }
}
</script>

<style>
.page {
  padding: 20rpx;
}

.custom-title {
  display: flex;
  flex-direction: column;
}

.main-title {
  font-size: 36rpx;
  font-weight: bold;
  color: #1890ff;
}

.sub-title {
  font-size: 24rpx;
  color: #999;
}

.custom-content {
  margin: 20rpx 0;
}

.custom-btn {
  background-color: #1890ff;
  color: white;
  font-size: 28rpx;
  padding: 10rpx 30rpx;
  border-radius: 30rpx;
}
</style>

2. 屏幕适配调试技巧

模拟不同屏幕尺寸进行测试,就像试衣服需要不同体型的模特。

<template>
  <view class="debug-container">
    <!-- 调试工具栏 -->
    <view v-if="showDebugTools" class="debug-toolbar">
      <!-- 设备选择器 -->
      <picker 
        :value="deviceIndex" 
        :range="deviceList" 
        range-key="name"
        @change="onDeviceChange"
      >
        <view class="device-picker">{
           { deviceList[deviceIndex].name }}</view>
      </picker>
      
      <!-- 方向切换 -->
      <view 
        class="orientation-toggle"
        @tap="toggleOrientation"
      >
        {
           { isLandscape ? '横屏' : '竖屏' }}
      </view>
      
      <!-- 关闭调试工具 -->
      <view class="close-btn" @tap="toggleDebugTools">关闭</view>
    </view>
    
    <!-- 调试启动按钮 -->
    <view 
      v-else 
      class="debug-toggle-btn"
      @tap="toggleDebugTools"
    >
      调试工具
    </view>
    
    <!-- 模拟设备框架 -->
    <view 
      :class="['device-simulator', {'landscape': isLandscape}]"
      :style="deviceStyle"
    >
      <!-- 应用内容 -->
      <view class="app-content">
        <slot></slot>
      </view>
    </view>
    
    <!-- 尺寸信息 -->
    <view v-if="showDebugTools" class="size-info">
      屏幕尺寸: {
           { deviceWidth }}px × {
           { deviceHeight }}px
      <text v-if="isLandscape">(横屏)</text>
      <text v-else>(竖屏)</text>
    </view>
  </view>
</template>

<script>
export default {
  name: 'ScreenDebugger',
  data() {
    return {
      showDebugTools: false,
      deviceIndex: 0,
      isLandscape: false,
      deviceList: [
        { name: 'iPhone SE', width: 320, height: 568 },
        { name: 'iPhone X/11', width: 375, height: 812 },
        { name: 'iPhone 12 Pro Max', width: 428, height: 926 },
        { name: 'Android Common', width: 360, height: 640 },
        { name: 'iPad Mini', width: 768, height: 1024 },
        { name: 'iPad Pro', width: 834, height: 1194 },
        { name: 'Desktop', width: 1280, height: 800 }
      ]
    }
  },
  computed: {
    currentDevice() {
      return this.deviceList[this.deviceIndex];
    },
    
    deviceWidth() {
      return this.isLandscape ? this.currentDevice.height : this.currentDevice.width;
    },
    
    deviceHeight() {
      return this.isLandscape ? this.currentDevice.width : this.currentDevice.height;
    },
    
    deviceStyle() {
      const scale = 0.9; // 缩放比例,确保设备显示在视窗内
      
      return {
        width: this.deviceWidth * scale + 'px',
        height: this.deviceHeight * scale + 'px'
      }
    }
  },
  methods: {
    toggleDebugTools() {
      this.showDebugTools = !this.showDebugTools;
    },
    
    onDeviceChange(e) {
      this.deviceIndex = e.detail.value;
    },
    
    toggleOrientation() {
      this.isLandscape = !this.isLandscape;
    }
  }
}
</script>

<style>
.debug-container {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 20rpx;
}

.debug-toolbar {
  display: flex;
  width: 100%;
  padding: 20rpx;
  background-color: #f0f0f0;
  border-radius: 10rpx;
  margin-bottom: 20rpx;
}

.device-picker {
  flex: 1;
  background-color: #fff;
  padding: 10rpx 20rpx;
  border-radius: 6rpx;
}

.orientation-toggle, .close-btn {
  margin-left: 20rpx;
  padding: 10rpx 20rpx;
  background-color: #1890ff;
  color: white;
  border-radius: 6rpx;
}

.close-btn {
  background-color: #ff4d4f;
}

.debug-toggle-btn {
  position: fixed;
  right: 20rpx;
  bottom: 20rpx;
  padding: 10rpx 20rpx;
  background-color: #1890ff;
  color: white;
  border-radius: 6rpx;
  z-index: 999;
}

.device-simulator {
  border: 4rpx solid #333;
  border-radius: 20rpx;
  overflow: hidden;
  transition: all 0.3s;
  background-color: white;
}

.device-simulator.landscape {
  transform: rotate(90deg);
}

.app-content {
  width: 100%;
  height: 100%;
  overflow: auto;
}

.size-info {
  margin-top: 20rpx;
  font-size: 28rpx;
  color: #666;
}
</style>

屏幕适配的终极智慧:智能变形,万能适配

屏幕适配就像一场智慧的游戏,你需要让你的应用像变色龙一样适应任何环境。

┌────────────────────────────────────────────────────────┐
│             【屏幕适配的终极智慧】                     │
├────────────────────────┬───────────────────────────────┤
│ 设计上思考              │ 代码上执行                    │
├────────────────────────┼───────────────────────────────┤
│ 内容决定形式           │ 弹性优先,固定次之           │
├────────────────────────┼───────────────────────────────┤
│ 重要内容优先展示       │ 响应式隐藏与显示             │
├────────────────────────┼───────────────────────────────┤
│ 功能体验一致           │ 布局可变,功能不变           │
├────────────────────────┼───────────────────────────────┤
│ 适应而非简单缩放       │ 重排优于等比缩放             │
└────────────────────────┴───────────────────────────────┘

UniApp 屏幕适配的最高境界,就是让用户忘记他们在使用什么屏幕,无论什么设备,体验都是流畅自然的。这就像一位出色的演说家,能够让不同背景的听众都感到她在专门对自己说话一样。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容