在本机移动应用开发中为Android和iOS生成图标集

目录

介绍

背景

Microsoft Visual Studio的Android库项目中的Android图标资源

iOS 图标资产

先决条件

SVG图标库

言论

撰写简单的图标

应用启动器图标

Google Material Icons中的可见性图标

调整现有图标以符合Google和Apple的设计指南

引用

使用代码

Android

svgForAndroid.ps1

iOS系统

图像集

svgForIOSImageset.ps1

应用图标

svgForIOSAppIconSet.ps1

兴趣点

MAUI


介绍

本文适用于在使用Xamarin开发Android应用和iOS应用方面已有一定经验的开发人员。虽然周围有很多图标库,无论是免费的还是商业的,但有时,您可能希望从现有的图像文件(理想情况下是SVG文件)生成图标集。

本文将分享我在开发原生移动应用程序时从SVG文件生成各种大小的图标的经验。虽然我使用的主要开发工具是Xamarin,但即使您一直在使用XCodeAndroid Studio等其他工具,这里提到的原则和工具也应该适用。

XCode 14+支持单一大小的应用程序图标Android Studio还包含 Image Asset Studio,可根据材质图标和自定义图片生成应用图标。但是,如果出于持续集成的目的不想使用这种交互式方式,或者你正在使用Xamarin,请继续阅读。

背景

几年前,我开发了Visual Acuity Charts,用于测试远视力,以检查近视的早期迹象。有一些免费的SVG图标,我使用了一些在线转换工具来生成所需的图标集。我一直在使用Nika NikabadzeVisual Sutido创建的材质图标生成器App Icon Generator

Microsoft Visual StudioAndroid库项目中的Android图标资源

iOS 图标资产

但是,使用这些VS扩展或在线工具的过程相当麻烦:

  • 使用VS扩展时,太多的用户交互困扰着我。
  • 使用在线工具:上传SVG文件 -> 配置 -> 生成 -> 等待 -> 下载zip -> 解压缩到项目文件夹。总的来说,这样的过程对持续集成并不友好,因此我编写了一系列PowerShell脚本来生成图标集。

总的来说,这两个工具相当不错,但是,如果您感到我所感受到的麻烦,您可以尝试本文中介绍的方法。

先决条件

SVG图标库

还有更多,免费或商业的。

言论

  • 确保免费图标的使用符合Google Material图标库使用的 SIL开放字体许可证(OFL) 等许可证。
  • Google Material Icons网站以ZIP格式提供了适用于Android和iOS的图像集下载。

Inkscape

"Inkscape 是一个免费的开源矢量图形编辑器,适用于GNU/LinuxWindowsmacOS

作为一名软件开发人员,我偶尔会做一些随意而简单的图形设计,我不打算购买专业平面设计师通常使用的多合一、功能强大且复杂的图形设计工具。Inkscape满足了我的需求。

撰写简单的图标

例如,我没有从头开始绘制所有内容,而是使用现有的SVG文件来编写应用程序启动图标。

应用启动器图标

Google Material Icons中的可见性图标

尽管如此,Inscape还是为您提供了丰富的功能,让您从头开始绘制复杂的图标或图像。

调整现有图标以符合GoogleApple的设计指南

有时,您可能会发现一些候选者不太符合特定的设计准则,并希望调整大小和边距。

引用

使用代码

以下PowerShell脚本利用Inkscape的命令行功能,脚本的起草参考了上面的图标设计指南。

Android

以下脚本为按钮和应用图标生成图标。

svgForAndroid.ps1

param([string]$svgFile, [int]$width, [int]$height, [boolean]$forAppIcon, 
      [string]$appIconName)
# Create icons for drawable and mipmap
# Examples:
# ./svgForAndroid.ps1 -svgFile "my.svg" -width 36
# ./svgForAndroid.ps1 "my.svg" 36
# For App Launcher Icons, 
# ref: https://developer.android.com/training/multiscreen/screendensities
# ./svgForAndroid.ps1 -svgFile "my.svg" -width 48 -forAppIcon $true 
# and this is excluding 36x36 ldpi
# The script file and the svg file should be in the same folder, 
# and the generated files will be in a sub-folder.
# For convenience of developing using Xamarin, follow such convension:
# Rename the svg file to something like send_36.svg if you want 36pt.
# Remarks: Android requires all resource file names are in lower case.

cd $PSScriptRoot

$baseFileName=[System.IO.Path]::GetFileNameWithoutExtension($svgFile)

