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

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

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

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

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

যেকোনো কাস্টম ইফেক্ট বাস্তবায়নের সময়, নিম্নলিখিত বিষয়গুলি বিবেচনা করুন:

  • প্রভাবের জন্য কোন ডিভাইসের ক্ষমতা প্রয়োজন
  • ডিভাইসটি যখন ইফেক্ট চালাতে সক্ষম না হয় তখন কী করবেন

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

আপনার ব্যবহারের ক্ষেত্রের উপর নির্ভর করে, আপনি কাস্টম প্রভাবগুলি অক্ষম করতে পারেন অথবা বিভিন্ন সম্ভাব্য ক্ষমতার উপর ভিত্তি করে বিকল্প কাস্টম প্রভাব প্রদান করতে পারেন।

ডিভাইসের ক্ষমতার নিম্নলিখিত উচ্চ-স্তরের শ্রেণীর জন্য পরিকল্পনা করুন:

  • যদি আপনি হ্যাপটিক প্রিমিটিভ ব্যবহার করেন: কাস্টম ইফেক্টের জন্য প্রয়োজনীয় প্রিমিটিভ সমর্থনকারী ডিভাইস। (প্রিমিটিভ সম্পর্কে বিস্তারিত জানতে পরবর্তী বিভাগটি দেখুন।)

  • প্রশস্ততা নিয়ন্ত্রণ সহ ডিভাইস।

  • মৌলিক কম্পন সমর্থন (চালু/বন্ধ) সহ ডিভাইসগুলি - অন্য কথায়, যেগুলিতে প্রশস্ততা নিয়ন্ত্রণের অভাব রয়েছে।

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

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

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

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

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

কম্পনের ধরণগুলি প্রায়শই মনোযোগ আকর্ষণকারী হ্যাপটিকগুলিতে ব্যবহৃত হয়, যেমন নোটিফিকেশন এবং রিংটোন। Vibrator পরিষেবা দীর্ঘ কম্পনের ধরণগুলি চালাতে পারে যা সময়ের সাথে সাথে কম্পনের প্রশস্ততা পরিবর্তন করে। এই ধরনের প্রভাবগুলিকে তরঙ্গরূপ বলা হয়।

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

কম্পন নিদর্শনের উদাহরণ

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

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

তরঙ্গরূপগুলিকে তিনটি পরামিতি সহ VibrationEffect হিসাবে উপস্থাপন করা হয়:

  1. সময়: প্রতিটি তরঙ্গরূপ অংশের জন্য মিলিসেকেন্ডে সময়কালের একটি অ্যারে।
  2. প্রশস্ততা: প্রথম যুক্তিতে উল্লেখিত প্রতিটি সময়কালের জন্য কাঙ্ক্ষিত কম্পন প্রশস্ততা, 0 থেকে 255 পর্যন্ত একটি পূর্ণসংখ্যা মান দ্বারা উপস্থাপিত, যেখানে 0 ভাইব্রেটর "অফ স্টেট" প্রতিনিধিত্ব করে এবং 255 হল ডিভাইসের সর্বোচ্চ প্রশস্ততা।
  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))

জাভা

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

কম্পন রচনা তৈরি করুন

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

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

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

অসমর্থিত প্রিমিটিভ সহ কম্পোজিশনের জন্য অ্যান্ড্রয়েড কোনও ফলব্যাক প্রদান করে না। অতএব, নিম্নলিখিত পদক্ষেপগুলি অনুসরণ করুন:

  1. আপনার উন্নত হ্যাপটিক্স সক্রিয় করার আগে, পরীক্ষা করে নিন যে একটি নির্দিষ্ট ডিভাইস আপনার ব্যবহৃত সমস্ত প্রিমিটিভ সমর্থন করে কিনা।

  2. শুধুমাত্র আদিম অভিজ্ঞতার অভাব থাকা প্রভাবগুলি নয়, বরং অসমর্থিত অভিজ্ঞতার ধারাবাহিক সেটটি অক্ষম করুন।

ডিভাইসের সাপোর্ট কীভাবে পরীক্ষা করবেন সে সম্পর্কে আরও তথ্য নিম্নলিখিত বিভাগগুলিতে দেখানো হয়েছে।

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

