কাস্টম হ্যাপ্টিক এফেক্ট তৈরি করা

এই পৃষ্ঠায় Android অ্যাপে স্ট্যান্ডার্ড ভাইব্রেশন ওয়েভফর্ম ছাড়াও কাস্টম এফেক্ট তৈরি করতে কীভাবে বিভিন্ন হ্যাপটিক্স API ব্যবহার করতে হয় তার উদাহরণ দেওয়া হয়েছে।

এই পৃষ্ঠায় নিম্নলিখিত উদাহরণগুলি অন্তর্ভুক্ত করা হয়েছে:

আরও উদাহরণের জন্য, ইভেন্টে হ্যাপটিক ফিডব্যাক যোগ করুন দেখুন এবং সবসময় হ্যাপটিক্স ডিজাইন নীতি অনুসরণ করুন।

ডিভাইসের কম্প্যাটিবিলিটি ম্যানেজ করতে ফলব্যাক ব্যবহার করুন

কাস্টম হ্যাপটিক এফেক্ট প্রয়োগ করার সময়, ডিভাইসের কম্প্যাটিবিলিটি এবং ফলব্যাক আচরণ আপনার বেছে নেওয়া API সারফেসের উপর নির্ভর করে:

  • VibrationEffect.Builder (সাজেস্ট করা): Android 16 (26Q4) থেকে শুরু করে, VibrationEffect.Builder ব্যবহার করে তৈরি করা এফেক্টে অটোমেটিক ফ্রেমওয়ার্ক-লেভেল ফলব্যাক অন্তর্ভুক্ত থাকে। কোনও ডিভাইস যদি নেটিভভাবে অনুরোধ করা Preset বা বেসিক Envelope সাপোর্ট না করে, তাহলে ফ্রেমওয়ার্ক প্লেব্যাক করার সময় অটোমেটিক এটিকে যথাসাধ্য চেষ্টা করে উপযুক্ত বিকল্পে অনুবাদ করে। VibrationEffect.Builder-এর মাধ্যমে কম্পোজ করা এফেক্ট চালানোর আগে আপনাকে ম্যানুয়ালি প্রতিটি প্রিমিটিভ ডিভাইসের ক্ষমতা চেক করতে হবে না।
    • ব্যতিক্রম: WaveformEnvelopeBuilder-এর মাধ্যমে তৈরি উন্নত ওয়েভফর্ম এনভেলপ অটোমেটিক ফলব্যাক সাপোর্ট করে না কারণ সেগুলি নির্দিষ্ট হার্ডওয়্যার ফ্রিকোয়েন্সি ম্যাপিংয়ের (FOAM) উপর নির্ভর করে। কাজ না করলে, সেগুলি চলবে না।
  • VibrationEffect.Composition: startComposition() API-এর মাধ্যমে তৈরি করা কম্পোজিশনে অটোমেটিক ফলব্যাক থাকে না। কোনও কম্পোজিশনে এমনকি একটিও কাজ করে না এমন প্রিমিটিভ থাকলে, সম্পূর্ণ ভাইব্রেশন চালু হয় না। আপনাকে অবশ্যই ম্যানুয়ালি vibrator.arePrimitivesSupported() ব্যবহার করে ক্ষমতা চেক করতে হবে।
  • অ্যাম্প্লিচিউড কন্ট্রোল সহ ওয়েভফর্ম: অ্যাম্প্লিচিউড কন্ট্রোল নেই এমন ডিভাইসে শূন্য নয় এমন অ্যাম্প্লিচিউড রাউন্ড আপ করে ১০০% করা হয়। vibrator.hasAmplitudeControl() চেক করুন এবং প্রয়োজন হলে স্পষ্টভাবে ডিজাইন করা চালু/বন্ধ প্যাটার্নে ফিরে যান।

হ্যাপটিক প্রিমিটিভের ব্যবহার

