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