আপনি 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());

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

কম্পন আদিম পদার্থে রূপ তৈরি করুন

যদি আপনি একই আদিমটির দুর্বল এবং শক্তিশালী সংস্করণ তৈরি করতে চান, তাহলে 1.4 বা তার বেশি শক্তি অনুপাত তৈরি করুন, যাতে তীব্রতার পার্থক্য সহজেই অনুধাবন করা যায়। একই আদিমটির তিনটির বেশি তীব্রতার স্তর তৈরি করার চেষ্টা করবেন না, কারণ এগুলি উপলব্ধিগতভাবে পৃথক নয়। উদাহরণস্বরূপ, একটি আদিমটির নিম্ন, মাঝারি এবং উচ্চ তীব্রতার সংস্করণ তৈরি করতে 0.5, 0.7 এবং 1.0 এর স্কেল ব্যবহার করুন।

কম্পন আদিমগুলির মধ্যে ফাঁক যোগ করুন

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

কোটলিন

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

জাভা

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

কম্পন রচনার উদাহরণ

নিম্নলিখিত বিভাগগুলিতে GitHub-এর হ্যাপটিক্স নমুনা অ্যাপ থেকে নেওয়া কম্পন রচনার বেশ কয়েকটি উদাহরণ দেওয়া হয়েছে।

প্রতিরোধ করুন (কম টিক দিয়ে)

আপনি প্রগতিশীল কোনও ক্রিয়ায় কার্যকর প্রতিক্রিয়া জানাতে আদিম কম্পনের প্রশস্ততা নিয়ন্ত্রণ করতে পারেন। একটি আদিমটির একটি মসৃণ ক্রিসেন্ডো প্রভাব তৈরি করতে ঘনিষ্ঠভাবে ব্যবধানযুক্ত স্কেল মান ব্যবহার করা যেতে পারে। ব্যবহারকারীর মিথস্ক্রিয়ার উপর ভিত্তি করে ধারাবাহিক আদিমগুলির মধ্যে বিলম্বও গতিশীলভাবে সেট করা যেতে পারে। এটি একটি ড্র্যাগ অঙ্গভঙ্গি দ্বারা নিয়ন্ত্রিত এবং হ্যাপটিক্স দ্বারা বর্ধিত একটি ভিউ অ্যানিমেশনের নিম্নলিখিত উদাহরণে চিত্রিত করা হয়েছে।

একটি বৃত্তকে টেনে নামানোর অ্যানিমেশন।
ইনপুট কম্পন তরঙ্গরূপের প্লট।

চিত্র ১. এই তরঙ্গরূপটি একটি ডিভাইসে কম্পনের আউটপুট ত্বরণকে প্রতিনিধিত্ব করে।

কোটলিন

@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 ব্যবহার করা। এই আদিমটি যখন একাধিকবার ডাকা হয় তখন সবচেয়ে কার্যকর। একাধিক স্পিন সংযুক্ত করলে একটি টলমল এবং অস্থির প্রভাব তৈরি হতে পারে, যা প্রতিটি আদিমটিতে কিছুটা এলোমেলো স্কেলিং প্রয়োগ করে আরও উন্নত করা যেতে পারে। আপনি ধারাবাহিক স্পিন আদিমগুলির মধ্যে ব্যবধান নিয়েও পরীক্ষা করতে পারেন। কোনও ফাঁক ছাড়াই দুটি স্পিন (মাঝখানে 0 মিলিসেকেন্ড) একটি টাইট স্পিনিং সংবেদন তৈরি করে। ইন্টার-স্পিন ব্যবধান 10 থেকে 50 মিলিসেকেন্ডে বৃদ্ধি করলে একটি আলগা স্পিনিং সংবেদন তৈরি হয় এবং একটি ভিডিও বা অ্যানিমেশনের সময়কাল মেলাতে ব্যবহার করা যেতে পারে।

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

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

একটি ইলাস্টিক আকৃতির লাফানোর অ্যানিমেশন
ইনপুট কম্পন তরঙ্গরূপের প্লট

চিত্র ৩. এই তরঙ্গরূপটি একটি ডিভাইসে কম্পনের আউটপুট ত্বরণকে প্রতিনিধিত্ব করে।