function export([string]$subDir, [decimal]$ratio){
    $dir= [System.IO.Path]::Combine($baseFileName,$subDir)
    $exportedFileName=If ($forAppIcon) {If ($appIconName) {$appIconName+".png"} 
    Else {"5367533/ic_launcher.png"}} Else {$baseFileName+"_"+ $width + ".png"}
    $exported= [System.IO.Path]::Combine($dir, $exportedFileName)
    $dwidth=[int]($width * $ratio)
    $dheight=0
    if ($height -gt 0){$dheight =  $height * $ratio} Else {$dheight = $dwidth}
    $arguments="$svgFile --export-filename=$exported -w $dwidth -h $dheight -d 96"
    New-Item -ItemType Directory -Force -Path $dir

    $procArgs = @{
        FilePath         = "C:\Program Files\Inkscape\bin\inkscape.exe"
        ArgumentList     = $arguments
        PassThru         = $true
    }
    $processTsc = Start-Process @procArgs
}

If ($forAppIcon){
    export "mipmap-mdpi" 1
    export "mipmap-hdpi" 1.5
    export "mipmap-xhdpi" 2
    export "mipmap-xxhdpi" 3
    export "mipmap-xxxhdpi" 4
} Else {
    export "drawable-mdpi" 1
    export "drawable-hdpi" 1.5
    export "drawable-xhdpi" 2
    export "drawable-xxhdpi" 3
    export "drawable-xxxhdpi" 4
}

iOS系统

图像集

以下脚本生成三个png文件并Contents.json

svgForIOSImageset.ps1

param([string]$svgFile, [Int32]$width, [Int32]$height, [boolean]$original)
# Create icons for imageset.
# Examples:
# .\svgForAndroid.ps1 -svgFile "my.svg" -width 36 -original $true
# .\svgForIOSImageset.ps1 -svgFile "my.svg" -width 36
# If $original is false, template-rendering-intent in Contents.json 
# will become template  for visual effects such as replacing colors.
# The script file and the svg file should be in the same folder, 
# and the generated files will be in a sub-folder.

cd $PSScriptRoot

$baseFileName=[System.IO.Path]::GetFileNameWithoutExtension($svgFile)
$dir= $baseFileName + "_" + $width + ".imageset"
New-Item -ItemType Directory -Force -Path $dir

function export([decimal]$ratio){
    $exportedFileName=$baseFileName + "_" + $width +"pt_" + $ratio + "x.png"
    $exported= [System.IO.Path]::Combine($dir, $exportedFileName)
    $dwidth=[int]($width * $ratio)
    $dheight=0
    if ($height -gt 0){$dheight = $height * $ratio} Else {$dheight = $dwidth}
    $arguments="$svgFile --export-filename=$exported -w $dwidth -h $dheight -d 96"
    Write-Host $arguments
    $procArgs = @{
        FilePath         = "C:\Program Files\Inkscape\bin\inkscape.exe"
        ArgumentList     = $arguments
        PassThru         = $true
    }
    $processTsc = Start-Process @procArgs
    return $exportedFileName
}

$f1=export 1
$f2=export 2
$f3=export 3
$intent=If ($original) {"original"} Else {"template"}

$contentsTemplate=
@"
{
    "images": [
        {
            "filename": "$f1",
            "idiom": "universal",
            "scale": "1x"
        },
        {
            "filename": "$f2",
            "idiom": "universal",
            "scale": "2x"
        },
        {
            "filename": "$f3",
            "idiom": "universal",
            "scale": "3x"
        }
    ],
    "info": {
        "author": "whocare",
        "template-rendering-intent": "$intent",
        "version": 1
    }
}
"@

$jsonFile=[System.IO.Path]::Combine($dir, "Contents.json")
Set-Content $jsonFile $contentsTemplate

运行脚本后,您将在 myicon.imageset 等文件夹中获取这些文件:

应用图标

运行脚本后,你将获得26个文件,包括 myAppIcon.appiconset 等文件夹中的Contents.json

svgForIOSAppIconSet.ps1

param([string]$svgFile)
# Create AppIcons.appiconset of Assets.xcassets
# Examples:
# ./svgForIOSAppIconSet.ps1 -svgFile "my.svg"
# The script file and the svg file should be in the same folder, 
# and the generated files will be in a sub-folder like "my.appiconset".
cd $PSScriptRoot

$baseFileName=[System.IO.Path]::GetFileNameWithoutExtension($svgFile)
$dir= $baseFileName + ".appiconset"
New-Item -ItemType Directory -Force -Path $dir

function export([decimal]$size){
    $exportedFileName=$size.ToString() + ".png"
    $exported= [System.IO.Path]::Combine($dir, $exportedFileName)
    $arguments="$svgFile --export-filename $exported -w $size -h $size"

    $procArgs = @{
        FilePath         = "C:\Program Files\Inkscape\bin\inkscape.exe"
        ArgumentList     = $arguments
        PassThru         = $true
    }
    $processTsc = Start-Process @procArgs
    return $exportedFileName
}

