এই পৃষ্ঠায় একটি অ্যান্ড্রয়েড অ্যাপে প্রচলিত ভাইব্রেশন ওয়েভফর্মের বাইরে কাস্টম ইফেক্ট তৈরি করতে বিভিন্ন হ্যাপটিক্স এপিআই ব্যবহারের উদাহরণ তুলে ধরা হয়েছে।
এই পৃষ্ঠায় নিম্নলিখিত উদাহরণগুলো অন্তর্ভুক্ত রয়েছে:
- কাস্টম কম্পন প্যাটার্ন
- র্যাম্প আপ প্যাটার্ন : এমন একটি প্যাটার্ন যা মসৃণভাবে শুরু হয়।
- পুনরাবৃত্তিমূলক নকশা : এমন একটি নকশা যার কোনো শেষ নেই।
- ফলব্যাক সহ প্যাটার্ন : একটি ফলব্যাক প্রদর্শনী।
- কম্পন রচনা
- প্রতিরোধ : গতিশীল তীব্রতাসহ একটি টান প্রভাব।
- সম্প্রসারণ : উত্থান-পতনের প্রভাব।
- Wobble :
SPINপ্রিমিটিভ ব্যবহার করে তৈরি একটি টলমলে এফেক্ট। - বাউন্স :
THUDপ্রিমিটিভ ব্যবহার করে তৈরি একটি বাউন্সিং এফেক্ট।
- খাম সহ কম্পন তরঙ্গরূপ
- বাউন্সিং স্প্রিং : মৌলিক এনভেলপ এফেক্ট ব্যবহার করে একটি স্প্রিংয়ের মতো বাউন্সিং এফেক্ট।
- রকেট উৎক্ষেপণ : ওয়েভফর্ম এনভেলপ এফেক্ট ব্যবহার করে একটি রকেট উৎক্ষেপণ এফেক্ট।
অতিরিক্ত উদাহরণের জন্য, “ইভেন্টগুলিতে হ্যাপটিক ফিডব্যাক যোগ করুন” দেখুন, এবং সর্বদা হ্যাপটিক্স ডিজাইনের নীতিগুলি অনুসরণ করুন।
ডিভাইসের সামঞ্জস্যতা সামলাতে ফলব্যাক ব্যবহার করুন
যেকোনো কাস্টম এফেক্ট প্রয়োগ করার সময় নিম্নলিখিত বিষয়গুলো বিবেচনা করুন:
- এই প্রভাবের জন্য ডিভাইসের কোন সক্ষমতাগুলো প্রয়োজন?
- ডিভাইসটি ইফেক্টটি প্লে করতে সক্ষম না হলে কী করতে হবে
অ্যান্ড্রয়েড হ্যাপটিক্স এপিআই রেফারেন্সে আপনার হ্যাপটিক্সের সাথে জড়িত কম্পোনেন্টগুলোর সাপোর্ট কীভাবে পরীক্ষা করতে হয়, সে সম্পর্কে বিস্তারিত তথ্য দেওয়া আছে, যাতে আপনার অ্যাপ একটি সামঞ্জস্যপূর্ণ সার্বিক অভিজ্ঞতা প্রদান করতে পারে।
আপনার ব্যবহারের ধরনের ওপর নির্ভর করে, আপনি কাস্টম ইফেক্টগুলো নিষ্ক্রিয় করতে চাইতে পারেন অথবা বিভিন্ন সম্ভাব্য সক্ষমতার ওপর ভিত্তি করে বিকল্প কাস্টম ইফেক্ট প্রদান করতে পারেন।
ডিভাইসের সক্ষমতার নিম্নলিখিত উচ্চ-স্তরের শ্রেণিগুলোর জন্য পরিকল্পনা করুন:
আপনি যদি হ্যাপটিক প্রিমিটিভ ব্যবহার করেন: কাস্টম ইফেক্টের জন্য প্রয়োজনীয় প্রিমিটিভগুলোকে সমর্থন করে এমন ডিভাইস। (প্রিমিটিভ সম্পর্কে বিস্তারিত জানতে পরবর্তী বিভাগ দেখুন।)
বিস্তার নিয়ন্ত্রণ সহ ডিভাইস।
যেসব ডিভাইসে সাধারণ ভাইব্রেশন সাপোর্ট (চালু/বন্ধ) রয়েছে—অন্য কথায়, যেগুলোতে অ্যাম্প্লিটিউড নিয়ন্ত্রণের ব্যবস্থা নেই।
যদি আপনার অ্যাপের হ্যাপটিক এফেক্ট নির্বাচনে এই বিভাগগুলো বিবেচনা করা হয়, তাহলে এর হ্যাপটিক ইউজার এক্সপেরিয়েন্স যেকোনো ডিভাইসের জন্যই অনুমানযোগ্য থাকবে।
স্পর্শমূলক আদিম উপাদানের ব্যবহার
অ্যান্ড্রয়েডে বেশ কিছু হ্যাপটিক্স প্রিমিটিভ রয়েছে, যেগুলোর বিস্তার (amplitude) এবং কম্পাঙ্ক (frequency) উভয়ই ভিন্ন ভিন্ন হয়। সমৃদ্ধ হ্যাপটিক এফেক্ট অর্জনের জন্য আপনি একটি প্রিমিটিভ একা অথবা একাধিক প্রিমিটিভ একত্রে ব্যবহার করতে পারেন।
- দুটি প্রিমিটিভের মধ্যে সুস্পষ্ট ব্যবধান রাখার জন্য ৫০ মিলিসেকেন্ড বা তার বেশি সময়ের ডিলে ব্যবহার করুন এবং সম্ভব হলে প্রিমিটিভের ডিউরেশনও বিবেচনায় রাখুন।
- এমন স্কেল ব্যবহার করুন যেগুলোর পার্থক্যের অনুপাত ১.৪ বা তার বেশি, যাতে তীব্রতার পার্থক্যটি আরও ভালোভাবে বোঝা যায়।
একটি প্রিমিটিভের নিম্ন, মধ্যম এবং উচ্চ তীব্রতার সংস্করণ তৈরি করতে ০.৫, ০.৭ এবং ১.০ স্কেল ব্যবহার করুন।
কাস্টম কম্পন প্যাটার্ন তৈরি করুন
মনোযোগ আকর্ষণকারী হ্যাপটিক্সে, যেমন নোটিফিকেশন এবং রিংটোনে, প্রায়শই ভাইব্রেশন প্যাটার্ন ব্যবহার করা হয়। Vibrator সার্ভিসটি দীর্ঘ ভাইব্রেশন প্যাটার্ন চালাতে পারে, যা সময়ের সাথে সাথে ভাইব্রেশনের বিস্তার পরিবর্তন করে। এই ধরনের প্রভাবকে ওয়েভফর্ম বলা হয়।
ওয়েভফর্ম ইফেক্ট সাধারণত উপলব্ধি করা যায়, কিন্তু শান্ত পরিবেশে বাজানো হলে হঠাৎ দীর্ঘ কম্পন ব্যবহারকারীকে চমকে দিতে পারে। খুব দ্রুত একটি নির্দিষ্ট অ্যাম্প্লিটিউডে পৌঁছালে তা থেকে শ্রাব্য গুঞ্জন শব্দও তৈরি হতে পারে। অ্যাম্প্লিটিউডের পরিবর্তন মসৃণ করে ধীরে ধীরে বাড়ানো ও কমানোর ইফেক্ট তৈরি করার জন্য ওয়েভফর্ম প্যাটার্ন ডিজাইন করুন।
কম্পন প্যাটার্নের উদাহরণ
নিম্নলিখিত বিভাগগুলিতে কম্পনের বিভিন্ন ধরনের উদাহরণ দেওয়া হয়েছে:
র্যাম্প-আপ প্যাটার্ন
তরঙ্গরূপগুলিকে তিনটি প্যারামিটার সহ VibrationEffect হিসাবে উপস্থাপন করা হয়:
- সময়কাল: প্রতিটি ওয়েভফর্ম সেগমেন্টের জন্য মিলিসেকেন্ডে পরিমাপকৃত সময়কালের একটি অ্যারে।
- অ্যাম্প্লিটিউড: প্রথম আর্গুমেন্টে নির্দিষ্ট করা প্রতিটি সময়কালের জন্য কাঙ্ক্ষিত কম্পনের অ্যাম্প্লিটিউড, যা ০ থেকে ২৫৫ পর্যন্ত একটি পূর্ণসংখ্যা দ্বারা প্রকাশ করা হয়, যেখানে ০ ভাইব্রেটরের 'বন্ধ অবস্থা' এবং ২৫৫ ডিভাইসটির সর্বোচ্চ অ্যাম্প্লিটিউড নির্দেশ করে।
- পুনরাবৃত্তি সূচক: প্রথম আর্গুমেন্টে নির্দিষ্ট করা অ্যারের সেই সূচক যেখান থেকে ওয়েভফর্মটির পুনরাবৃত্তি শুরু হবে, অথবা -১ যদি প্যাটার্নটি শুধু একবার বাজানো হয়।
এখানে একটি উদাহরণ তরঙ্গরূপ দেওয়া হলো, যা দুটি স্পন্দনের মাঝে ৩৫০ মিলিসেকেন্ড বিরতি দিয়ে দুইবার স্পন্দিত হয়। প্রথম স্পন্দনটি হলো সর্বোচ্চ বিস্তার পর্যন্ত একটি মসৃণ ক্রমবৃদ্ধি, এবং দ্বিতীয়টি হলো সর্বোচ্চ বিস্তার ধরে রাখার জন্য একটি দ্রুত ক্রমবৃদ্ধি। শেষে থামার বিষয়টি ঋণাত্মক পুনরাবৃত্তি সূচক মান দ্বারা নির্ধারিত হয়।
কোটলিন
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 প্যারামিটার সেট করা। যখন আপনি একটি পুনরাবৃত্তিমূলক ওয়েভফর্ম চালান, তখন সার্ভিসটিতে স্পষ্টভাবে বাতিল না করা পর্যন্ত কম্পনটি চলতে থাকে:
কোটলিন
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();
}
এটি এমন সব অনিয়মিত ঘটনার জন্য খুব উপযোগী, যেগুলোকে স্বীকৃতি দেওয়ার জন্য ব্যবহারকারীর পদক্ষেপের প্রয়োজন হয়। এই ধরনের ঘটনার উদাহরণ হলো আগত ফোন কল এবং বেজে ওঠা অ্যালার্ম।
ফলব্যাক সহ প্যাটার্ন
কম্পনের বিস্তার নিয়ন্ত্রণ করা একটি হার্ডওয়্যার-নির্ভর ক্ষমতা । এই ক্ষমতা ছাড়া কোনো নিম্নমানের ডিভাইসে একটি ওয়েভফর্ম চালালে, অ্যাম্প্লিটিউড অ্যারের প্রতিটি ধনাত্মক এন্ট্রির জন্য ডিভাইসটি সর্বোচ্চ বিস্তারে কম্পিত হয়। যদি আপনার অ্যাপকে এই ধরনের ডিভাইস সমর্থন করতে হয়, তবে এমন একটি প্যাটার্ন ব্যবহার করুন যা সেই অবস্থায় চালালে কোনো গুঞ্জন প্রভাব তৈরি করে না, অথবা একটি সহজতর অন/অফ প্যাটার্ন ডিজাইন করুন যা এর পরিবর্তে ফলব্যাক হিসেবে চালানো যেতে পারে।
কোটলিন
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.Composition ব্যবহার করে সম্মিলিত কম্পন প্রভাব তৈরি করতে পারেন। এখানে ধীরে ধীরে বাড়তে থাকা একটি প্রভাবের পরে একটি তীক্ষ্ণ ক্লিক প্রভাবের একটি উদাহরণ দেওয়া হলো:
কোটলিন
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());
ক্রমানুসারে বাজানোর জন্য প্রিমিটিভগুলো যোগ করে একটি কম্পোজিশন তৈরি করা হয়। প্রতিটি প্রিমিটিভ স্কেলেবলও, ফলে আপনি সেগুলোর প্রত্যেকটি দ্বারা উৎপন্ন কম্পনের বিস্তার নিয়ন্ত্রণ করতে পারেন। স্কেলটিকে ০ থেকে ১-এর মধ্যে একটি মান হিসাবে সংজ্ঞায়িত করা হয়, যেখানে ০ আসলে একটি সর্বনিম্ন বিস্তারকে নির্দেশ করে, যে বিস্তারে এই প্রিমিটিভটি ব্যবহারকারী (কষ্টে হলেও) অনুভব করতে পারেন।
কম্পন আদিম উপাদানগুলিতে বৈচিত্র্য তৈরি করুন
যদি আপনি একই প্রিমিটিভের একটি দুর্বল এবং একটি শক্তিশালী সংস্করণ তৈরি করতে চান, তবে শক্তির অনুপাত ১.৪ বা তার বেশি রাখুন, যাতে তীব্রতার পার্থক্য সহজেই উপলব্ধি করা যায়। একই প্রিমিটিভের তিনটির বেশি তীব্রতার স্তর তৈরি করার চেষ্টা করবেন না, কারণ সেগুলো উপলব্ধিগতভাবে স্বতন্ত্র নয়। উদাহরণস্বরূপ, একটি প্রিমিটিভের নিম্ন, মাঝারি এবং উচ্চ তীব্রতার সংস্করণ তৈরি করতে ০.৫, ০.৭ এবং ১.০ স্কেল ব্যবহার করুন।
কম্পন আদিম উপাদানগুলোর মধ্যে ফাঁক যোগ করুন
কম্পোজিশনে পরপর দুটি প্রিমিটিভের মাঝে ডিলে বা বিলম্ব যোগ করার বিষয়টিও নির্দিষ্ট করা যায়। এই ডিলে পূর্ববর্তী প্রিমিটিভ শেষ হওয়ার পর থেকে মিলিসেকেন্ডে প্রকাশ করা হয়। সাধারণত, দুটি প্রিমিটিভের মধ্যে ৫ থেকে ১০ মিলিসেকেন্ডের ব্যবধান এতটাই কম যে তা শনাক্ত করা যায় না। দুটি প্রিমিটিভের মধ্যে একটি সুস্পষ্ট ব্যবধান তৈরি করতে চাইলে প্রায় ৫০ মিলিসেকেন্ড বা তার বেশি সময়ের ব্যবধান ব্যবহার করুন। এখানে ডিলে সহ একটি কম্পোজিশনের উদাহরণ দেওয়া হলো:
কোটলিন
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-গুলো ব্যবহার করা যেতে পারে:
কোটলিন
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.
}
এছাড়াও একাধিক প্রিমিটিভ যাচাই করে, তারপর ডিভাইস সাপোর্ট লেভেলের উপর ভিত্তি করে কোনগুলো কম্পোজ করা হবে তা স্থির করা সম্ভব:
কোটলিন
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);
কম্পন রচনার উদাহরণ
নিম্নলিখিত বিভাগগুলিতে গিটহাবের হ্যাপটিক্স স্যাম্পল অ্যাপ থেকে নেওয়া কম্পন বিন্যাসের কয়েকটি উদাহরণ দেওয়া হয়েছে।
প্রতিরোধ করুন (কম টিকের সাথে)
চলমান কোনো কাজের প্রয়োজনীয় ফিডব্যাক দেওয়ার জন্য আপনি প্রিমিটিভ ভাইব্রেশনের বিস্তার নিয়ন্ত্রণ করতে পারেন। কোনো প্রিমিটিভের মসৃণ ক্রমবর্ধমান প্রভাব তৈরি করতে কাছাকাছি স্কেল মান ব্যবহার করা যেতে পারে। ব্যবহারকারীর ইন্টারঅ্যাকশনের উপর ভিত্তি করে পরপর প্রিমিটিভগুলোর মধ্যবর্তী বিলম্বও গতিশীলভাবে সেট করা যায়। ড্র্যাগ জেসচার দ্বারা নিয়ন্ত্রিত এবং হ্যাপটিক্স দ্বারা বর্ধিত একটি ভিউ অ্যানিমেশনের নিম্নলিখিত উদাহরণে এটি দেখানো হয়েছে।