কোটলিন

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

এখানে একটি বল ড্রপ অ্যানিমেশনের উদাহরণ দেওয়া হল যেখানে স্ক্রিনের নিচ থেকে বল লাফানোর সময় প্রতিবার থাড ইফেক্ট ব্যবহার করা হয়:

স্ক্রিনের নিচ থেকে লাফিয়ে পড়া একটি বলের অ্যানিমেশন।
ইনপুট কম্পন তরঙ্গরূপের প্লট।

চিত্র ৪. এই তরঙ্গরূপটি একটি ডিভাইসে কম্পনের আউটপুট ত্বরণকে প্রতিনিধিত্ব করে।

কোটলিন

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

খাম সহ কম্পন তরঙ্গরূপ

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

অ্যান্ড্রয়েড ১৬ (এপিআই লেভেল ৩৬) থেকে শুরু করে, সিস্টেমটি নিয়ন্ত্রণ বিন্দুর ক্রম নির্ধারণ করে একটি কম্পন তরঙ্গরূপ তৈরি করতে নিম্নলিখিত API গুলি সরবরাহ করে:

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

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

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

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

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

  • একটি সময়কাল মান, যা শেষ নিয়ন্ত্রণ বিন্দু থেকে - অর্থাৎ, একটি তীব্রতা এবং তীক্ষ্ণতা জোড়া - নতুন নিয়ন্ত্রণ বিন্দুতে রূপান্তরের জন্য মিলিসেকেন্ডে নেওয়া সময়ের প্রতিনিধিত্ব করে।

এখানে একটি উদাহরণ তরঙ্গরূপ দেওয়া হল যা 500 মিলিসেকেন্ডের বেশি গতিতে নিম্ন-পিচ থেকে উচ্চ-পিচ, সর্বোচ্চ-শক্তির কম্পনের তীব্রতা বৃদ্ধি করে এবং তারপর আবার নীচের দিকে র‍্যাম্প করে\( 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 \) প্রদত্ত ফ্রিকোয়েন্সিতে অর্জন করা যেতে পারে এমন সর্বোচ্চ আউটপুট ত্বরণের অর্ধেক উৎপন্ন করে।
  • হার্টজে নির্দিষ্ট একটি ফ্রিকোয়েন্সি মান।

  • একটি সময়কাল মান, যা শেষ নিয়ন্ত্রণ বিন্দু থেকে নতুন নিয়ন্ত্রণ বিন্দুতে রূপান্তরের জন্য মিলিসেকেন্ডে সময়কে প্রতিনিধিত্ব করে।

নিচের কোডটি একটি উদাহরণ তরঙ্গরূপ দেখায় যা 400 ms কম্পন প্রভাবকে সংজ্ঞায়িত করে। এটি 50 ms প্রশস্ততা র‍্যাম্প দিয়ে শুরু হয়, বন্ধ থেকে পূর্ণ পর্যন্ত, একটি ধ্রুবক 60 Hz এ। তারপর, পরবর্তী 100 ms ধরে ফ্রিকোয়েন্সি র‍্যাম্প 120 Hz পর্যন্ত বৃদ্ধি পায় এবং 200 ms পর্যন্ত সেই স্তরে থাকে। অবশেষে, প্রশস্ততা র‍্যাম্প 100 ms পর্যন্ত নেমে আসে। \( 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 ব্যবহার করে ভৌত বাউন্স ইন্টারঅ্যাকশন অনুকরণ করে। বেসিক এনভেলপ 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 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")
    }
  }
}

রকেট উৎক্ষেপণ

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

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

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

স্ক্রিনের নিচ থেকে একটি রকেট জাহাজের উড্ডয়নের অ্যানিমেশন।
ইনপুট কম্পন তরঙ্গরূপের প্লট।

চিত্র ৬। একটি কম্পনের জন্য একটি আউটপুট ত্বরণ তরঙ্গরূপ গ্রাফ যা একটি রকেট উৎক্ষেপণের অনুকরণ করে।

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

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

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

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

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

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

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

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

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

  vibrator.vibrate(
    VibrationEffect.WaveformEnvelopeBuilder()
      // Quickly reach the 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()
  )
}