如何在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
相关产品推荐
相关产品推荐

