पसंद के मुताबिक हैप्टिक इफ़ेक्ट बनाएं

Android में कस्टम हैप्टिक इफ़ेक्ट बनाने के बारे में उदाहरण और दिशा-निर्देश दिए गए हैं. इनमें टाइमलाइन ऐंकर की गई कंपोज़िशन, VibrationEffect.Builder, कस्टम वाइब्रेशन पैटर्न, और अडवांस वेवफ़ॉर्म एनवलप शामिल हैं. keywords_public: > Android, हैप्टिक, कस्टम इफ़ेक्ट, वाइब्रेशन, हैप्टिक एपीआई, वाइब्रेशन पैटर्न, VibrationEffect.Builder, कंपोज़िशन, हैप्टिक प्रिमिटिव, वेवफ़ॉर्म एनवलप, यूज़र इंटरफ़ेस (यूआई)

इस पेज पर, Android ऐप्लिकेशन में स्टैंडर्ड वाइब्रेशन वेवफ़ॉर्म से अलग कस्टम इफ़ेक्ट बनाने के लिए, अलग-अलग हैप्टिक एपीआई इस्तेमाल करने के उदाहरण दिए गए हैं.

इस पेज पर ये उदाहरण दिए गए हैं:

ज़्यादा उदाहरणों के लिए, इवेंट में हैप्टिक फ़ीडबैक जोड़ना लेख पढ़ें. साथ ही, हमेशा हैप्टिक डिज़ाइन के सिद्धांतों का पालन करें.

डिवाइस के साथ काम करने से जुड़ी समस्याओं को हल करने के लिए फ़ॉलबैक का इस्तेमाल करना

कस्टम हैप्टिक इफ़ेक्ट लागू करते समय, डिवाइस के साथ काम करने की सुविधा और फ़ॉलबैक का व्यवहार, आपके चुने गए एपीआई प्लैटफ़ॉर्म पर निर्भर करता है:

  • VibrationEffect.Builder (सुझाया गया): Android 16 (26Q4) से, VibrationEffect.Builder का इस्तेमाल करके बनाए गए इफ़ेक्ट में फ़्रेमवर्क-लेवल पर अपने-आप फ़ॉलबैक होने की सुविधा शामिल होती है. अगर कोई डिवाइस, अनुरोध किए गए Preset या बुनियादी Envelope को नेटिव तौर पर सपोर्ट नहीं करता है, तो फ़्रेमवर्क, वीडियो चलाने के समय उसे अपने-आप किसी सही विकल्प में बदल देता है. VibrationEffect.Builder की मदद से बनाए गए इफ़ेक्ट चलाने से पहले, आपको हर प्रिमिटिव डिवाइस की क्षमताओं को मैन्युअल तरीके से जांचने की ज़रूरत नहीं है.
    • अपवाद: WaveformEnvelopeBuilder की मदद से बनाए गए ऐडवांस वेवफ़ॉर्म एनवलप में, ऑटोमैटिक फ़ॉलबैक की सुविधा काम नहीं करती. इसकी वजह यह है कि ये खास हार्डवेयर फ़्रीक्वेंसी मैपिंग (FOAM) पर निर्भर करते हैं. अगर ये फ़ाइलें काम नहीं करती हैं, तो इन्हें नहीं चलाया जा सकेगा.
  • VibrationEffect.Composition: startComposition() API की मदद से बनाई गई कंपोज़िशन में, फ़ॉलबैक की सुविधा अपने-आप काम नहीं करती. अगर किसी कंपोज़िशन में एक भी ऐसी प्रिमिटिव है जो काम नहीं करती, तो पूरा वाइब्रेशन नहीं चलेगा. आपको vibrator.arePrimitivesSupported() का इस्तेमाल करके, मैन्युअल तरीके से क्षमताओं की जांच करनी होगी.
  • ऐम्प्लिट्यूड कंट्रोल के साथ वेवफ़ॉर्म: जिन डिवाइसों में ऐम्प्लिट्यूड कंट्रोल की सुविधा नहीं होती है उनमें नॉन-ज़ीरो ऐम्प्लिट्यूड को 100% तक राउंड अप किया जाता है. जांच करें vibrator.hasAmplitudeControl() और अगर ज़रूरी हो, तो साफ़ तौर पर डिज़ाइन किए गए चालू/बंद पैटर्न पर वापस जाएं.

हैप्टिक प्रिमिटिव का इस्तेमाल

Android में कई हैप्टिक प्रिमिटिव शामिल हैं. ये ऐम्प्लिट्यूड और फ़्रीक्वेंसी, दोनों में अलग-अलग होते हैं. रिच हैप्टिक इफ़ेक्ट पाने के लिए, एक प्रिमिटिव का इस्तेमाल अकेले किया जा सकता है या कई प्रिमिटिव को एक साथ इस्तेमाल किया जा सकता है.

  • दो प्रिमिटिव के बीच अंतर दिखाने के लिए, 50 मि॰से॰ या इससे ज़्यादा का समय लगाएं. अगर हो सके, तो प्रिमिटिव की अवधि को भी ध्यान में रखें.
  • ऐसी स्केल का इस्तेमाल करें जिनमें 1.4 या इससे ज़्यादा का अनुपात हो, ताकि इंटेंसिटी में अंतर को बेहतर तरीके से समझा जा सके.
  • किसी प्रिमिटिव का कम, सामान्य, और ज़्यादा इंटेंसिटी वाला वर्शन बनाने के लिए, 0.5, 0.7, और 1.0 स्केल का इस्तेमाल करें.

वाइब्रेशन के कस्टम पैटर्न बनाना

वाइब्रेशन पैटर्न का इस्तेमाल अक्सर ध्यान खींचने वाले हैप्टिक में किया जाता है. जैसे, सूचनाएँ और रिंगटोन. Vibrator सेवा, लंबे समय तक वाइब्रेशन के पैटर्न चला सकती है. इससे समय के साथ वाइब्रेशन की तीव्रता में बदलाव होता है. इस तरह के इफ़ेक्ट को वेवफ़ॉर्म कहा जाता है.

आम तौर पर, वेवफ़ॉर्म इफ़ेक्ट को सुना जा सकता है. हालांकि, अगर शांत माहौल में अचानक लंबे समय तक वाइब्रेशन होता है, तो इससे उपयोगकर्ता डर सकता है. टारगेट ऐम्प्लिट्यूड तक बहुत तेज़ी से पहुंचने पर, सुनाई देने वाली गड़गड़ाहट की आवाज़ें भी आ सकती हैं. रैंप अप और डाउन इफ़ेक्ट बनाने के लिए, ऐंप्लीट्यूड ट्रांज़िशन को स्मूद करने के लिए वेवफ़ॉर्म पैटर्न डिज़ाइन करें.

वाइब्रेशन पैटर्न के उदाहरण

यहां दिए गए सेक्शन में, वाइब्रेशन पैटर्न के कई उदाहरण दिए गए हैं:

रैंप-अप पैटर्न

वेवफ़ॉर्म को VibrationEffect के तौर पर दिखाया जाता है. इसमें तीन पैरामीटर होते हैं:

  1. समय: हर वेवफ़ॉर्म सेगमेंट के लिए, मिलीसेकंड में अवधि का एक कलेक्शन.
  2. ऐम्प्लिट्यूड: पहले आर्ग्युमेंट में बताई गई हर अवधि के लिए, वाइब्रेशन का मनचाहा ऐम्प्लिट्यूड. इसे 0 से 255 के बीच की पूर्णांक वैल्यू से दिखाया जाता है. इसमें 0 का मतलब है कि वाइब्रेटर "बंद है" और 255 का मतलब है कि डिवाइस का वाइब्रेटर सबसे ज़्यादा ऐम्प्लिट्यूड पर वाइब्रेट कर रहा है.
  3. दोहराने का इंडेक्स: यह ऐरे में मौजूद वह इंडेक्स होता है जिसे पहले आर्ग्युमेंट में बताया गया है. इस इंडेक्स से वेवफ़ॉर्म को दोहराना शुरू किया जाता है. अगर पैटर्न को सिर्फ़ एक बार चलाना है, तो -1 डालें.

यहां एक वेवफ़ॉर्म का उदाहरण दिया गया है, जिसमें दो बार पल्स होता है. दोनों पल्स के बीच 350 मि॰से॰ का पॉज़ होता है. पहले पल्स में, ऐम्प्लिट्यूड को धीरे-धीरे बढ़ाया जाता है और दूसरे पल्स में, ऐम्प्लिट्यूड को तेज़ी से बढ़ाया जाता है, ताकि इसे ज़्यादा से ज़्यादा समय तक बनाए रखा जा सके. आखिरी में रुकने की सुविधा, नेगेटिव रिपीट इंडेक्स वैल्यू से तय होती है.

Kotlin

val timings: LongArray = longArrayOf(
    50, 50, 50, 50, 50, 100, 350, 25, 25, 25, 25, 200)
val amplitudes: IntArray = intArrayOf(
    33, 51, 75, 113, 170, 255, 0, 38, 62, 100, 160, 255)
val repeatIndex = -1 // Don't repeat.

vibrator.vibrate(VibrationEffect.createWaveform(
    timings, amplitudes, repeatIndex))

Java

long[] timings = new long[] {
    50, 50, 50, 50, 50, 100, 350, 25, 25, 25, 25, 200 };
int[] amplitudes = new int[] {
    33, 51, 75, 113, 170, 255, 0, 38, 62, 100, 160, 255 };
int repeatIndex = -1; // Don't repeat.

vibrator.vibrate(VibrationEffect.createWaveform(
    timings, amplitudes, repeatIndex));

बार-बार होने वाला पैटर्न

