Özel dokunsal efektler oluştur

Bu sayfada, bir Android uygulamasında özel efektler oluşturmak için farklı dokunma API'lerinin nasıl kullanılacağına dair örnekler yer almaktadır. Bu sayfadaki bilgilerin çoğu bir titreşim aktüatörünün işleyişi hakkındaki iyi bilgilere dayandığından Titreşim aktüatörü primerini okumanızı öneririz.

Bu sayfada aşağıdaki örnekler yer almaktadır.

Daha fazla örnek için Etkinliklere dokunsal geri bildirim ekleme bölümüne bakın ve her zaman dokunma tasarımı ilkelerini uygulayın.

Cihaz uyumluluğunu işlemek için yedekleri kullanma

Herhangi bir özel efekt uygularken aşağıdakileri göz önünde bulundurun:

  • Efekt için hangi cihaz özellikleri gereklidir?
  • Cihaz efekti oynatamıyorsa yapılması gerekenler

Android dokunma teknolojisi API referansı, uygulamanızın tutarlı bir genel deneyim sunabilmesi için dokunma teknolojisiyle ilgili bileşenlerin desteğinin nasıl kontrol edileceğiyle ilgili ayrıntılar sağlar.

Kullanım alanınıza bağlı olarak, özel efektleri devre dışı bırakmak veya farklı potansiyel özelliklere dayalı alternatif özel efektler sunmak isteyebilirsiniz.

Aşağıdaki üst düzey cihaz yeteneği sınıflarını planlayın:

  • Dokunsal temel öğeler kullanıyorsanız: Özel efektlerin ihtiyaç duyduğu bu temel öğeleri destekleyen cihazlar. (Temel öğelerle ilgili ayrıntılar için bir sonraki bölüme bakın.)

  • Genişlik kontrolü olan cihazlar.

  • Temel titreşim desteği (açık/kapalı) olan, diğer bir deyişle genlik kontrolü olmayan cihazlar.

Uygulamanızın dokunsal etki seçimi bu kategorileri hesaba katıyorsa dokunsal kullanıcı deneyimi her bir cihaz için tahmin edilebilir olmalıdır.

Temel dokunsal öğelerin kullanımı

Android, hem genlik hem de sıklık bakımından farklılık gösteren çeşitli dokunma teknolojisi temel öğeleri içerir. Zengin dokunsal efektler elde etmek için tek başına bir temel veya birden fazla temel öğeyi birlikte kullanabilirsiniz.

  • İki temel öğe arasındaki ayırt edilebilir boşluklar için 50 ms veya daha uzun gecikmeler kullanın. Mümkünse temel süreyi de dikkate alın.
  • Yoğunluk farkının daha iyi anlaşılması için en az 1,4 oranında farklılık gösteren ölçekler kullanın.
  • Bir temel öğenin düşük, orta ve yüksek yoğunluklu sürümünü oluşturmak için 0,5, 0,7 ve 1,0 ölçeklerini kullanın.

Özel titreşim desenleri oluşturma

Titreşim kalıpları, genellikle dokunma teknolojisinde (bildirimler ve zil sesleri gibi) kullanılır. Vibrator hizmeti, zaman içinde titreşim genliğini değiştiren uzun titreşim desenleri oynatabilir. Bu efektlere dalga biçimi denir.

Dalga biçimi efektleri kolayca algılanabilir, ancak ani uzun titreşimler sessiz bir ortamda oynatılırsa kullanıcıyı ürkütebilir. Hedef genliğe çok hızlı bir şekilde yükselmek de sesli gürültülere neden olabilir. Dalga biçimi desenleri tasarlamanız için öneri, artış ve indirme efektleri oluşturmak için genlik geçişlerini yumuşatmaktır.

Örnek: Artış kalıbı

