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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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
    )
)

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

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

fun 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.
    val repeatingEffect: VibrationEffect = VibrationEffect.createWaveform(
        timings, amplitudes, repeat
    )
    // repeatingEffect can be used in multiple places.

    vibrator.vibrate(repeatingEffect)
}

fun stopVibrating() {
    vibrator.cancel()
}

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

फ़ॉलबैक के साथ पैटर्न

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

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 का इस्तेमाल करें. डिवाइस बनाने वाली कंपनी, प्रीसेट लागू करती है, ताकि साफ़ तौर पर हैप्टिक्स के लिए, हैप्टिक्स के सिद्धांतों के मुताबिक, साफ़, छोटा, और अच्छा वाइब्रेशन मिल सके. इन सुविधाओं और इनके काम करने के तरीके के बारे में ज़्यादा जानने के लिए, वाइब्रेशन ऐक्चुएटर प्राइमर देखें.

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

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

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

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

  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 की मदद से, वाइब्रेशन के अलग-अलग इफ़ेक्ट बनाए जा सकते हैं. यहां धीरे-धीरे बढ़ने वाले इफ़ेक्ट के बाद, तेज़ क्लिक इफ़ेक्ट का एक उदाहरण दिया गया है:

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 मि॰से॰ या उससे ज़्यादा का गैप इस्तेमाल करें. यहां देरी से होने वाले कंपोज़िशन का एक उदाहरण दिया गया है:

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()
)

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

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

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.
}

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

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

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

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

रेज़िस्ट (कम टिक के साथ)

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

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

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

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

    // Only vibrates while the user is dragging
    LaunchedEffect(isDragging) {
        // Continuously run the effect for vibration to occur even when the view
        // is not being drawn, when user stops dragging midway through gesture.
        while (isDragging) {
            // 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()
            )
        }
    }

    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)
    }
}

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

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

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

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

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

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)

    Column(
        Modifier
            .clickable(
                onClick = {
                    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)
    }
}

स्पिन के साथ वॉबल

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

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

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

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

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

@Composable
fun WobbleScreen() {
    // Control variables for the dragging and animating state of the elastic.
    var dragDistance by remember { mutableFloatStateOf(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
        ),
        // The spring ends on its target. An end value of 0f means that the
        // shape is back at rest, so the wobble is over.
        finishedListener = { endValue -> if (endValue == 0f) isWobbling = false },
    )

    LaunchedEffect(isWobbling) {
        while (isWobbling) {
            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 = {
                    // Wobble only if the shape is far enough from rest to vibrate.
                    // It then springs back to 0f, and the finishedListener ends
                    // the wobble.
                    val displacement = dragDistanceAnimated / MAX_DRAG_DISTANCE
                    isWobbling = displacement > SPIN_MIN_DISPLACEMENT
                    dragDistance = 0f
                },
                orientation = Orientation.Vertical,
                state = rememberDraggableState { delta ->
                    isWobbling = false
                    dragDistance += delta
                }
            )
    ) {
        // Draw the wobbling shape using the animated spring-like value. The
        // lambda defers the read to the draw phase, so the animation doesn't
        // recompose this screen on every frame.
        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)
}

बाउंस (धम-धम की आवाज़ के साथ)

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

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

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

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

enum class BallPosition {
    Start,
    End
}