जब तक रद्द नहीं किया जाता, तब तक वेवफ़ॉर्म को बार-बार चलाया जा सकता है. बार-बार दोहराई जाने वाली वेवफ़ॉर्म बनाने के लिए, एक नॉन-नेगेटिव repeat पैरामीटर सेट करें. बार-बार होने वाली वेवफ़ॉर्म चलाने पर, वाइब्रेशन तब तक जारी रहता है, जब तक सेवा में इसे साफ़ तौर पर रद्द नहीं किया जाता:

Kotlin

void startVibrating() {
val timings: LongArray = longArrayOf(50, 50, 100, 50, 50)
val amplitudes: IntArray = intArrayOf(64, 128, 255, 128, 64)
val repeat = 1 // Repeat from the second entry, index = 1.
VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
    timings, amplitudes, repeat)
// repeatingEffect can be used in multiple places.

vibrator.vibrate(repeatingEffect)
}

void stopVibrating() {
vibrator.cancel()
}

Java

void startVibrating() {
long[] timings = new long[] { 50, 50, 100, 50, 50 };
int[] amplitudes = new int[] { 64, 128, 255, 128, 64 };
int repeat = 1; // Repeat from the second entry, index = 1.
VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
    timings, amplitudes, repeat);
// repeatingEffect can be used in multiple places.

vibrator.vibrate(repeatingEffect);
}

void stopVibrating() {
vibrator.cancel();
}

यह रुक-रुक कर होने वाले ऐसे इवेंट के लिए बहुत काम का है जिनके लिए उपयोगकर्ता को कार्रवाई करके पुष्टि करनी होती है. इस तरह के इवेंट के उदाहरणों में, इनकमिंग फ़ोन कॉल और ट्रिगर किए गए अलार्म शामिल हैं.

फ़ॉलबैक वाला पैटर्न

वाइब्रेशन के ऐम्प्लिट्यूड को कंट्रोल करना, हार्डवेयर पर निर्भर करने वाली सुविधा है. इस सुविधा के बिना, कम कॉन्फ़िगरेशन वाले डिवाइस पर वेवफ़ॉर्म चलाने से, डिवाइस हर पॉज़िटिव एंट्री के लिए ज़्यादा से ज़्यादा ऐम्प्लिट्यूड पर वाइब्रेट होता है. अगर आपके ऐप्लिकेशन को ऐसे डिवाइसों के साथ काम करना है, तो ऐसा पैटर्न इस्तेमाल करें जो इस स्थिति में चलने पर गड़गड़ाहट की आवाज़ न करे. इसके अलावा, चालू/बंद करने का कोई ऐसा आसान पैटर्न डिज़ाइन करें जिसे फ़ॉलबैक के तौर पर चलाया जा सके.

Kotlin

if (vibrator.hasAmplitudeControl()) {
  vibrator.vibrate(VibrationEffect.createWaveform(
    smoothTimings, amplitudes, smoothRepeatIdx))
} else {
  vibrator.vibrate(VibrationEffect.createWaveform(
    onOffTimings, onOffRepeatIdx))
}

Java

if (vibrator.hasAmplitudeControl()) {
  vibrator.vibrate(VibrationEffect.createWaveform(
    smoothTimings, amplitudes, smoothRepeatIdx));
} else {
  vibrator.vibrate(VibrationEffect.createWaveform(
    onOffTimings, onOffRepeatIdx));
}

VibrationEffect.Builder के साथ टाइमलाइन में एंकर की गई कंपोज़िशन

Android 16 (26Q4) से, VibrationEffect.Builder, वाइब्रेशन के जटिल इफ़ेक्ट और कंपोज़िशन बनाने के लिए पसंदीदा एपीआई है. इसकी मदद से, हैप्टिक के अलग-अलग एलिमेंट को startTimeMillis के साथ एक तय समयसीमा में क्रम से लगाकर, हैप्टिक की अलग-अलग तरह की संवेदनाएं तैयार की जा सकती हैं.

VibrationEffect.Builder में वाइब्रेशन के कई टाइप को एक साथ इस्तेमाल किया जा सकता है:

  • प्रीसेट: पहले से तय किए गए हैप्टिक पल्स (VibrationEffect.Preset), जैसे कि क्लिक और टिक.
  • एनवलप: डाइनैमिक कंटीन्यूअस वेवफ़ॉर्म. इनमें हार्डवेयर-एग्नोस्टिक बेसिक एनवलप (BasicEnvelopeBuilder) और अडवांस फ़्रीक्वेंसी-मॉड्यूलेटेड एनवलप (WaveformEnvelopeBuilder) शामिल हैं.
  • मौजूदा VibrationEffects: प्रिमिटिव कंपोज़िशन (VibrationEffect.Composition), स्टेप वेवफ़ॉर्म (VibrationEffect.createWaveform), पहले से तय किए गए इफ़ेक्ट (VibrationEffect.createPredefined), और वन-शॉट (VibrationEffect.createOneShot) को addEvents() या कंस्ट्रक्टर कॉपी करने की सुविधा का इस्तेमाल करके इंपोर्ट किया जाता है.
  • दोहराई जाने वाली सीक्वेंस: setRepeatingEffect() के साथ कॉन्फ़िगर किए गए लगातार पैटर्न.

VibrationEffect.Builder की मदद से बनाए गए वाइब्रेशन में, फ़्रेमवर्क-लेवल का अपने-आप फ़ॉलबैक होने वाला सिस्टम पहले से मौजूद होता है. यह सिस्टम, वाइब्रेशन के सभी एलिमेंट (जैसे कि प्रीसेट, बेसिक एनवलप, और कंबाइंड वाइब्रेशन इफ़ेक्ट) पर लागू होता है. इससे अलग-अलग डिवाइसों पर उपयोगकर्ताओं को एक जैसा अनुभव मिलता है. इसके लिए, मैन्युअल तरीके से वाइब्रेशन की सुविधा की जांच करने की ज़रूरत नहीं होती. (WaveformEnvelopeBuilder की मदद से बनाए गए ऐडवांस वेवफ़ॉर्म एनवलप के लिए, हार्डवेयर सपोर्ट की ज़रूरत होती है. साथ ही, इनमें अपने-आप फ़ॉलबैक होने की सुविधा नहीं होती.)

प्रीसेट की मदद से कंपोज़ करना

कंपोज़िशन में, पहले से तय किए गए छोटे हैप्टिक पल्स (जैसे कि PRESET_CLICK, PRESET_TICK या PRESET_LOW_TICK) जोड़ने के लिए, VibrationEffect.Preset का इस्तेमाल करें. डिवाइस बनाने वाली कंपनी, प्रीसेट लागू करती है. इससे आपको साफ़ हैप्टिक के लिए, हैप्टिक सिद्धांतों के मुताबिक, तेज़, कम समय के लिए, और अच्छा वाइब्रेशन मिलता है. इन सुविधाओं और इनके काम करने के तरीके के बारे में ज़्यादा जानने के लिए, वाइब्रेशन ऐक्टuator के बारे में बुनियादी जानकारी लेख पढ़ें.

प्रीसेट, VibrationEffect.Composition एपीआई के शॉर्ट प्रिमिटिव की जगह लेते हैं. अगर आपको लंबे समय तक या लगातार हैप्टिक सेंसेशन देना है, तो एनवेलप वेवफ़ॉर्म (पीडब्ल्यूएलई) का इस्तेमाल करें. जैसे, हैप्टिक सेंसेशन को धीरे-धीरे बढ़ाना और फिर धीरे-धीरे कम करना.

हर प्रीसेट को 0.0f से 1.0f के बीच का कोई भी स्केल असाइन किया जा सकता है. साथ ही, इसे कंपोज़िशन की शुरुआत से एक तय समय (मिलीसेकंड में) पर रखा जाता है.

Kotlin

val clickPreset = VibrationEffect.Preset.create(
    VibrationEffect.Preset.PRESET_CLICK, /* scale= */ 0.8f
)
val tickPreset = VibrationEffect.Preset.create(
    VibrationEffect.Preset.PRESET_TICK, /* scale= */ 0.5f
)

val effect = VibrationEffect.Builder()
    .addPreset(/* startTimeMillis= */ 0L, clickPreset)
    .addPreset(/* startTimeMillis= */ 100L, tickPreset)
    .build()

vibrator.vibrate(effect)

Java

VibrationEffect.Preset clickPreset = VibrationEffect.Preset.create(
    VibrationEffect.Preset.PRESET_CLICK, /* scale= */ 0.8f
);
VibrationEffect.Preset tickPreset = VibrationEffect.Preset.create(
    VibrationEffect.Preset.PRESET_TICK, /* scale= */ 0.5f
);

VibrationEffect effect = new VibrationEffect.Builder()
    .addPreset(/* startTimeMillis= */ 0L, clickPreset)
    .addPreset(/* startTimeMillis= */ 100L, tickPreset)
    .build();

vibrator.vibrate(effect);

एनवलप और प्रीसेट की मदद से कंपोज़ करना

VibrationEffect.Envelope इंस्टेंस (BasicEnvelopeBuilder या WaveformEnvelopeBuilder का इस्तेमाल करके बनाए गए) को प्रीसेट के साथ आसानी से जोड़ा जा सकता है. इससे, कई सेगमेंट वाले हैप्टिक पैटर्न बनाए जा सकते हैं.

यहां एक ऐसा उदाहरण दिया गया है जिसमें पहले धीरे-धीरे आवाज़ बढ़ती है और फिर धीरे-धीरे कम होती है. इसके बाद, एक तेज़ क्लिक प्रीसेट बजता है:

Kotlin