Dalga biçimleri, üç parametreyle VibrationEffect olarak temsil edilir:

  1. Zamanlamalar: Her dalga biçimi segmenti için milisaniye cinsinden bir süre dizisi.
  2. Erginlik: İlk bağımsız değişkende belirtilen her bir süre için istenen titreşim genliği. 0 ile 255 arasında bir tam sayı değeriyle temsil edilir. 0 titreşimi "kapalı", 255 ise cihazın maksimum genliğini temsil eder.
  3. Tekrarlama dizini: Dalga biçimini tekrarlamaya başlamak için ilk bağımsız değişkende belirtilen dizideki dizin veya kalıbı yalnızca bir kez oynatması gerekiyorsa -1.

Aşağıda, nabızlar arasında 350 ms'lik bir duraklamayla iki kez yanıp sönen bir dalga formu örneği verilmiştir. İlk titreşim, maksimum genliğe kadar yumuşak bir yükseliş, ikincisi ise maksimum genliği korumak için hızlı bir rampadır. Sonda durdurma ise negatif tekrarlama dizini değeriyle tanımlanır.

Kotlin

val timings: LongArray = longArrayOf(50, 50, 50, 50, 50, 100, 350, 25, 25, 25, 25, 200)
val amplitudes: IntArray = intArrayOf(33, 51, 75, 113, 170, 255, 0, 38, 62, 100, 160, 255)
val repeatIndex = -1 // Do not repeat.

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

Java

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

Örnek: Yinelenen kalıp

Dalga biçimleri ayrıca iptal edilene kadar tekrar tekrar oynatılabilir. Yinelenen dalga formu oluşturmanın yolu, negatif olmayan bir "repeat" parametresi ayarlamaktır. Tekrarlayan bir dalga formu oynattığınızda titreşim, hizmette açıkça iptal edilene kadar devam eder:

Kotlin

void startVibrating() {
  val timings: LongArray = longArrayOf(50, 50, 100, 50, 50)
  val amplitudes: IntArray = intArrayOf(64, 128, 255, 128, 64)
  val repeat = 1 // Repeat from the second entry, index = 1.
  VibrationEffect repeatingEffect = VibrationEffect.createWaveform(timings, amplitudes, repeat)
  // repeatingEffect can be used in multiple places.

  vibrator.vibrate(repeatingEffect)
}

void stopVibrating() {
  vibrator.cancel()
}

Java

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

Bu, kullanıcının onay vermesi için işlem gerektiren aralıklı etkinlikler için çok kullanışlıdır. Bu tür etkinliklere örnek olarak gelen telefon aramaları ve tetiklenen alarmlar verilebilir.

Örnek: Yedekli desen

Titreşim genliğini kontrol etmek donanıma bağlı bir özelliktir. Düşük uçlu bir cihazda bu özellik olmadan bir dalga formu oynatmak, cihazın genlik dizisindeki her pozitif giriş için maksimum genlikte titreşmesine neden olur. Uygulamanızın bu tür cihazlara uyum sağlaması gerekiyorsa, bu durumda çalındığında deseninizin uğultu oluşturmamasını sağlamanız veya bunun yerine yedek olarak oynatılabilecek daha basit bir AÇMA/KAPATMA kalıbı tasarlamanız önerilir.

Kotlin

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

Java

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

Titreşim kompozisyonları oluşturma

Bu bölümde, bunların daha uzun ve daha karmaşık özel efektler halinde derlenmesi için yöntemler sunulmakta ve daha gelişmiş donanım özellikleri kullanarak zengin dokunma yöntemlerini keşfetmek için ele alınmaktadır. Daha geniş frekans bant genişliğine sahip dokunsal aktüatörlere sahip cihazlarda daha karmaşık dokunsal efektler oluşturmak için genlik ve frekans arasında değişen efekt kombinasyonlarını kullanabilirsiniz.