চিত্র ১। এই তরঙ্গরূপটি একটি ডিভাইসের কম্পনের ফলে সৃষ্ট ত্বরণের আউটপুটকে উপস্থাপন করে।
কোটলিন
@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 । এই প্রিমিটিভগুলো একসাথে আরও ভালোভাবে কাজ করে এমন একটি ওয়েভফর্ম সেগমেন্ট তৈরি করতে, যার তীব্রতা প্রথমে বাড়ে এবং তারপর কমে যায়। আপনি স্কেল করা প্রিমিটিভগুলোকে অ্যালাইন করতে পারেন যাতে তাদের মধ্যে অ্যাম্প্লিটিউডের হঠাৎ পরিবর্তন না ঘটে, যা সামগ্রিক ইফেক্টের স্থায়িত্বকাল বাড়ানোর জন্যও বেশ কার্যকর। উপলব্ধিগতভাবে, মানুষ সবসময় হ্রাসমান অংশের চেয়ে ক্রমবর্ধমান অংশটি বেশি লক্ষ্য করে, তাই ক্রমবর্ধমান অংশটিকে হ্রাসমান অংশের চেয়ে ছোট করে গুরুত্বকে হ্রাসমান অংশের দিকে নিয়ে যাওয়া যেতে পারে।
একটি বৃত্তকে প্রসারিত ও সংকুচিত করার জন্য এই কম্পোজিশনের একটি প্রয়োগের উদাহরণ এখানে দেওয়া হলো। অ্যানিমেশন চলাকালীন রাইজ ইফেক্টটি প্রসারণের অনুভূতিকে আরও বাড়িয়ে তুলতে পারে। রাইজ ও ফল ইফেক্টের সংমিশ্রণটি অ্যানিমেশনের শেষে সংকোচনকে আরও জোরালো করতে সাহায্য করে।