val basicEnvelope = VibrationEffect.Envelope.create(
    VibrationEffect.BasicEnvelopeBuilder()
        .setInitialSharpness(0.0f)
        .addControlPoint(1.0f, 1.0f, 300L)
        .addControlPoint(0.0f, 0.5f, 100L)
)
val clickPreset = VibrationEffect.Preset.create(
    VibrationEffect.Preset.PRESET_CLICK, 0.9f
)

val mixedEffect = VibrationEffect.Builder()
    .addEnvelope(/* startTimeMillis= */ 0L, basicEnvelope)
    .addPreset(/* startTimeMillis= */ 450L, clickPreset)
    .build()

vibrator.vibrate(mixedEffect)

Java

VibrationEffect.Envelope basicEnvelope = VibrationEffect.Envelope.create(
    new VibrationEffect.BasicEnvelopeBuilder()
        .setInitialSharpness(0.0f)
        .addControlPoint(1.0f, 1.0f, 300L)
        .addControlPoint(0.0f, 0.5f, 100L)
);
VibrationEffect.Preset clickPreset = VibrationEffect.Preset.create(
    VibrationEffect.Preset.PRESET_CLICK, 0.9f
);

VibrationEffect mixedEffect = new VibrationEffect.Builder()
    .addEnvelope(/* startTimeMillis= */ 0L, basicEnvelope)
    .addPreset(/* startTimeMillis= */ 450L, clickPreset)
    .build();

vibrator.vibrate(mixedEffect);

मौजूदा इवेंट को फिर से इस्तेमाल करना और उन्हें दूसरी जगह ले जाना

किसी मौजूदा VibrationEffect (इसमें VibrationEffect.Composition भी शामिल है) का फिर से इस्तेमाल करने या उसे जोड़ने के लिए, getEvents() का इस्तेमाल करके VibrationEffect.Event ऑब्जेक्ट की सूची पाएं. इसके बाद, addEvents(startTimeShiftMillis, events) का इस्तेमाल करके, उन्हें ऑफ़सेट के साथ जोड़ें. इसके अलावा, इफ़ेक्ट को सीधे VibrationEffect.Builder(effect) कंस्ट्रक्टर को भी पास किया जा सकता है. इस तरीके से VibrationEffect.Composition इंस्टेंस इंपोर्ट करने पर, फ़्रेमवर्क उनके प्रिमिटिव को प्रीसेट में अपने-आप बदल देता है. इससे रनटाइम फ़ॉलबैक की सुविधा चालू हो जाती है.

Kotlin

val existingEffect = VibrationEffect.Builder()
    .addPreset(
        0L,
        VibrationEffect.Preset.create(VibrationEffect.Preset.PRESET_CLICK)
    )
    .addPreset(
        80L,
        VibrationEffect.Preset.create(VibrationEffect.Preset.PRESET_TICK)
    )
    .build()

// Shift and append the existing events 200ms into the new composition.
val combinedEffect = VibrationEffect.Builder()
    .addEvents(/* startTimeShiftMillis= */ 200L, existingEffect.events)
    .build()

vibrator.vibrate(combinedEffect)

Java

VibrationEffect existingEffect = new VibrationEffect.Builder()
    .addPreset(
        0L,
        VibrationEffect.Preset.create(VibrationEffect.Preset.PRESET_CLICK)
    )
    .addPreset(
        80L,
        VibrationEffect.Preset.create(VibrationEffect.Preset.PRESET_TICK)
    )
    .build();

// Shift and append the existing events 200ms into the new composition.
VibrationEffect combinedEffect = new VibrationEffect.Builder()
    .addEvents(/* startTimeShiftMillis= */ 200L, existingEffect.getEvents())
    .build();

vibrator.vibrate(combinedEffect);

बार-बार होने वाली कंपोज़िशन बनाना

किसी कंपोज़िशन में बार-बार दिखने वाला पैटर्न जोड़ने के लिए, setRepeatingEffect(startTimeMillis, repeatingEffect, durationMillis) का इस्तेमाल करें:

Kotlin

val repeatingPattern = VibrationEffect.Builder()
    .addPreset(
        0L,
        VibrationEffect.Preset.create(
            VibrationEffect.Preset.PRESET_CLICK, 1.0f
        )
    )
    .addPreset(
        150L,
        VibrationEffect.Preset.create(
            VibrationEffect.Preset.PRESET_LOW_TICK, 0.6f
        )
    )
    .build()

val repeatingEffect = VibrationEffect.Builder()
    .setRepeatingEffect(
        /* startTimeMillis= */ 0L,
        /* effect= */ repeatingPattern,
        /* durationMillis= */ 300L
    )
    .build()

vibrator.vibrate(repeatingEffect)

Java

VibrationEffect repeatingPattern = new VibrationEffect.Builder()
    .addPreset(
        0L,
        VibrationEffect.Preset.create(
            VibrationEffect.Preset.PRESET_CLICK, 1.0f
        )
    )
    .addPreset(
        150L,
        VibrationEffect.Preset.create(
            VibrationEffect.Preset.PRESET_LOW_TICK, 0.6f
        )
    )
    .build();

VibrationEffect repeatingEffect = new VibrationEffect.Builder()
    .setRepeatingEffect(
        /* startTimeMillis= */ 0L,
        /* effect= */ repeatingPattern,
        /* durationMillis= */ 300L
    )
    .build();

vibrator.vibrate(repeatingEffect);

टाइमिंग, पुष्टि, और ड्रिफ़्ट मैनेजमेंट

VibrationEffect.Builder की मदद से कंपोज़िशन डिज़ाइन करते समय, समय और पुष्टि करने से जुड़े इन नियमों का ध्यान रखें:

  • शुरू होने का समय बढ़ते क्रम में होना चाहिए: बिल्डर में जोड़े गए हर एलिमेंट का startTimeMillis, पिछले एलिमेंट के शुरू होने के समय से ज़्यादा या उसके बराबर होना चाहिए.
  • बिल्ड-टाइम की पुष्टि: बिल्डर, build() समय पर सबसे सही पुष्टि करता है. इसके लिए, वह एलिमेंट की जानी-पहचानी अवधि का इस्तेमाल करता है. इसके अलावा, वह प्रीसेट के लिए कम से कम 1 मि॰से॰ का इस्तेमाल करता है. अगर ओवरलैप होने की कोई ऐसी स्थिति मिलती है जो मुमकिन नहीं है, तो यह IllegalArgumentException दिखाता है.
  • प्लेबैक के दौरान क्रम से बदलाव होना: अगर पिछले वाइब्रेशन एलिमेंट का वाइब्रेशन, अगले एलिमेंट के शुरू होने के समय तक भी जारी रहता है, तो फ़्रेमवर्क अगले एलिमेंट को अपने-आप सबसे पहले उपलब्ध टाइम स्लॉट पर ले जाता है. इससे यह पक्का होता है कि इवेंट एक-दूसरे से ओवरलैप न हों और कोई भी वाइब्रेशन न छूटे. हालांकि, अगर इवेंट बहुत कम समय के अंतर पर शेड्यूल किए जाते हैं, तो हो सकता है कि टाइमिंग में थोड़ा बदलाव हो जाए. ड्रift को कम करने के लिए, लगातार हैप्टिक इवेंट के बीच काफ़ी समय (जैसे, 50 मि॰से॰ या इससे ज़्यादा) दें.

वाइब्रेशन प्रिमिटिव कंपोज़िशन बनाना

इस सेक्शन में, VibrationEffect.Composition का इस्तेमाल करके वाइब्रेशन बनाने का तरीका बताया गया है. प्रिमिटिव कंपोज़िशन को डिवाइस बनाने वाली कंपनी लागू करती है. इसके बारे में इस पेज पर पहले बताया गया है. ये वाइब्रेशन, हैप्टिक सिद्धांतों के मुताबिक होते हैं. इनसे साफ़ तौर पर हैप्टिक का पता चलता है. ये वाइब्रेशन, कम समय के लिए होते हैं और इनसे अच्छा अनुभव मिलता है. इन सुविधाओं और इनके काम करने के तरीके के बारे में ज़्यादा जानने के लिए, वाइब्रेशन ऐक्चुएटर के बारे में बुनियादी जानकारी लेख पढ़ें.

VibrationEffect.Builder के उलट, VibrationEffect.Composition API में, काम न करने वाले प्रिमिटिव के लिए ऑटोमैटिक फ़ॉलबैक नहीं होते. इसलिए:

  1. ऐडवांस हैप्टिक की सुविधा चालू करने से पहले, यह देख लें कि आपके डिवाइस पर इस्तेमाल की जा रही सभी प्रिमिटिव काम करती हों.

  2. उन सभी सुविधाओं को बंद करें जो काम नहीं करती हैं. सिर्फ़ उन इफ़ेक्ट को बंद न करें जिनमें प्रिमिटिव मौजूद नहीं है.

  • Kotlin: val fallbackEffect = VibrationEffect.Builder(compositionEffect).build() (या .addEvents(0L, compositionEffect.events))
  • Java: VibrationEffect fallbackEffect = new VibrationEffect.Builder(compositionEffect).build(); (या .addEvents(0L, compositionEffect.getEvents()))

VibrationEffect.Builder के साथ बनाए जाने पर, फ़्रेमवर्क कंपोज़िशन प्रिमिटिव को प्रीसेट में बदल देता है. साथ ही, अगर कोई प्रिमिटिव उपयोगकर्ता के डिवाइस पर काम नहीं करता है, तो यह अपने-आप रनटाइम फ़ॉलबैक उपलब्ध कराता है.

वाइब्रेशन के कंपोज़ किए गए इफ़ेक्ट

VibrationEffect.Composition की मदद से, वाइब्रेशन के कंपोज़्ड इफ़ेक्ट बनाए जा सकते हैं. यहां धीरे-धीरे बढ़ने वाले इफ़ेक्ट का एक उदाहरण दिया गया है. इसके बाद, तेज़ी से क्लिक करने वाले इफ़ेक्ट का उदाहरण दिया गया है:

