Bu sayfada, 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, titreşim aktüatörünün işleyişine dair iyi bir bilgiye dayandığı için Titreşim aktüatörüyle ilgili temel bilgiler başlıklı makaleyi okumanızı öneririz.
Bu sayfada aşağıdaki örnekler yer almaktadır.
- Özel titreşim kalıpları
- Artış kalıbı: Yumuşak bir şekilde başlayan bir kalıp.
- Yinelenen desen: Sonu olmayan bir desen.
- Yedek içeren desen: Yedek gösterimi.
- Titreşim bileşimleri
Daha fazla örnek için Etkinliklere dokunma geri bildirimi ekleme başlıklı makaleyi inceleyin ve her zaman dokunma geri bildirimi tasarım ilkelerine uyun.
Cihaz uyumluluğunu yönetmek için yedekleri kullanma
Özel efekt uygularken aşağıdakileri göz önünde bulundurun:
- Etki için hangi cihaz özellikleri gereklidir?
- Cihaz efekti oynatamıyorsa yapılması gerekenler
Android dokunma API'si referansı, uygulamanızın tutarlı bir genel deneyim sunabilmesi için dokunma teknolojinizdeki bileşenlerin desteğini nasıl kontrol edeceğinizle ilgili ayrıntılı bilgi sağlar.
Kullanım alanınıza bağlı olarak özel efektleri devre dışı bırakmak veya farklı potansiyel özelliklere göre alternatif özel efektler sunmak isteyebilirsiniz.
Aşağıdaki üst düzey cihaz özelliği sınıfları için planlama yapın:
Dokunsal ilkel öğeler kullanıyorsanız: Özel efektlerin ihtiyaç duyduğu bu ilkel öğeleri destekleyen cihazlar. (Temel öğeler hakkında ayrıntılı bilgi için sonraki bölüme bakın.)
Genlik kontrolü olan cihazlar.
Temel titreşim desteğine (açma/kapatma) sahip cihazlar (yani, genlik kontrolü olmayan cihazlar).
Uygulamanızın dokunma etkisi seçimi bu kategorileri hesaba katıyorsa dokunma kullanıcı deneyimi her cihazda tahmin edilebilir olmalıdır.
Dokunma duyusuna yönelik temel öğelerin kullanımı
Android, hem genlik hem de sıklık açısından değişen çeşitli dokunma ilkelleri içerir. Zengin dokunsal efektler elde etmek için tek bir ilkel türünü tek başına veya birden fazla ilkel türünü birlikte kullanabilirsiniz.
- İki primitif arasında belirgin boşluklar için 50 ms veya daha uzun gecikmeler kullanın. Mümkünse primitif süresini de dikkate alın.
- Yoğunluktaki farkın daha iyi anlaşılması için 1,4 veya daha yüksek oranda farklılık gösteren ölçekler kullanın.
Bir ilkel öğenin düşük, orta ve yüksek yoğunluktaki sürümlerini oluşturmak için 0,5, 0,7 ve 1,0 ölçeklerini kullanın.
Özel titreşim kalıpları oluşturma
Titreşim kalıpları genellikle bildirimler ve zil sesleri gibi dikkat çekme amaçlı dokunma teknolojilerinde kullanılır. Vibrator
hizmeti, zaman içinde titreşim genliğini değiştiren uzun titreşim kalıpları çalabilir. Bu tür efektlere dalga biçimleri adı verilir.
Dalga şekli efektleri kolayca algılanabilir ancak sessiz bir ortamda çalındığında ani uzun titreşimler kullanıcıyı korkutabilir. Hedef genliğe çok hızlı bir şekilde çıkmak da sesli bir vızıltı sesi oluşturabilir. Dalga biçimi kalıpları tasarlarken, yukarı ve aşağı eğim efektleri oluşturmak için genlik geçişlerini yumuşatmanız önerilir.
Örnek: Artış modeli
Dalga formları, üç parametreyle VibrationEffect
olarak gösterilir:
- Zamanlamalar: Her dalga biçimi segmenti için milisaniye cinsinden bir süre dizisi.
- Genlikler: İlk bağımsız değişkende belirtilen her süre için istenen titreşim genliğidir. 0 ile 255 arasında bir tam sayı değeriyle temsil edilir. 0, titreştiricinin "kapalı" olduğunu, 255 ise cihazın maksimum genliğini gösterir.
- Tekrarlama dizini: Dalga formunu tekrarlamaya başlamak için ilk bağımsız değişkende belirtilen dizindeki dizin veya kalıbın yalnızca bir kez çalınması gerekiyorsa -1.
Aşağıda, darbeler arasında 350 ms duraklatmayla iki kez darbe veren örnek bir dalga biçimi verilmiştir. İlk darbe, maksimum genliğe kadar düz bir rampadır ve ikinci darbe, maksimum genliği korumak için hızlı bir rampadır. Sonunda durma, negatif tekrar 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 desen
Dalga formları, iptal edilene kadar tekrar tekrar çalınabilir. Tekrarlanan bir dalga biçimi oluşturmanın yolu, negatif olmayan bir "tekrar" parametresi ayarlamaktır. Tekrarlanan bir dalga biçimi çaldığınızda, hizmette açıkça iptal edilene kadar titreşim 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 özellik, kullanıcının onaylaması gereken aralıklı etkinlikler için çok kullanışlıdır. Gelen telefon aramaları ve tetiklenen alarmlar bu tür etkinliklere örnek olarak verilebilir.
Örnek: Yedek içeren desen
Titreşimin genliğini kontrol etmek donanıma bağlı bir özelliktir. Bu özellik olmadan düşük kaliteli bir cihazda dalga biçimi çalmak, cihazın genlik dizisindeki her pozitif giriş için maksimum genlikte titremesine neden olur. Uygulamanızın bu tür cihazlara uyum sağlaması gerekiyorsa deseninizin bu durumda çalındığında uğultu etkisi oluşturmamasını sağlamanızı veya bunun yerine yedek olarak çalınabilecek daha basit bir AÇ/KAPATMA deseni tasarlamanızı öneririz.
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, bu sesleri daha uzun ve daha karmaşık özel efektler halinde derleme yöntemleri sunulur. Ayrıca, daha gelişmiş donanım özelliklerini kullanarak zengin dokunma deneyimleri keşfedebilirsiniz. Daha geniş frekans bant genişliğine sahip dokunsal aktüatörlere sahip cihazlarda daha karmaşık dokunsal efektler oluşturmak için genliği ve frekansı değiştiren efekt kombinasyonlarını kullanabilirsiniz.
Bu sayfada daha önce açıklanan özel titreşim kalıpları oluşturma işleminde, titreşim genliğinin nasıl kontrol edileceği açıklanarak titreşimin yukarı ve aşağı doğru kademeli olarak artması veya azalması gibi yumuşak efektler oluşturulur. Zengin dokunma, etkiyi daha da yumuşatmak için cihaz titreştiricisinin daha geniş frekans aralığını keşfederek bu konsepti iyileştirir. Bu dalga formları özellikle crescendo veya diminuendo efekti oluşturmada etkilidir.
Bu sayfanın üst kısmında açıklanan kompozisyon ilkelleri cihaz üreticisi tarafından uygulanır. Net dokunma hissi için dokunma prensiplerine uygun, net, kısa ve hoş bir titreşim sağlar. Bu özellikler ve işleyiş şekilleri hakkında daha fazla bilgi için Titreme aktüatörleri başlangıç kılavuzu başlıklı makaleyi inceleyin.
Android, desteklenmeyen primitifler içeren kompozisyonlar için yedek seçenekler sunmaz. Aşağıdaki adımları uygulamanızı öneririz:
Gelişmiş dokunma teknolojinizi etkinleştirmeden önce, belirli bir cihazın kullandığınız tüm primitifleri destekleyip desteklemediğini kontrol edin.
Yalnızca ilkel öğesi eksik olan efektleri değil, desteklenmeyen tutarlı deneyim grubunu da devre dışı bırakın. Cihazın destek durumunu nasıl kontrol edeceğinizle ilgili daha fazla bilgiyi aşağıda bulabilirsiniz.
VibrationEffect.Composition
ile bestelenmiş titreşim efektleri oluşturabilirsiniz.
Aşağıda, yavaşça yükselen bir efektin ardından keskin bir tıklama efektinin gösterildiği bir örnek 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());
Sırayla çalınacak primitifler eklenerek bir kompozisyon oluşturulur. Her primitif ölçeklenebilir olduğundan, 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. Bu değerde 0, bu ilkel öğenin kullanıcı tarafından (zar zor) hissedilebileceği minimum genliğe eşlenir.
Aynı ilkel öğenin zayıf ve güçlü bir sürümünü oluşturmak istiyorsanız yoğunluktaki farkın kolayca anlaşılabilmesi için ölçeklerin 1,4 veya daha yüksek bir oranda farklı olması önerilir. Aynı ilkel için üçten fazla yoğunluk seviyesi oluşturmaya çalışmayın. Bu seviyeler algısal olarak farklı değildir. Örneğin, bir ilkel öğenin düşük, orta ve yüksek yoğunluktaki sürümlerini oluşturmak için 0,5, 0,7 ve 1,0 ölçeklerini kullanın.
Kompozisyon, art arda gelen primitifler arasına eklenecek gecikmeleri de belirtebilir. Bu gecikme, önceki ilkel öğenin bitişinden itibaren milisaniye cinsinden ifade edilir. Genel olarak, iki primitif arasındaki 5 ila 10 ms'lik bir boşluk algılanabilir olmaktan çok kısadır. İki primitif arasında belirgin bir boşluk oluşturmak istiyorsanız 50 ms veya daha uzun bir boşluk kullanmayı deneyin. Gecikmeli bir beste örneğini aşağıda bulabilirsiniz:
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 primitifler için cihaz desteğini doğrulamak üzere 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. }
Birden fazla ilkel kontrol edip cihaz destek düzeyine göre hangilerinin derleneceğine 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 (düşük tik işaretleriyle)
Devam eden bir işlemle ilgili yararlı geri bildirim vermek için ilkel titreşimin genliğini kontrol edebilirsiniz. Yakın aralıklı ölçek değerleri, bir ilkel için yumuşak bir crescendo efekti oluşturmak için kullanılabilir. Ardışık ilkel öğeler arasındaki gecikme, kullanıcı etkileşimine göre dinamik olarak da ayarlanabilir. Bu durum, sürükleme hareketiyle kontrol edilen ve dokunma teknolojisiyle desteklenen bir görünüm animasyonunun aşağıdaki örneğinde gösterilmektedir.

