快速生成 Android 的 Splash 的 9 Patch 图片

import java.io.File;

import java.awt.Color;
import java.awt.Image;
import java.awt.Graphics2D;

import java.awt.image.BufferedImage;

import javax.imageio.ImageIO;

/**
 * 实在受不了 Android Studio 里制作 9 Patch 难用的 Editor
 *
 * 快速生成  Android 的 Splash 的 9 Patch 图片
 *
 * 找最大一张图片,作为  xxxdpi,比如 1024 * 1024,生成如下尺寸的,注意图片里的上下左右空白边距必须大于 4 * 3 / 0.75 = 16 个像素
 *
 * xxxhdpi  1024  4.00x
 *
 *  xxhdpi   768  3.00x
 *
 *   xhdpi   512  2.00x
 *
 *   hdpi    384  1.50x
 *
 *   mdpi    256  1.00x
 *
 *   ldpi    192  0.75x
 *
 * @author NeedJava@126.com
 *
 * @since 2025.07.12
 */
public final class Create9Patch
{
    private static final Item[] ITEMS = new Item[]
    {
        new Item( "drawable-xxxhdpi", 4.00F ),  //1024 = * 4.00F / 4

        new Item( "drawable-xxhdpi",  3.00F ),  // 768 = * 3.00F / 4

        new Item( "drawable-xhdpi",   2.00F ),  // 512 = * 2.00F / 4

        new Item( "drawable-hdpi",    1.50F ),  // 384 = * 1.50F / 4

        new Item( "drawable-mdpi",    1.00F ),  // 256 = * 1.00F / 4

        new Item( "drawable-ldpi",    0.75F ),  // 192 = * 0.75F / 4
    };

    public static void main( final String[] args ) throws Exception
    {
        final java.io.BufferedReader reader = new java.io.BufferedReader( new java.io.InputStreamReader( System.in ) );

        while( true )
        {
            System.err.println( "\r\nPlease input image path and press Enter:" );

            final String source = reader.readLine().trim();

            final File sourceFile = new File( source.startsWith( "\"" ) && source.endsWith( "\"" ) ? source.substring( 1, source.length() - 1 ) : source );

            if( sourceFile.isFile() == false ){ System.err.println( "Image not exists! " + sourceFile ); continue; }

            ////////////////////////////////////////////////////////////////////////////////////////////////////////////

            System.err.println( "\r\nPlease input 9 patch file name and press Enter:" );

            final String outputName = reader.readLine().trim();

            ////////////////////////////////////////////////////////////////////////////////////////////////////////////

            create( sourceFile, outputName );

            System.err.println( "\r\nDONE" );
        }
    }

    public static void create( final File oldFile, final String outputName ) throws Exception
    {
        final File outputFolder = new File( oldFile.getParent(), "output"/*HARD_CODE*/ );

        final String oldName = isTrimEmpty( outputName ) ? "image_splash.9.png"/*HARD_CODE*/ : outputName + ".9.png"/*HARD_CODE*/;

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        final BufferedImage oldImage = ImageIO.read( oldFile );

        final int oldWidth  = oldImage.getWidth();

        final int oldHeight = oldImage.getHeight();

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        Item item = null; File folder = null; int newWidth = 0, newHeight = 0;

        for( int i = 0; i < ITEMS.length; i ++ )
        {
            if( ( item = ITEMS[i] ) == null || isTrimEmpty( item.mFolder ) ){ continue; }

            folder = new File( outputFolder, item.mFolder ); folder.mkdirs();

            newWidth  = (int) ( oldWidth  * item.mTimes / 4/*HARD_CODE*/ );

            newHeight = (int) ( oldHeight * item.mTimes / 4/*HARD_CODE*/ );

            ImageIO.write( scaleImageByWidth( oldImage, newWidth, newHeight ), "png"/*HARD_CODE*/, new File( folder, oldName ) );
        }
    }

