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

এই পৃষ্ঠাটি একটি Android অ্যাপ্লিকেশনে কাস্টম প্রভাব তৈরি করতে বিভিন্ন হ্যাপটিক্স API ব্যবহার করার উদাহরণগুলি কভার করে৷ এই পৃষ্ঠার বেশিরভাগ তথ্য একটি ভাইব্রেশন অ্যাকচুয়েটরের কাজের ভাল জ্ঞানের উপর নির্ভর করে, আমরা ভাইব্রেশন অ্যাকচুয়েটর প্রাইমার পড়ার পরামর্শ দিই।

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

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

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

কোন কাস্টম প্রভাব বাস্তবায়ন করার সময়, নিম্নলিখিত বিবেচনা করুন:

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

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

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

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

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

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

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

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

হ্যাপটিক আদিম ব্যবহার

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

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

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

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

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

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

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

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

এখানে একটি উদাহরণ তরঙ্গরূপ যা ডালের মধ্যে 350 ms বিরতি দিয়ে দুবার স্পন্দন করে। প্রথম পালস সর্বাধিক প্রশস্ততা পর্যন্ত একটি মসৃণ র‌্যাম্প এবং দ্বিতীয়টি সর্বাধিক প্রশস্ততা ধরে রাখার জন্য একটি দ্রুত র‌্যাম্প। শেষে থামানো ঋণাত্মক পুনরাবৃত্তি সূচক মান দ্বারা সংজ্ঞায়িত করা হয়।

কোটলিন

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 // Do not 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; // Do not repeat.

vibrator.vibrate(VibrationEffect.createWaveform(timings, amplitudes, repeatIndex));

নমুনা: পুনরাবৃত্তি প্যাটার্ন

বাতিল না হওয়া পর্যন্ত ওয়েভফর্মগুলিও বারবার চালানো যেতে পারে। একটি পুনরাবৃত্তিমূলক তরঙ্গরূপ তৈরি করার উপায় হল একটি অ-নেতিবাচক 'পুনরাবৃত্তি' পরামিতি সেট করা। যখন আপনি একটি পুনরাবৃত্তিমূলক তরঙ্গরূপ বাজান, তখন কম্পন চলতে থাকে যতক্ষণ না এটি পরিষেবাতে স্পষ্টভাবে বাতিল করা হয়:

কোটলিন

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 ms ব্যবধান শনাক্ত করার জন্য খুব কম। আপনি যদি দুটি আদিম ব্যবধান তৈরি করতে চান তবে 50 ms বা তার বেশি ক্রমানুসারে একটি ফাঁক ব্যবহার করার কথা বিবেচনা করুন। এখানে বিলম্ব সহ একটি রচনার একটি উদাহরণ রয়েছে:

কোটলিন

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

আমরা 100 ms-এর বেশি ব্যবধান ব্যবহার করার পরামর্শ দিই না, কারণ ধারাবাহিক স্পিনগুলি আর ভালভাবে সংহত হয় না এবং পৃথক প্রভাবের মতো অনুভব করতে শুরু করে।

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

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

কোটলিন

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