Kotlin

vibrator.vibrate(
    VibrationEffect.startComposition().addPrimitive(
    VibrationEffect.Composition.PRIMITIVE_SLOW_RISE
    ).addPrimitive(
    VibrationEffect.Composition.PRIMITIVE_CLICK
    ).compose()
)

Java

vibrator.vibrate(
    VibrationEffect.startComposition()
        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE)
        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
        .compose());

कंपोज़िशन बनाने के लिए, प्रिमिटिव को क्रम से जोड़ा जाता है. हर प्रिमिटिव को स्केल भी किया जा सकता है. इसलिए, आपके पास हर प्रिमिटिव से जनरेट होने वाले वाइब्रेशन के ऐम्प्लिट्यूड को कंट्रोल करने का विकल्प होता है. स्केल को 0 से 1 के बीच की वैल्यू के तौर पर तय किया जाता है. यहां 0 का मतलब है कि यह प्रिमिटिव, कम से कम ऐम्प्लिट्यूड पर मैप की जाती है. इस ऐम्प्लिट्यूड पर उपयोगकर्ता को यह (बमुश्किल) महसूस हो सकती है.

वाइब्रेशन प्रिमिटिव में वैरिएंट बनाना

अगर आपको एक ही प्रिमिटिव का कम और ज़्यादा इंटेंसिटी वाला वर्शन बनाना है, तो इंटेंसिटी के अनुपात को 1.4 या इससे ज़्यादा पर सेट करें. इससे इंटेंसिटी के अंतर को आसानी से समझा जा सकेगा. एक ही प्रिमिटिव के लिए, तीन से ज़्यादा इंटेंसिटी लेवल न बनाएं. ऐसा इसलिए, क्योंकि वे देखने में अलग-अलग नहीं लगते. उदाहरण के लिए, किसी प्रिमिटिव के कम, सामान्य, और ज़्यादा इंटेंसिटी वाले वर्शन बनाने के लिए, 0.5, 0.7, और 1.0 के स्केल का इस्तेमाल करें.

वाइब्रेशन प्रिमिटिव के बीच गैप जोड़ना

कंपोज़िशन में, लगातार चलने वाले प्रिमिटिव के बीच में होने वाली देरी के बारे में भी बताया जा सकता है. यह देरी, पिछली प्रिमिटिव के खत्म होने के बाद से मिलीसेकंड में दिखाई जाती है. आम तौर पर, दो प्रिमिटिव के बीच 5 से 10 मिलीसेकंड का अंतर, पता लगाने के लिए बहुत कम होता है. अगर आपको दो प्रिमिटिव के बीच अंतर दिखाना है, तो 50 मि॰से॰ या इससे ज़्यादा का अंतर रखें. यहां देरी वाली कंपोज़िशन का एक उदाहरण दिया गया है:

Kotlin

val delayMs = 100
vibrator.vibrate(
    VibrationEffect.startComposition().addPrimitive(
    VibrationEffect.Composition.PRIMITIVE_SPIN, 0.8f
    ).addPrimitive(
    VibrationEffect.Composition.PRIMITIVE_SPIN, 0.6f
    ).addPrimitive(
    VibrationEffect.Composition.PRIMITIVE_THUD, 1.0f, delayMs
    ).compose()
)

Java

int delayMs = 100;
vibrator.vibrate(
    VibrationEffect.startComposition()
        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.8f)
        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.6f)
        .addPrimitive(
            VibrationEffect.Composition.PRIMITIVE_THUD, 1.0f, delayMs)
        .compose());

देखें कि कौनसी प्रिमिटिव इस्तेमाल की जा सकती हैं

इन एपीआई का इस्तेमाल करके, यह पुष्टि की जा सकती है कि डिवाइस पर कुछ खास प्रिमिटिव काम करते हैं या नहीं:

Kotlin

val primitive = VibrationEffect.Composition.PRIMITIVE_LOW_TICK

if (vibrator.areAllPrimitivesSupported(primitive)) {
  vibrator.vibrate(VibrationEffect.startComposition()
        .addPrimitive(primitive).compose())
} else {
  // Play a predefined effect or custom pattern as a fallback.
}

Java

int primitive = VibrationEffect.Composition.PRIMITIVE_LOW_TICK;

if (vibrator.areAllPrimitivesSupported(primitive)) {
  vibrator.vibrate(VibrationEffect.startComposition()
        .addPrimitive(primitive).compose());
} else {
  // Play a predefined effect or custom pattern as a fallback.
}

कई प्रिमिटिव की जांच करके यह भी तय किया जा सकता है कि डिवाइस के साथ काम करने वाले लेवल के आधार पर, किन प्रिमिटिव को कंपोज़ करना है:

Kotlin

val effects: IntArray = intArrayOf(
VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
VibrationEffect.Composition.PRIMITIVE_TICK,
VibrationEffect.Composition.PRIMITIVE_CLICK
)
val supported: BooleanArray = vibrator.arePrimitivesSupported(primitives)

Java

int[] primitives = new int[] {
VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
VibrationEffect.Composition.PRIMITIVE_TICK,
VibrationEffect.Composition.PRIMITIVE_CLICK
};
boolean[] supported = vibrator.arePrimitivesSupported(effects);

वाइब्रेशन कंपोज़िशन के उदाहरण

यहां दिए गए सेक्शन में, वाइब्रेशन कंपोज़िशन के कई उदाहरण दिए गए हैं. ये उदाहरण, GitHub पर मौजूद हैप्टिक्स सैंपल ऐप्लिकेशन से लिए गए हैं.

विरोध करना (कम टिक के साथ)

प्रोग्रेस में मौजूद किसी कार्रवाई के बारे में काम का फ़ीडबैक देने के लिए, वाइब्रेशन के ऐम्प्लिट्यूड को कंट्रोल किया जा सकता है. स्केल की वैल्यू को एक-दूसरे के करीब रखकर, किसी प्रिमिटिव का क्रेसेंडो इफ़ेक्ट बनाया जा सकता है. उपयोगकर्ता के इंटरैक्शन के आधार पर, लगातार प्रिमिटिव के बीच के समय को भी डाइनैमिक तरीके से सेट किया जा सकता है. इसे नीचे दिए गए उदाहरण में दिखाया गया है. इसमें व्यू ऐनिमेशन को ड्रैग करने के जेस्चर से कंट्रोल किया जाता है और हैप्टिक फ़ीडबैक की सुविधा का इस्तेमाल किया जाता है.

एक सर्कल को नीचे की ओर खींचने का ऐनिमेशन.
इनपुट वाइब्रेशन वेवफ़ॉर्म का प्लॉट.

पहली इमेज. इस वेवफ़ॉर्म से, डिवाइस पर वाइब्रेशन की स्पीड के बारे में पता चलता है.

Kotlin

@Composable
fun ResistScreen() {
    // Control variables for the dragging of the indicator.
    var isDragging by remember { mutableStateOf(false) }
    var dragOffset by remember { mutableStateOf(0f) }

    // Only vibrates while the user is dragging
    if (isDragging) {
        LaunchedEffect(Unit) {
        // Continuously run the effect for vibration to occur even when the view
        // is not being drawn, when user stops dragging midway through gesture.
        while (true) {
            // Calculate the interval inversely proportional to the drag offset.
            val vibrationInterval = calculateVibrationInterval(dragOffset)
            // Calculate the scale directly proportional to the drag offset.
            val vibrationScale = calculateVibrationScale(dragOffset)

            delay(vibrationInterval)
            vibrator.vibrate(
            VibrationEffect.startComposition().addPrimitive(
                VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
                vibrationScale
            ).compose()
            )
        }
        }
    }

    Screen() {
        Column(
        Modifier
            .draggable(
            orientation = Orientation.Vertical,
            onDragStarted = {
                isDragging = true
            },
            onDragStopped = {
                isDragging = false
            },
            state = rememberDraggableState { delta ->
                dragOffset += delta
            }
            )
        ) {
        // Build the indicator UI based on how much the user has dragged it.
        ResistIndicator(dragOffset)
        }
    }
}

Java

class DragListener implements View.OnTouchListener {
    // Control variables for the dragging of the indicator.
    private int startY;
    private int vibrationInterval;
    private float vibrationScale;

    @Override
    public boolean onTouch(View view, MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            startY = event.getRawY();
            vibrationInterval = calculateVibrationInterval(0);
            vibrationScale = calculateVibrationScale(0);
            startVibration();
            break;
        case MotionEvent.ACTION_MOVE:
            float dragOffset = event.getRawY() - startY;
            // Calculate the interval inversely proportional to the drag offset.
            vibrationInterval = calculateVibrationInterval(dragOffset);
            // Calculate the scale directly proportional to the drag offset.
            vibrationScale = calculateVibrationScale(dragOffset);
            // Build the indicator UI based on how much the user has dragged it.
            updateIndicator(dragOffset);
            break;
        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
            // Only vibrates while the user is dragging
            cancelVibration();
            break;
        }
        return true;
    }

    private void startVibration() {
        vibrator.vibrate(
            VibrationEffect.startComposition()
                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
                        vibrationScale)
                .compose());

        // Continuously run the effect for vibration to occur even when the view
        // is not being drawn, when user stops dragging midway through gesture.
        handler.postDelayed(this::startVibration, vibrationInterval);
    }

    private void cancelVibration() {
        handler.removeCallbacksAndMessages(null);
    }
}

बढ़ाएं (बढ़ने और घटने के साथ)