    public static BufferedImage scaleImageByWidth( final BufferedImage oldImage, final int newWidth, final int newHeight )
    {
        final BufferedImage newImage = new BufferedImage( newWidth, newHeight, BufferedImage.TYPE_INT_ARGB );

        final Graphics2D g2d = newImage.createGraphics();

        g2d.drawImage( oldImage.getScaledInstance( newWidth, newHeight, Image.SCALE_SMOOTH ), 0, 0, null );

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        g2d.setColor( Color.BLACK );

        //////////////////////////////////////////////////////////// Top

        g2d.drawLine( 1, 0, 2, 0 );

        g2d.drawLine( newWidth - 3, 0, newWidth - 2, 0 );

        //////////////////////////////////////////////////////////// Left

        g2d.drawLine( 0, 1, 0, 2 );

        g2d.drawLine( 0, newHeight - 3, 0, newHeight - 2 );

        //////////////////////////////////////////////////////////// Right

        g2d.drawLine( newWidth - 1, 3, newWidth - 1, newHeight - 4 );

        //////////////////////////////////////////////////////////// Bottom

        g2d.drawLine( 3, newHeight - 1, newWidth - 4, newHeight - 1 );

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        g2d.dispose();

        return newImage;
    }

    public static boolean isTrimEmpty( final String string )
    {
        return ( string == null ? true : string.trim().length() == 0 );
    }
}

final class Item
{
    public final String mFolder;

    public final float mTimes;

    public Item( final String folder, final float times )
    {
        this.mFolder = folder;

        this.mTimes  = times;
    }
}