Android-এ একাধিক হ্যাপটিক্স প্রিমিটিভ অন্তর্ভুক্ত থাকে যা অ্যামপ্লিটিউড ও ফ্রিকোয়েন্সি, উভয় ক্ষেত্রেই আলাদা হয়। আপনি একটি প্রিমিটিভ একা বা একাধিক প্রিমিটিভ একসাথে ব্যবহার করতে পারেন যাতে সমৃদ্ধ হ্যাপটিক এফেক্ট পাওয়া যায়।

  • দুটি প্রিমিটিভের মধ্যে স্পষ্ট ব্যবধানের জন্য ৫০ মিমি বা তার বেশি বিলম্ব ব্যবহার করুন, এছাড়াও, সম্ভব হলে প্রিমিটিভের সময়সীমা বিবেচনা করুন।
  • ১.৪ বা তার বেশি অনুপাতের পার্থক্য আছে এমন স্কেল ব্যবহার করুন যাতে তীব্রতার পার্থক্য আরও ভালোভাবে বোঝা যায়।
  • প্রিমিটিভের কম, মাঝারি ও বেশি ইনটেনসিটি ভার্সন তৈরি করতে ০.৫, ০.৭ ও ১.০ স্কেল ব্যবহার করুন।

কাস্টম ভাইব্রেশন প্যাটার্ন তৈরি করুন

ভাইব্রেশন প্যাটার্ন প্রায়শই মনোযোগ আকর্ষণকারী হ্যাপটিক্সে ব্যবহার করা হয়, যেমন বিজ্ঞপ্তি এবং রিংটোন। Vibrator পরিষেবা দীর্ঘ ভাইব্রেশন প্যাটার্ন চালাতে পারে যা সময়ের সাথে সাথে ভাইব্রেশন অ্যামপ্লিটিউড পরিবর্তন করে। এই ধরনের এফেক্টকে ওয়েভফর্ম বলা হয়।

ওয়েভফর্ম এফেক্ট সাধারণত বোঝা যায়, তবে শান্ত পরিবেশে হঠাৎ দীর্ঘ কম্পন হলে ব্যবহারকারী চমকে যেতে পারেন। খুব দ্রুত টার্গেট অ্যাম্প্লিটিউডে র‍্যাম্পিং করার ফলেও শ্রবণযোগ্য গুঞ্জন শব্দ তৈরি হতে পারে। র‍্যাম্প আপ ও ডাউন এফেক্ট তৈরি করতে অ্যাম্প্লিচিউড ট্রানজিশন মসৃণ করার জন্য ওয়েভফর্ম প্যাটার্ন ডিজাইন করুন।

ভাইব্রেশন প্যাটার্নের উদাহরণ

নিম্নলিখিত বিভাগে ভাইব্রেশন প্যাটার্নের কয়েকটি উদাহরণ দেওয়া হল:

র‍্যাম্প-আপ প্যাটার্ন

ওয়েভফর্মকে তিনটি প্যারামিটার সহ VibrationEffect হিসেবে দেখানো হয়:

  1. টাইমিং: প্রতিটি ওয়েভফর্ম সেগমেন্টের জন্য মিলিসেকেন্ডে সময়ের ব্যাপ্তির অ্যারে।
  2. অ্যাম্প্লিটিউড: প্রথম আর্গুমেন্টে নির্দিষ্ট করা প্রতিটি সময়সীমার জন্য কাঙ্ক্ষিত ভাইব্রেশন অ্যাম্প্লিটিউড, যা ০ থেকে ২৫৫ পর্যন্ত পূর্ণসংখ্যা দ্বারা প্রকাশ করা হয়, যেখানে ০ ভাইব্রেটরের "বন্ধ অবস্থা" এবং ২৫৫ ডিভাইসের সর্বাধিক অ্যাম্প্লিটিউডকে বোঝায়।
  3. রিপিট ইন্ডেক্স: প্রথম আর্গুমেন্টে নির্দিষ্ট করা অ্যারের ইন্ডেক্স, যা থেকে ওয়েভফর্ম রিপিট করা শুরু হবে অথবা -1 যদি প্যাটার্নটি একবারই চালাতে হয়।

এখানে একটি উদাহরণ ওয়েভফর্ম দেওয়া হল যা দুবার পালস করে এবং পালসের মধ্যে ৩৫০ মিলি সেকেন্ডের বিরতি থাকে। প্রথম পাল্সটি হল সর্বোচ্চ অ্যাম্প্লিটিউড পর্যন্ত একটি মসৃণ র‌্যাম্প আপ এবং দ্বিতীয়টি হল সর্বোচ্চ অ্যাম্প্লিটিউড ধরে রাখার জন্য একটি দ্রুত র‌্যাম্প। নেগেটিভ রিপিট ইনডেক্স ভ্যালু দ্বারা শেষে থামার বিষয়টি নির্ধারণ করা হয়।

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