वाइब्रेशन की तीव्रता को बढ़ाने के लिए, दो प्रिमिटिव उपलब्ध हैं: PRIMITIVE_QUICK_RISE और PRIMITIVE_SLOW_RISE. दोनों एक ही टारगेट तक पहुंचते हैं, लेकिन अलग-अलग अवधि में. रैंप डाउन करने के लिए, सिर्फ़ एक प्रिमिटिव है: PRIMITIVE_QUICK_FALL. ये प्रिमिटिव एक साथ मिलकर बेहतर तरीके से काम करते हैं. इनसे एक ऐसा वेवफ़ॉर्म सेगमेंट बनता है जिसकी इंटेंसिटी बढ़ती है और फिर कम हो जाती है. स्केल किए गए प्रिमिटिव को अलाइन करके, उनके बीच ऐम्प्लिट्यूड में अचानक होने वाले बदलावों को रोका जा सकता है. इससे, पूरे इफ़ेक्ट की अवधि को बढ़ाने में भी मदद मिलती है. लोगों का ध्यान हमेशा, बढ़ने वाले हिस्से पर ज़्यादा जाता है. इसलिए, बढ़ने वाले हिस्से को घटने वाले हिस्से से छोटा करके, घटने वाले हिस्से पर ज़्यादा ध्यान दिया जा सकता है.

यहां एक सर्कल को बड़ा और छोटा करने के लिए, इस कंपोज़िशन का इस्तेमाल करने का उदाहरण दिया गया है. राइस इफ़ेक्ट से, ऐनिमेशन के दौरान विस्तार का एहसास बढ़ सकता है. राइज़ और फ़ॉल इफ़ेक्ट के कॉम्बिनेशन से, ऐनिमेशन के आखिर में कोलैप्स होने पर ज़ोर देने में मदद मिलती है.

बढ़ते हुए सर्कल का ऐनिमेशन.
इनपुट वाइब्रेशन वेवफ़ॉर्म का प्लॉट.

दूसरी इमेज. इस वेवफ़ॉर्म से, डिवाइस पर वाइब्रेशन के आउटपुट ऐक्सेलरेटर के बारे में पता चलता है.

Kotlin

enum class ExpandShapeState {
    Collapsed,
    Expanded
}

@Composable
fun ExpandScreen() {
    // Control variable for the state of the indicator.
    var currentState by remember { mutableStateOf(ExpandShapeState.Collapsed) }

    // Animation between expanded and collapsed states.
    val transitionData = updateTransitionData(currentState)

    Screen() {
        Column(
        Modifier
            .clickable(
            {
                if (currentState == ExpandShapeState.Collapsed) {
                currentState = ExpandShapeState.Expanded
                vibrator.vibrate(
                    VibrationEffect.startComposition().addPrimitive(
                    VibrationEffect.Composition.PRIMITIVE_SLOW_RISE,
                    0.3f
                    ).addPrimitive(
                    VibrationEffect.Composition.PRIMITIVE_QUICK_FALL,
                    0.3f
                    ).compose()
                )
                } else {
                currentState = ExpandShapeState.Collapsed
                vibrator.vibrate(
                    VibrationEffect.startComposition().addPrimitive(
                    VibrationEffect.Composition.PRIMITIVE_SLOW_RISE
                    ).compose()
                )
            }
            )
        ) {
        // Build the indicator UI based on the current state.
        ExpandIndicator(transitionData)
        }
    }
}

Java

class ClickListener implements View.OnClickListener {
    private final Animation expandAnimation;
    private final Animation collapseAnimation;
    private boolean isExpanded;

    ClickListener(Context context) {
        expandAnimation = AnimationUtils.loadAnimation(context, R.anim.expand);
        expandAnimation.setAnimationListener(new Animation.AnimationListener() {

        @Override
        public void onAnimationStart(Animation animation) {
            vibrator.vibrate(
            VibrationEffect.startComposition()
                .addPrimitive(
                    VibrationEffect.Composition.PRIMITIVE_SLOW_RISE, 0.3f)
                .addPrimitive(
                    VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, 0.3f)
                .compose());
        }
        });

        collapseAnimation = AnimationUtils
                .loadAnimation(context, R.anim.collapse);
        collapseAnimation.setAnimationListener(new Animation.AnimationListener() {

            @Override
            public void onAnimationStart(Animation animation) {
                vibrator.vibrate(
                VibrationEffect.startComposition()
                    .addPrimitive(
                        VibrationEffect.Composition.PRIMITIVE_SLOW_RISE)
                    .compose());
            }
        });
    }

    @Override
    public void onClick(View view) {
        view.startAnimation(isExpanded ? collapseAnimation : expandAnimation);
        isExpanded = !isExpanded;
    }
}

डगमगाना (स्पिन के साथ)

हैप्टिक के सिद्धांतों में से एक यह है कि उपयोगकर्ताओं को खुश रखा जाए. PRIMITIVE_SPIN का इस्तेमाल करके, अचानक होने वाले वाइब्रेशन इफ़ेक्ट को मज़ेदार तरीके से पेश किया जा सकता है. इस प्रिमिटिव का सबसे ज़्यादा फ़ायदा तब मिलता है, जब इसे एक से ज़्यादा बार कॉल किया जाता है. एक साथ कई स्पिन जोड़ने से, हिलने-डुलने और अस्थिर होने का इफ़ेक्ट मिलता है. इसे और बेहतर बनाने के लिए, हर प्रिमिटिव पर कुछ हद तक रैंडम स्केलिंग लागू की जा सकती है. इसके अलावा, लगातार स्पिन होने वाली प्रिमिटिव के बीच के अंतर को भी बदला जा सकता है. बिना किसी अंतराल (0 मि॰से॰) के दो बार घूमने से, तेज़ी से घूमने का एहसास होता है. स्पिन के बीच के अंतर को 10 से 50 मि॰से॰ तक बढ़ाने पर, स्पिनिंग की गति कम हो जाती है. इसका इस्तेमाल, वीडियो या ऐनिमेशन की अवधि से मेल खाने के लिए किया जा सकता है.

100 मि॰से॰ से ज़्यादा का गैप इस्तेमाल न करें. ऐसा करने पर, स्पिन के बाद आने वाले इफ़ेक्ट ठीक से इंटिग्रेट नहीं होते और अलग-अलग इफ़ेक्ट की तरह दिखने लगते हैं.

यहां एक ऐसे इलास्टिक शेप का उदाहरण दिया गया है जिसे नीचे की ओर खींचने के बाद छोड़ दिया जाता है और वह वापस अपनी जगह पर आ जाता है. ऐनिमेशन को स्पिन इफ़ेक्ट के साथ बेहतर बनाया गया है. ये इफ़ेक्ट अलग-अलग इंटेंसिटी के साथ चलते हैं. इनकी इंटेंसिटी, बाउंस की दूरी के हिसाब से तय होती है.

लचीले आकार के उछलने का ऐनिमेशन
इनपुट वाइब्रेशन वेवफ़ॉर्म का प्लॉट

तीसरी इमेज. इस वेवफ़ॉर्म से, डिवाइस पर वाइब्रेशन की स्पीड के बारे में पता चलता है.

Kotlin

@Composable
fun WobbleScreen() {
    // Control variables for the dragging and animating state of the elastic.
    var dragDistance by remember { mutableStateOf(0f) }
    var isWobbling by remember { mutableStateOf(false) }

    // Use drag distance to create an animated float value behaving like a spring.
    val dragDistanceAnimated by animateFloatAsState(
        targetValue = if (dragDistance > 0f) dragDistance else 0f,
        animationSpec = spring(
            dampingRatio = Spring.DampingRatioHighBouncy,
            stiffness = Spring.StiffnessMedium
        ),
    )

    if (isWobbling) {
        LaunchedEffect(Unit) {
            while (true) {
                val displacement = dragDistanceAnimated / MAX_DRAG_DISTANCE
                // Use some sort of minimum displacement so the final few frames
                // of animation don't generate a vibration.
                if (displacement > SPIN_MIN_DISPLACEMENT) {
                    vibrator.vibrate(
                        VibrationEffect.startComposition().addPrimitive(
                            VibrationEffect.Composition.PRIMITIVE_SPIN,
                            nextSpinScale(displacement)
                        ).addPrimitive(
                        VibrationEffect.Composition.PRIMITIVE_SPIN,
                        nextSpinScale(displacement)
                        ).compose()
                    )
                }
                // Delay the next check for a sufficient duration until the
                // current composition finishes. Note that you can use
                // Vibrator.getPrimitiveDurations API to calculcate the delay.
                delay(VIBRATION_DURATION)
            }
        }
    }

    Box(
        Modifier
            .fillMaxSize()
            .draggable(
                onDragStopped = {
                    isWobbling = true
                    dragDistance = 0f
                },
                orientation = Orientation.Vertical,
                state = rememberDraggableState { delta ->
                    isWobbling = false
                    dragDistance += delta
                }
            )
    ) {
        // Draw the wobbling shape using the animated spring-like value.
        WobbleShape(dragDistanceAnimated)
    }
}

// Calculate a random scale for each spin to vary the full effect.
fun nextSpinScale(displacement: Float): Float {
    // Generate a random offset in the range [-0.1, +0.1] to be added to the
    // vibration scale so the spin effects have slightly different values.
    val randomOffset: Float = Random.Default.nextFloat() * 0.2f - 0.1f
    return (displacement + randomOffset).absoluteValue.coerceIn(0f, 1f)
}

Java

class AnimationListener implements DynamicAnimation.OnAnimationUpdateListener {
    private final Random vibrationRandom = new Random(seed);
    private final long lastVibrationUptime;

    @Override
    public void onAnimationUpdate(
        DynamicAnimation animation, float value, float velocity) {
        // Delay the next check for a sufficient duration until the current
        // composition finishes. Note that you can use
        // Vibrator.getPrimitiveDurations API to calculcate the delay.
        if (SystemClock.uptimeMillis() - lastVibrationUptime < VIBRATION_DURATION) {
            return;
        }

        float displacement = calculateRelativeDisplacement(value);

        // Use some sort of minimum displacement so the final few frames
        // of animation don't generate a vibration.
        if (displacement < SPIN_MIN_DISPLACEMENT) {
            return;
        }

        lastVibrationUptime = SystemClock.uptimeMillis();
        vibrator.vibrate(
        VibrationEffect.startComposition()
            .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN,
            nextSpinScale(displacement))
            .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN,
            nextSpinScale(displacement))
            .compose());
    }

    // Calculate a random scale for each spin to vary the full effect.
    float nextSpinScale(float displacement) {
        // Generate a random offset in the range [-0.1,+0.1] to be added to
        // the vibration scale so the spin effects have slightly different
        // values.
        float randomOffset = vibrationRandom.nextFloat() * 0.2f - 0.1f
        return MathUtils.clamp(displacement + randomOffset, 0f, 1f)
    }
}

