「不自然の谷」の排除 - 気にならないを極めるAWAインタラクション
2016年6月14日、音楽配信アプリ「AWA」では大幅なアップデートを実施しました。
これまで音楽のトレンドを閲覧できるTRENDINGは、最新の情報も閲覧できる BROWSING へとリニューアル。
また、ユーザーごとにリコメンドするDISCOVERYをいつでも変更することのできる機能 Quick Discovery をリリースしました。
アップデート内容詳細はこちら
> より自分好みになるDISCOVERY、充実したTRENDINGへ
そして、この大型アップデートでは、インタラクションも一新しました。
Spring Effect
まず大きく一新したのはスクロール時のエフェクトです。フィード上のプレイリストが表示面積によって大きさを変える Scaling Effect から、プレイリストに関わらず全ての要素に対してバネの特性を与える Spring Effect へと変更しました。 Spring Effect では一つ一つの要素にバネの特性を与えることで浮遊感を表現し、上部にのっているコンテンツであることを示唆しています。
UIKit Dynamics
Spring Effect における主役は、バネの特性を表現するための物理演算です。
ここ数年で物理演算の利用ハードルはかなり下がってきており、
iOSには物理演算を手助けする UIKit Dynamics という機構がiOS7から用意されています。
この UIKit Dynamics はスクロールコンテンツに使われることも意識されており、 UICollectionView のレイアウトクラスである UICollectionViewLayout にはOSが良しなに処理してくれる標準使用の仕組みも用意されています。
UIDynamicAnimator#init(collectionViewLayout:)
let dynamicAnimator = UIDynamicAnimator(collectionViewLayout: collectionViewLayout)
今回追加した Spring Effect も UIKit Dynamics を利用し実現しています。
しかしAWAではパフォーマンスを考慮し、UICollectionView の使用を控え、UITableView のカスタムクラスを作ることで実現しました。
SpringTableView
UITableView にはレイアウト専用クラスはないため、AutoLayoutやデータ更新を考慮してOSが良しなに物理演算を行ってくれる仕組みは用意されていません。
そこで UITableView で UIKit Dynamics を利用する際には、それらを考慮した実装が必要です。
■ AutoLayout対策
まずこのエフェクトを実現するために、表示要素が画面に映しだされる前に、各要素に対する UIAttachmentBehavior を設定しておく必要があります。
このとき注意すべき点は2点。
- 初期表示では要素の大きさが確定しておらず高さが0のことがあり、そのままエフェクトを設定してしまうとクラッシュしてしまう
- 途中で要素の位置が変更されるとBehaviorに設定した位置とのズレが生じ、実際に表示される要素が意図しない位置に描画される
self.layoutIfNeeded()
guard cell.frame.height > 0 else {
return
}
// Setting new value of behavior
...
dynamicAnimator.addBehavior(behavior)
■ データ更新対策次に注意すべき点は、データを更新する機構があることです。
データ更新により要素の個数が変動した場合、またもBehaviorに設定した位置とのズレが生じてしまうため、更新に応じてすぐにBehaviorも再設定する必要があります。
override func beginUpdates() {
// Remove all behaviors configured before updating
...
super.beginUpdates()
}
override func endUpdates() {
super.endUpdates()
// Attach the behavior to the visible cells after updating
...
}
以上の要点を押えることで、UITableView でもスクロールコンテンツに対してUIKit Dynamics の 標準的な 物理演算アニメーションを使用することが可能になります。「不自然の谷」
UIKit Dynamics を利用すれば、標準的な実装だけでも、物理演算によって現実空間に近いバネの動きを実現することが可能です。
しかし、そのまま使用すると、演算された数値に従い、0.5~1pxほどの現実世界では気にならないバネの振動を忠実に描画するため、逆に不自然さを与えることがあります。
また、あくまで物理演算を手軽にする機構であるため、指の動きを考慮した計算まではできず、操作感としても不自然な感覚をユーザーに抱かせてしまいます。
このときに発生する急な不自然さを、ロボット工学の「不気味の谷」に習い、現実空間に近づけるうえで発生するインタラクションの「 不自然の谷 」と呼んでいます。
指を離した後、バネ特有の小刻みな上下振動が
忠実に具現化し不自然さを生む
指を離した後、不要な小刻みな振動が抑えられ、
慣性で本来の位置にFIXする
for behavior in dynamicAnimator.behaviors {
...
// If the finger is away, increase the damping factor.
if isNotTouching {
attachmentBehavior.damping = dampingForNotTouching
}
// Apply calculated frequency (following the touch translation and velocity)
attachmentBehavior.frequency = calculatedFrequency
...
dynamicAnimator.updateItemUsingCurrentState(currentItem)
}
自然さには何も感じないが、不自然さには不快感があるSpring Effect を創る過程でも生じたように、直感的なインタラクションを目指すにあたり現実世界とのギャップをなくしていくと、その途中で逆に不自然さを与える「不自然の谷」に陥ることがあります。
自然な動きは、ユーザーとしては自然であるがゆえに何も感じません。しかし、不自然さはすぐに不快感に繋がります。
インタラクションは派手にして気をひくことはそれほど重要ではなく、仔細な考慮を積み重ね、 気にさせない ことが重要です。
結果的にそれが快適性や直感性に変わるでしょう。
Quick Discovery
新機能である Quick Discovery でも新たなインタラクションを追加しています。機能詳細はこちら
> 新機能「Quick Discovery」でDISCOVERYを更新する方法
Circular Collection
Quick Discovery では円形スクロールのUIが2箇所追加されました。 この円形スクロールを実現するために使用したのが、先ほども出てきた UICollectionViewLayout です。
UICollectionViewLayout は UICollectionView のレイアウトを管理するクラスです。
これを円形レイアウトにすること自体はそこまで難しいことではなく、単なる縦/横スクロールのコンテンツを、見た目だけ曲線で描くようにするだけです。
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
...
switch self.alignment {
case .Left:
// Calculate delta (translation), angle (rotation) for left-align
...
case .Bottom:
// Calculate delta (translation), angle (rotation) for bottom-align
...
}
attributes.transform = CGAffineTransformConcat(translation, rotation)
return attributes
}
円形レイアウトの表示要素の再利用処理ただし、円形レイアウトを制御する際には注意すべき点があります。
表示要素の再利用処理です。
UICollectionView も UITableView 同様に、画面外にいった要素を次に表示される要素で再利用することでパフォーマンスを維持しています。
しかし、単純な縦/横スクロールコンテンツと違い、円形にしたことで先頭の要素と末尾の要素が隣り合わせになり、標準実装のままでは再利用処理が噛み合わず、本来表示されるはずの要素が非表示になってしまいます。
そのため末尾と先頭が繋がるパターンでも、画面内に表示されるであろう要素のレイアウトを正確に設定してあげる必要があります。
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
// If the tail cell is visible, append layout attributes for it.
if isVisibleTail {
...
} else {
...
}
return layoutAttributes
}
Keep Item Rotationまた、このレイアウトクラスでは一つ一つの要素の向きを変えずに、配置のみ円形にできるようにカスタムしています。
これは UICollectionViewLayoutAttributes をカスタムし、対象のViewの apply(_:) メソッドでレイアウトクラスとは逆の回転をかけることで、実質要素が回転していないように見せています。
override func applyLayoutAttributes(layoutAttributes: UICollectionViewLayoutAttributes) {
super.applyLayoutAttributes(layoutAttributes)
// If keepItemRotation is ON, processing to rotate the view in the opposite direction
// * Be aware that the layout is broken If you do not change the process for each OS version.
if #available(iOS 8.0, *) {
...
} else {
...
}
}
Swaying Animation Quick Discoveryではワンタップで好きなアーティストをどんどん掘り出してもらえるように、選択モーションはドラッグを採用したUIとなっています。
初回はドラッグを伝えるガイドが表示されますが、より直感的に伝えるため、ゆらゆら揺れることで浮遊感を表す Swaying Animation を各要素に適用しています。
let rotation = CAKeyframeAnimation(keyPath: "transform.rotation.z")
rotation.cumulative = true
rotation.values = ...
let position = CAKeyframeAnimation(keyPath: "position")
position.path = ...
let group = CAAnimationGroup()
group.animations = [position, rotation]
...
self.layer.addAnimation(group, forKey: animationKey)
この Swaying Animation で注意した点は、なんといってもアニメーションの変化量です。iOSのホーム画面でも、アプリアイコンを長押しすると各アイコンがカクカク動くエフェクトがあります。
このエフェクトはひと目でわかるほど激しく動くアニメーションになっていますが、ユーザー自身が設定しているアイコンに対して「編集」のときのみ適用されるため、このくらいハッキリとしたアニメーションでも問題ありません。
しかし、Quick Discoveryではユーザーが把握していない要素を常にアニメーションさせるため、同じくらいハッキリとした変化量にするとアーティストの探索を逆に邪魔してしまうアニメーションとなってしまいます。
そのため触っている人が気にならない程度のゆったりとしたアニメーションになるように調整を重ねました。
ディグる行為を途絶えさせない継続的アニメーション
さらに、Quick Discoveryでは、好きなアーティストをどんどん深掘って探せる=ディグる行為を途切れず行えるように、遷移アニメーションはすべて、要素の継続性を維持したアニメーションにしています。
"気にならない"を極める
今回の新インタラクションを加える中で最も注意した点は、如何に不自然さを排除するか
言い換えれば
如何に気にならないレベルに仕上げるか
です。
Spring Effect では、物理演算結果が忠実に描画されるがために発生する不自然さを排除しました。
Swaying Animation では、アーティストの探索を阻害しないようにアニメーションの変化量を調整しました。
遷移アニメーションでは、継続性を与えることでユーザーの考える量を減らし、アーティストの探索により集中してもらうようにしました。
そこでの本質的な体験に集中してもらうために "気にならない"を極めていく ことがインタラクション開発の真髄といえるでしょう。