এটি মাঝে মাঝে ঘটা ইভেন্টের জন্য খুবই উপযোগী, যেখানে ব্যবহারকারীকে অ্যাকশন নিয়ে এটি স্বীকার করতে হয়। এই ধরনের ইভেন্টের উদাহরণের মধ্যে ইনকামিং ফোন কল ও ট্রিগার করা অ্যালার্ম অন্তর্ভুক্ত।

ফলব্যাক সহ প্যাটার্ন

ভাইব্রেশনের অ্যাম্প্লিটিউড নিয়ন্ত্রণ করা হল হার্ডওয়্যার-নির্ভর ক্ষমতা। এই ক্ষমতা নেই এমন লো-এন্ড ডিভাইসে ওয়েভফর্ম চালালে অ্যাম্প্লিটিউড অ্যারেতে প্রতিটি পজিটিভ এন্ট্রির জন্য ডিভাইসটি সর্বাধিক অ্যাম্প্লিটিউডে ভাইব্রেট করে। আপনার অ্যাপকে এই ধরনের ডিভাইসের সাথে মানানসই হতে হলে, এমন প্যাটার্ন ব্যবহার করুন যা এই পরিস্থিতিতে চালানোর সময় গুঞ্জন তৈরি করে না, অথবা একটি সহজ চালু/বন্ধ প্যাটার্ন ডিজাইন করুন যা ফলব্যাক হিসেবে চালানো যেতে পারে।

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

VibrationEffect.Builder-এর সাথে টাইমলাইন অ্যাঙ্কর করা কম্পোজিশন

Android 16 (26Q4) থেকে শুরু করে, জটিল ভাইব্রেশন এফেক্ট ও কম্পোজিশন তৈরি করার জন্য VibrationEffect.Builder হল পছন্দের API। এটি আপনাকে 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 API থেকে শর্ট প্রিমিটিভগুলি প্রতিস্থাপন করে। আরও দীর্ঘ বা একটানা হ্যাপটিক সেনসেশনের জন্য—যেমন র‍্যাম্পিং আপ এবং ডাউন—পরিবর্তে এনভেলপ ওয়েভফর্ম (PWLE) ব্যবহার করুন।

প্রতিটি প্রিসেটকে 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)

জাভা

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)

জাভা

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)

জাভা

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)

জাভা

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() সময়ে যথাসাধ্য যাচাইকরণ করে। If an impossible overlap is detected, it throws an IllegalArgumentException.
  • প্লেব্যাক সিকোয়েন্সিয়াল শিফ্টিং: পরবর্তী এলিমেন্ট শুরু হওয়ার সময় এসে গেলেও যদি আগের ভাইব্রেশন এলিমেন্ট এখনও ফিজিক্যালি চলতে থাকে, তাহলে ফ্রেমওয়ার্ক অটোমেটিক পরবর্তী এলিমেন্টকে সবচেয়ে আগে উপলভ্য টাইম স্লটে শিফট করে দেয়। এটি নিশ্চিত করে যে ইভেন্টগুলি ওভারল্যাপ না করে এবং কোনও ভাইব্রেশন বাদ না যায়, যদিও ইভেন্টগুলি খুব কাছাকাছি শিডিউল করা থাকলে এটি সামান্য টাইমিং ড্রিফ্ট প্রবর্তন করতে পারে। ড্রিফ্ট কমাতে, পরপর হ্যাপটিক ইভেন্টের মধ্যে পর্যাপ্ত সময় (যেমন, ৫০ মি.সে. বা তার বেশি) দিন।

ভাইব্রেশন প্রিমিটিভ কম্পোজিশন তৈরি করুন

এই বিভাগে দেখানো হয়েছে যে কীভাবে VibrationEffect.Composition ব্যবহার করে ভাইব্রেশন কম্পোজ করতে হয়। এই পৃষ্ঠায় আগে বর্ণনা করা কম্পোজিশন প্রিমিটিভ, ডিভাইস প্রস্তুতকারক প্রয়োগ করে। এগুলি একটি স্পষ্ট, সংক্ষিপ্ত এবং মনোরম ভাইব্রেশন প্রদান করে যা স্পষ্ট হ্যাপটিক্সের জন্য হ্যাপটিক্স নীতির সাথে সামঞ্জস্যপূর্ণ। এইসব ক্ষমতা ও সেগুলি কীভাবে কাজ করে সেই সম্পর্কে আরও জানতে, ভাইব্রেশন অ্যাকচুয়েটর প্রাইমার দেখুন।