Bu sayfada daha önce açıklanan özel titreşim kalıpları oluşturma işlemi, yukarı ve aşağı doğru itme efektlerini düzgün şekilde oluşturmak için titreşim genliğinin nasıl kontrol edileceğini açıklamaktadır. Zengin dokunma teknolojisi, efekti daha da yumuşak hale getirmek için cihaz titreşicinin daha geniş frekans aralığını keşfederek bu kavramı geliştiriyor. Bu dalga şekilleri, özellikle "kreşendo" veya "diminuendo" etkisi oluşturmada etkilidir.

Bu sayfada daha önce açıklanan beste temel öğeleri cihaz üreticisi tarafından uygulanır. Bu uygulamalar, net dokunma teknolojisi için dokunma ilkelerine uygun net, kısa ve hoş titreşim sağlar. Bu özellikler ve işleyiş şekilleri hakkında daha fazla bilgi için Titreşim aktüatörleri prime bölümüne bakın.

Android, desteklenmeyen temel öğelere sahip besteler için yedek sağlamaz. Aşağıdaki adımları uygulamanızı öneririz:

  1. Gelişmiş dokunma teknolojisini etkinleştirmeden önce, belirli bir cihazın kullandığınız tüm temel öğeleri desteklediğinden emin olun.

  2. Yalnızca temel bir öğenin eksik olduğu efektleri değil, desteklenmeyen tutarlı deneyimler grubunu devre dışı bırakın. Cihazın desteğini nasıl kontrol edeceğinizle ilgili daha fazla bilgi aşağıda verilmiştir.

VibrationEffect.Composition ile oluşturulmuş titreşim efektleri oluşturabilirsiniz. Aşağıda, yavaşça yükselen ve ardından keskin bir tıklama efektinin bir örneği verilmiştir:

Kotlin

vibrator.vibrate(
    VibrationEffect.startComposition().addPrimitive(
      VibrationEffect.Composition.PRIMITIVE_SLOW_RISE
    ).addPrimitive(
      VibrationEffect.Composition.PRIMITIVE_CLICK
    ).compose()
  )

Java

vibrator.vibrate(
    VibrationEffect.startComposition()
        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE)
        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
        .compose());

Bir beste, sırayla çalınacak temel öğeler eklenerek oluşturulur. Her primitif aynı zamanda ölçeklenebilir. Böylece her birinin oluşturduğu titreşimin genliğini kontrol edebilirsiniz. Ölçek, 0 ile 1 arasında bir değer olarak tanımlanır. Burada 0, aslında bu temel öğenin kullanıcı tarafından (neredeyse) hissedilebileceği minimum genliğe karşılık gelir.

Aynı temel öğenin zayıf ve güçlü bir sürümünü oluşturmak isterseniz, yoğunluk farkının kolayca algılanabilmesi için ölçeklerin 1, 4 veya daha yüksek bir oranda farklılık göstermesi önerilir. Aynı temel öğe için üçten fazla yoğunluk seviyesi oluşturmaya çalışmayın, çünkü bu seviyeler algı olarak farklı değildir. Örneğin, bir basit öğenin düşük, orta ve yüksek yoğunluklu sürümünü oluşturmak için 0,5, 0,7 ve 1,0 ölçeklerini kullanın.

Kompozisyon, ardışık temel öğeler arasına eklenecek gecikmeleri de belirtebilir. Bu gecikme, önceki temel öğenin sonundan itibaren milisaniye cinsinden ifade edilir. Genel olarak, iki temel öğe arasındaki 5 ila 10 ms'lik boşluk, algılanamayacak kadar kısadır. İki temel öğe arasında ayırt edilebilir bir boşluk oluşturmak istiyorsanız 50 ms veya daha uzun bir aralık değeri kullanabilirsiniz. Burada, gecikmeli bir beste örneği görebilirsiniz:

Kotlin

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

Java

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

Belirli temel öğeler için cihaz desteğini doğrulamak amacıyla aşağıdaki API'ler kullanılabilir:

Kotlin

val primitive = VibrationEffect.Composition.PRIMITIVE_LOW_TICK