চিত্র ২। এই তরঙ্গরূপটি একটি ডিভাইসের কম্পনের ফলে সৃষ্ট ত্বরণের আউটপুটকে উপস্থাপন করে।
কোটলিন
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 ব্যবহার করা। এই প্রিমিটিভটি সবচেয়ে কার্যকর হয় যখন এটিকে একাধিকবার কল করা হয়। একাধিক স্পিন একসাথে জুড়ে দিলে একটি টলমল ও অস্থির প্রভাব তৈরি হতে পারে, যা প্রতিটি প্রিমিটিভের উপর কিছুটা র্যান্ডম স্কেলিং প্রয়োগ করে আরও বাড়ানো যায়। আপনি পরপর স্পিন প্রিমিটিভগুলোর মধ্যবর্তী ব্যবধান নিয়েও পরীক্ষা করতে পারেন। কোনো ব্যবধান ছাড়া (মাঝখানে ০ মিলিসেকেন্ড) দুটি স্পিন একটি তীব্র ঘূর্ণনের অনুভূতি তৈরি করে। স্পিনগুলোর মধ্যবর্তী ব্যবধান ১০ থেকে ৫০ মিলিসেকেন্ডে বাড়ালে একটি ঢিলেঢালা ঘূর্ণনের অনুভূতি হয়, এবং এটি কোনো ভিডিও বা অ্যানিমেশনের সময়কালের সাথে মেলানোর জন্য ব্যবহার করা যেতে পারে।
১০০ মিলিসেকেন্ডের বেশি ব্যবধান ব্যবহার করবেন না, কারণ এতে ধারাবাহিক ঘূর্ণনগুলো আর ভালোভাবে সমন্বিত হয় না এবং স্বতন্ত্র প্রভাব হিসেবে অনুভূত হতে শুরু করে।
এখানে একটি স্থিতিস্থাপক আকৃতির উদাহরণ দেওয়া হলো, যা নিচে টেনে ছেড়ে দেওয়ার পর আবার আগের অবস্থায় ফিরে আসে। এই অ্যানিমেশনটিকে একজোড়া ঘূর্ণন প্রভাব দিয়ে আরও আকর্ষণীয় করা হয়েছে, যেগুলোর তীব্রতা লাফিয়ে ওঠার সরণের সমানুপাতিকভাবে বিভিন্ন মাত্রায় প্রয়োগ করা হয়।

