Bu sayfada, Android uygulamasında standart titreşim dalga formlarının ötesinde özel efektler oluşturmak için farklı dokunma API'lerinin nasıl kullanılacağına dair örnekler yer almaktadır.
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 temel öğelerinin kullanımı
Android, hem genlik hem de sıklık açısından değişen çeşitli dokunma ilkelleri içerir. Zengin dokunma etkileri 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 denir.
Dalga biçimi efektleri genellikle 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. Artış ve düşüş efektleri oluşturmak için genlik geçişlerini yumuşatmak üzere dalga biçimi kalıpları tasarlayın.
Titreşim modelleri örnekleri
Aşağıdaki bölümlerde, titreşim kalıplarına dair çeşitli örnekler verilmiştir:
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şörün "kapalı durumunu", 255 ise cihazın maksimum genliğini temsil eder.
- 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 // Don't 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; // Don't repeat.
vibrator.vibrate(VibrationEffect.createWaveform(
timings, amplitudes, repeatIndex));
Yinelenen desen
Dalga formları, iptal edilene kadar tekrar tekrar da oynatılabilir. Tekrarlanan bir dalga biçimi oluşturmanın yolu, negatif olmayan bir repeat
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.
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 bu durumda oynatıldığında uğultu efekti oluşturmayan bir desen kullanın veya bunun yerine yedek olarak oynatılabilen daha basit bir AÇ/KAPA desenini tasarlayın.
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, titreşimleri daha uzun ve daha karmaşık özel efektler halinde derleme yöntemleri sunulmaktadır. Ayrıca, daha gelişmiş donanım özelliklerini kullanarak zengin dokunma deneyimlerini keşfetme olanakları da ele alınmaktadır. Daha geniş frekans bant genişliğine sahip dokunma aktüatörleri olan cihazlarda daha karmaşık dokunma efektleri 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 sürecinde, titreşim genliğinin nasıl kontrol edileceği açıklanarak titreşimin kademeli olarak artması ve 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 sayfada daha önce 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 bunların işleyiş şekli hakkında daha fazla bilgi için Titreşim aktüatörleri ile ilgili temel bilgiler başlıklı makaleyi inceleyin.
Android, desteklenmeyen primitifler içeren kompozisyonlar için yedek seçenekler sunmaz. Bu nedenle, aşağıdaki adımları uygulayın:
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ğiniz hakkında daha fazla bilgiyi aşağıdaki bölümlerde bulabilirsiniz.
Bestelenmiş titreşim efektleri oluşturma
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.
Titreşim primitiflerinde varyant oluşturma
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 1,4 veya daha yüksek yoğunluk oranları oluşturun. Aynı ilkel için üçten fazla yoğunluk seviyesi oluşturmaya çalışmayın. Bunlar 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.
Titreşim temel öğeleri arasına boşluk ekleme
Kompozisyonda, art arda gelen primitifler arasına eklenecek gecikmeler de belirtilebilir. 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 kullanın. Aşağıda, gecikmeli bir kompozisyon örneği verilmiştir:
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());
Hangi ilkellerin desteklendiğini kontrol etme
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);
Titreşim besteleri örnekleri
Aşağıdaki bölümlerde, GitHub'daki dokunma örnek uygulamasından alınan çeşitli titreşim kompozisyon örnekleri verilmiştir.
Direnme (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üntü 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);
}
}
Genişlet (artış ve düşüşle)
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. Azalma 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.
Aşağıda, bir daireyi genişletmek ve daraltmak için bu kompozisyonun uygulandığı bir örnek verilmiştir. 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;
}
}
Titreşim (dönme hareketi ile)
Dokunma duyusuyla ilgili temel ilkelerden biri, kullanıcıları memnun etmektir. Beklenmedik ve hoş bir titreşim efekti eklemenin eğlenceli bir yolu PRIMITIVE_SPIN
simgesini kullanmaktır. Bu ilkel, birden fazla kez çağrıldığında en etkili olur. Birleştirilen birden fazla dönüş, sallanan ve kararsız bir etki yaratabilir. Bu etki, her primitife biraz rastgele ölçeklendirme uygulanarak daha da artırılabilir. Art arda gelen dönme ilkelleri arasındaki boşluğu da deneyebilirsiniz. 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 kullanmayın.
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 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)
}
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 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)
}
}
Zıplama (gümbürtülerle)
Titreşim efektlerinin gelişmiş bir başka uygulaması da fiziksel etkileşimleri simüle etmektir. PRIMITIVE_THUD
, güçlü ve yankılanan bir etki yaratabilir. Bu etki, 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 bir "bom" efekti ile geliştirilmiş bir top bırakma 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;
}
}
});
}
}