Şekil 1. Bu dalga biçimi, bir cihazdaki titreşimin çıkış ivmesini temsil eder.
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şlet (yükselme ve düşmeyle)
Algılanan titreşim yoğunluğunu artırmak için iki ilkel vardır: PRIMITIVE_QUICK_RISE
ve PRIMITIVE_SLOW_RISE
.
Her ikisi de aynı hedefe ulaşır ancak farklı sürelerde. Azaltma için yalnızca bir primitif vardır: PRIMITIVE_QUICK_FALL
.
Bu primitifler, yoğunluk olarak artan ve sonra kaybolan bir dalga biçimi segmenti oluşturmak için birlikte daha iyi çalışır. Ölçeklendirilmiş primitifleri, aralarındaki genlikte ani sıçramaların olmasını önlemek için hizalayabilirsiniz. Bu, genel efekt süresini uzatmak için de iyi bir yöntemdir. Kullanıcılar, yükselen kısmı düşen kısımdan her zaman daha fazla fark eder. Bu nedenle, yükselen kısmı düşen kısımdan daha kısa yapmak, vurguyu düşen bölüme kaydırmaya yardımcı olabilir.
Bir daireyi genişletmek ve daraltmak için bu kompozisyonun uygulandığı bir örneği aşağıda bulabilirsiniz. Yükselme efekti, animasyon sırasında genişleme hissini artırabilir. Artış ve düşüş efektlerinin kombinasyonu, animasyon sonunda çöküşü vurgulamaya yardımcı olur.

