You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在NativeScript-Angular项目中创建自定义及高级形状

实现NativeScript-Angular中的自定义摇篮式标签栏形状

要实现你需要的带圆形镂空的矩形标签栏背景(让中间区域透明,底层页面内容可透过显示),NativeScript本身没有内置复杂形状的直接支持,但可以通过调用原生平台API结合Angular组件封装来灵活实现。下面是具体的方案和步骤:

核心思路

我们需要为标签栏创建一个“矩形挖去圆形”的自定义背景,让中间圆形区域完全透明。最佳方式是利用Android和iOS的原生绘图能力,分别实现对应平台的自定义形状,再在Angular组件中集成适配。

方案一:原生平台自定义视图(推荐)

1. Android端:自定义Drawable绘制镂空形状

创建一个继承自Android原生Drawable的类,通过画笔模式实现圆形镂空:

// src/app/components/cradle-tab-bar/android-drawable.ts
import { android } from "@nativescript/core";

export class CradleTabBarDrawable extends android.graphics.drawable.Drawable {
    private paint: android.graphics.Paint;
    private circleRadius = 40; // 可根据你的按钮大小调整

    constructor() {
        super();
        this.paint = new android.graphics.Paint();
        this.paint.setColor(android.graphics.Color.parseColor("#FFFFFF")); // 标签栏背景色
        this.paint.setAntiAlias(true);
    }

    public draw(canvas: android.graphics.Canvas): void {
        const bounds = this.getBounds();
        // 先绘制完整的矩形背景
        canvas.drawRect(bounds.left, bounds.top, bounds.right, bounds.bottom, this.paint);
        
        // 切换画笔模式为CLEAR,绘制圆形实现镂空
        this.paint.setXfermode(new android.graphics.PorterDuffXfermode(android.graphics.PorterDuff.Mode.CLEAR));
        const circleCenterX = bounds.centerX();
        // 圆形顶部与标签栏顶部对齐,保证镂空区域刚好容纳中间按钮
        canvas.drawCircle(circleCenterX, bounds.top + this.circleRadius, this.circleRadius, this.paint);
        
        // 恢复画笔默认模式,避免影响后续绘制
        this.paint.setXfermode(null);
    }

    public setAlpha(alpha: number): void {
        this.paint.setAlpha(alpha);
    }

    public setColorFilter(colorFilter: android.graphics.ColorFilter): void {
        this.paint.setColorFilter(colorFilter);
    }

    public getOpacity(): number {
        return android.graphics.PixelFormat.TRANSLUCENT;
    }
}

2. iOS端:用CAShapeLayer创建镂空路径

iOS通过CAShapeLayer和路径运算实现镂空效果:

// src/app/components/cradle-tab-bar/ios-shape-layer.ts
import { ios } from "@nativescript/core";

export class CradleTabBarShapeLayer extends CAShapeLayer {
    private circleRadius = 40;

    constructor() {
        super();
        this.fillColor = UIColor.whiteColor().CGColor; // 标签栏背景色
    }

    public updateShape(frame: CGRect): void {
        this.frame = frame;
        // 创建矩形路径作为基础背景
        const rectPath = UIBezierPath.bezierPathWithRect(frame);
        // 创建圆形路径(中心位置,顶部与标签栏对齐)
        const circleCenter = CGPointMake(frame.size.width / 2, this.circleRadius);
        const circlePath = UIBezierPath.bezierPathWithArcCenter(circleCenter, radius: this.circleRadius, startAngle: 0, endAngle: CGFloat(2 * Math.PI), clockwise: true);
        
        // 将圆形路径从矩形路径中减去,开启奇偶填充规则实现镂空
        rectPath.appendPath(circlePath);
        rectPath.usesEvenOddFillRule = true;
        this.path = rectPath.CGPath;
        this.fillRule = kCAFillRuleEvenOdd;
    }
}

3. 在Angular组件中集成跨平台视图

创建一个Angular组件,根据当前平台加载对应的自定义形状视图:

// src/app/components/cradle-tab-bar/cradle-tab-bar.component.ts
import { Component, ElementRef, OnInit } from "@angular/core";
import { isAndroid, isIOS } from "@nativescript/core";
import { CradleTabBarDrawable } from "./android-drawable";
import { CradleTabBarShapeLayer } from "./ios-shape-layer";

@Component({
    selector: "app-cradle-tab-bar",
    template: `<GridLayout rows="*" columns="*" #shapeContainer></GridLayout>`
})
export class CradleTabBarComponent implements OnInit {
    constructor(private elementRef: ElementRef) {}

    ngOnInit(): void {
        const container = this.elementRef.nativeElement;
        if (isAndroid) {
            const drawable = new CradleTabBarDrawable();
            container.android.setBackground(drawable);
        } else if (isIOS) {
            const shapeLayer = new CradleTabBarShapeLayer();
            shapeLayer.updateShape(container.ios.bounds);
            container.ios.layer.addSublayer(shapeLayer);
            
            // 监听布局变化,确保形状随标签栏尺寸调整
            container.on("layoutChanged", () => {
                shapeLayer.updateShape(container.ios.bounds);
            });
        }
    }
}

4. 整合到你的标签栏布局

将自定义形状组件与左中右标签布局结合,注意设置标签栏容器为透明:

<!-- src/app/components/tab-bar/tab-bar.component.html -->
<GridLayout rows="60" columns="*, auto, *" backgroundColor="transparent">
    <!-- 左侧标签组 -->
    <StackLayout col="0" row="0" horizontalAlignment="center" verticalAlignment="center">
        <Label text="首页" class="tab-label"></Label>
    </StackLayout>
    <!-- 中间圆形按钮(悬浮在镂空区域上方) -->
    <StackLayout col="1" row="0" width="80" height="80" borderRadius="40" backgroundColor="#2196F3" verticalAlignment="top">
        <Label text="+" class="center-btn-text"></Label>
    </StackLayout>
    <!-- 右侧标签组 -->
    <StackLayout col="2" row="0" horizontalAlignment="center" verticalAlignment="center">
        <Label text="我的" class="tab-label"></Label>
    </StackLayout>
    <!-- 自定义镂空背景(覆盖整个标签栏) -->
    <app-cradle-tab-bar row="0" colSpan="3" />
</GridLayout>

方案二:尝试社区插件简化开发

如果不想编写原生代码,可以尝试社区维护的形状插件(如nativescript-shapes),但这类插件对复杂镂空形状的支持有限,若需求特殊,还是原生方案更可靠。

关键注意事项

  • 确保标签栏容器的backgroundColor设置为transparent,否则底层内容无法透过镂空区域显示。
  • 根据实际按钮尺寸调整circleRadius参数,保证圆形镂空区域与中间按钮完全匹配。
  • Android端需确保页面窗口支持透明(可在AndroidManifest.xml中对应Activity添加android:background="@android:color/transparent")。

内容的提问来源于stack exchange,提问作者Andrew Pugach

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 13:02:32