Coordinator Layout Behavior

3,260 views 32 slides Jan 25, 2016
Slide 1
Slide 1 of 32
Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
Slide 6
6
Slide 7
7
Slide 8
8
Slide 9
9
Slide 10
10
Slide 11
11
Slide 12
12
Slide 13
13
Slide 14
14
Slide 15
15
Slide 16
16
Slide 17
17
Slide 18
18
Slide 19
19
Slide 20
20
Slide 21
21
Slide 22
22
Slide 23
23
Slide 24
24
Slide 25
25
Slide 26
26
Slide 27
27
Slide 28
28
Slide 29
29
Slide 30
30
Slide 31
31
Slide 32
32

About This Presentation

kyobashi.dex #2の資料です。(Android向け)


Slide Content

Coordinator Layout Behavior
kyobashi.dex #2

自己紹介
釘宮 愼之介 / @kgmyshin
・ Androidエンジニア
・ Androidエンジニア

今回お話すること
CoordinatorLayoutのBehaviorについて
本当はCoordinatorLayoutについて隅から隅まで話そうと思ったけど
時間がなさそうだったので、 今回はBehaviorに焦点を当てます。

この発表で達成したいこと
聴いてくれた方が、聴き終わったあとに
Behaviorの仕組みを理解し、
右のようなカスタム Behaviorを
作れるようになっている 状態にすること。

目次
・CoordinatorLayoutとは
・Behaviorとは
・Behaviorの仕組み
・すでにある Behaviorたち
・カスタムBehaviorを作ってみる

CoordinatorLayoutとは
CoordinatorLayoutというのは子ビュー同士が
相互に動くようなインタラクションをする 場合に使われるViewGroupです。
とくにMaterial Designガイドラインの Scrolling techniquesを実現するときに使う印象。

Behaviorとは
CoordinatorLayoutの子ビューの動きのプラグインです。

だいたいがこの 二つのメソッドを Overrideして使っているみたい
•layoutDependsOn
•onDependentViewChanged

Behaviorの仕組み(簡易版)

これ以外にも
TouchEventやScrollのイベントが 取れたりもする
~onNestedFling
~onNestedPreFling
~onNestedPreScroll
~onNestedScroll
~onTouchEvent

実際のBehaviorを見てみましょう

FloatingActionButton.Behavior

FloatingActionButton.Behaviorの各メソッドはこのようになってます。(※1 )
public boolean layoutDependsOn(CoordinatorLayout parent,
FloatingActionButton child, View dependency) {
return dependency instanceof Snackbar.SnackbarLayout;
}
@Override
public boolean onDependentViewChanged (
CoordinatorLayout parent,
FloatingActionButton child,
View dependency
) {
if (dependency instanceof Snackbar.SnackbarLayout) {
updateFabTranslationForSnackbar (parent, child, dependency);
}
return false;
}
※1 説明のために一部ソースを削除してます

FloatingActionButton.Behaviorの各メソッドはこのようになってます。(※1 )
public boolean layoutDependsOn(CoordinatorLayout parent,
FloatingActionButton child, View dependency) {
return dependency instanceof Snackbar.SnackbarLayout;
}
@Override
public boolean onDependentViewChanged (
CoordinatorLayout parent,
FloatingActionButton child,
View dependency
) {
if (dependency instanceof Snackbar.SnackbarLayout) {
updateFabTranslationForSnackbar (parent, child, dependency);
}
return false;
}
※1 説明のために一部ソースを削除してます

FloatingActionButton.Behaviorの各メソッドはこのようになってます。(※1 )
public boolean layoutDependsOn(CoordinatorLayout parent,
FloatingActionButton child, View dependency) {
return dependency instanceof Snackbar.SnackbarLayout;
}
@Override
public boolean onDependentViewChanged (
CoordinatorLayout parent,
FloatingActionButton child,
View dependency
) {
if (dependency instanceof Snackbar.SnackbarLayout) {
updateFabTranslationForSnackbar (parent, child, dependency);
}
return false;
}
※1 説明のために一部ソースを削除してます

onPreDrawをトリガーにしている

AppBarLayout.ScrollingViewBehavior
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof AppBarLayout;
}