চিত্র ৩। এই তরঙ্গরূপটি একটি ডিভাইসের কম্পনের ফলে সৃষ্ট ত্বরণের আউটপুটকে উপস্থাপন করে।
কোটলিন
@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 একটি শক্তিশালী ও প্রতিধ্বনিত প্রভাব তৈরি করতে পারে, যা সামগ্রিক অভিজ্ঞতাকে আরও সমৃদ্ধ করার জন্য, উদাহরণস্বরূপ কোনো ভিডিও বা অ্যানিমেশনে, কোনো সংঘর্ষের দৃশ্যায়নের সাথে যুক্ত করা যেতে পারে।
এখানে একটি বল পড়ার অ্যানিমেশনের উদাহরণ দেওয়া হলো, যেখানে বলটি স্ক্রিনের নিচ থেকে প্রতিবার বাউন্স করার সময় একটি ধপাস শব্দ (thud effect) বাজানো হয়:

চিত্র ৪। এই তরঙ্গরূপটি একটি ডিভাইসের কম্পনের ফলে সৃষ্ট ত্বরণের আউটপুটকে উপস্থাপন করে।
কোটলিন
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;
}
}
});
}
}
খাম সহ কম্পন তরঙ্গরূপ
কাস্টম ভাইব্রেশন প্যাটার্ন তৈরির প্রক্রিয়াটি আপনাকে ভাইব্রেশনের বিস্তার নিয়ন্ত্রণ করে ধীরে ধীরে বাড়ানো ও কমানোর মসৃণ প্রভাব তৈরি করতে দেয়। এই বিভাগে ওয়েভফর্ম এনভেলপ ব্যবহার করে কীভাবে ডায়নামিক হ্যাপটিক এফেক্ট তৈরি করা যায় তা ব্যাখ্যা করা হয়েছে, যা সময়ের সাথে সাথে ভাইব্রেশনের বিস্তার এবং ফ্রিকোয়েন্সির উপর সুনির্দিষ্ট নিয়ন্ত্রণ রাখতে সাহায্য করে। এটি আপনাকে আরও সমৃদ্ধ এবং সূক্ষ্ম হ্যাপটিক অভিজ্ঞতা তৈরি করতে সক্ষম করে।
অ্যান্ড্রয়েড ১৬ (এপিআই লেভেল ৩৬) থেকে, সিস্টেমটি কন্ট্রোল পয়েন্টের একটি ক্রম নির্ধারণ করে একটি ভাইব্রেশন ওয়েভফর্ম এনভেলপ তৈরি করার জন্য নিম্নলিখিত এপিআইগুলো প্রদান করে:
-
BasicEnvelopeBuilder: হার্ডওয়্যার-নিরপেক্ষ স্পর্শ-সংবেদনশীল প্রভাব তৈরির একটি সহজলভ্য পদ্ধতি। -
WaveformEnvelopeBuilder: হ্যাপটিক এফেক্ট তৈরির একটি আরও উন্নত পদ্ধতি; এর জন্য হ্যাপটিক্স হার্ডওয়্যার সম্পর্কে পরিচিতি থাকা প্রয়োজন।
অ্যান্ড্রয়েড এনভেলপ এফেক্টের জন্য কোনো ফলব্যাক প্রদান করে না। আপনার যদি এই সাপোর্টের প্রয়োজন হয়, তবে নিম্নলিখিত ধাপগুলো সম্পন্ন করুন:
-
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 \) প্রদত্ত কম্পাঙ্কে অর্জনযোগ্য সর্বোচ্চ আউটপুট ত্বরণের অর্ধেক উৎপন্ন করে।
একটি কম্পাঙ্ক মান, যা হার্টজ এককে নির্দিষ্ট করা হয়।
একটি সময়কাল মান, যা সর্বশেষ কন্ট্রোল পয়েন্ট থেকে নতুনটিতে যেতে লাগা সময়কে (মিলিসেকেন্ডে) নির্দেশ করে।
নিম্নলিখিত কোডটি একটি উদাহরণ তরঙ্গরূপ দেখায় যা একটি ৪০০ মিলিসেকেন্ডের কম্পন প্রভাব নির্ধারণ করে। এটি একটি স্থির ৬০ হার্জ কম্পাঙ্কে, বন্ধ অবস্থা থেকে পূর্ণ অবস্থা পর্যন্ত, ৫০ মিলিসেকেন্ডের একটি বিস্তার বৃদ্ধির মাধ্যমে শুরু হয়। তারপর, পরবর্তী ১০০ মিলিসেকেন্ডে কম্পাঙ্ক বেড়ে ১২০ হার্জে পৌঁছায় এবং ২০০ মিলিসেকেন্ড ধরে সেই স্তরে থাকে। অবশেষে, বিস্তারটি ধীরে ধীরে কমে আসে। \( 0 \)এবং শেষ ৫০ মিলিসেকেন্ডে কম্পাঙ্ক আবার ৬০ হার্জে ফিরে আসে:
vibrator.vibrate(VibrationEffect.WaveformEnvelopeBuilder()
.addControlPoint(1.0f, 60f, 50)
.addControlPoint(1.0f, 120f, 100)
.addControlPoint(1.0f, 120f, 200)
.addControlPoint(0.0f, 60f, 50)
.build()
)
নিম্নলিখিত বিভাগগুলিতে এনভেলপসহ কম্পন তরঙ্গরূপের কয়েকটি উদাহরণ দেওয়া হয়েছে।
লাফানো স্প্রিং
পূর্ববর্তী একটি নমুনা ভৌত বাউন্স ইন্টারঅ্যাকশন অনুকরণ করতে PRIMITIVE_THUD ব্যবহার করে। বেসিক এনভেলপ এপিআই আরও সূক্ষ্ম নিয়ন্ত্রণ প্রদান করে, যা আপনাকে কম্পনের তীব্রতা এবং তীক্ষ্ণতা সুনির্দিষ্টভাবে নির্ধারণ করতে দেয়। এর ফলে প্রাপ্ত হ্যাপটিক ফিডব্যাক অ্যানিমেটেড ঘটনাগুলোকে আরও নির্ভুলভাবে অনুসরণ করে।
এখানে একটি অবাধে পতনশীল স্প্রিং-এর উদাহরণ দেওয়া হলো, যেখানে অ্যানিমেশনটিকে একটি সাধারণ এনভেলপ এফেক্ট দিয়ে উন্নত করা হয়েছে; এই এফেক্টটি প্রতিবার স্প্রিংটি স্ক্রিনের নিচের অংশে ধাক্কা খেয়ে ফিরে আসার সময় বাজানো হয়:

চিত্র ৫. একটি লাফানো স্প্রিংয়ের কম্পনের অনুকরণে প্রাপ্ত আউটপুট ত্বরণের তরঙ্গরূপ গ্রাফ।
@Composable
fun BouncingSpringAnimation() {
var springX by remember { mutableStateOf(SPRING_WIDTH) }
var springY by remember { mutableStateOf(SPRING_HEIGHT) }
var velocityX by remember { mutableFloatStateOf(INITIAL_VELOCITY) }
var velocityY by remember { mutableFloatStateOf(INITIAL_VELOCITY) }
var sharpness by remember { mutableFloatStateOf(INITIAL_SHARPNESS) }
var intensity by remember { mutableFloatStateOf(INITIAL_INTENSITY) }
var multiplier by remember { mutableFloatStateOf(INITIAL_MULTIPLIER) }
var bottomBounceCount by remember { mutableIntStateOf(0) }
var animationStartTime by remember { mutableLongStateOf(0L) }
var isAnimating by remember { mutableStateOf(false) }
val (screenHeight, screenWidth) = getScreenDimensions(context)
LaunchedEffect(isAnimating) {
animationStartTime = System.currentTimeMillis()
isAnimating = true
while (isAnimating) {
velocityY += GRAVITY
springX += velocityX.dp
springY += velocityY.dp
// Handle bottom collision
if (springY > screenHeight - FLOOR_HEIGHT - SPRING_HEIGHT / 2) {
// Set the spring's y-position to the bottom bounce point, to keep it
// above the floor.
springY = screenHeight - FLOOR_HEIGHT - SPRING_HEIGHT / 2
// Reverse the vertical velocity and apply damping to simulate a bounce.
velocityY *= -BOUNCE_DAMPING
bottomBounceCount++
// Calculate the fade-out duration of the vibration based on the
// vertical velocity.
val fadeOutDuration =
((abs(velocityY) / GRAVITY) * FRAME_DELAY_MS).toLong()
// Create a "boing" envelope vibration effect that fades out.
vibrator.vibrate(
VibrationEffect.BasicEnvelopeBuilder()
// Starting from zero sharpness here, will simulate a smoother
// "boing" effect.
.setInitialSharpness(0f)
// Add a control point to reach the desired intensity and
// sharpness very quickly.
.addControlPoint(intensity, sharpness, 20L)
// Add a control point to fade out the vibration intensity while
// maintaining sharpness.
.addControlPoint(0f, sharpness, fadeOutDuration)
.build()
)
// Decrease the intensity and sharpness of the vibration for subsequent
// bounces, and reduce the multiplier to create a fading effect.
intensity *= multiplier
sharpness *= multiplier
multiplier -= 0.1f
}
if (springX > screenWidth - SPRING_WIDTH / 2) {
// Prevent the spring from moving beyond the right edge of the screen.
springX = screenWidth - SPRING_WIDTH / 2
}
// Check for 3 bottom bounces and then slow down.
if (bottomBounceCount >= MAX_BOTTOM_BOUNCE &&
System.currentTimeMillis() - animationStartTime > 1000) {
velocityX *= 0.9f
velocityY *= 0.9f
}
delay(FRAME_DELAY_MS) // Control animation speed.
// Determine if the animation should continue based on the spring's
// position and velocity.
isAnimating = (springY < screenHeight + SPRING_HEIGHT ||
springX < screenWidth + SPRING_WIDTH)
&& (velocityX >= 0.1f || velocityY >= 0.1f)
}
}
Box(
modifier = Modifier
.fillMaxSize()
.noRippleClickable {
if (!isAnimating) {
resetAnimation()
}
}
.width(screenWidth)
.height(screenHeight)
) {
DrawSpring(mutableStateOf(springX), mutableStateOf(springY))
DrawFloor()
if (!isAnimating) {
DrawText("Tap to restart")
}
}
}
রকেট উৎক্ষেপণ
পূর্ববর্তী একটি নমুনায় দেখানো হয়েছে কীভাবে বেসিক এনভেলপ এপিআই ব্যবহার করে একটি বাউন্সি স্প্রিং রিঅ্যাকশন সিমুলেট করা যায়। WaveformEnvelopeBuilder ডিভাইসটির সম্পূর্ণ ফ্রিকোয়েন্সি রেঞ্জের উপর সুনির্দিষ্ট নিয়ন্ত্রণ প্রদান করে, যা অত্যন্ত কাস্টমাইজড হ্যাপটিক ইফেক্ট তৈরি করতে সক্ষম করে। এটিকে FOAM ডেটার সাথে একত্রিত করে, আপনি নির্দিষ্ট ফ্রিকোয়েন্সি সক্ষমতা অনুযায়ী ভাইব্রেশন তৈরি করতে পারেন।
এখানে একটি উদাহরণ দেওয়া হলো যা একটি ডাইনামিক ভাইব্রেশন প্যাটার্ন ব্যবহার করে রকেট উৎক্ষেপণের সিমুলেশন প্রদর্শন করে। এই ইফেক্টটি সর্বনিম্ন সমর্থিত ফ্রিকোয়েন্সির ত্বরণ আউটপুট, ০.১ G, থেকে রেজোনেন্ট ফ্রিকোয়েন্সি পর্যন্ত যায় এবং সবসময় ১০% অ্যাম্প্লিটিউড ইনপুট বজায় রাখে। এর ফলে ইফেক্টটি একটি যথেষ্ট শক্তিশালী আউটপুট দিয়ে শুরু হয় এবং ড্রাইভিং অ্যাম্প্লিটিউড একই থাকা সত্ত্বেও এর অনুভূত তীব্রতা ও তীক্ষ্ণতা বৃদ্ধি পায়। রেজোনেন্সে পৌঁছানোর পর, ইফেক্টের ফ্রিকোয়েন্সি আবার সর্বনিম্ন মানে নেমে আসে, যা হ্রাসমান তীব্রতা ও তীক্ষ্ণতা হিসেবে অনুভূত হয়। এটি প্রথমে একটি বাধা এবং তারপরে মুক্তির অনুভূতি তৈরি করে, যা মহাকাশে উৎক্ষেপণের অনুকরণ করে।
বেসিক এনভেলপ এপিআই দিয়ে এই এফেক্টটি সম্ভব নয়, কারণ এটি ডিভাইসের রেজোনেন্ট ফ্রিকোয়েন্সি এবং আউটপুট অ্যাক্সিলারেশন কার্ভ সম্পর্কিত ডিভাইস-নির্দিষ্ট তথ্যকে আড়াল করে রাখে। শার্পনেস বাড়ালে তা ইকুইভ্যালেন্ট ফ্রিকোয়েন্সিকে রেজোন্যান্সের বাইরে ঠেলে দিতে পারে, যার ফলে অনাকাঙ্ক্ষিত অ্যাক্সিলারেশন ডিপ তৈরি হতে পারে।

চিত্র ৬। রকেট উৎক্ষেপণের অনুকরণকারী একটি কম্পনের আউটপুট ত্বরণ তরঙ্গরূপ গ্রাফ।
@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 desired 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()
)
}