Şekil 2. Bu dalga biçimi, bir cihazdaki titreşimin çıkış ivmesini temsil eder.
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: Titreşim (dönme hareketi ile)
Dokunma duyusuyla ilgili temel ilkelerden biri, kullanıcıları memnun etmektir. Beklenmedik ve hoş bir titreşim efekti eklemek için PRIMITIVE_SPIN
simgesini kullanabilirsiniz.
Bu ilkel, birden fazla kez çağrıldığında en etkili olur. Birleştirilen birden fazla dönüş, sallanan ve kararsız bir efekt oluşturabilir. Bu efekt, her primitife biraz rastgele ölçeklendirme uygulanarak daha da geliştirilebilir. Art arda gelen spin ilkelleri arasındaki boşlukla da denemeler yapabilirsiniz. Aralarında boşluk olmayan iki dönüş (aradaki süre 0 ms) sıkı bir dönme hissi oluşturur. Dönme hareketleri arasındaki boşluğu 10 ms'den 50 ms'ye çıkarmak, dönme hareketinin daha gevşek olmasını sağlar ve bir videonun veya animasyonun süresini eşleştirmek için kullanılabilir.
Art arda gelen dönüşler artık iyi entegre olmadığı ve ayrı efektler gibi görünmeye başladığı için 100 ms'den uzun bir boşluk kullanılması önerilmez.
Aşağı sürüklenip bırakıldıktan sonra geri sıçrayan elastik bir şeklin örneğini aşağıda görebilirsiniz. Animasyon, sıçrama yer değiştirmesine orantılı olarak değişen yoğunluklarda oynatılan bir çift dönme efekti ile geliştirilir.

Şekil 3. Bu dalga biçimi, bir cihazdaki titreşimin çıkış ivmesini temsil eder.
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: Sıçrama (yankılarla)
Titreşim efektlerinin gelişmiş bir başka uygulaması da fiziksel etkileşimleri simüle etmektir. PRIMITIVE_THUD
, güçlü ve yankılanan bir efekt oluşturabilir. Bu efekt, genel deneyimi artırmak için örneğin bir videoda veya animasyonda bir darbenin görselleştirmesiyle birlikte kullanılabilir.
Aşağıdaki örnekte, topun ekranın alt kısmından her sekmesinde çalınan tok bir ses efektiyle geliştirilmiş basit bir top düşme animasyonu gösterilmektedir:

Şekil 4. Bu dalga biçimi, bir cihazdaki titreşimin çıkış ivmesini temsil eder.
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; } } }); } }