बाउंस (धमाकों के साथ)

वाइब्रेशन इफ़ेक्ट का एक और बेहतर इस्तेमाल, फ़िज़िकल इंटरैक्शन को सिम्युलेट करना है. PRIMITIVE_THUD से एक दमदार और गूंजने वाला इफ़ेक्ट तैयार किया जा सकता है. इसे वीडियो या ऐनिमेशन में, किसी इफ़ेक्ट के विज़ुअलाइज़ेशन के साथ जोड़ा जा सकता है. उदाहरण के लिए, इससे व्यूअर को बेहतर अनुभव मिलता है.

यहाँ बॉल ड्रॉप ऐनिमेशन का एक उदाहरण दिया गया है. इसमें बॉल के नीचे गिरने पर आवाज़ का इफ़ेक्ट जोड़ा गया है. यह आवाज़, हर बार बॉल के स्क्रीन के सबसे निचले हिस्से से टकराने पर सुनाई देती है:

स्क्रीन पर, नीचे गिरी हुई गेंद के उछलने का ऐनिमेशन.
इनपुट वाइब्रेशन वेवफ़ॉर्म का प्लॉट.

चौथी इमेज. इस वेवफ़ॉर्म से, डिवाइस पर वाइब्रेशन की स्पीड के बारे में पता चलता है.

Kotlin

enum class BallPosition {
    Start,
    End
}

@Composable
fun BounceScreen() {
    // Control variable for the state of the ball.
    var ballPosition by remember { mutableStateOf(BallPosition.Start) }
    var bounceCount by remember { mutableStateOf(0) }

    // Animation for the bouncing ball.
    var transitionData = updateTransitionData(ballPosition)
    val collisionData = updateCollisionData(transitionData)

    // Ball is about to contact floor, only vibrating once per collision.
    var hasVibratedForBallContact by remember { mutableStateOf(false) }
    if (collisionData.collisionWithFloor) {
        if (!hasVibratedForBallContact) {
        val vibrationScale = 0.7.pow(bounceCount++).toFloat()
        vibrator.vibrate(
            VibrationEffect.startComposition().addPrimitive(
            VibrationEffect.Composition.PRIMITIVE_THUD,
            vibrationScale
            ).compose()
        )
        hasVibratedForBallContact = true
        }
    } else {
        // Reset for next contact with floor.
        hasVibratedForBallContact = false
    }

    Screen() {
        Box(
        Modifier
            .fillMaxSize()
            .clickable {
            if (transitionData.isAtStart) {
                ballPosition = BallPosition.End
            } else {
                ballPosition = BallPosition.Start
                bounceCount = 0
            }
            },
        ) {
        // Build the ball UI based on the current state.
        BouncingBall(transitionData)
        }
    }
}

Java

class ClickListener implements View.OnClickListener {
    @Override
    public void onClick(View view) {
        view.animate()
        .translationY(targetY)
        .setDuration(3000)
        .setInterpolator(new BounceInterpolator())
        .setUpdateListener(new AnimatorUpdateListener() {

            boolean hasVibratedForBallContact = false;
            int bounceCount = 0;

            @Override
            public void onAnimationUpdate(ValueAnimator animator) {
            boolean valueBeyondThreshold = (float) animator.getAnimatedValue() > 0.98;
            if (valueBeyondThreshold) {
                if (!hasVibratedForBallContact) {
                float vibrationScale = (float) Math.pow(0.7, bounceCount++);
                vibrator.vibrate(
                    VibrationEffect.startComposition()
                    .addPrimitive(
                        VibrationEffect.Composition.PRIMITIVE_THUD,
                        vibrationScale)
                    .compose());
                hasVibratedForBallContact = true;
                }
            } else {
                // Reset for next contact with floor.
                hasVibratedForBallContact = false;
            }
            }
        });
    }
}

एनवेलप के साथ वाइब्रेशन वेवफ़ॉर्म

वाइब्रेशन के कस्टम पैटर्न बनाने की प्रोसेस में, वाइब्रेशन के ऐम्प्लिट्यूड को कंट्रोल किया जा सकता है. इससे वाइब्रेशन को धीरे-धीरे बढ़ाने और कम करने के इफ़ेक्ट को आसानी से बनाया जा सकता है. इस सेक्शन में, वेवफ़ॉर्म एनवेलप का इस्तेमाल करके डाइनैमिक हैप्टिक इफ़ेक्ट बनाने का तरीका बताया गया है. इससे समय के साथ वाइब्रेशन के ऐम्प्लिट्यूड और फ़्रीक्वेंसी को सटीक तरीके से कंट्रोल किया जा सकता है. इससे आपको बेहतर और ज़्यादा बारीकी से हैप्टिक अनुभव बनाने में मदद मिलती है.

Android 16 (एपीआई लेवल 36) से, सिस्टम कंट्रोल पॉइंट का क्रम तय करके वाइब्रेशन वेवफ़ॉर्म एनवलप बनाने के लिए, ये एपीआई उपलब्ध कराता है:

  • BasicEnvelopeBuilder: यह एक ऐसा तरीका है जिससे किसी भी हार्डवेयर पर काम करने वाले हैप्टिक इफ़ेक्ट बनाए जा सकते हैं.
  • WaveformEnvelopeBuilder: हैप्टिक इफ़ेक्ट बनाने का ज़्यादा बेहतर तरीका. इसके लिए, हैप्टिक हार्डवेयर के बारे में जानकारी होना ज़रूरी है.

Android, एनवलप इफ़ेक्ट के लिए फ़ॉलबैक उपलब्ध नहीं कराता. अगर आपको इस सहायता की ज़रूरत है, तो यह तरीका अपनाएं:

  1. यह जांच करें कि कोई डिवाइस, Vibrator.areEnvelopeEffectsSupported() का इस्तेमाल करके एनवलप इफ़ेक्ट के साथ काम करता है या नहीं.
  2. उन सुविधाओं को बंद करें जो काम नहीं करती हैं. इसके अलावा, फ़ॉलबैक के तौर पर वाइब्रेशन के कस्टम पैटर्न या कंपोज़िशन का इस्तेमाल करें.

बेसिक एनवलप इफ़ेक्ट बनाने के लिए, इन पैरामीटर के साथ BasicEnvelopeBuilder का इस्तेमाल करें:

  • रेंज \( [0, 1] \)में मौजूद इंटेंसिटी वैल्यू, जो वाइब्रेशन की अनुमानित ताकत को दिखाती है. उदाहरण के लिए, \( 0.5 \)वैल्यू को डिवाइस की ग्लोबल मैक्सिमम इंटेंसिटी का आधा माना जाता है.
  • रेंज \( [0, 1] \)में शार्पनेस की वैल्यू, जो वाइब्रेशन की तेज़ी को दिखाती है. वैल्यू कम होने पर, वाइब्रेशन कम होता है. वहीं, वैल्यू ज़्यादा होने पर, वाइब्रेशन ज़्यादा होता है.

  • duration वैल्यू, जो मिलीसेकंड में यह समय दिखाती है कि आखिरी कंट्रोल पॉइंट से नए कंट्रोल पॉइंट पर ट्रांज़िशन होने में कितना समय लगा. आखिरी कंट्रोल पॉइंट का मतलब है इंटेंसिटी और शार्पनेस का पेयर.

यहां एक वेवफ़ॉर्म का उदाहरण दिया गया है. इसमें 500 मि॰से॰ से ज़्यादा समय तक, कम पिच से ज़्यादा पिच वाली, सबसे ज़्यादा तीव्रता वाली वाइब्रेशन को बढ़ाया जाता है. इसके बाद, 100 मि॰से॰ से ज़्यादा समय तक, इसे\( 0 \) (बंद) कर दिया जाता है.

vibrator.vibrate(VibrationEffect.BasicEnvelopeBuilder()
    .setInitialSharpness(0.0f)
    .addControlPoint(1.0f, 1.0f, 500)
    .addControlPoint(0.0f, 1.0f, 100)
    .build()
)

अगर आपको हैप्टिक के बारे में ज़्यादा जानकारी है, तो WaveformEnvelopeBuilder का इस्तेमाल करके एनवलप इफ़ेक्ट तय किए जा सकते हैं. इस ऑब्जेक्ट का इस्तेमाल करते समय, VibratorFrequencyProfile के ज़रिए फ़्रीक्वेंसी-टू-आउटपुट-ऐक्सेलरेशन मैपिंग (एफ़ओएएम) को ऐक्सेस किया जा सकता है.

  • यह \( [0, 1] \)की रेंज में मौजूद ऐम्प्लिट्यूड वैल्यू है. यह दी गई फ़्रीक्वेंसी पर हासिल की जा सकने वाली वाइब्रेशन स्ट्रेंथ को दिखाती है. इसे डिवाइस के फ़ोम से तय किया जाता है. उदाहरण के लिए, \( 0.5 \) वैल्यू से, दी गई फ़्रीक्वेंसी पर हासिल की जा सकने वाली ज़्यादा से ज़्यादा आउटपुट ऐक्सलरेशन का आधा हिस्सा जनरेट होता है.
  • हर्ट्ज़ में तय की गई फ़्रीक्वेंसी वैल्यू.

  • duration वैल्यू, जो मिलीसेकंड में यह दिखाती है कि पिछले कंट्रोल पॉइंट से नए कंट्रोल पॉइंट पर ट्रांज़िशन होने में कितना समय लगा.