VibrationEffect.Builder-এর মতো, VibrationEffect.Composition API-তে কাজ করে না এমন প্রিমিটিভের জন্য অটোমেটিক ফলব্যাক নেই। সুতরাং:

  1. আপনার উন্নত হ্যাপটিক্স অ্যাক্টিভেট করার আগে, চেক করে নিন যে প্রদত্ত ডিভাইস আপনার ব্যবহার করা সব প্রিমিটিভ সাপোর্ট করে কিনা।

  2. শুধুমাত্র প্রিমিটিভ নেই এমন এফেক্ট নয়, বরং কাজ করে না এমন অভিজ্ঞতার ধারাবাহিক সেট বন্ধ করুন।

  • Kotlin: val fallbackEffect = VibrationEffect.Builder(compositionEffect).build() (বা .addEvents(0L, compositionEffect.events))
  • জাভা: 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()
)

একটি কম্পোজিশন তৈরি করা হয় যাতে প্রিমিটিভ যোগ করা হয় যা ক্রমানুসারে চালানো হয়। প্রতিটি প্রিমিটিভও স্কেল করা যায়, তাই আপনি এগুলির মাধ্যমে তৈরি হওয়া কম্পনের অ্যাম্প্লিটিউড কন্ট্রোল করতে পারবেন। স্কেলটি ০ এবং ১-এর মধ্যে একটি মান হিসাবে সংজ্ঞায়িত করা হয়, যেখানে ০ আসলে একটি ন্যূনতম প্রশস্ততা ম্যাপ করে যেখানে এই প্রিমিটিভটি ব্যবহারকারী (খুব কমই) অনুভব করতে পারেন।

ভাইব্রেশন প্রিমিটিভে ভ্যারিয়েন্ট তৈরি করুন

আপনি যদি একই প্রিমিটিভের দুর্বল ও শক্তিশালী ভার্সন তৈরি করতে চান, তাহলে ১.৪ বা তার বেশি ইনটেনসিটি রেশিও তৈরি করুন, যাতে ইনটেনসিটির পার্থক্য সহজেই বোঝা যায়। একই প্রিমিটিভের তিনটির বেশি ইনটেনসিটি লেভেল তৈরি করার চেষ্টা করবেন না, কারণ সেগুলি অনুভূতিগতভাবে আলাদা নয়। যেমন, কোনও প্রিমিটিভের কম, মাঝারি ও বেশি তীব্রতার ভার্সন তৈরি করতে ০.৫, ০.৭ ও ১.০ স্কেল ব্যবহার করুন।

ভাইব্রেশন প্রিমিটিভের মধ্যে গ্যাপ যোগ করুন

কম্পোজিশন পরপর প্রিমিটিভের মধ্যে যোগ করার জন্য বিলম্বও নির্দিষ্ট করতে পারে। এই বিলম্ব পূর্ববর্তী প্রিমিটিভের শেষ থেকে মিলিসেকেন্ডে প্রকাশ করা হয়। সাধারণত, দুটি প্রিমিটিভের মধ্যে ৫ থেকে ১০ মিলি সেকেন্ডের ব্যবধান খুবই কম, তাই এটি শনাক্ত করা যায় না। দুটি প্রিমিটিভের মধ্যে স্পষ্ট ব্যবধান তৈরি করতে চাইলে, ৫০ মিলি সেকেন্ড বা তার বেশি ব্যবধান ব্যবহার করুন। এখানে বিলম্ব সহ কম্পোজিশনের একটি উদাহরণ দেওয়া হল:

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

কোন কোন প্রিমিটিভ কাজ করে তা চেক করুন

নির্দিষ্ট প্রিমিটিভের জন্য ডিভাইস সাপোর্ট যাচাই করতে নিম্নলিখিত API ব্যবহার করা যেতে পারে:

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

Wobble (with spins)