if (vibrator.areAllPrimitivesSupported(primitive)) {
  vibrator.vibrate(VibrationEffect.startComposition().addPrimitive(primitive).compose())
} else {
  // Play a predefined effect or custom pattern as a fallback.
}

Java

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

Ayrıca, birden çok temel öğeyi kontrol etmek ve ardından cihaz destek düzeyine göre hangilerinin oluşturulacağına karar vermek de mümkündür:

Kotlin

val effects: IntArray = intArrayOf(
  VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
  VibrationEffect.Composition.PRIMITIVE_TICK,
  VibrationEffect.Composition.PRIMITIVE_CLICK
)
val supported: BooleanArray = vibrator.arePrimitivesSupported(primitives);

Java

int[] primitives = new int[] {
  VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
  VibrationEffect.Composition.PRIMITIVE_TICK,
  VibrationEffect.Composition.PRIMITIVE_CLICK
};
boolean[] supported = vibrator.arePrimitivesSupported(effects);

Örnek: Direnç (alçak değer çizgileriyle)

Devam eden bir eyleme faydalı geri bildirim iletmek için temel titreşimin genliğini kontrol edebilirsiniz. Yakın aralıklı ölçek değerleri, ilkel bir öğenin yumuşak bir ayrıntılandırma efekti oluşturmak için kullanılabilir. Ardışık temel öğeler arasındaki gecikme, kullanıcı etkileşimine göre dinamik olarak da ayarlanabilir. Bu, aşağıdaki sürükleme hareketiyle kontrol edilen ve dokunma teknolojisiyle güçlendirilen bir görüntüleme animasyonu örneğinde gösterilmektedir.

Aşağı sürüklenen bir dairenin animasyonu
Giriş titreşimi dalga formu grafiği

Kotlin

@Composable
fun ResistScreen() {
  // Control variables for the dragging of the indicator.
  var isDragging by remember { mutableStateOf(false) }
  var dragOffset by remember { mutableStateOf(0f) }

  // Only vibrates while the user is dragging
  if (isDragging) {
    LaunchedEffect(Unit) {
      // Continuously run the effect for vibration to occur even when the view
      // is not being drawn, when user stops dragging midway through gesture.
      while (true) {
        // Calculate the interval inversely proportional to the drag offset.
        val vibrationInterval = calculateVibrationInterval(dragOffset)
        // Calculate the scale directly proportional to the drag offset.
        val vibrationScale = calculateVibrationScale(dragOffset)

        delay(vibrationInterval)
        vibrator.vibrate(
          VibrationEffect.startComposition().addPrimitive(
            VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
            vibrationScale
          ).compose()
        )
      }
    }
  }

  Screen() {
    Column(
      Modifier
        .draggable(
          orientation = Orientation.Vertical,
          onDragStarted = {
            isDragging = true
          },
          onDragStopped = {
            isDragging = false
          },
          state = rememberDraggableState { delta ->
            dragOffset += delta
          }
        )
    ) {
      // Build the indicator UI based on how much the user has dragged it.
      ResistIndicator(dragOffset)
    }
  }
}

Java

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

Örnek: Genişletme (yükselme ve düşüşle)

Algılanan titreşim yoğunluğunu artırmak için iki temel öğe vardır: PRIMITIVE_QUICK_RISE ve PRIMITIVE_SLOW_RISE. İkisi de aynı hedefe ancak farklı sürelerle ulaşır. Düşüş için yalnızca bir temel öğe vardır, o da PRIMITIVE_QUICK_FALL. Bu temel öğeler, yoğunluğu artan ve sonra kaybolan bir dalga formu segmenti oluşturmak için birlikte daha iyi çalışır. Aralarındaki genlikte ani sıçramalar olmasını önlemek için ölçeklendirilmiş temel öğeleri hizalayabilirsiniz. Bu, genel etki süresini uzatmak için de işe yarar. İnsanlar genellikle yükselen kısmı düşen bölümden daha çok fark ederler. Bu yüzden, yükselen kısmı düşmeden daha kısa tutmak, vurguyu düşen kısma yöneltmek için kullanılabilir.