नीचे दिए गए कोड में, 400 मि॰से॰ तक वाइब्रेशन का असर दिखाने वाला एक उदाहरण वेवफ़ॉर्म दिखाया गया है. यह 50 मि॰से॰ के ऐम्प्लिट्यूड रैंप से शुरू होता है. यह बंद से लेकर पूरी तरह से चालू होने तक, 60 हर्ट्ज़ की फ़्रीक्वेंसी पर काम करता है. इसके बाद, अगले 100 मि॰से॰ में फ़्रीक्वेंसी 120 हर्ट्ज़ तक बढ़ जाती है और 200 मि॰से॰ तक इसी लेवल पर बनी रहती है. आखिर में, ऐम्प्लिट्यूड रैंप डाउन होकर \( 0 \)हो जाता है. साथ ही, फ़्रीक्वेंसी पिछले 50 मि॰से॰ में 60 हर्ट्ज़ पर वापस आ जाती है:

vibrator.vibrate(VibrationEffect.WaveformEnvelopeBuilder()
    .addControlPoint(1.0f, 60f, 50)
    .addControlPoint(1.0f, 120f, 100)
    .addControlPoint(1.0f, 120f, 200)
    .addControlPoint(0.0f, 60f, 50)
    .build()
)

यहां दिए गए सेक्शन में, वाइब्रेशन वेवफ़ॉर्म के कई उदाहरण दिए गए हैं. इनमें एनवेलप भी शामिल हैं.

बाउंसिंग स्प्रिंग

पिछले सैंपल में, फ़िज़िकल बाउंस इंटरैक्शन को सिम्युलेट करने के लिए, PRIMITIVE_THUD का इस्तेमाल किया गया है. बेसिक एनवलप एपीआई से, वाइब्रेशन को ज़्यादा बेहतर तरीके से कंट्रोल किया जा सकता है. इससे, वाइब्रेशन की तीव्रता और शार्पनेस को सटीक तरीके से सेट किया जा सकता है. इससे हैप्टिक फ़ीडबैक मिलता है, जो ऐनिमेशन वाले इवेंट को ज़्यादा सटीक तरीके से फ़ॉलो करता है.

यहां एक स्प्रिंग के नीचे गिरने का उदाहरण दिया गया है. इसमें ऐनिमेशन को बेहतर बनाने के लिए, हर बार स्प्रिंग के स्क्रीन के सबसे निचले हिस्से से टकराने पर, बेसिक एनवलप इफ़ेक्ट का इस्तेमाल किया गया है:

स्क्रीन पर नीचे की ओर गिरती हुई स्प्रिंग का ऐनिमेशन.
इनपुट वाइब्रेशन वेवफ़ॉर्म का प्लॉट.

पांचवीं इमेज. यह एक वाइब्रेशन के लिए आउटपुट ऐक्सलरेशन वेवफ़ॉर्म ग्राफ़ है. यह वाइब्रेशन, उछलने वाली स्प्रिंग जैसा दिखता है.

@Composable
fun BouncingSpringAnimation() {
  var springX by remember { mutableStateOf(SPRING_WIDTH) }
  var springY by remember { mutableStateOf(SPRING_HEIGHT) }
  var velocityX by remember { mutableFloatStateOf(INITIAL_VELOCITY) }
  var velocityY by remember { mutableFloatStateOf(INITIAL_VELOCITY) }
  var sharpness by remember { mutableFloatStateOf(INITIAL_SHARPNESS) }
  var intensity by remember { mutableFloatStateOf(INITIAL_INTENSITY) }
  var multiplier by remember { mutableFloatStateOf(INITIAL_MULTIPLIER) }
  var bottomBounceCount by remember { mutableIntStateOf(0) }
  var animationStartTime by remember { mutableLongStateOf(0L) }
  var isAnimating by remember { mutableStateOf(false) }

  val (screenHeight, screenWidth) = getScreenDimensions(context)

  LaunchedEffect(isAnimating) {
    animationStartTime = System.currentTimeMillis()
    isAnimating = true

    while (isAnimating) {
      velocityY += GRAVITY
      springX += velocityX.dp
      springY += velocityY.dp

      // Handle bottom collision
      if (springY > screenHeight - FLOOR_HEIGHT - SPRING_HEIGHT / 2) {
        // Set the spring's y-position to the bottom bounce point, to keep it
        // above the floor.
        springY = screenHeight - FLOOR_HEIGHT - SPRING_HEIGHT / 2

        // Reverse the vertical velocity and apply damping to simulate a bounce.
        velocityY *= -BOUNCE_DAMPING
        bottomBounceCount++

        // Calculate the fade-out duration of the vibration based on the
        // vertical velocity.
        val fadeOutDuration =
            ((abs(velocityY) / GRAVITY) * FRAME_DELAY_MS).toLong()

        // Create a "boing" envelope vibration effect that fades out.
        vibrator.vibrate(
            VibrationEffect.BasicEnvelopeBuilder()
                // Starting from zero sharpness here, will simulate a smoother
                // "boing" effect.
                .setInitialSharpness(0f)

                // Add a control point to reach the target intensity and
                // sharpness very quickly.
                .addControlPoint(intensity, sharpness, 20L)

                // Add a control point to fade out the vibration intensity while
                // maintaining sharpness.
                .addControlPoint(0f, sharpness, fadeOutDuration)
                .build()
        )

        // Decrease the intensity and sharpness of the vibration for subsequent
        // bounces, and reduce the multiplier to create a fading effect.
        intensity *= multiplier
        sharpness *= multiplier
        multiplier -= 0.1f
      }

      if (springX > screenWidth - SPRING_WIDTH / 2) {
        // Prevent the spring from moving beyond the right edge of the screen.
        springX = screenWidth - SPRING_WIDTH / 2
      }

      // Check for 3 bottom bounces and then slow down.
      if (bottomBounceCount >= MAX_BOTTOM_BOUNCE &&
            System.currentTimeMillis() - animationStartTime > 1000) {
        velocityX *= 0.9f
        velocityY *= 0.9f
      }

      delay(FRAME_DELAY_MS) // Control animation speed.

      // Determine if the animation should continue based on the spring's
      // position and velocity.
      isAnimating = (springY < screenHeight + SPRING_HEIGHT ||
            springX < screenWidth + SPRING_WIDTH)
        && (velocityX >= 0.1f || velocityY >= 0.1f)
    }
  }

  Box(
    modifier = Modifier
      .fillMaxSize()
      .noRippleClickable {
        if (!isAnimating) {
          resetAnimation()
        }
      }
      .width(screenWidth)
      .height(screenHeight)
  ) {
    DrawSpring(mutableStateOf(springX), mutableStateOf(springY))
    DrawFloor()
    if (!isAnimating) {
      DrawText("Tap to restart")
    }
  }
}

रॉकेट लॉन्च

पिछले सैंपल में, बाउंस होने वाली स्प्रिंग की प्रतिक्रिया को सिम्युलेट करने के लिए, बुनियादी एनवलप एपीआई का इस्तेमाल करने का तरीका बताया गया है. WaveformEnvelopeBuilder से, डिवाइस की फ़्रीक्वेंसी रेंज को सटीक तरीके से कंट्रोल किया जा सकता है. इससे, हैप्टिक इफ़ेक्ट को अपनी पसंद के मुताबिक बनाया जा सकता है. इसे FOAM डेटा के साथ मिलाकर, वाइब्रेशन को फ़्रीक्वेंसी की खास क्षमताओं के हिसाब से बनाया जा सकता है.

यहां एक उदाहरण दिया गया है, जिसमें डाइनैमिक वाइब्रेशन पैटर्न का इस्तेमाल करके रॉकेट लॉन्च करने की प्रोसेस को दिखाया गया है. यह इफ़ेक्ट, कम से कम फ़्रीक्वेंसी से लेकर रेज़ोनेंट फ़्रीक्वेंसी तक जाता है. इस दौरान, ऐक्सेलरेटर का आउटपुट 0.1 G होता है. साथ ही, ऐम्प्लिट्यूड का इनपुट हमेशा 10% बना रहता है. इससे इफ़ेक्ट, काफ़ी हद तक बेहतर आउटपुट के साथ शुरू होता है. साथ ही, इससे तीव्रता और शार्पनेस बढ़ती है. भले ही, ड्राइविंग ऐम्प्लिट्यूड एक जैसा हो. रेज़ोनेंस पर पहुंचने के बाद, इफ़ेक्ट फ़्रीक्वेंसी वापस कम हो जाती है. इसे इंटेंसिटी और शार्पनेस में कमी के तौर पर देखा जाता है. इससे, शुरुआत में एक तरह का प्रतिरोध महसूस होता है. इसके बाद, यह प्रतिरोध कम हो जाता है. यह स्पेस में लॉन्च होने जैसा अनुभव देता है.

बेसिक एनवलप एपीआई के साथ ऐसा नहीं किया जा सकता, क्योंकि यह डिवाइस की रेज़ोनेंट फ़्रीक्वेंसी और आउटपुट ऐक्सलरेशन कर्व के बारे में डिवाइस के हिसाब से जानकारी को अलग कर देता है. शार्पनेस बढ़ाने से, फ़्रीक्वेंसी को रेज़ोनेंस से आगे बढ़ाया जा सकता है. इससे अनचाहे तरीके से ऐक्सलरेशन में गिरावट आ सकती है.