@Composable
fun BounceScreen() {
    // Control variable for the state of the ball.
    var ballPosition by remember { mutableStateOf(BallPosition.Start) }
    // Plain holder for the bounce count. It's read only in the effect and in
    // the click handler, never in composition, so it needs no snapshot state.
    val bounceCount = remember { intArrayOf(0) }

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

    // Ball is about to contact floor. The effect runs only when the collision
    // state changes, so it vibrates once per collision. Nothing here suspends,
    // so a keyed SideEffect does the job without launching a coroutine.
    SideEffect(collisionData.collisionWithFloor) {
        if (collisionData.collisionWithFloor) {
            val vibrationScale = 0.7.pow(bounceCount[0]++).toFloat()
            vibrator.vibrate(
                VibrationEffect.startComposition().addPrimitive(
                    VibrationEffect.Composition.PRIMITIVE_THUD,
                    vibrationScale
                ).compose()
            )
        }
    }

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

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

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

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

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

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

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

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

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

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

यहां एक उदाहरण दिया गया है, जिसमें वेवफ़ॉर्म की इंटेंसिटी को 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] \)की रेंज में एम्प्लीट्यूड की वैल्यू, जो दी गई फ़्रीक्वेंसी पर वाइब्रेशन की क्षमता को दिखाती है. इसे डिवाइस के FOAM से तय किया जाता है. उदाहरण के लिए, \( 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() {
    // The spring's height above the floor: 1f is the drop height, 0f is the floor.
    val springHeight = remember { Animatable(1f) }
    var isAnimating by remember { mutableStateOf(true) }

    LaunchedEffect(isAnimating) {
        if (!isAnimating) return@LaunchedEffect
        springHeight.snapTo(1f)

        // Each bounce reaches a fraction of the previous height.
        var bounceHeight = 1f
        while (bounceHeight > MIN_BOUNCE_HEIGHT) {
            // Fall to the floor, speeding up like a falling object. A fall from
            // a greater height takes longer.
            val fallDurationMs = (DROP_DURATION_MS * sqrt(bounceHeight)).toInt()
            springHeight.animateTo(0f, tween(fallDurationMs, easing = EaseInQuad))

            // The spring hits the floor: play a "boing" envelope effect that fades
            // out. A higher bounce gives a stronger and sharper vibration.
            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(bounceHeight, bounceHeight * MAX_SHARPNESS, 20L)
                    // Add a control point to fade out the vibration intensity while
                    // maintaining sharpness.
                    .addControlPoint(0f, bounceHeight * MAX_SHARPNESS, fallDurationMs.toLong())
                    .build()
            )

            // Bounce back up to a lower height, slowing down like a thrown object.
            // When the next bounce is too small, the spring stays on the floor.
            bounceHeight *= BOUNCE_DAMPING
            if (bounceHeight > MIN_BOUNCE_HEIGHT) {
                val riseDurationMs = (DROP_DURATION_MS * sqrt(bounceHeight)).toInt()
                springHeight.animateTo(bounceHeight, tween(riseDurationMs, easing = EaseOutQuad))
            }
        }
        isAnimating = false
    }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .clickable(enabled = !isAnimating) { isAnimating = true }
            .drawBehind {
                // Read the height in the draw phase, and scale it to the distance
                // between the top of the box and the floor.
                val floorTop = size.height - FLOOR_HEIGHT.toPx()
                val dropDistance = floorTop - SPRING_HEIGHT.toPx()
                drawSpring(topY = dropDistance * (1f - springHeight.value))
                drawFloor(top = floorTop)
            }
    ) {
        if (!isAnimating) {
            Text("Tap to restart", Modifier.align(Alignment.Center))
        }
    }
}

रॉकेट लॉन्च

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

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

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

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

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

@Composable
fun RocketLaunchAnimation() {
    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)
                )
            )
            animation.snapTo(0f)
            isLaunched = false
        }
    }

    Canvas(
        modifier = Modifier
            .fillMaxSize()
            .clickable {
                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.toLong(), 0.7f)
                    isLaunched = true
                }
            }
            .background(colorResource(R.color.background))
    ) {
        // Read the animation in the draw phase, and scale it to the canvas height.
        drawRocket(size.height * animation.value)
    }
}

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 - rampUpDurationMs - 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 को आज़माया जा सकता है. साथ ही, लावा-लैंप विज़ुअलाइज़ेशन के साथ इफ़ेक्ट को महसूस किया जा सकता है. यह विज़ुअलाइज़ेशन, हैप्टिक इफ़ेक्ट की तरह ही बीट करता है. इफ़ेक्ट की सेटिंग में बदलाव करके, अलग-अलग तरह की बीट बनाई जा सकती हैं. इसके लिए, दोनों पल्स के ऐंप्लीट्यूड, फ़्रीक्वेंसी, अवधि, और डिले में बदलाव करें.

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

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

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 */
data class BeatParameter(
    val description: String = "",
    val value: Float = 0f,
    val range: ClosedFloatingPointRange<Float> = 0f..1f,
    val steps: Int = 0,
    val isFrequencyType: Boolean = false,
)