Bu bileşimin, bir daireyi genişletmek ve daraltmak için kullanılan bir örneğini aşağıda görebilirsiniz. Yükseliş efekti, animasyon sırasında genişleme hissini artırabilir. Yükseliş ve düşüş efektlerinin kombinasyonu, animasyonun sonundaki daraltmanın vurgulanmasına yardımcı olur.

Genişleyen bir dairenin animasyonu
Giriş titreşimi dalga formu grafiği

Kotlin

enum class ExpandShapeState {
    Collapsed,
    Expanded
}

@Composable
fun ExpandScreen() {
  // Control variable for the state of the indicator.
  var currentState by remember { mutableStateOf(ExpandShapeState.Collapsed) }

  // Animation between expanded and collapsed states.
  val transitionData = updateTransitionData(currentState)

  Screen() {
    Column(
      Modifier
        .clickable(
          {
            if (currentState == ExpandShapeState.Collapsed) {
              currentState = ExpandShapeState.Expanded
              vibrator.vibrate(
                VibrationEffect.startComposition().addPrimitive(
                  VibrationEffect.Composition.PRIMITIVE_SLOW_RISE,
                  0.3f
                ).addPrimitive(
                  VibrationEffect.Composition.PRIMITIVE_QUICK_FALL,
                  0.3f
                ).compose()
              )
            } else {
              currentState = ExpandShapeState.Collapsed
              vibrator.vibrate(
                VibrationEffect.startComposition().addPrimitive(
                  VibrationEffect.Composition.PRIMITIVE_SLOW_RISE
                ).compose()
              )
          }
        )
    ) {
      // Build the indicator UI based on the current state.
      ExpandIndicator(transitionData)
    }
  }
}

Java

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

Örnek: Wobble (dönmelerle)

Temel dokunma ilkelerinden biri kullanıcıları memnun etmektir. Hoş bir beklenmedik titreşim efekti sunmanın eğlenceli bir yolu PRIMITIVE_SPIN kullanmaktır. Bu temel öğe en çok bir defadan fazla çağrıldığında etkili olur. Birden fazla döndürmenin birleştirilmesi sallanma ve kararsız bir etki oluşturabilir. Bu etki, her temel öğeye rastgele bir ölçek uygulanarak daha da artırılabilir. Ardışık dönen temel öğeler arasındaki boşlukla da deneme yapabilirsiniz. Boşluk olmadan (arada 0 ms.) iki kez dönüş yapmak, sıkı bir dönüş hissi yaratır. Dönme noktası boşluğunun 10 ms'den 50 ms'ye yükseltilmesi, daha rahat dönme hissine yol açar ve video ya da animasyonun süresini eşleştirmek için kullanılabilir.

Arka arkaya yapılan döndürmeler artık iyi entegre olmadığından ve ayrı efektler hissetmeye başladığından 100 ms'den uzun bir boşluk kullanmanızı önermeyiz.

Aşağıda, aşağı sürüklenip serbest bırakıldıktan sonra geri dönen elastik bir şekle bir örnek verilmiştir. Animasyon, hemen çıkma yer değiştirmesiyle orantılı olarak değişen yoğunluklarda oynatılan bir çift döndürme efektiyle geliştirilmiştir.

Geri dönen elastik şeklin animasyonu
Giriş titreşimi dalga formu grafiği

Kotlin

@Composable
fun WobbleScreen() {
    // Control variables for the dragging and animating state of the elastic.
    var dragDistance by remember { mutableStateOf(0f) }
    var isWobbling by remember { mutableStateOf(false) }
 
    // Use drag distance to create an animated float value behaving like a spring.
    val dragDistanceAnimated by animateFloatAsState(
        targetValue = if (dragDistance > 0f) dragDistance else 0f,
        animationSpec = spring(
            dampingRatio = Spring.DampingRatioHighBouncy,
            stiffness = Spring.StiffnessMedium
        ),
    )
 
    if (isWobbling) {
        LaunchedEffect(Unit) {
            while (true) {
                val displacement = dragDistanceAnimated / MAX_DRAG_DISTANCE
                // Use some sort of minimum displacement so the final few frames
                // of animation don't generate a vibration.
                if (displacement > SPIN_MIN_DISPLACEMENT) {
                    vibrator.vibrate(
                        VibrationEffect.startComposition().addPrimitive(
                            VibrationEffect.Composition.PRIMITIVE_SPIN,
                            nextSpinScale(displacement)
                        ).addPrimitive(
                          VibrationEffect.Composition.PRIMITIVE_SPIN,
                          nextSpinScale(displacement)
                        ).compose()
                    )
                }
                // Delay the next check for a sufficient duration until the current
                // composition finishes. Note that you can use
                // Vibrator.getPrimitiveDurations API to calculcate the delay.
                delay(VIBRATION_DURATION)
            }
        }
    }
 
    Box(
        Modifier
            .fillMaxSize()
            .draggable(
                onDragStopped = {
                    isWobbling = true
                    dragDistance = 0f
                },
                orientation = Orientation.Vertical,
                state = rememberDraggableState { delta ->
                    isWobbling = false
                    dragDistance += delta
                }
            )
    ) {
        // Draw the wobbling shape using the animated spring-like value.
        WobbleShape(dragDistanceAnimated)
    }
}

// Calculate a random scale for each spin to vary the full effect.
fun nextSpinScale(displacement: Float): Float {
  // Generate a random offset in [-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)
}

Java

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

Örnek: Hemen çıkma (sesli)

Titreşim efektlerinin bir başka gelişmiş uygulaması da fiziksel etkileşimleri simüle etmektir. PRIMITIVE_THUD, örneğin bir video veya animasyonda, genel deneyimi artırmak için bir etkinin görselleştirilmesiyle birlikte güçlü ve yansıtıcı bir efekt oluşturabilir.

Burada, top ekranın altından her sektiğinde oynatılan bir güm sesiyle geliştirilmiş basit bir top düşmesi animasyonu örneğini görebilirsiniz:

Ekranın altından zıplayan topun animasyonu
Giriş titreşimi dalga formu grafiği

Kotlin

enum class BallPosition {
    Start,
    End
}

@Composable
fun BounceScreen() {
  // Control variable for the state of the ball.
  var ballPosition by remember { mutableStateOf(BallPosition.Start) }
  var bounceCount by remember { mutableStateOf(0) }

  // Animation for the bouncing ball.
  var transitionData = updateTransitionData(ballPosition)
  val collisionData = updateCollisionData(transitionData)

  // Ball is about to contact floor, only vibrating once per collision.
  var hasVibratedForBallContact by remember { mutableStateOf(false) }
  if (collisionData.collisionWithFloor) {
    if (!hasVibratedForBallContact) {
      val vibrationScale = 0.7.pow(bounceCount++).toFloat()
      vibrator.vibrate(
        VibrationEffect.startComposition().addPrimitive(
          VibrationEffect.Composition.PRIMITIVE_THUD,
          vibrationScale
        ).compose()
      )
      hasVibratedForBallContact = true
    }
  } else {
    // Reset for next contact with floor.
    hasVibratedForBallContact = false
  }

  Screen() {
    Box(
      Modifier
        .fillMaxSize()
        .clickable {
          if (transitionData.isAtStart) {
            ballPosition = BallPosition.End
          } else {
            ballPosition = BallPosition.Start
            bounceCount = 0
          }
        },
    ) {
      // Build the ball UI based on the current state.
      BouncingBall(transitionData)
    }
  }
}

Java

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