<think>我们正在讨论Android SplashScreen无法展示圆形图片的问题。根据用户的问题,我们需要分析原因并提供解决方法。 注意:在Android中,SplashScreen API是在Android 12(API级别31)引入的。因此,这个问题可能只出现在Android 12及更高版本上。 原因分析: 1. 默认情况下,SplashScreen使用启动图标(通常来自manifest中设置的app icon)作为启动画面中心的图标。 2. 启动图标在SplashScreen上会以两种方式展示: - 在设备上,图标会被放置在一个圆形背景(称为“镂空图标背景”)上。这个圆形背景的颜色由主题属性`android:windowSplashScreenBackground`定义。 - 图标的周围区域会被裁剪成圆形。但是,如果提供的图标本身不是圆形的,并且没有透明背景,那么裁剪后的效果可能不如预期。 3. 问题可能在于图标资源的本身:如果图标不是按照Android自适应图标的规范(例如,没有使用图层,或者没有提供圆形图标),那么系统在裁剪时可能会显示为方形或者有白色背景。 解决方法: 1. 确保图标资源符合自适应图标的要求。在res/mipmap-anydpi目录下,应该有`ic_launcher.xml`文件(或类似名称),它定义了两个图层:背景层和前景层。这样系统可以裁剪成圆形。 例如: ```xml <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <background android:drawable="@drawable/ic_launcher_background" /> <foreground android:drawable="@drawable/ic_launcher_foreground" /> </adaptive-icon> ``` 2. 如果希望使用自定义的圆形图标,可以尝试将圆形图标作为前景层,并设置背景层为透明或者与前景匹配的颜色。 3. 如果应用只支持Android 12及以上,可以在主题中设置`android:windowSplashScreenAnimatedIcon`属性,指定一个可绘制资源(可以是矢量图、位图等)。然后,确保这个可绘制资源是圆形的(包括透明背景),这样在显示时就不会有额外的背景。 例如,在res/values-v31/themes.xml中: ```xml <style name="Theme.MyApp" parent="..."> ... <item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_splash</item> <!-- 设置背景颜色 --> <item name="android:windowSplashScreenBackground">@color/splash_background</item> </style> ``` 其中`@drawable/ic_splash`应该是一个圆形图标(例如,一个圆形矢量图,或者是一个带有透明背景的圆形PNG)。 4. 如果图标仍然显示为方形,检查矢量图或图片是否本身有非透明的背景。如果是矢量图,确保路径只包含圆形,并且背景是透明的。 5. 另外,还可以通过设置主题属性`android:windowSplashScreenIconBackgroundColor`来调整图标背景颜色(注意:这个属性是在API31中添加的),将其设置为透明或者与图标匹配的颜色。 6. 如果上述方法无效,可以考虑不使用系统自带的SplashScreen,而是使用传统的启动Activity方式(例如,设置一个全屏的theme,并在其中放置一个ImageView,然后在该Activity中跳转到主Activity)。这样,就可以完全控制启动画面的显示,包括使用圆形图片。 注意:如果使用传统方式,需要确保启动时间不会过长,否则可能会出现ANR。 总结: 问题通常是由于启动图标不符合自适应图标规范导致的。解决方法主要是优化图标资源,或者使用自定义的SplashScreen主题属性来指定一个圆形的可绘制资源。 相关问题: 1. 如何为Android 12及以上设备创建自适应启动图标? 2. 如何为不同的Android版本提供不同的启动画面? 3. 如何自定义SplashScreen的动画效果? 4. 如何解决SplashScreen显示延迟或白屏问题? </think>### Android SplashScreen 无法展示圆形图片的原因及解决方法 #### **原因分析** 1. **API 兼容性问题** Android 官方 SplashScreen API (Android 12+) 默认使用 **自适应图标** 显示启动画面 [^1]。自适应图标会被系统强制裁剪为 **圆形/方形** 等形状(取决于设备),但实际效果可能因设备厂商定制而不同: ```xml <!-- 自适应图标规范 --> <adaptive-icon> <background android:drawable="@color/icon_bg"/> <foreground android:drawable="@drawable/icon_fg"/> <!-- 圆形区域 --> </adaptive-icon> ``` 2. **图标设计限制** - 如果图标资源未遵循[自适应图标规范](https://developer.android.com/develop/ui/views/launch/splash-screen#customize-icon)(如透明区域不足) - 非矢量图(如 PNG)在低分辨率设备上边缘锯齿 3. **主题配置错误** 主题未正确声明 `windowSplashScreenAnimatedIcon` 或背景色不匹配: ```xml <!-- 错误示例:未指定圆形图标 --> <style name="Theme.MyApp" parent="..."> <item name="android:windowSplashScreenBackground">@color/white</item> <item name="android:windowSplashScreenAnimatedIcon">@mipmap/ic_launcher</item> </style> ``` 4. **Android 11及以下系统** 旧版系统需使用 `Activity` 模拟启动页,但自定义视图可能被系统缩放变形。 --- #### **解决方法** ##### ✅ 方法 1:遵循自适应图标规范(推荐) 1. **创建圆形前景层** 在 `res/drawable/ic_splash_fg.xml` 中定义圆形矢量图: ```xml <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="108dp" android:height="108dp" android:viewportWidth="108" android:viewportHeight="108"> <path android:fillColor="#FF0000" android:pathData="M54,0A54,54 0 1,1 0,54A54,54 0 0,1 54,0Z"/> </vector> ``` 2. **配置自适应图标** 在 `res/mipmap-anydpi-v26/ic_splash.xml` 声明: ```xml <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <background android:drawable="@android:color/transparent"/> <!-- 透明背景 --> <foreground android:drawable="@drawable/ic_splash_fg"/> <!-- 圆形前景 --> </adaptive-icon> ``` 3. **更新主题** ```xml <style name="Theme.MyApp.Splash" parent="Theme.SplashScreen"> <item name="android:windowSplashScreenBackground">@color/splash_bg</item> <item name="android:windowSplashScreenAnimatedIcon">@mipmap/ic_splash</item> <item name="android:windowSplashScreenIconBackgroundColor">@android:color/transparent</item> </style> ``` ##### ✅ 方法 2:自定义启动页(兼容旧版) 1. **创建 SplashActivity** 使用独立 `Activity` 并设置全屏主题: ```java public class SplashActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 使用自定义布局显示圆形图片 setContentView(R.layout.activity_splash); // 跳转主页面 startActivity(new Intent(this, MainActivity.class)); finish(); } } ``` 2. **布局中强制圆形裁剪** ```xml <ImageView android:layout_width="120dp" android:layout_height="120dp" android:src="@drawable/logo" android:scaleType="centerCrop" android:clipToOutline="true" android:outlineProvider="background" /> <!-- 关键属性 --> ``` ##### ✅ 方法 3:修复常见问题 - **边缘锯齿问题**:使用 **矢量图(SVG)** 替代位图 - **背景色穿透**:确保主题中 `windowSplashScreenBackground` 与图标背景色一致 - **测试覆盖**:在 `res/mipmap-anydpi-v26` 和 `res/mipmap-hdpi` 等目录放置不同分辨率资源 --- #### **验证步骤** 1.Android 12+ 设备/模拟器上测试 2. 检查 Logcat 过滤 `SplashScreen` 关键字,排查系统警告 3. 使用 [Material Icon Generator](https://github.com/android/splashscreen-samples) 调试图标 > 官方文档参考:[SplashScreen API 指南](https://developer.android.com/develop/ui/views/launch/splash-screen)[^1]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值