function exportForWatch(){
    $exportedFileName="watch.png"
    $exported= [System.IO.Path]::Combine($dir, $exportedFileName)
    $arguments="$svgFile --export-filename $exported -w 55 -h 55"

    $procArgs = @{
        FilePath         = "C:\Program Files\Inkscape\bin\inkscape.exe"
        ArgumentList     = $arguments
        PassThru         = $true
    }
    $processTsc = Start-Process @procArgs
    return $exportedFileName
}

export 20
export 29
export 32
export 40
export 50
export 57
export 58
export 60
export 64
export 72
export 76
export 80
export 87
export 100
export 114
export 120
export 128
export 144
export 152
export 167
export 180
export 256
export 512
export 1024

exportForWatch 

$contentsTemplate=
@"
{
    "images": [
        {
            "size": "60x60",
            "expected-size": "180",
            "filename": "180.png",

            "idiom": "iphone",
            "scale": "3x"
        },
        {
            "size": "40x40",
            "expected-size": "80",
            "filename": "80.png",

            "idiom": "iphone",
            "scale": "2x"
        },
        {
            "size": "40x40",
            "expected-size": "120",
            "filename": "120.png",

            "idiom": "iphone",
            "scale": "3x"
        },
        {
            "size": "60x60",
            "expected-size": "120",
            "filename": "120.png",

            "idiom": "iphone",
            "scale": "2x"
        },
        {
            "size": "57x57",
            "expected-size": "57",
            "filename": "57.png",

            "idiom": "iphone",
            "scale": "1x"
        },
        {
            "size": "29x29",
            "expected-size": "58",
            "filename": "58.png",

            "idiom": "iphone",
            "scale": "2x"
        },
        {
            "size": "29x29",
            "expected-size": "29",
            "filename": "29.png",

            "idiom": "iphone",
            "scale": "1x"
        },
        {
            "size": "29x29",
            "expected-size": "87",
            "filename": "87.png",

            "idiom": "iphone",
            "scale": "3x"
        },
        {
            "size": "57x57",
            "expected-size": "114",
            "filename": "114.png",

            "idiom": "iphone",
            "scale": "2x"
        },
        {
            "size": "20x20",
            "expected-size": "40",
            "filename": "40.png",

            "idiom": "iphone",
            "scale": "2x"
        },
        {
            "size": "20x20",
            "expected-size": "60",
            "filename": "60.png",

            "idiom": "iphone",
            "scale": "3x"
        },
        {
            "size": "1024x1024",
            "filename": "1024.png",
            "expected-size": "1024",
            "idiom": "ios-marketing",

            "scale": "1x"
        },
        {
            "size": "40x40",
            "expected-size": "80",
            "filename": "80.png",

            "idiom": "ipad",
            "scale": "2x"
        },
        {
            "size": "72x72",
            "expected-size": "72",
            "filename": "72.png",

            "idiom": "ipad",
            "scale": "1x"
        },
        {
            "size": "76x76",
            "expected-size": "152",
            "filename": "152.png",

            "idiom": "ipad",
            "scale": "2x"
        },
        {
            "size": "50x50",
            "expected-size": "100",
            "filename": "100.png",

            "idiom": "ipad",
            "scale": "2x"
        },
        {
            "size": "29x29",
            "expected-size": "58",
            "filename": "58.png",

            "idiom": "ipad",
            "scale": "2x"
        },
        {
            "size": "76x76",
            "expected-size": "76",
            "filename": "76.png",

            "idiom": "ipad",
            "scale": "1x"
        },
        {
            "size": "29x29",
            "expected-size": "29",
            "filename": "29.png",

            "idiom": "ipad",
            "scale": "1x"
        },
        {
            "size": "50x50",
            "expected-size": "50",
            "filename": "50.png",

            "idiom": "ipad",
            "scale": "1x"
        },
        {
            "size": "72x72",
            "expected-size": "144",
            "filename": "144.png",

            "idiom": "ipad",
            "scale": "2x"
        },
        {
            "size": "40x40",
            "expected-size": "40",
            "filename": "40.png",

            "idiom": "ipad",
            "scale": "1x"
        },
        {
            "size": "83.5x83.5",
            "expected-size": "167",
            "filename": "167.png",

            "idiom": "ipad",
            "scale": "2x"
        },
        {
            "size": "20x20",
            "expected-size": "20",
            "filename": "20.png",

            "idiom": "ipad",
            "scale": "1x"
        },
        {
            "size": "20x20",
            "expected-size": "40",
            "filename": "40.png",

            "idiom": "ipad",
            "scale": "2x"
        },
        {
            "idiom": "watch",
            "filename": "watch.png",

            "subtype": "38mm",
            "scale": "2x",
            "size": "86x86",
            "expected-size": "172",
            "role": "quickLook"
        },
        {
            "idiom": "watch",
            "filename": "watch.png",

            "subtype": "38mm",
            "scale": "2x",
            "size": "40x40",
            "expected-size": "80",
            "role": "appLauncher"
        },
        {
            "idiom": "watch",
            "filename": "watch.png",

            "subtype": "42mm",
            "scale": "2x",
            "size": "98x98",
            "expected-size": "196",
            "role": "quickLook"
        },
        {
            "idiom": "watch",
            "filename": "watch.png",

            "subtype": "38mm",
            "scale": "2x",
            "size": "24x24",
            "expected-size": "48",
            "role": "notificationCenter"
        },
        {
            "idiom": "watch",
            "filename": "watch.png",

            "subtype": "42mm",
            "scale": "2x",
            "size": "27.5x27.5",
            "expected-size": "55",
            "role": "notificationCenter"
        },
        {
            "size": "29x29",
            "expected-size": "87",
            "filename": "87.png",

            "idiom": "watch",
            "role": "companionSettings",
            "scale": "3x"
        },
        {
            "idiom": "watch",
            "filename": "watch.png",

            "subtype": "42mm",
            "scale": "2x",
            "size": "44x44",
            "expected-size": "88",
            "role": "longLook"
        },
        {
            "size": "29x29",
            "expected-size": "58",
            "filename": "58.png",

            "idiom": "watch",
            "role": "companionSettings",
            "scale": "2x"
        },
        {
            "size": "1024x1024",
            "expected-size": "1024",
            "filename": "1024.png",

            "idiom": "watch-marketing",
            "scale": "1x"
        },
        {
            "size": "128x128",
            "expected-size": "128",
            "filename": "128.png",

            "idiom": "mac",
            "scale": "1x"
        },
        {
            "size": "256x256",
            "expected-size": "256",
            "filename": "256.png",

            "idiom": "mac",
            "scale": "1x"
        },
        {
            "size": "128x128",
            "expected-size": "256",
            "filename": "256.png",

            "idiom": "mac",
            "scale": "2x"
        },
        {
            "size": "256x256",
            "expected-size": "512",
            "filename": "512.png",

            "idiom": "mac",
            "scale": "2x"
        },
        {
            "size": "32x32",
            "expected-size": "32",
            "filename": "32.png",

            "idiom": "mac",
            "scale": "1x"
        },
        {
            "size": "512x512",
            "expected-size": "512",
            "filename": "512.png",

            "idiom": "mac",
            "scale": "1x"
        },
        {
            "size": "16x16",
            "expected-size": "32",
            "filename": "32.png",

            "idiom": "mac",
            "scale": "2x"
        },
        {
            "size": "32x32",
            "expected-size": "64",
            "filename": "64.png",

            "idiom": "mac",
            "scale": "2x"
        },
        {
            "size": "512x512",
            "expected-size": "1024",
            "filename": "1024.png",

            "idiom": "mac",
            "scale": "2x"
        }
    ]
}
"@