হ্যাপটিক্স নীতির অন্যতম মূল উদ্দেশ্য হল ব্যবহারকারীদের আনন্দ দেওয়া। PRIMITIVE_SPIN ব্যবহার করে আপনি মজাদার উপায়ে অপ্রত্যাশিত ভাইব্রেশন এফেক্ট যোগ করতে পারবেন। এই প্রিমিটিভ একাধিকবার কল করা হলে সবচেয়ে বেশি কার্যকর হয়। একাধিক স্পিন কনক্যাটিনেট করা হলে তা একটি টলমল এবং অস্থির প্রভাব তৈরি করতে পারে, যা প্রতিটি প্রিমিটিভে কিছুটা র‍্যান্ডম স্কেলিং প্রয়োগ করে আরও উন্নত করা যেতে পারে। এছাড়াও, আপনি পরপর স্পিন প্রিমিটিভের মধ্যে ব্যবধান নিয়ে পরীক্ষা করতে পারেন। কোনও বিরতি ছাড়া দুবার স্পিন করলে (মাঝে ০ মি.সে.) স্পিন করার অনুভূতি হয়। স্পিনের মধ্যেকার ব্যবধান ১০ থেকে ৫০ মিলিসেকেন্ডে বাড়ালে স্পিনিংয়ের অনুভূতি আরও হালকা হয় এবং এটি কোনও ভিডিও বা অ্যানিমেশনের সময়সীমার সাথে ম্যাচ করার জন্য ব্যবহার করা যেতে পারে।

১০০ মিলিসেকেন্ডের বেশি ব্যবধান ব্যবহার করবেন না, কারণ পরপর স্পিনগুলি আর ভালোভাবে একীভূত হয় না এবং স্বতন্ত্র প্রভাবের মতো মনে হতে শুরু করে।

এখানে একটি ইলাস্টিক আকারের উদাহরণ দেওয়া হল যা নিচের দিকে টেনে ছেড়ে দেওয়ার পরে বাউন্স করে ফিরে আসে। অ্যানিমেশনটি এক জোড়া স্পিন এফেক্টের মাধ্যমে উন্নত করা হয়েছে, যা বাউন্স ডিসপ্লেসমেন্টের সাথে আনুপাতিকভাবে বিভিন্ন তীব্রতার সাথে চালানো হয়।

ইলাস্টিক শেপের বাউন্স করার অ্যানিমেশন
ইনপুট ভাইব্রেশন ওয়েভফর্মের প্লট

ছবি ৩. এই ওয়েভফর্ম ডিভাইসে ভাইব্রেশনের আউটপুট অ্যাক্সিলারেশন দেখায়।

@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 (API লেভেল ৩৬) থেকে শুরু করে, সিস্টেম নিম্নলিখিত API প্রদান করে কন্ট্রোল পয়েন্টের ক্রম নির্ধারণ করে ভাইব্রেশন ওয়েভফর্ম এনভেলপ তৈরি করতে:

  • BasicEnvelopeBuilder: হার্ডওয়্যার-অ্যাগনস্টিক হ্যাপটিক এফেক্ট তৈরি করার অ্যাক্সেসযোগ্য পদ্ধতি।
  • WaveformEnvelopeBuilder: হ্যাপটিক এফেক্ট তৈরি করার আরও উন্নত পদ্ধতি; হ্যাপটিক্স হার্ডওয়্যার সম্পর্কে জানা থাকতে হবে।

Android-এ এনভেলপ এফেক্টের জন্য কোনও ফলব্যাক প্রদান করা হয় না। আপনার এই সহায়তা প্রয়োজন হলে, নিম্নলিখিত ধাপগুলি সম্পূর্ণ করুন:

  1. Vibrator.areEnvelopeEffectsSupported() ব্যবহার করে কোনও নির্দিষ্ট ডিভাইসে এনভেলপ এফেক্ট কাজ করে কিনা তা চেক করুন।
  2. অসমর্থিত অভিজ্ঞতার ধারাবাহিক সেট বন্ধ করুন অথবা বিকল্প হিসেবে কাস্টম ভাইব্রেশন প্যাটার্ন বা কম্পোজিশন ব্যবহার করুন।