स्क्रीन के सबसे नीचे से रॉकेट के उड़ान भरने का ऐनिमेशन.
इनपुट वाइब्रेशन वेवफ़ॉर्म का प्लॉट.

छठी इमेज. रॉकेट लॉन्च को सिम्युलेट करने वाले कंपन के लिए, आउटपुट ऐक्सेलरेटेड वेवफ़ॉर्म ग्राफ़.

@Composable
fun RocketLaunchAnimation() {
  val context = LocalContext.current
  val screenHeight = remember { mutableFloatStateOf(0f) }
  var rocketPositionY by remember { mutableFloatStateOf(0f) }
  var isLaunched by remember { mutableStateOf(false) }
  val animation = remember { Animatable(0f) }

  val animationDuration = 3000
  LaunchedEffect(isLaunched) {
    if (isLaunched) {
      animation.animateTo(
        1.2f, // Overshoot so that the rocket goes off the screen.
        animationSpec = tween(
          durationMillis = animationDuration,
          // Applies an easing curve with a slow start and rapid acceleration
          // towards the end.
          easing = CubicBezierEasing(1f, 0f, 0.75f, 1f)
        )
      ) {
        rocketPositionY = screenHeight.floatValue * value
      }
      animation.snapTo(0f)
      rocketPositionY = 0f;
      isLaunched = false;
    }
  }

  Box(
    modifier = Modifier
      .fillMaxSize()
      .noRippleClickable {
        if (!isLaunched) {
          // Play vibration with same duration as the animation, using 70% of
          // the time for the rise of the vibration, to match the easing curve
          // defined previously.
          playVibration(vibrator, animationDuration, 0.7f)
          isLaunched = true
        }
      }
      .background(Color(context.getColor(R.color.background)))
      .onSizeChanged { screenHeight.floatValue = it.height.toFloat() }
  ) {
    drawRocket(rocketPositionY)
  }
}

private fun playVibration(
  vibrator: Vibrator,
  totalDurationMs: Long,
  riseBias: Float,
  minOutputAccelerationGs: Float = 0.1f,
) {
  require(riseBias in 0f..1f) { "Rise bias must be between 0 and 1." }

  if (!vibrator.areEnvelopeEffectsSupported()) {
    return
  }

  val resonantFrequency = vibrator.resonantFrequency
  if (resonantFrequency.isNaN()) {
    // Device doesn't have or expose a resonant frequency.
    return
  }

  val startFrequency = vibrator.frequencyProfile?.getFrequencyRange(minOutputAccelerationGs)?.lower ?: return

  if (startFrequency >= resonantFrequency) {
    // Vibrator can't generate the minimum required output at lower frequencies.
    return
  }

  val minDurationMs = vibrator.envelopeEffectInfo.minControlPointDurationMillis
  val rampUpDurationMs = (riseBias * totalDurationMs).toLong() - minDurationMs
  val rampDownDurationMs = totalDurationMs - rampUpDuration - minDurationMs

  vibrator.vibrate(
    VibrationEffect.WaveformEnvelopeBuilder()
      // Quickly reach the target output at the start frequency
      .addControlPoint(0.1f, startFrequency, minDurationMs)
      .addControlPoint(0.1f, resonantFrequency, rampUpDurationMs)
      .addControlPoint(0.1f, startFrequency, rampDownDurationMs)

      // Controlled ramp down to zero to avoid ringing after the vibration.
      .addControlPoint(0.0f, startFrequency, minDurationMs)
      .build()
  )
}

LavaBeats

रॉकेट लॉन्च के उदाहरण में बताया गया है कि WaveformEnvelopeBuilder एपीआई, वाइब्रेशन में ऐम्प्लिट्यूड और फ़्रीक्वेंसी सेगमेंट को कंट्रोल करने की सुविधा देता है. इससे कई जटिल हैप्टिक इफ़ेक्ट डिज़ाइन किए जा सकते हैं. इस तरह के डिज़ाइन का एक और उदाहरण, ज़्यादा ऐब्स्ट्रैक्ट फ़िज़िकल सेंसेशन का इम्यूलेशन है. जैसे, "जीवंतता".

इसके लिए, सामान्य इलेक्ट्रोकार्डियोग्राम (ईसीजी) सिग्नल के बायोमार्कर को, कुछ ऐम्प्लिट्यूड और फ़्रीक्वेंसी वाले वाइब्रेशन सेगमेंट के तौर पर दिखाया जा सकता है. LavaBeats एक ऐसा उदाहरण है जिसमें ईसीजी रिकॉर्डिंग के दो खास सेगमेंट को दो पल्स के तौर पर दिखाया गया है. इन दोनों पल्स के बीच कुछ समय का अंतर है. पहली खास पल्स, क्यूआरएस कॉम्प्लेक्स होती है. यह ज़्यादा ऐम्प्लिट्यूड और कम अवधि की एक तेज़ पीक के तौर पर दिखती है. दूसरी पल्स, टी वेव होती है. इसका ऐम्प्लिट्यूड कम होता है, अवधि लंबी होती है, और शेप ज़्यादा स्मूद होता है (आकृति 7 देखें).

WaveformEnvelopeBuilder का इस्तेमाल करके, इन दोनों पल्स को कई बार दोहराएं. इन दोनों पल्स के बीच का समय एक जैसा होना चाहिए. पहला पल्स, एक चहचहाहट वाला सिग्नल हो सकता है. यह कम फ़्रीक्वेंसी से शुरू होता है और कम समय में ज़्यादा फ़्रीक्वेंसी पर खत्म होता है. दूसरे पल्स को कम फ़्रीक्वेंसी वाले साइनसोएड के एक पीरियड के तौर पर दिखाया जा सकता है. हम दो पल्स को एक बीट में कंपोज़ कर सकते हैं. साथ ही, बीट के बीच में कुछ समय का अंतराल रखकर, कंपोज़िशन को कई बार दोहरा सकते हैं. इसके लिए, हम बीट प्रति मिनट (बीपीएम) की सामान्य दर का इस्तेमाल करते हैं. इसकी वजह से, दिल की धड़कन जैसा हैप्टिक इफ़ेक्ट मिलता है.

GitHub पर मौजूद हमारे हैप्टिक सैंपल ऐप्लिकेशन में LavaBeats को आज़माएं. साथ ही, हैप्टिक इफ़ेक्ट के साथ-साथ लावा-लैंप विज़ुअलाइज़ेशन का अनुभव लें. यह विज़ुअलाइज़ेशन, हैप्टिक इफ़ेक्ट के साथ एक ही लय में चलता है. इफ़ेक्ट की सेटिंग में बदलाव करके, धड़कन की अलग-अलग तरह की संवेदनाएं भी बनाई जा सकती हैं. इसके लिए, दोनों पल्स के ऐम्प्लिट्यूड, फ़्रीक्वेंसी, अवधि, और डिले में बदलाव करें.

लावा लैंप का ऐनिमेशन, जो दिल की धड़कन के हैप्टिक पैटर्न के साथ चलता है.
ईसीजी सेगमेंट और उसकी खास वेवफ़ॉर्म का प्लॉट

सातवीं इमेज. ईसीजी रिकॉर्डिंग का एक सेगमेंट, जिसमें क्यूआरएस कॉम्प्लेक्स और टी वेव शामिल है

@RequiresApi(Build.VERSION_CODES.BAKLAVA)
private fun createEnvelopeEffect(
beatParameters: List<BeatParameter>
):VibrationEffect =
    VibrationEffect.WaveformEnvelopeBuilder()
        .apply {
            repeat(beatParameters.getNumBeats()) {
                // First pulse chirp
                addControlPoint(
                    beatParameters.getFirstPulseAmplitude(),
                    beatParameters.getFirstPulseStartFreq(),
                    ENVELOPE_RAMP_DURATION_MILLIS,
                )
                addControlPoint(
                    beatParameters.getFirstPulseAmplitude(),
                    beatParameters.getFirstPulseEndFreq(),
                    beatParameters.getFirstPulseDurationMillis().toLong(),
                )
                addControlPoint(
                    0f,
                    beatParameters.getFirstPulseEndFreq(),
                    ENVELOPE_RAMP_DURATION_MILLIS,
                )

                // Delay between first and second pulse
                addControlPoint(
                    0f,
                    beatParameters.getFirstPulseEndFreq(),
                    beatParameters.getFirstToSecondPulseDelayMillis().toLong(),
                )

                // Second pulse
                addControlPoint(
                    beatParameters.getSecondPulseAmplitude(),
                    beatParameters.getSecondPulseFreq(),
                    ENVELOPE_RAMP_DURATION_MILLIS,
                )
                addControlPoint(
                    beatParameters.getSecondPulseAmplitude(),
                    beatParameters.getSecondPulseFreq(),
                    (1_000 / (2f * beatParameters.getSecondPulseFreq())).toLong(),
                )
                addControlPoint(
                    0f,
                    beatParameters.getSecondPulseFreq(),
                    ENVELOPE_RAMP_DURATION_MILLIS,
                )
                addControlPoint(
                    0f,
                    beatParameters.getSecondPulseFreq(),
                    beatParameters.getBeatDelayMillis().toLong(),
                )
            }
        }
        .build()

/** A parameter of a haptic beat effect that represents an ECG signal parameter */
@Stable
data class BeatParameter(
    val description: String = "",
    val value: Float = 0f,
    val range: ClosedFloatingPointRange<Float> = 0f..1f,
    val steps: Int = 0,
    val isFrequencyType: Boolean = false,
)