এই পৃষ্ঠায় Android অ্যাপে স্ট্যান্ডার্ড ভাইব্রেশন ওয়েভফর্ম-এর বাইরে কাস্টম এফেক্ট তৈরি করতে বিভিন্ন হ্যাপটিক API কীভাবে ব্যবহার করতে হয় তার উদাহরণ দেওয়া আছে।
এই পৃষ্ঠায় নিম্নলিখিত উদাহরণগুলি অন্তর্ভুক্ত:
VibrationEffect.Builder-এর সাথে টাইমলাইন অ্যাঙ্কর করা কম্পোজিশন- প্রিসেট ব্যবহার করে কম্পোজ করা: আগে থেকে সংজ্ঞায়িত হ্যাপ্টিক সেনসেশন সিকোয়েন্স করুন।
- এনভেলপ ও প্রিসেট ব্যবহার করে কম্পোজ করা: টাইমলাইন বরাবর এনভেলপ ও প্রিসেট একত্রিত করুন।
- ইভেন্ট আবার ব্যবহার করা ও শিফট করা: আগে থেকে থাকা কম্পোজিশন ইভেন্ট শিফট করা ও আবার ব্যবহার করা।
- কম্পোজিশন রিপিট করা: রিপিট করা যায় এমন টাইমলাইন এফেক্ট তৈরি করুন।
- কাস্টম ভাইব্রেশন প্যাটার্ন
- ক্রমশ বাড়তে থাকা প্যাটার্ন: এমন প্যাটার্ন যা ধীরে ধীরে শুরু হয়।
- পুনরাবৃত্তিমূলক প্যাটার্ন: এমন প্যাটার্ন যার কোনও শেষ নেই।
- ফলব্যাক সহ প্যাটার্ন: ফলব্যাক ডেমোনস্ট্রেশন।
- ভাইব্রেশন প্রিমিটিভ কম্পোজিশন
- প্রতিরোধ: ডায়নামিক তীব্রতা সহ ড্র্যাগ এফেক্ট।
- এক্সপ্যান্ড: প্রথমে বৃদ্ধি তারপর হ্রাস পাওয়ার এফেক্ট।
- টলমল:
SPINপ্রিমিটিভ ব্যবহার করে টলমল এফেক্ট। - বাউন্স:
THUDপ্রিমিটিভ ব্যবহার করে বাউন্সিং এফেক্ট।
- এনভেলপ সহ ভাইব্রেশন ওয়েভফর্ম
- বাউন্সিং স্প্রিং: সাধারণ এনভেলপ এফেক্ট ব্যবহার করে স্প্রিংয়ের মতো বাউন্স করার এফেক্ট।
- রকেট লঞ্চ: ওয়েভফর্ম এনভেলপ এফেক্ট ব্যবহার করে রকেট লঞ্চের এফেক্ট।
অতিরিক্ত উদাহরণের জন্য, ইভেন্টে হ্যাপ্টিক ফিডব্যাক যোগ করুন দেখুন এবং সবসময় হ্যাপ্টিক ডিজাইন সংক্রান্ত নীতি মেনে চলুন।
ডিভাইস মানানসই হওয়া সংক্রান্ত সমস্যা সমাধান করতে ফলব্যাক ব্যবহার করা
কাস্টম হ্যাপ্টিক এফেক্ট প্রয়োগ করার সময়, ডিভাইস কম্প্যাটিবিলিটি ও ফলব্যাক বিহেভিয়ার আপনার বেছে নেওয়া 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 হিসেবে দেখানো হয়:
- টাইমিং: প্রতিটি ওয়েভফর্ম সেগমেন্টের জন্য মিলিসেকেন্ডে মেয়াদের একটি অ্যারে।
- কম্পনের তীব্রতা: প্রথম আর্গুমেন্টে নির্দিষ্ট করা প্রতিটি মেয়াদের জন্য কাঙ্ক্ষিত কম্পনের তীব্রতা ০ থেকে ২৫৫ পর্যন্ত পূর্ণসংখ্যা মান দ্বারা উপস্থাপিত হয়, যেখানে ০ কম্পনকারী "বন্ধ অবস্থা" এবং ২৫৫ হল ডিভাইসের সর্বাধিক তীব্রতা।
- রিপিট ইন্ডেক্স: ওয়েভফর্ম রিপিট করা শুরু করতে প্রথম আর্গুমেন্টে নির্দিষ্ট করা অ্যারের ইন্ডেক্স অথবা প্যাটার্নটি একবারই চালানো হলে -১।
এখানে একটি ওয়েভফর্মের উদাহরণ দেওয়া হল যা দু'বার পালস করে এবং পালসগুলির মধ্যে ৩৫০ ms বিরতি নেয় পালস। প্রথম পালসটি হল সর্বাধিক অ্যামপ্লিটিউড পর্যন্ত মসৃণভাবে বৃদ্ধি পাওয়া এবং দ্বিতীয়টি হল সর্বাধিক অ্যামপ্লিটিউড ধরে রাখার জন্য দ্রুত বৃদ্ধি পাওয়া। নেগেটিভ রিপিট ইনডেক্স ভ্যালু দ্বারা শেষ হওয়ার সময় বন্ধ করাকে সংজ্ঞায়িত করা হয়।
Kotlin
val timings: LongArray = longArrayOf(
50, 50, 50, 50, 50, 100, 350, 25, 25, 25, 25, 200)
val amplitudes: IntArray = intArrayOf(
33, 51, 75, 113, 170, 255, 0, 38, 62, 100, 160, 255)
val repeatIndex = -1 // Don't repeat.
vibrator.vibrate(VibrationEffect.createWaveform(
timings, amplitudes, repeatIndex))
জাভা
long[] timings = new long[] {
50, 50, 50, 50, 50, 100, 350, 25, 25, 25, 25, 200 };
int[] amplitudes = new int[] {
33, 51, 75, 113, 170, 255, 0, 38, 62, 100, 160, 255 };
int repeatIndex = -1; // Don't repeat.
vibrator.vibrate(VibrationEffect.createWaveform(
timings, amplitudes, repeatIndex));
পুনরাবৃত্তিমূলক প্যাটার্ন
বাতিল না করা পর্যন্ত ওয়েভফর্ম বারবার চালানো যেতে পারে। পুনরাবৃত্ত তরঙ্গরূপ তৈরি করার উপায় হল, একটি অ-ঋণাত্মক repeat প্যারামিটার সেট করা। আপনি কোনও
পুনরাবৃত্ত ওয়েভফর্ম চালালে, পরিষেবাতে
এটি স্পষ্টভাবে বাতিল না করা পর্যন্ত ভাইব্রেশন চলতে থাকে:
Kotlin
void startVibrating() {
val timings: LongArray = longArrayOf(50, 50, 100, 50, 50)
val amplitudes: IntArray = intArrayOf(64, 128, 255, 128, 64)
val repeat = 1 // Repeat from the second entry, index = 1.
VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
timings, amplitudes, repeat)
// repeatingEffect can be used in multiple places.
vibrator.vibrate(repeatingEffect)
}
void stopVibrating() {
vibrator.cancel()
}
জাভা
void startVibrating() {
long[] timings = new long[] { 50, 50, 100, 50, 50 };
int[] amplitudes = new int[] { 64, 128, 255, 128, 64 };
int repeat = 1; // Repeat from the second entry, index = 1.
VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
timings, amplitudes, repeat);
// repeatingEffect can be used in multiple places.
vibrator.vibrate(repeatingEffect);
}
void stopVibrating() {
vibrator.cancel();
}
যেসব বিরতিহীন ইভেন্টকে স্বীকৃতি দেওয়ার জন্য ব্যবহারকারীর অ্যাকশন প্রয়োজন হয়, সেগুলির ক্ষেত্রে এটি খুবই উপযোগী। এই ধরনের ইভেন্টের উদাহরণের মধ্যে ইনকামিং ফোন কল এবং ট্রিগার করা অ্যালার্ম অন্তর্ভুক্ত।
ফলব্যাক সহ প্যাটার্ন
ভাইব্রেশনের অ্যামপ্লিটিউড কন্ট্রোল করা হল একটি হার্ডওয়্যার-নির্ভর ক্ষমতা। এই ক্ষমতা ছাড়া কোনও লো-এন্ড ডিভাইসে ওয়েভফর্ম চালালে অ্যামপ্লিটিউড অ্যারের প্রতিটি পজিটিভ এন্ট্রির জন্য ডিভাইসটি সর্বাধিক অ্যামপ্লিটিউডে ভাইব্রেট করে। আপনার অ্যাপে যদি এই ধরনের ডিভাইস অ্যাকোমোডেট করার প্রয়োজন হয়, তাহলে এমন প্যাটার্ন ব্যবহার করুন যা ওই অবস্থায় বাজানো হলে কোনও বাজানোর মতো এফেক্ট তৈরি করে না অথবা এর পরিবর্তে ফলব্যাক হিসেবে বাজানো যায় এমন আরও সহজ চালু/বন্ধ করার প্যাটার্ন ডিজাইন করুন।
Kotlin
if (vibrator.hasAmplitudeControl()) {
vibrator.vibrate(VibrationEffect.createWaveform(
smoothTimings, amplitudes, smoothRepeatIdx))
} else {
vibrator.vibrate(VibrationEffect.createWaveform(
onOffTimings, onOffRepeatIdx))
}
জাভা
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:
addEvents()বা কনস্ট্রাক্টর কপি করার মাধ্যমে ইমপোর্ট করা আদিম কম্পোজিশন (VibrationEffect.Composition), স্টেপ ওয়েভফর্ম (VibrationEffect.createWaveform), আগে থেকে সংজ্ঞায়িত এফেক্ট (VibrationEffect.createPredefined) এবং ওয়ান-শট (VibrationEffect.createOneShot)। - পুনরাবৃত্তিমূলক সিকোয়েন্স: একটানা প্যাটার্ন যা
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()বিল্ড-টাইমে যাচাইকরণ করার জন্য যথাসাধ্য চেষ্টা করে। যদি কোনও অসম্ভব ওভারল্যাপ শনাক্ত করা হয়, তাহলে এটি একটিIllegalArgumentException। - প্লেব্যাক সিকোয়েন্সিয়াল শিফটিং: আগের কোনও ভাইব্রেশন এলিমেন্ট এখনও ফিজিক্যালি প্লে হচ্ছে, এমন সময় যদি পরের এলিমেন্টের শুরু হওয়ার সময় চলে আসে, তাহলে ফ্রেমওয়ার্ক পরের এলিমেন্টকে অটোমেটিক সবচেয়ে আগে উপলভ্য টাইম স্লটে শিফট করে দেয়। এটি নিশ্চিত করে যে ইভেন্টগুলি ওভারল্যাপ করে না এবং কোনও ভাইব্রেশন বাদ পড়ে না, যদিও ইভেন্টগুলি খুব কাছাকাছি শিডিউল করা হলে এটি সামান্য টাইমিং ড্রাফ্ট তৈরি করতে পারে। ড্রিফ্ট কমানোর জন্য, পরপর হ্যাপ্টিক ইভেন্টের মধ্যে পর্যাপ্ত সময় (যেমন, ৫০ মিলি সেকেন্ড বা এর বেশি) দিন।
ভাইব্রেশন প্রিমিটিভ কম্পোজিশন তৈরি করা
এই বিভাগে, কীভাবে
VibrationEffect.Composition ব্যবহার করে ভাইব্রেশন কম্পোজ করতে হয় তা দেখানো হয়েছে। এই পৃষ্ঠায় আগে বর্ণনা করা কম্পোজিশন প্রিমিটিভ
ডিভাইস প্রস্তুতকারক দ্বারা প্রয়োগ করা হয়। এগুলি স্পষ্ট হ্যাপটিকের জন্য হ্যাপটিক নীতি
অনুযায়ী
পরিষ্কার, ছোট ও মনোরম ভাইব্রেশন প্রদান করে। এইসব ক্ষমতা ও সেগুলি কীভাবে কাজ করে সেই সম্পর্কে আরও বিবরণের জন্য,
ভাইব্রেশন অ্যাকচুয়েটর প্রাইমার দেখুন।
VibrationEffect.Builder-এর মতো, VibrationEffect.Composition API-তে
কাজ করে না এমন প্রিমিটিভের জন্য অটোমেটিক ফলব্যাক নেই। তাই:
উন্নত হ্যাপটিক অ্যাক্টিভেট করার আগে, চেক করে নিন যে আপনি যেসব প্রিমিটিভ ব্যবহার করছেন সেগুলি নির্দিষ্ট ডিভাইসে কাজ করে কিনা।
যেসব ধারাবাহিক অভিজ্ঞতা কাজ করে না সেগুলি বন্ধ করুন, শুধু প্রিমিটিভ নেই এমন এফেক্ট নয়।
- Kotlin:
val fallbackEffect = VibrationEffect.Builder(compositionEffect).build()(বা.addEvents(0L, compositionEffect.events)) - Java:
VibrationEffect fallbackEffect =new VibrationEffect.Builder(compositionEffect).build();(বা.addEvents(0L, compositionEffect.getEvents()))
VibrationEffect.Builder-এর সাথে তৈরি করা হলে, ফ্রেমওয়ার্ক কম্পোজিশন
প্রিমিটিভকে প্রিসেটে কনভার্ট করে এবং কোনও প্রিমিটিভ ব্যবহারকারীর ডিভাইসে কাজ না করলে, অটোমেটিক রানটাইম ফলব্যাক
প্রদান করে।
কম্পোজ করা ভাইব্রেশন এফেক্ট
আপনি
VibrationEffect.Composition-এর মাধ্যমে কম্পোজ করা ভাইব্রেশন এফেক্ট তৈরি করতে পারবেন। এখানে ধীরে ধীরে বাড়তে থাকা
এফেক্টের একটি উদাহরণ দেওয়া হল, যার পরে একটি তীক্ষ্ণ ক্লিক এফেক্ট রয়েছে:
Kotlin
vibrator.vibrate(
VibrationEffect.startComposition().addPrimitive(
VibrationEffect.Composition.PRIMITIVE_SLOW_RISE
).addPrimitive(
VibrationEffect.Composition.PRIMITIVE_CLICK
).compose()
)
জাভা
vibrator.vibrate(
VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE)
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
.compose());
পরপর চালানোর জন্য আদিম উপাদান যোগ করে কম্পোজিশন তৈরি করা হয়। এছাড়াও, প্রতিটি প্রিমিটিভ স্কেলেবল, তাই আপনি প্রতিটি থেকে তৈরি হওয়া ভাইব্রেশনের অ্যামপ্লিটিউড কন্ট্রোল করতে পারবেন। স্কেলকে ০ থেকে ১-এর মধ্যে একটি মান হিসেবে সংজ্ঞায়িত করা হয়, যেখানে ০ আসলে একটি ন্যূনতম অ্যামপ্লিটিউডের সাথে ম্যাপ করে যেখানে এই আদিমকে ব্যবহারকারী (কষ্টে) অনুভব করতে পারেন।
ভাইব্রেশন প্রিমিটিভের ভ্যারিয়েন্ট তৈরি করা
আপনি যদি একই প্রিমিটিভের দুর্বল ও শক্তিশালী ভার্সন তৈরি করতে চান, তাহলে ১.৪ বা তার বেশি শক্তির অনুপাত তৈরি করুন, যাতে তীব্রতার পার্থক্য সহজেই বোঝা যায়। একই প্রিমিটিভের তিনটির বেশি ইনটেনসিটি লেভেল তৈরি করার চেষ্টা করবেন না, কারণ সেগুলি পারসেপচুয়ালি আলাদা নয়। যেমন, কোনও প্রিমিটিভের কম, মাঝারি ও বেশি ইনটেনসিটি ভার্সন তৈরি করতে, ০.৫, ০.৭ ও ১.০-এর স্কেল ব্যবহার করুন।
ভাইব্রেশন প্রিমিটিভের মধ্যে গ্যাপ যোগ করা
এছাড়াও, কম্পোজিশন পরপর প্রিমিটিভের মধ্যে যোগ করার জন্য বিলম্ব নির্দিষ্ট করতে পারে। আগের প্রিমিটিভের শেষ হওয়ার পর থেকে এই বিলম্বকে মিলিসেকেন্ডে প্রকাশ করা হয়। সাধারণত, দুটি প্রিমিটিভের মধ্যে ৫ থেকে ১০ মিলি সেকেন্ডের গ্যাপ থাকলে তা শনাক্ত করা যায় না। দুটি প্রিমিটিভের মধ্যে স্পষ্ট পার্থক্য তৈরি করতে চাইলে, ৫০ মিলিসেকেন্ড বা তার বেশি সময়ের ব্যবধান ব্যবহার করুন। বিলম্ব সহ কম্পোজিশনের একটি উদাহরণ এখানে দেওয়া হল:
Kotlin
val delayMs = 100
vibrator.vibrate(
VibrationEffect.startComposition().addPrimitive(
VibrationEffect.Composition.PRIMITIVE_SPIN, 0.8f
).addPrimitive(
VibrationEffect.Composition.PRIMITIVE_SPIN, 0.6f
).addPrimitive(
VibrationEffect.Composition.PRIMITIVE_THUD, 1.0f, delayMs
).compose()
)
জাভা
int delayMs = 100;
vibrator.vibrate(
VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.8f)
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.6f)
.addPrimitive(
VibrationEffect.Composition.PRIMITIVE_THUD, 1.0f, delayMs)
.compose());
কোন কোন প্রিমিটিভ কাজ করে তা চেক করুন
নির্দিষ্ট প্রিমিটিভের জন্য ডিভাইস কাজ করে কিনা তা যাচাই করতে নিম্নলিখিত API ব্যবহার করা যেতে পারে:
Kotlin
val primitive = VibrationEffect.Composition.PRIMITIVE_LOW_TICK
if (vibrator.areAllPrimitivesSupported(primitive)) {
vibrator.vibrate(VibrationEffect.startComposition()
.addPrimitive(primitive).compose())
} else {
// Play a predefined effect or custom pattern as a fallback.
}
জাভা
int primitive = VibrationEffect.Composition.PRIMITIVE_LOW_TICK;
if (vibrator.areAllPrimitivesSupported(primitive)) {
vibrator.vibrate(VibrationEffect.startComposition()
.addPrimitive(primitive).compose());
} else {
// Play a predefined effect or custom pattern as a fallback.
}
এছাড়াও, একাধিক প্রিমিটিভ চেক করা এবং তারপরে ডিভাইস সাপোর্ট লেভেলের উপর ভিত্তি করে কোনগুলি কম্পোজ করতে হবে তা সিদ্ধান্ত নেওয়া সম্ভব:
Kotlin
val effects: IntArray = intArrayOf(
VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
VibrationEffect.Composition.PRIMITIVE_TICK,
VibrationEffect.Composition.PRIMITIVE_CLICK
)
val supported: BooleanArray = vibrator.arePrimitivesSupported(primitives)
জাভা
int[] primitives = new int[] {
VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
VibrationEffect.Composition.PRIMITIVE_TICK,
VibrationEffect.Composition.PRIMITIVE_CLICK
};
boolean[] supported = vibrator.arePrimitivesSupported(effects);
ভাইব্রেশন কম্পোজিশনের উদাহরণ
নিচের বিভাগে, GitHub-এ হ্যাপটিক স্যাম্পেল অ্যাপ থেকে নেওয়া ভাইব্রেশন কম্পোজিশনের বিভিন্ন উদাহরণ দেওয়া হয়েছে।
প্রতিরোধ (কম টিক সহ)
চলমান অ্যাকশনের ব্যাপারে প্রয়োজনীয় ফিডব্যাক দিতে, আপনি আদিম ভাইব্রেশনের অ্যামপ্লিটিউড কন্ট্রোল করতে পারবেন। কাছাকাছি স্পেসযুক্ত স্কেল ভ্যালু ব্যবহার করে প্রিমিটিভের মসৃণ ক্রেসেন্ডো এফেক্ট তৈরি করা যেতে পারে। এছাড়াও, পরপর প্রিমিটিভের মধ্যে বিলম্ব ব্যবহারকারীর ইন্টার্যাকশনের উপর ভিত্তি করে ডায়নামিক সেট করা যেতে পারে। এটি ড্র্যাগ জেসচার দ্বারা নিয়ন্ত্রিত ভিউ অ্যানিমেশনের নিম্নলিখিত উদাহরণে দেখানো হয়েছে এবং হ্যাপটিকের মাধ্যমে আরও উন্নত করা হয়েছে।
ছবি ১. এই ওয়েভফর্মটি ডিভাইসে ভাইব্রেশনের আউটপুট অ্যাক্সিলারেশনকে দেখায়।
Kotlin
@Composable
fun ResistScreen() {
// Control variables for the dragging of the indicator.
var isDragging by remember { mutableStateOf(false) }
var dragOffset by remember { mutableStateOf(0f) }
// Only vibrates while the user is dragging
if (isDragging) {
LaunchedEffect(Unit) {
// Continuously run the effect for vibration to occur even when the view
// is not being drawn, when user stops dragging midway through gesture.
while (true) {
// Calculate the interval inversely proportional to the drag offset.
val vibrationInterval = calculateVibrationInterval(dragOffset)
// Calculate the scale directly proportional to the drag offset.
val vibrationScale = calculateVibrationScale(dragOffset)
delay(vibrationInterval)
vibrator.vibrate(
VibrationEffect.startComposition().addPrimitive(
VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
vibrationScale
).compose()
)
}
}
}
Screen() {
Column(
Modifier
.draggable(
orientation = Orientation.Vertical,
onDragStarted = {
isDragging = true
},
onDragStopped = {
isDragging = false
},
state = rememberDraggableState { delta ->
dragOffset += delta
}
)
) {
// Build the indicator UI based on how much the user has dragged it.
ResistIndicator(dragOffset)
}
}
}
জাভা
class DragListener implements View.OnTouchListener {
// Control variables for the dragging of the indicator.
private int startY;
private int vibrationInterval;
private float vibrationScale;
@Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startY = event.getRawY();
vibrationInterval = calculateVibrationInterval(0);
vibrationScale = calculateVibrationScale(0);
startVibration();
break;
case MotionEvent.ACTION_MOVE:
float dragOffset = event.getRawY() - startY;
// Calculate the interval inversely proportional to the drag offset.
vibrationInterval = calculateVibrationInterval(dragOffset);
// Calculate the scale directly proportional to the drag offset.
vibrationScale = calculateVibrationScale(dragOffset);
// Build the indicator UI based on how much the user has dragged it.
updateIndicator(dragOffset);
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
// Only vibrates while the user is dragging
cancelVibration();
break;
}
return true;
}
private void startVibration() {
vibrator.vibrate(
VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
vibrationScale)
.compose());
// Continuously run the effect for vibration to occur even when the view
// is not being drawn, when user stops dragging midway through gesture.
handler.postDelayed(this::startVibration, vibrationInterval);
}
private void cancelVibration() {
handler.removeCallbacksAndMessages(null);
}
}
সম্প্রসারণ (বৃদ্ধি ও হ্রাস সহ)
অনুভূত ভাইব্রেশন ইনটেনসিটি বাড়ানোর জন্য দুটি প্রিমিটিভ আছে:
PRIMITIVE_QUICK_RISE ও PRIMITIVE_SLOW_RISE. দুটিই একই টার্গেটে
পৌঁছায়, তবে আলাদা আলাদা সময়সীমা নিয়ে। PRIMITIVE_QUICK_FALL
র্যাম্পিং ডাউন করার জন্য শুধুমাত্র একটি প্রিমিটিভ আছে। এইসব প্রিমিটিভ একসাথে কাজ করে
ওয়েভফর্ম সেগমেন্ট তৈরি করে, যা তীব্রতা বৃদ্ধি করে এবং তারপর ধীরে ধীরে কমে যায়।
আপনি স্কেল করা প্রিমিটিভগুলিকে অ্যালাইন করে তাদের মধ্যে হঠাৎ করে অ্যামপ্লিটিউড বেড়ে যাওয়া
প্রতিরোধ করতে পারেন, যা সামগ্রিক প্রভাবের সময়সীমা বাড়ানোর জন্যও ভাল কাজ করে।
মানসিক দিক থেকে, লোকজন সবসময় পড়তির চেয়ে বাড়তির অংশ বেশি লক্ষ্য করেন, তাই বাড়তির অংশকে পড়তির চেয়ে ছোট করে
পড়তির অংশের উপর জোর দেওয়া যেতে পারে।
একটি বৃত্তকে বড় ও ছোট করার জন্য এই কম্পোজিশনের প্রয়োগের একটি উদাহরণ এখানে দেওয়া হল। অ্যানিমেশনের সময় রাইজ এফেক্ট, বড় হওয়ার অনুভূতিকে আরও বাড়িয়ে তুলতে পারে। রাইজ ও ফল এফেক্টের সংমিশ্রণ অ্যানিমেশনের শেষে কলাপ্স করার বিষয়টি তুলে ধরতে সাহায্য করে।
ছবি ২.এই ওয়েভফর্মটি কোনও ডিভাইসে ভাইব্রেশনের আউটপুট অ্যাক্সিলারেশনকে উপস্থাপন করে।
Kotlin
enum class ExpandShapeState {
Collapsed,
Expanded
}
@Composable
fun ExpandScreen() {
// Control variable for the state of the indicator.
var currentState by remember { mutableStateOf(ExpandShapeState.Collapsed) }
// Animation between expanded and collapsed states.
val transitionData = updateTransitionData(currentState)
Screen() {
Column(
Modifier
.clickable(
{
if (currentState == ExpandShapeState.Collapsed) {
currentState = ExpandShapeState.Expanded
vibrator.vibrate(
VibrationEffect.startComposition().addPrimitive(
VibrationEffect.Composition.PRIMITIVE_SLOW_RISE,
0.3f
).addPrimitive(
VibrationEffect.Composition.PRIMITIVE_QUICK_FALL,
0.3f
).compose()
)
} else {
currentState = ExpandShapeState.Collapsed
vibrator.vibrate(
VibrationEffect.startComposition().addPrimitive(
VibrationEffect.Composition.PRIMITIVE_SLOW_RISE
).compose()
)
}
)
) {
// Build the indicator UI based on the current state.
ExpandIndicator(transitionData)
}
}
}
জাভা
class ClickListener implements View.OnClickListener {
private final Animation expandAnimation;
private final Animation collapseAnimation;
private boolean isExpanded;
ClickListener(Context context) {
expandAnimation = AnimationUtils.loadAnimation(context, R.anim.expand);
expandAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
vibrator.vibrate(
VibrationEffect.startComposition()
.addPrimitive(
VibrationEffect.Composition.PRIMITIVE_SLOW_RISE, 0.3f)
.addPrimitive(
VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, 0.3f)
.compose());
}
});
collapseAnimation = AnimationUtils
.loadAnimation(context, R.anim.collapse);
collapseAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
vibrator.vibrate(
VibrationEffect.startComposition()
.addPrimitive(
VibrationEffect.Composition.PRIMITIVE_SLOW_RISE)
.compose());
}
});
}
@Override
public void onClick(View view) {
view.startAnimation(isExpanded ? collapseAnimation : expandAnimation);
isExpanded = !isExpanded;
}
}
ডগমগ (স্পিন সহ)
হ্যাপটিকের মূল নীতিগুলির মধ্যে একটি হল ব্যবহারকারীদের আনন্দ দেওয়া। PRIMITIVE_SPIN ব্যবহার করে
অপ্রত্যাশিত আনন্দদায়ক ভাইব্রেশন এফেক্ট
যোগ করা যায়। এই প্রিমিটিভটি একাধিকবার কল করা হলে সবচেয়ে বেশি কার্যকর হয়।
একাধিক স্পিন একসাথে করলে তা টলমল করা ও অস্থির
এফেক্ট তৈরি করতে পারে, যা প্রতিটি আদিম বস্তুর উপর কিছুটা র্যান্ডম স্কেলিং প্রয়োগ করে আরও বাড়ানো
যেতে পারে। এছাড়াও, আপনি পরপর স্পিন করা
প্রিমিটিভের মধ্যে গ্যাপ নিয়ে পরীক্ষা-নিরীক্ষা করতে পারেন। দুটি স্পিন কোনও গ্যাপ (মাঝে ০ ms) ছাড়াই হলে, একটি টাইট
স্পিনিং সেনসেশন তৈরি হয়। স্পিন করার মধ্যবর্তী গ্যাপ ১০ থেকে বাড়িয়ে ৫০ মিলি সেকেন্ড করলে স্পিন করার অনুভূতি
আরও আলগা হয়ে যায় এবং এটি ভিডিও বা
অ্যানিমেশনের সময়সীমার সাথে ম্যাচ করার জন্য ব্যবহার করা যেতে পারে।
১০০ মিলি সেকেন্ডের বেশি গ্যাপ ব্যবহার করবেন না, কারণ পরপর স্পিনগুলি আর ভালোভাবে ইন্টিগ্রেট হয় না এবং আলাদা আলাদা এফেক্ট হিসেবে মনে হতে শুরু করে।
এখানে একটি ইলাস্টিক শেপের উদাহরণ দেওয়া হল যেটি টেনে নামানোর পরে ছেড়ে দিলে আবার আগের অবস্থায় ফিরে আসে। অ্যানিমেশনটি স্পিন এফেক্টের একটি পেয়ারের মাধ্যমে উন্নত করা হয়েছে, যা বাউন্স ডিসপ্লেসমেন্টের সাথে আনুপাতিকভাবে পরিবর্তিত ইনটেনসিটি সহ প্লে করা হয়।
ছবি ৩. এই ওয়েভফর্মটি ডিভাইসে ভাইব্রেশনের আউটপুট অ্যাক্সিলারেশনকে দেখায়।
Kotlin
@Composable
fun WobbleScreen() {
// Control variables for the dragging and animating state of the elastic.
var dragDistance by remember { mutableStateOf(0f) }
var isWobbling by remember { mutableStateOf(false) }
// Use drag distance to create an animated float value behaving like a spring.
val dragDistanceAnimated by animateFloatAsState(
targetValue = if (dragDistance > 0f) dragDistance else 0f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioHighBouncy,
stiffness = Spring.StiffnessMedium
),
)
if (isWobbling) {
LaunchedEffect(Unit) {
while (true) {
val displacement = dragDistanceAnimated / MAX_DRAG_DISTANCE
// Use some sort of minimum displacement so the final few frames
// of animation don't generate a vibration.
if (displacement > SPIN_MIN_DISPLACEMENT) {
vibrator.vibrate(
VibrationEffect.startComposition().addPrimitive(
VibrationEffect.Composition.PRIMITIVE_SPIN,
nextSpinScale(displacement)
).addPrimitive(
VibrationEffect.Composition.PRIMITIVE_SPIN,
nextSpinScale(displacement)
).compose()
)
}
// Delay the next check for a sufficient duration until the
// current composition finishes. Note that you can use
// Vibrator.getPrimitiveDurations API to calculcate the delay.
delay(VIBRATION_DURATION)
}
}
}
Box(
Modifier
.fillMaxSize()
.draggable(
onDragStopped = {
isWobbling = true
dragDistance = 0f
},
orientation = Orientation.Vertical,
state = rememberDraggableState { delta ->
isWobbling = false
dragDistance += delta
}
)
) {
// Draw the wobbling shape using the animated spring-like value.
WobbleShape(dragDistanceAnimated)
}
}
// Calculate a random scale for each spin to vary the full effect.
fun nextSpinScale(displacement: Float): Float {
// Generate a random offset in the range [-0.1, +0.1] to be added to the
// vibration scale so the spin effects have slightly different values.
val randomOffset: Float = Random.Default.nextFloat() * 0.2f - 0.1f
return (displacement + randomOffset).absoluteValue.coerceIn(0f, 1f)
}
জাভা
class AnimationListener implements DynamicAnimation.OnAnimationUpdateListener {
private final Random vibrationRandom = new Random(seed);
private final long lastVibrationUptime;
@Override
public void onAnimationUpdate(
DynamicAnimation animation, float value, float velocity) {
// Delay the next check for a sufficient duration until the current
// composition finishes. Note that you can use
// Vibrator.getPrimitiveDurations API to calculcate the delay.
if (SystemClock.uptimeMillis() - lastVibrationUptime < VIBRATION_DURATION) {
return;
}
float displacement = calculateRelativeDisplacement(value);
// Use some sort of minimum displacement so the final few frames
// of animation don't generate a vibration.
if (displacement < SPIN_MIN_DISPLACEMENT) {
return;
}
lastVibrationUptime = SystemClock.uptimeMillis();
vibrator.vibrate(
VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN,
nextSpinScale(displacement))
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN,
nextSpinScale(displacement))
.compose());
}
// Calculate a random scale for each spin to vary the full effect.
float nextSpinScale(float displacement) {
// Generate a random offset in the range [-0.1,+0.1] to be added to
// the vibration scale so the spin effects have slightly different
// values.
float randomOffset = vibrationRandom.nextFloat() * 0.2f - 0.1f
return MathUtils.clamp(displacement + randomOffset, 0f, 1f)
}
}
বাউন্স (ধপ ধপ শব্দ সহ)
ভাইব্রেশন এফেক্টের আরেকটি উন্নত প্রয়োগ হল, এর মাধ্যমে কোনও ফিজিক্যাল
ইন্টার্যাকশনকে সিমুলেট করা যায়। PRIMITIVE_THUD একটি শক্তিশালী ও প্রতিধ্বনিত
এফেক্ট তৈরি করতে পারে, যা সামগ্রিক অভিজ্ঞতাকে আরও উন্নত করতে, ভিডিও বা
অ্যানিমেশনে প্রভাবের ভিজ্যুয়ালিজেশনের সাথে পেয়ার করা যেতে পারে।
এখানে একটি বল ড্রপ অ্যানিমেশনের উদাহরণ দেওয়া হল যা একটি ধপ করে পড়ার এফেক্ট দিয়ে উন্নত করা হয়েছে। প্রতিবার বলটি স্ক্রিনের নিচে থেকে লাফিয়ে উঠলে এফেক্টটি প্লে করা হয়:
ছবি ৪. এই ওয়েভফর্মটি ডিভাইসে ভাইব্রেশনের আউটপুট অ্যাক্সিলারেশনকে দেখায়।
Kotlin
enum class BallPosition {
Start,
End
}
@Composable
fun BounceScreen() {
// Control variable for the state of the ball.
var ballPosition by remember { mutableStateOf(BallPosition.Start) }
var bounceCount by remember { mutableStateOf(0) }
// Animation for the bouncing ball.
var transitionData = updateTransitionData(ballPosition)
val collisionData = updateCollisionData(transitionData)
// Ball is about to contact floor, only vibrating once per collision.
var hasVibratedForBallContact by remember { mutableStateOf(false) }
if (collisionData.collisionWithFloor) {
if (!hasVibratedForBallContact) {
val vibrationScale = 0.7.pow(bounceCount++).toFloat()
vibrator.vibrate(
VibrationEffect.startComposition().addPrimitive(
VibrationEffect.Composition.PRIMITIVE_THUD,
vibrationScale
).compose()
)
hasVibratedForBallContact = true
}
} else {
// Reset for next contact with floor.
hasVibratedForBallContact = false
}
Screen() {
Box(
Modifier
.fillMaxSize()
.clickable {
if (transitionData.isAtStart) {
ballPosition = BallPosition.End
} else {
ballPosition = BallPosition.Start
bounceCount = 0
}
},
) {
// Build the ball UI based on the current state.
BouncingBall(transitionData)
}
}
}
জাভা
class ClickListener implements View.OnClickListener {
@Override
public void onClick(View view) {
view.animate()
.translationY(targetY)
.setDuration(3000)
.setInterpolator(new BounceInterpolator())
.setUpdateListener(new AnimatorUpdateListener() {
boolean hasVibratedForBallContact = false;
int bounceCount = 0;
@Override
public void onAnimationUpdate(ValueAnimator animator) {
boolean valueBeyondThreshold = (float) animator.getAnimatedValue() > 0.98;
if (valueBeyondThreshold) {
if (!hasVibratedForBallContact) {
float vibrationScale = (float) Math.pow(0.7, bounceCount++);
vibrator.vibrate(
VibrationEffect.startComposition()
.addPrimitive(
VibrationEffect.Composition.PRIMITIVE_THUD,
vibrationScale)
.compose());
hasVibratedForBallContact = true;
}
} else {
// Reset for next contact with floor.
hasVibratedForBallContact = false;
}
}
});
}
}
এনভেলপ সহ ভাইব্রেশন ওয়েভফর্ম
কাস্টম ভাইব্রেশন প্যাটার্ন তৈরি করার প্রসেস আপনাকে ভাইব্রেশন অ্যাম্পলিটিউড কন্ট্রোল করতে দেয় যাতে ধীরে ধীরে বাড়া ও কমার মসৃণ এফেক্ট তৈরি করা যায়। এই বিভাগে, ওয়েভফর্ম এনভেলপ ব্যবহার করে কীভাবে ডায়নামিক হ্যাপ্টিক এফেক্ট তৈরি করতে হয় তা ব্যাখ্যা করা হয়েছে। এটি সময়ের সাথে সাথে কম্পনের অ্যামপ্লিটিউড এবং ফ্রিকোয়েন্সি সঠিকভাবে নিয়ন্ত্রণ করতে দেয়। এর ফলে আপনি আরও উন্নত ও সূক্ষ্ম হ্যাপ্টিক অভিজ্ঞতা তৈরি করতে পারবেন।
Android 16 (API লেভেল ৩৬) থেকে শুরু করে, কন্ট্রোল পয়েন্টের একটি ক্রম নির্ধারণ করে ভাইব্রেশন ওয়েভফর্ম এনভেলপ তৈরি করতে সিস্টেম নিম্নলিখিত API প্রদান করে:
BasicEnvelopeBuilder: হার্ডওয়্যার-অ্যাগনস্টিক হ্যাপ্টিক এফেক্ট তৈরি করার একটি অ্যাক্সেসিবল পদ্ধতি।WaveformEnvelopeBuilder: হ্যাপ্টিক এফেক্ট তৈরি করার আরও উন্নত পদ্ধতি; হ্যাপ্টিক হার্ডওয়্যার সম্পর্কে জ্ঞান থাকতে হবে।
Android এনভেলপ এফেক্টের জন্য ফলব্যাক প্রদান করে না। আপনার এই সহায়তা প্রয়োজন হলে, নিম্নলিখিত ধাপগুলি সম্পূর্ণ করুন:
-
Vibrator.areEnvelopeEffectsSupported()ব্যবহার করে কোনও ডিভাইসে এনভেলপ এফেক্ট কাজ করে কিনা তা চেক করুন। - যেসব ধারাবাহিক অভিজ্ঞতা কাজ করে না সেগুলি বন্ধ করুন অথবা ফলব্যাক অল্টারনেটিভ হিসেবে কাস্টম ভাইব্রেশন প্যাটার্ন বা কম্পোজিশন ব্যবহার করুন।
আরও সাধারণ এনভেলপ এফেক্ট তৈরি করতে, এইসব
প্যারামিটার সহ 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() {
var springX by remember { mutableStateOf(SPRING_WIDTH) }
var springY by remember { mutableStateOf(SPRING_HEIGHT) }
var velocityX by remember { mutableFloatStateOf(INITIAL_VELOCITY) }
var velocityY by remember { mutableFloatStateOf(INITIAL_VELOCITY) }
var sharpness by remember { mutableFloatStateOf(INITIAL_SHARPNESS) }
var intensity by remember { mutableFloatStateOf(INITIAL_INTENSITY) }
var multiplier by remember { mutableFloatStateOf(INITIAL_MULTIPLIER) }
var bottomBounceCount by remember { mutableIntStateOf(0) }
var animationStartTime by remember { mutableLongStateOf(0L) }
var isAnimating by remember { mutableStateOf(false) }
val (screenHeight, screenWidth) = getScreenDimensions(context)
LaunchedEffect(isAnimating) {
animationStartTime = System.currentTimeMillis()
isAnimating = true
while (isAnimating) {
velocityY += GRAVITY
springX += velocityX.dp
springY += velocityY.dp
// Handle bottom collision
if (springY > screenHeight - FLOOR_HEIGHT - SPRING_HEIGHT / 2) {
// Set the spring's y-position to the bottom bounce point, to keep it
// above the floor.
springY = screenHeight - FLOOR_HEIGHT - SPRING_HEIGHT / 2
// Reverse the vertical velocity and apply damping to simulate a bounce.
velocityY *= -BOUNCE_DAMPING
bottomBounceCount++
// Calculate the fade-out duration of the vibration based on the
// vertical velocity.
val fadeOutDuration =
((abs(velocityY) / GRAVITY) * FRAME_DELAY_MS).toLong()
// Create a "boing" envelope vibration effect that fades out.
vibrator.vibrate(
VibrationEffect.BasicEnvelopeBuilder()
// Starting from zero sharpness here, will simulate a smoother
// "boing" effect.
.setInitialSharpness(0f)
// Add a control point to reach the target intensity and
// sharpness very quickly.
.addControlPoint(intensity, sharpness, 20L)
// Add a control point to fade out the vibration intensity while
// maintaining sharpness.
.addControlPoint(0f, sharpness, fadeOutDuration)
.build()
)
// Decrease the intensity and sharpness of the vibration for subsequent
// bounces, and reduce the multiplier to create a fading effect.
intensity *= multiplier
sharpness *= multiplier
multiplier -= 0.1f
}
if (springX > screenWidth - SPRING_WIDTH / 2) {
// Prevent the spring from moving beyond the right edge of the screen.
springX = screenWidth - SPRING_WIDTH / 2
}
// Check for 3 bottom bounces and then slow down.
if (bottomBounceCount >= MAX_BOTTOM_BOUNCE &&
System.currentTimeMillis() - animationStartTime > 1000) {
velocityX *= 0.9f
velocityY *= 0.9f
}
delay(FRAME_DELAY_MS) // Control animation speed.
// Determine if the animation should continue based on the spring's
// position and velocity.
isAnimating = (springY < screenHeight + SPRING_HEIGHT ||
springX < screenWidth + SPRING_WIDTH)
&& (velocityX >= 0.1f || velocityY >= 0.1f)
}
}
Box(
modifier = Modifier
.fillMaxSize()
.noRippleClickable {
if (!isAnimating) {
resetAnimation()
}
}
.width(screenWidth)
.height(screenHeight)
) {
DrawSpring(mutableStateOf(springX), mutableStateOf(springY))
DrawFloor()
if (!isAnimating) {
DrawText("Tap to restart")
}
}
}
রকেট লঞ্চ
আগের একটি নমুনা থেকে বোঝা যায় যে কীভাবে একটি বাউন্সি স্প্রিংয়ের প্রতিক্রিয়া
সিমুলেট করতে, প্রাথমিক এনভেলপ API ব্যবহার করতে হয়। WaveformEnvelopeBuilder আনলক করলে
ডিভাইসের সম্পূর্ণ ফ্রিকোয়েন্সি রেঞ্জের উপর নিখুঁত কন্ট্রোল পাওয়া যায়, এর ফলে
খুবই কাস্টমাইজ করা হ্যাপ্টিক এফেক্ট পাওয়া যায়। FOAM ডেটার সাথে এটি মিলিয়ে, আপনি নির্দিষ্ট ফ্রিকোয়েন্সি
ক্ষমতা অনুযায়ী ভাইব্রেশন কাস্টমাইজ করতে পারবেন।
এখানে একটি উদাহরণ দেওয়া হল যা ডায়নামিক ভাইব্রেশন প্যাটার্ন ব্যবহার করে রকেট লঞ্চ সিমুলেশন দেখায়। এই প্রভাবটি সর্বনিম্ন কাজ করে এমন ফ্রিকোয়েন্সি অ্যাক্সিলারেশন আউটপুট, ০.১ জি থেকে রেজোনেন্ট ফ্রিকোয়েন্সি পর্যন্ত যায়, সবসময় ১০% অ্যাম্প্লিটিউড ইনপুট বজায় রাখে। এর ফলে এফেক্টটি যথেষ্ট শক্তিশালী আউটপুট দিয়ে শুরু হয় এবং এর তীব্রতা ও তীক্ষ্ণতা আরও বেশি বলে মনে হয়, যদিও ড্রাইভিং অ্যাম্পলিটিউড একই থাকে। রেজোনেন্সে পৌঁছানোর পরে, এফেক্ট ফ্রিকোয়েন্সি সর্বনিম্নতে নেমে আসে, যা তীব্রতা এবং তীক্ষ্ণতা কমে যাওয়া হিসেবে অনুভূত হয়। এর ফলে প্রথমে বাধা এবং পরে মুক্তি পাওয়ার অনুভূতি হয়, যা মহাকাশে রকেট উৎক্ষেপণের মতো।
সাধারণ এনভেলপ API-এর মাধ্যমে এই এফেক্ট তৈরি করা সম্ভব নয়, কারণ এটি রেজোন্যান্ট ফ্রিকোয়েন্সি ও আউটপুট অ্যাক্সিলারেশন কার্ভ সংক্রান্ত ডিভাইস-নির্দিষ্ট তথ্য অ্যাবস্ট্র্যাক্ট করে। শার্পনেস বাড়ালে, সমতুল্য ফ্রিকোয়েন্সি রেসোন্যান্সের বাইরে চলে যেতে পারে, যার ফলে অ্যাক্সিলারেশনে অপ্রত্যাশিত হ্রাস হতে পারে।
ছবি ৬. রকেট লঞ্চ সিমুলেট করে এমন কম্পনের জন্য আউটপুট অ্যাক্সিলারেশন ওয়েভফর্ম গ্রাফ।
@Composable
fun RocketLaunchAnimation() {
val context = LocalContext.current
val screenHeight = remember { mutableFloatStateOf(0f) }
var rocketPositionY by remember { mutableFloatStateOf(0f) }
var isLaunched by remember { mutableStateOf(false) }
val animation = remember { Animatable(0f) }
val animationDuration = 3000
LaunchedEffect(isLaunched) {
if (isLaunched) {
animation.animateTo(
1.2f, // Overshoot so that the rocket goes off the screen.
animationSpec = tween(
durationMillis = animationDuration,
// Applies an easing curve with a slow start and rapid acceleration
// towards the end.
easing = CubicBezierEasing(1f, 0f, 0.75f, 1f)
)
) {
rocketPositionY = screenHeight.floatValue * value
}
animation.snapTo(0f)
rocketPositionY = 0f;
isLaunched = false;
}
}
Box(
modifier = Modifier
.fillMaxSize()
.noRippleClickable {
if (!isLaunched) {
// Play vibration with same duration as the animation, using 70% of
// the time for the rise of the vibration, to match the easing curve
// defined previously.
playVibration(vibrator, animationDuration, 0.7f)
isLaunched = true
}
}
.background(Color(context.getColor(R.color.background)))
.onSizeChanged { screenHeight.floatValue = it.height.toFloat() }
) {
drawRocket(rocketPositionY)
}
}
private fun playVibration(
vibrator: Vibrator,
totalDurationMs: Long,
riseBias: Float,
minOutputAccelerationGs: Float = 0.1f,
) {
require(riseBias in 0f..1f) { "Rise bias must be between 0 and 1." }
if (!vibrator.areEnvelopeEffectsSupported()) {
return
}
val resonantFrequency = vibrator.resonantFrequency
if (resonantFrequency.isNaN()) {
// Device doesn't have or expose a resonant frequency.
return
}
val startFrequency = vibrator.frequencyProfile?.getFrequencyRange(minOutputAccelerationGs)?.lower ?: return
if (startFrequency >= resonantFrequency) {
// Vibrator can't generate the minimum required output at lower frequencies.
return
}
val minDurationMs = vibrator.envelopeEffectInfo.minControlPointDurationMillis
val rampUpDurationMs = (riseBias * totalDurationMs).toLong() - minDurationMs
val rampDownDurationMs = totalDurationMs - rampUpDuration - minDurationMs
vibrator.vibrate(
VibrationEffect.WaveformEnvelopeBuilder()
// Quickly reach the target output at the start frequency
.addControlPoint(0.1f, startFrequency, minDurationMs)
.addControlPoint(0.1f, resonantFrequency, rampUpDurationMs)
.addControlPoint(0.1f, startFrequency, rampDownDurationMs)
// Controlled ramp down to zero to avoid ringing after the vibration.
.addControlPoint(0.0f, startFrequency, minDurationMs)
.build()
)
}
LavaBeats
রকেট লঞ্চ উদাহরণে যেমন দেখানো হয়েছে, WaveformEnvelopeBuilder API
কম্পনের মধ্যে অ্যামপ্লিটিউড ও ফ্রিকোয়েন্সি সেগমেন্টের কন্ট্রোল চালু করার মাধ্যমে
অনেক জটিল হ্যাপ্টিক এফেক্ট ডিজাইন করার সুবিধা দেয়। এই ধরনের ডিজাইনের আরেকটি উদাহরণ হল আরও বিমূর্ত শারীরিক অনুভূতি, যেমন
"প্রাণবন্ততা" ইত্যাদির
ইমুলেশন।
নির্দিষ্ট অ্যামপ্লিটিউড ও ফ্রিকোয়েন্সির ভাইব্রেশন সেগমেন্টের মাধ্যমে সাধারণ
ইলেক্ট্রোকার্ডিওগ্রাম (ECG) সিগন্যালের বায়োমার্কারের উপস্থাপনার মাধ্যমে এটি সম্পন্ন করা
যায়। LavaBeats হল একটি উদাহরণ যেখানে ECG রেকর্ডিংয়ের দুটি বৈশিষ্ট্যমূলক সেগমেন্টকে
দুটি পালস হিসেবে দেখানো হয়েছে যেগুলি সময়ের বিলম্ব দ্বারা আলাদা করা হয়েছে। প্রথম
বৈশিষ্ট্যমূলক পালস হল QRS কমপ্লেক্স, যা
উচ্চ অ্যামপ্লিচিউড ও স্বল্প দৈর্ঘ্যের তীক্ষ্ণ পিক হিসেবে দেখা যায়। দ্বিতীয় পালস হল T ওয়েভ, যার
কম অ্যামপ্লিটিউড, বেশি সময়সীমা ও মসৃণ আকার থাকে (ছবি ৭ দেখুন)।
এই দুটি পাল্সের মধ্যে প্রথম থেকে দ্বিতীয় পাল্স পর্যন্ত একটি নির্দিষ্ট বিলম্ব রেখে
এগুলির বিভিন্ন রেপিটিশন তৈরি করতে WaveformEnvelopeBuilder ব্যবহার করুন। প্রথম
পাল্সটি একটি চের্প সিগন্যাল হতে পারে যা কম ফ্রিকোয়েন্সি থেকে শুরু হয় এবং অল্প সময়ের মধ্যে উচ্চতর
ফ্রিকোয়েন্সিতে শেষ হয়। দ্বিতীয় পালসকে কম ফ্রিকোয়েন্সির সাইন ওয়েভের একটি
সিঙ্গেল পিরিয়ড হিসেবে দেখানো যেতে পারে। আমরা দুটি পালসকে একটি বিটে কম্পোজ করতে পারি,
এবং একটি সাধারণ বিটস-পার-মিনিট (bpm) রেট অনুসরণ করে, দুটি কম্পোজিশনের মধ্যে বিলম্ব সহ কম্পোজিশনটি একাধিকবার রিপিট করতে পারি। এর ফলে এমন হ্যাপ্টিক এফেক্ট তৈরি হয় যা
ধুকপুক করা হৃদস্পন্দনের মতো।
আপনি GitHub-এ আমাদের হ্যাপটিক স্যাম্পেল অ্যাপ-এ LavaBeats ব্যবহার করে দেখতে পারেন এবং
হ্যাপটিক এফেক্টের সাথে থাকা লাভা-ল্যাম্প ভিজ্যুয়ালাইজেশন অনুভব করতে পারেন যা একই তালে বিট করে
যেমন হ্যাপটিক এফেক্ট। এছাড়াও, আপনি দুটি পালসের অ্যামপ্লিটিউড, ফ্রিকোয়েন্সি, সময়কাল
এবং বিলম্ব পরিবর্তন করে আলাদা আলাদা বিটিং সেনসেশন তৈরি করতে
এফেক্টের সেটিংস পরিবর্তন করতে পারেন।
ছবি ৭. QRS কমপ্লেক্স ও T ওয়েভ সহ ECG রেকর্ডিংয়ের একটি সেগমেন্ট
@RequiresApi(Build.VERSION_CODES.BAKLAVA)
private fun createEnvelopeEffect(
beatParameters: List<BeatParameter>
):VibrationEffect =
VibrationEffect.WaveformEnvelopeBuilder()
.apply {
repeat(beatParameters.getNumBeats()) {
// First pulse chirp
addControlPoint(
beatParameters.getFirstPulseAmplitude(),
beatParameters.getFirstPulseStartFreq(),
ENVELOPE_RAMP_DURATION_MILLIS,
)
addControlPoint(
beatParameters.getFirstPulseAmplitude(),
beatParameters.getFirstPulseEndFreq(),
beatParameters.getFirstPulseDurationMillis().toLong(),
)
addControlPoint(
0f,
beatParameters.getFirstPulseEndFreq(),
ENVELOPE_RAMP_DURATION_MILLIS,
)
// Delay between first and second pulse
addControlPoint(
0f,
beatParameters.getFirstPulseEndFreq(),
beatParameters.getFirstToSecondPulseDelayMillis().toLong(),
)
// Second pulse
addControlPoint(
beatParameters.getSecondPulseAmplitude(),
beatParameters.getSecondPulseFreq(),
ENVELOPE_RAMP_DURATION_MILLIS,
)
addControlPoint(
beatParameters.getSecondPulseAmplitude(),
beatParameters.getSecondPulseFreq(),
(1_000 / (2f * beatParameters.getSecondPulseFreq())).toLong(),
)
addControlPoint(
0f,
beatParameters.getSecondPulseFreq(),
ENVELOPE_RAMP_DURATION_MILLIS,
)
addControlPoint(
0f,
beatParameters.getSecondPulseFreq(),
beatParameters.getBeatDelayMillis().toLong(),
)
}
}
.build()
/** A parameter of a haptic beat effect that represents an ECG signal parameter */
@Stable
data class BeatParameter(
val description: String = "",
val value: Float = 0f,
val range: ClosedFloatingPointRange<Float> = 0f..1f,
val steps: Int = 0,
val isFrequencyType: Boolean = false,
)