ArkTs-按压动画效果

发布于:2025-06-23 ⋅ 阅读:(20) ⋅ 点赞:(0)

背景

很多地方的点击事件并非使用的Button,在点击时没有按压动效的反馈;想要用一种通用的方式,为现有控件增加按压动效。

方案

1.多态样式(只支持通用属性,未实现想要的效果)

多态样式可以将固定的一套样式整理在一起,不同的UI直接使用即可。但实现上有局限性:

  • 1.多态样式仅支持通用属性。如果多态样式不生效,则该属性可能为组件的私有属性,例如:fontColor、TextInput组件的backgroundColor等。
  • 2.多态样式必须直接使用,不能在使用过程中插入其他的逻辑,如数据计算等。

官方文档在多态样式的描述中推荐使用动态属性来实现多态样式无法实现的效果。

2.动态属性设置

状态变化的控制通过动态属性进行设置,优点是:

  • 1.动态属性的modifier自带状态的区分,按照需求实现对应的方法即可
动态属性设置
1.不同控件使用时,需实现对应控件的Modifier,系统api命名格式为:XxxAttribute
2.modifier可实现的状态:普通状态、按压状态、获焦状态、禁用状态、选中状态
  • 2.动态属性的方法实现时,可在赋值前做其他数据处理

  • 3.动态属性使用上对原有结构的侵入性不强

动态属性使用
在对应控件下调用.attributeModifier(new XxxModifier)

遮罩效果实现
尝试了color、foreground相关的api,都无法实现遮罩的效果;只有backgoundColor可以改变整体的颜色变化。遮罩的色值为10%的黑色,使用ColorMetrics将10%的黑色与当前控件的背景色进行叠加,设置给pressed状态,实现按压效果;恢复normal状态时,将背景色修改回去。

代码实现如下:

import { ColorMetrics } from "@kit.ArkUI";

/**
 * 按压效果组件-Column
 */
export class ColumnPressModifier implements AttributeModifier<ColumnAttribute> {
    private backgroundColor: ResourceColor = '#ffffff';

    constructor(backgroundColor: ResourceColor) {
        this.backgroundColor = backgroundColor;
    }

    applyNormalAttribute(instance: ColumnAttribute): void {
        instance.backgroundColor(this.backgroundColor);
    }

    applyPressedAttribute(instance: ColumnAttribute): void {
        let bgColor = ColorMetrics.resourceColor(this.backgroundColor)
            .blendColor(ColorMetrics.resourceColor('#1A000000'));
        instance.backgroundColor(bgColor.color);
    }
}

// 使用示例
// @Entry
// @Component
// struct AttributePressedDemo {
//     build() {
//         Row() {
//             Column() {
//                 Button("Button")
//                     .attributeModifier(new MyCommonModifier(this.backgroundColor))
//             }
//             .width('100%')
//         }
//         .height('100%')
//     }
// }}
PS:自定义按压效果组件

原理:
1.将UI嵌套在一个容器内,上层盖一个同样大小的view作为阴影遮罩
2.实现onTouch监听用户操作,判断是否为按压事件;同时改变阴影遮罩背景色
3.增加动画,使过度更自然

代码实现如下:

/**
 * 动画时间
 */
const ANIM_DURATION = 100;

/**
 * 按压效果组件
 */
@Component
export struct PressEffectComponent {
    /* ----------------  传入的参数  ------------------- */
    /**
     * 自定义 UI
     */
    @BuilderParam customUI: () => void;
    /**
     * 圆角半径
     */
    @Prop radius: number = 0;
    /**
     * 点击事件
     */
    clickEvent?: () => void = () => {};

    /* ----------------  控件状态  ------------------- */
    /**
     * 按压状态
     */
    @State @Watch('onPressedChanged') pressed: boolean = false;
    /**
     * 页面背景色
     */
    @State pageBackgroundColor: string = '#00000000';
    /**
     * 自定义UI宽度
     */
    customUIWidth: number = 0;
    /**
     * 自定义UI高度
     */
    customUIHeight: number = 0;
    /**
     * 上一次触摸点坐标x
     */
    lastX: number = 0;
    /**
     * 上一次触摸点坐标y
     */
    lastY: number = 0;

    /**
     * 按压状态变化时触发
     */
    onPressedChanged() {
        // 背景颜色动画
        animateTo({
            duration: ANIM_DURATION,
            curve: Curve.Linear,
            iterations: 1,
            playMode: PlayMode.Normal
        }, () => {
            this.pageBackgroundColor = this.pressed ? '#1A000000' : '#00000000'; // 叠加10%透明度的黑色
        })
    }

    /**
     * 判断触摸点是否在组件内
     * @returns boolean 是否在组件内
     */
    private isInView(): boolean {
        return this.lastX >= 0 &&
            this.lastY >= 0 &&
            this.lastX <= this.customUIWidth &&
            this.lastY <= this.customUIHeight;
    }

    build() {
        Column() {
            // UI
            this.customUI();
            // 按压遮罩层
            Blank()
                .width(this.customUIWidth)
                .height(this.customUIHeight)
                .borderRadius(this.radius)
                .backgroundColor(this.pageBackgroundColor)
                .position({
                    top: 0,
                    left: 0
                });
        }
        .onTouch((event: TouchEvent) => {
            if (event.type === TouchType.Down) {
                try {
                    this.lastX = event.touches[0].x;
                    this.lastY = event.touches[0].y;
                } catch (e) {
                    Log.info(TAG, `onTouch: TouchType.Down error: ${e.message}`);
                }
                this.pressed = true;
            } else if (event.type === TouchType.Up) {
                this.pressed = false;
                if (this.isInView()) {
                    this.clickEvent?.();
                }
            } else if (event.type === TouchType.Move) {
                try {
                    this.lastX = event.touches[0].x;
                    this.lastY = event.touches[0].y;
                } catch (e) {
                    Log.info(TAG, `onTouch: TouchType.Move error: ${e.message}`);
                }
            } else {
                this.pressed = false;
            }
        })
        .onAreaChange((_: Area, value: Area) => {
            this.customUIWidth = Number(value.width);
            this.customUIHeight = Number(value.height);
        });
    }
}

// 使用示例
// @Entry
// @Component
// struct ExamplePage {
//     build() {
//         Column() {
//             PressEffectComponent({
//                 customUI: () => {
//                     Text('组件方式实现')
//                         .fontSize(20)
//                         .padding(20)
//                         .backgroundColor(Color.Green)
//                         .borderRadius(10)
//                 },
//                 clickEvent: () => {
//                     console.log('点击事件触发')
//                 }
//             })
//         }
//     }
// }

网站公告

今日签到

点亮在社区的每一天
去签到