আরও বেসিক এনভেলপ এফেক্ট তৈরি করতে, এইসব প্যারামিটার সহ BasicEnvelopeBuilder ব্যবহার করুন:

  • \( [0, 1] \)রেঞ্জে তীব্রতার মান, যা ভাইব্রেশনের অনুভূত শক্তিকে বোঝায়। যেমন, \( 0.5 \) ভ্যালুটিকে ডিভাইসের মাধ্যমে অর্জন করা যেতে পারে এমন গ্লোবাল সর্বোচ্চ ইনটেনসিটির অর্ধেক হিসেবে ধরা হয়।
  • \( [0, 1] \)রেঞ্জে শার্পনেস ভ্যালু, যা ভাইব্রেশনের ক্রিস্পনেসকে দেখায়। কম ভ্যালু থাকলে ভাইব্রেশন মসৃণ হয়, বেশি ভ্যালু থাকলে ভাইব্রেশন আরও তীক্ষ্ণ হয়।

  • সময়সীমা সংক্রান্ত ভ্যালু, যা শেষ কন্ট্রোল পয়েন্ট (অর্থাৎ, ইনটেনসিটি ও শার্পনেস পেয়ার) থেকে নতুন কন্ট্রোল পয়েন্টে ট্রানজিশন করতে মিলিসেকেন্ডে কত সময় লেগেছে তা দেখায়।

এখানে একটি উদাহরণ ওয়েভফর্ম দেওয়া হল যা ৫০০ মিলিসেকেন্ড ধরে কম-পিচ থেকে হাই-পিচ, সর্বাধিক-তীব্রতার ভাইব্রেশনে তীব্রতা বাড়ায় এবং তারপরে ১০০ মিলিসেকেন্ড ধরে \( 0 \) (বন্ধ)-তে ফিরে আসে।

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

হ্যাপটিক্স সম্পর্কে আপনার আরও উন্নত জ্ঞান থাকলে, আপনি WaveformEnvelopeBuilder ব্যবহার করে এনভেলপ এফেক্ট সংজ্ঞায়িত করতে পারেন। এই অবজেক্ট ব্যবহার করার সময়, আপনি VibratorFrequencyProfile-এর মাধ্যমে ফ্রিকোয়েন্সি-টু-আউটপুট-অ্যাক্সিলারেশন ম্যাপিং (FOAM) অ্যাক্সেস করতে পারবেন।

  • \( [0, 1] \)রেঞ্জে একটি অ্যাম্প্লিচিউড ভ্যালু, যা ডিভাইসের FOAM দ্বারা নির্ধারিত, প্রদত্ত ফ্রিকোয়েন্সিতে অর্জনযোগ্য ভাইব্রেশন স্ট্রেন্থকে প্রতিনিধিত্ব করে। যেমন, \( 0.5 \) -এর মান প্রদত্ত ফ্রিকোয়েন্সিতে অর্জন করা যেতে পারে এমন সর্বাধিক আউটপুট ত্বরণের অর্ধেক তৈরি করে।
  • ফ্রিকোয়েন্সি ভ্যালু, হার্জে উল্লেখ করা হয়েছে।

  • একটি সময়সীমা ভ্যালু, যা শেষ কন্ট্রোল পয়েন্ট থেকে নতুন কন্ট্রোল পয়েন্টে ট্রানজিশন করতে মিলিসেকেন্ডে নেওয়া সময়কে দেখায়।

ব্যবহার করে এনভেলপ এফেক্টের জন্য ডিভাইস সাপোর্ট চেক করুন।

নিচের কোডে একটি উদাহরণ ওয়েভফর্ম দেখানো হয়েছে যা ৪০০ মিলিসেকেন্ডের ভাইব্রেশন এফেক্টকে সংজ্ঞায়িত করে। এটি ৫০ ms অ্যাম্প্লিচিউড র‍্যাম্প দিয়ে শুরু হয়, বন্ধ থেকে সম্পূর্ণ, কনস্ট্যান্ট ৬০ Hz-এ। তারপর, পরবর্তী ১০০ ms-এ ফ্রিকোয়েন্সি র‍্যাম্প ১২০ Hz পর্যন্ত বাড়ে এবং ২০০ ms-এর জন্য সেই লেভেলে থাকে। শেষে, অ্যাম্প্লিচিউড র‍্যাম্প কমে \( 0 \)হয় এবং শেষ ৫০ ms-এ ফ্রিকোয়েন্সি ৬০ Hz-এ ফিরে আসে:

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 ব্যবহার করে। বেসিক এনভেলপ API অনেক বেশি সূক্ষ্ম কন্ট্রোল অফার করে, যার সাহায্যে আপনি ভাইব্রেশনের তীব্রতা ও শার্পনেস নিখুঁতভাবে কাস্টমাইজ করতে পারবেন। এর ফলে হ্যাপটিক ফিডব্যাক আরও নিখুঁতভাবে অ্যানিমেটেড ইভেন্ট ফলো করে।