@Override
public boolean onDependentViewChanged (CoordinatorLayout parent, View child,
View dependency) {
final CoordinatorLayout.Behavior behavior =
((CoordinatorLayout.LayoutParams) dependency. getLayoutParams()).getBehavior();
if (behavior instanceof Behavior) {
// Offset the child so that it is below the app-bar (with any overlap)
final int appBarOffset = ((Behavior) behavior)
.getTopBottomOffsetForScrollingSibling ();
final int expandedMax = dependency. getHeight() - mOverlayTop;
final int collapsedMin = parent. getHeight() - child.getHeight();
if (mOverlayTop != 0 && dependency instanceof AppBarLayout) {
// If we have an overlap top, and the dependency is an AppBarLayout, we control
// the offset ourselves based on the appbar's scroll progress. This is so that
// the scroll happens sequentially rather than linearly
final int scrollRange = ((AppBarLayout) dependency). getTotalScrollRange();
setTopAndBottomOffset (AnimationUtils.lerp(expandedMax, collapsedMin,
Math. abs(appBarOffset) / (float) scrollRange));
} else {
setTopAndBottomOffset (dependency.getHeight() - mOverlayTop + appBarOffset);
}
}
return false;
}

@Override
public boolean onDependentViewChanged (CoordinatorLayout parent, View child,
View dependency) {
final CoordinatorLayout.Behavior behavior =
((CoordinatorLayout.LayoutParams) dependency. getLayoutParams()).getBehavior();
if (behavior instanceof Behavior) {
// Offset the child so that it is below the app-bar (with any overlap)
final int appBarOffset = ((Behavior) behavior)
.getTopBottomOffsetForScrollingSibling ();
final int expandedMax = dependency. getHeight() - mOverlayTop;
final int collapsedMin = parent. getHeight() - child.getHeight();
if (mOverlayTop != 0 && dependency instanceof AppBarLayout) {
// If we have an overlap top, and the dependency is an AppBarLayout, we control
// the offset ourselves based on the appbar's scroll progress. This is so that
// the scroll happens sequentially rather than linearly
final int scrollRange = ((AppBarLayout) dependency). getTotalScrollRange();
setTopAndBottomOffset (AnimationUtils.lerp(expandedMax, collapsedMin,
Math. abs(appBarOffset) / (float) scrollRange));
} else {
setTopAndBottomOffset (dependency.getHeight() - mOverlayTop + appBarOffset);
}
}
return false;
}

SwipeDismissBehavior
下記二つは実装していない。
•layoutDependsOn
•onDependentViewChanged
タッチイベントでごりごりやってる。

カスタムBehaviorを作ってみる
すごく簡単なやつ

•layoutDependsOn
•onDependentViewChanged
下記を実装するだけ

public class CustomBehavior extends CoordinatorLayout.Behavior<View> {
public CustomBehavior(Context context, AttributeSet attrs) {
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof AppBarLayout;
}
@Override
public boolean onDependentViewChanged (CoordinatorLayout parent, View child, View
dependency) {
if (dependency instanceof AppBarLayout) {
AppBarLayout appBarLayout = (AppBarLayout) dependency;
int totalScrollRange = appBarLayout. getTotalScrollRange();
int scrollY = appBarLayout. getTop();
float ratio = -scrollY / ( float) totalScrollRange;
child.setAlpha(1.f - ratio);
}
return true;
}
}

public class CustomBehavior extends CoordinatorLayout.Behavior<View> {
public CustomBehavior(Context context, AttributeSet attrs) {
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof AppBarLayout;
}
@Override
public boolean onDependentViewChanged (CoordinatorLayout parent, View child, View
dependency) {
if (dependency instanceof AppBarLayout) {
AppBarLayout appBarLayout = (AppBarLayout) dependency;
int totalScrollRange = appBarLayout. getTotalScrollRange();
int scrollY = appBarLayout. getTop();
float ratio = -scrollY / ( float) totalScrollRange;
child.setAlpha(1.f - ratio);
}
return true;
}
}

public class CustomBehavior extends CoordinatorLayout.Behavior<View> {
public CustomBehavior(Context context, AttributeSet attrs) {
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof AppBarLayout;
}
@Override
public boolean onDependentViewChanged (CoordinatorLayout parent, View child, View
dependency) {
if (dependency instanceof AppBarLayout) {
AppBarLayout appBarLayout = (AppBarLayout) dependency;
int totalScrollRange = appBarLayout. getTotalScrollRange();
int scrollY = appBarLayout. getTop();
float ratio = -scrollY / ( float) totalScrollRange;
child.setAlpha(1.f - ratio);
}
return true;
}
}

注意 コンストラクタも 実装しよう
public CustomBehavior(Context context, AttributeSet attrs) {
}

リフレクションに 失敗して落ちます

まとめ
・Behaviorを使うと他の子ビューに依存した動きを定義しやすくなります。
・使う場合は大抵 layoutDependsOn とonDependentViewChanged を
Overrideすればなんとかなります。

宣伝
・ shinobu.apkってのやります!
http://shinobu-apk.connpass.com/event/24921/
shinobu.apkとは
「Shinobu Okanoと愉快な仲間たちが繰り広げるファンタジーな 勉強会」
です。

ご静聴ありがとうございました。