$jsonFile=[System.IO.Path]::Combine($dir, "Contents.json")
Set-Content $jsonFile $contentsTemplate 

兴趣点

您可以根据SDLC/CI流程调整本文中提供的PowerShell脚本。

我在开发原生移动应用程序方面的经验仅限于智能手机和平板电脑。因此,如果您正在为智能手表或智能电视开发本机应用程序,您可能需要根据各自的图标设计规范添加更多行。

MAUI

若要开发适用于AndroidiOS的本机应用,需要创建至少两个特定于平台的应用项目,一个用于Android,另一个用于iOS,而共享库代码包含在Xamarin.Forms项目和.NET Standard项目中。

使用 MAUI,您只需要一个适用于AndroidiOSWindowsMac等的应用程序项目。图标源可以是矢量图像(SVG文件)。显然,MAUI将读取SVG文件并根据各自平台的要求生成PNG文件,就像本文中的脚本文件一样。这听起来很自然,很有前途,而且很有成效。

几年前,我为Xamarin重写了“Tour of Heroes”(Angular的官方教程应用),以演示如何在实际项目中使用 WebApiClientGen202310月,我已将应用程序迁移到MAUI。Xamarin构建的Android可部署版本约为22MB,但MAUI的可部署版本约为33MB,而两者都是发布版本。

这种规模上的通胀显然是不可取的。我将有兴趣检查Microsoft是否可以在.NET Conf 2023之后以及20244月计划停用Xamarin之前解决这个问题。

https://www.codeproject.com/Articles/5367533/Generate-Icon-Sets-for-Android-and-iOS-in-Native-M

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值