এখানে একটি ফ্রি-ফলিং স্প্রিংয়ের উদাহরণ দেওয়া হল, যেখানে স্প্রিংটি স্ক্রিনের নিচে বাউন্স করার সময় বেসিক এনভেলপ এফেক্ট সহ অ্যানিমেশনটি উন্নত করা হয়েছে:

স্ক্রিনের নিচের দিক থেকে বাউন্স করে উপরে উঠে আসা স্প্রিংয়ের অ্যানিমেশন।
ইনপুট ভাইব্রেশন ওয়েভফর্মের প্লট।

ছবি ৫. একটি ভাইব্রেশনের জন্য আউটপুট অ্যাক্সিলারেশন ওয়েভফর্ম গ্রাফ যা একটি বাউন্সিং স্প্রিং সিমুলেট করে।

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

রকেট লঞ্চ

আগের একটি স্যাম্পেলে দেখানো হয়েছে যে কীভাবে বাউন্সি স্প্রিং রিঅ্যাকশন সিমুলেট করতে বেসিক এনভেলপ API ব্যবহার করতে হয়। WaveformEnvelopeBuilder ডিভাইসের সম্পূর্ণ ফ্রিকোয়েন্সি রেঞ্জের উপর সুনির্দিষ্ট নিয়ন্ত্রণ আনলক করে, যা অত্যন্ত কাস্টমাইজড হ্যাপটিক এফেক্ট সক্ষম করে। FOAM ডেটার সাথে এটি একত্রিত করে, আপনি নির্দিষ্ট ফ্রিকোয়েন্সি ক্ষমতার জন্য ভাইব্রেশন কাস্টমাইজ করতে পারবেন।

এখানে একটি উদাহরণ দেওয়া হল যা ডাইনামিক ভাইব্রেশন প্যাটার্ন ব্যবহার করে রকেট লঞ্চ সিমুলেশন দেখায়। এফেক্টটি ন্যূনতম সমর্থিত ফ্রিকোয়েন্সি ত্বরণ আউটপুট, 0.1 G থেকে রেজোন্যান্ট ফ্রিকোয়েন্সি পর্যন্ত যায়, সর্বদা 10% প্রশস্ততা ইনপুট বজায় রাখে। এর ফলে এফেক্টটি যথেষ্ট শক্তিশালী আউটপুট দিয়ে শুরু হয় এবং ড্রাইভিং অ্যামপ্লিটিউড একই থাকলেও, অনুভূত তীব্রতা এবং তীক্ষ্ণতা বৃদ্ধি পায়। রেজোনেন্সে পৌঁছানোর পরে, এফেক্ট ফ্রিকোয়েন্সি আবার সর্বনিম্ন স্তরে নেমে আসে, যা তীব্রতা এবং তীক্ষ্ণতা কমার অনুভূতি দেয়। এটি প্রথমে বাধা এবং তারপরে মুক্তির অনুভূতি তৈরি করে, যা মহাকাশে উৎক্ষেপণের অনুকরণ করে।

এই এফেক্টটি বেসিক এনভেলপ API-এর মাধ্যমে করা সম্ভব নয়, কারণ এটি রেজোন্যান্ট ফ্রিকোয়েন্সি এবং আউটপুট অ্যাক্সিলারেশন কার্ভ সম্পর্কে ডিভাইস-নির্দিষ্ট তথ্য অ্যাবস্ট্র্যাক্ট করে। শার্পনেস বাড়ালে তা রেজোন্যান্সের বাইরে সমতুল্য ফ্রিকোয়েন্সি পুশ করতে পারে, যার ফলে অনিচ্ছাকৃত অ্যাক্সিলারেশন ডিপ হতে পারে।

স্ক্রিনের নিচ থেকে রকেট শিপ উড়ে যাওয়ার অ্যানিমেশন।
ইনপুট ভাইব্রেশন ওয়েভফর্মের প্লট।

ছবি ৬. রকেট লঞ্চের সিমুলেশন করে এমন কম্পনের জন্য আউটপুট অ্যাক্সিলারেশন ওয়েভফর্ম গ্রাফ।

@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 API ভাইব্রেশনের ক্ষেত্রে অ্যামপ্লিটিউড ও ফ্রিকোয়েন্সি সেগমেন্ট নিয়ন্ত্রণ করার সুবিধা প্রদান করে অনেক জটিল হ্যাপটিক এফেক্ট ডিজাইন করার সুবিধা দেয়। এই ধরনের ডিজাইনের আরেকটি উদাহরণ হল আরও অ্যাবস্ট্র্যাক্ট শারীরিক অনুভূতির অনুকরণ, যেমন "প্রাণবন্ততা"।

নির্দিষ্ট অ্যাম্প্লিটিউড ও ফ্রিকোয়েন্সির ভাইব্রেশন সেগমেন্টের মাধ্যমে সাধারণ ইলেক্ট্রোকার্ডিওগ্রাম (ECG) সিগন্যালের বায়োমার্কার উপস্থাপন করে এটি করা যেতে পারে। LavaBeats হল এমন একটি উদাহরণ যেখানে ইসিজি রেকর্ডিংয়ের দুটি বৈশিষ্ট্যপূর্ণ অংশকে দুটি পালস হিসেবে দেখানো হয়েছে এবং তাদের মধ্যে সময়ের ব্যবধান রয়েছে। প্রথম বৈশিষ্ট্যপূর্ণ পালস হল QRS কমপ্লেক্স, যা উচ্চ অ্যাম্প্লিটিউড এবং স্বল্প সময়ের একটি তীক্ষ্ণ পিক হিসাবে দেখায়। দ্বিতীয় পালস হল T ওয়েভ, যার অ্যাম্প্লিটিউড কম, সময়কাল বেশি এবং আকার মসৃণ (ছবি ৭ দেখুন)।

WaveformEnvelopeBuilder ব্যবহার করে এই দুটি পাল্সের বিভিন্ন পুনরাবৃত্তি তৈরি করুন, যেগুলি একটি নির্দিষ্ট প্রথম-থেকে-দ্বিতীয় পাল্স বিলম্ব দ্বারা পৃথক করা হয়। প্রথম পাল্সটি একটি চিরপ সিগন্যাল হতে পারে যা কম ফ্রিকোয়েন্সিতে শুরু হয় এবং অল্প সময়ের মধ্যে উচ্চতর ফ্রিকোয়েন্সিতে শেষ হয়। দ্বিতীয় পালসকে কম ফ্রিকোয়েন্সির সাইনুসয়েডের একটি সিঙ্গেল পিরিয়ড হিসেবে দেখানো যেতে পারে। আমরা দুটি পালসকে একটি বিটে কম্পোজ করতে পারি, এবং একটি সাধারণ বিট-প্রতি-মিনিট (bpm) রেট অনুসরণ করে, মাঝে মাঝে বিলম্ব সহ কম্পোজিশনটি কয়েকবার রিপিট করতে পারি। এর ফলে এমন হ্যাপটিক এফেক্ট তৈরি হয় যা হার্টবিটের মতো অনুভূত হয়।

আপনি GitHub-এ আমাদের হ্যাপটিক্স স্যাম্পেল অ্যাপে LavaBeats ব্যবহার করে দেখতে পারেন এবং লাভা-ল্যাম্প ভিজ্যুয়ালাইজেশনের সাথে ইফেক্ট অনুভব করতে পারেন যা হ্যাপটিক ইফেক্টের মতো একই রিদমে বিট করে। এছাড়াও, আপনি দুটি পালসের অ্যাম্প্লিটিউড, ফ্রিকোয়েন্সি, সময়কাল এবং বিলম্ব পরিবর্তন করে বিভিন্ন বিটিং সেনসেশন তৈরি করতে এফেক্টের সেটিংস পরিবর্তন করতে পারেন।

লাভা ল্যাম্প অ্যানিমেশন যা হ্যাপটিক হার্টবিট প্যাটার্নের সাথে বিট করে।
ECG সেগমেন্টের প্লট এবং এর বৈশিষ্ট্যপূর্ণ তরঙ্গরূপ

ছবি ৭. QRS কমপ্লেক্স ও T ওয়েভ সহ ECG রেকর্ডিংয়ের একটি সেগমেন্ট

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