ODD開発について
この記事は 学生エンジニア Advent Calendar 3日目の記事です。
ODD
Oyakodon Driven Development
ふんわりたまごのこだわり親子丼
玉子と鶏肉、青ねぎを使い、風味豊かなだしで仕上げた親子丼です。お好みで別添の七味唐辛子をかけてお楽しみください。
セブンイレブンで売っているこの親子丼、これめちゃっくちゃ美味しいんですよ。
さらっと画像フォルダ探しただけでもこんな感じ
てか、普通にたまごが好きなだけなんですけどね。
コンビニの親子丼なのにふんわり半熟卵で、和風だしが軽く香る絶妙な一品になっています。
また、親の鳥はプリプリとして非常に美味しく、時々焦げ目があるなど香ばしさも感じさせます。
ほんと美味しいのでぜひ食べてみてください。
少し幸せになる買い方
・ふんわりたまごの親子丼
・ハムとポテトのサラダ
・セブンプレミアム 和風ドレッシング
・肉まん
これをセットで買うと非常に幸せになります。
バグが仕様に変わります。
DroidKaigiのCFP書いた
DroidKaigiのCFP募集が始まってたので出しました!!!!
とりあえず、Viewの細かいことについて話したいなーって思ってるのでそこら辺をアピってます。
ポテチやkyoubashi.dexで話した内容にプラスαした内容をちゃんと資料作って発表しようかなって。
概要
ValueAnimatorに付随するInterpolatorやTypeEvaluterなど一度知見まとめたやつや、
IllustratorからPathへの簡単な移植を可能にしたEXTKのJSのScriptの紹介、
実際のコード、完成したViewを踏まえつつ紹介します。
で、新しいViewをサクッと紹介
また変なものを作ってしまった… pic.twitter.com/Ok92CSBCZo
— ニモ (@amyu_san) 2015, 10月 15
今までのはココらへんにamyu.hatenadiary.com
最後に
まだ10人しか出していないらしいので...。
自分もちゃんと選ばれたら資料作ります!!
(絶対にRの名前は出さないという強い意志)
RecyclerViewのpreloadが素敵だよねっていう話
preload
・List形式で1行が1画面のImageView
・次の行のImageViewも先に描画しておきたい
という感じでスムーズに高画質な画像を描画することによって、ストレスを与えないようにListしようと言うお話。
ListViewでの実装
わからなかった。
そもそもgetView()まわりをどういじってもきつそうだし、根本的にListViewからいじらないといけなさそうだった。
RecyclerViewでの実装
LinearLayoutManager | Android Developers
LinearLayoutManagerのgetExtraLayoutSpaceを実装することによってpreloadを実現できた。
ドキュメント自体にも
You can override this method to implement your custom layout pre-cache logic.
と、あるようにpre-cache周りをイジれる。
ただ、当然パフォーマンスコストも上がるよという注意も。
ということで、実装したLinearLayoutManagerがこちら。
さらにRecyclerView#setItemViewCacheSizeでキャッシュサイズをデフォルトの1から増やすことによってViewPagerのPageCacheのようにキャッシュすることが出来る。
かなり素敵機能。
これもパフォーマンスコストが上がるが、結構ハード面がクリアしてくれるんじゃないかという期待をかなりしている。
最後に
ListViewだとかなりの工数をかけて実装しないといけないところだったが、RecyclerViewがあったおかげで、結構爆速で実装できた。
多分今後もこんな感じでListViewだと出来ない!っていうところからRecyclerViewを使おうと言う流れになることもあるのかなと思ったり。
はー難しい。
公開したView達
WaveSwipeRefreshLayout
BeerSwipeRefreshLayout
PlayPauseButton
ColoringLoading
CustomViewを作るときのテンプレートをAndroid初心者が考えた
CustomViewを作り機会が多い今日このごろ。
そこで考えるのがAPI制限だったり、パフォーマンスの事だったりなんです。
API制限はどうしようもないからパスするとして、パフォーマンスは出来るだけ対策、良くしていきたいと考えています。
多発するinvalidate();
ValueAnimatorなど使っているとAnimatorUpdateListenerのonAnimationUpdateでinvalidate();をそのままサクッと書くパターンがあるかと思います。
こんな感じに。
ValueAnimator animator = ValueAnimator.ofFloat(0.f, 1.f); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { invalidate(); } }); animator.setDuration(10000); animator.start();
この時、invalidateが呼ばれる間隔は大体17msで58FPSぐらいになっています。
60FPSぐらい出てる場合は滑らかに描画しているとされていますが、ViewによってはそこまでのFPSを必要としていない時が多いと感じます。
また、複雑なAnimationをする時、ValueAnimatorは一つとは限らず複数使います。
その際すべてのValueAnimatorでinvalidateを17ms間隔で呼ぶと無駄にonDrawが呼ばれ描画されない計算が走ることになります。
イケないことです。
画面を閉じても呼ばれるinvalidate();
ViewのライフサイクルであるonDetachedFromWindow()が呼ばれてもValueAnimatorにaddUpdateListenerしたAnimatorUpdateListenerは生き続けています。
onDetachedFromWindow()が呼ばれた時にはonDrawは呼ばれないのですが、AnimatorUpdateListenerのonAnimationUpdateでしている処理はDurationの間、呼ばれ続けます。
RepeatするようなAnimatorでは常に処理が走り続けてしまいます。
無駄な計算はパフォーマンスを下げる要因になるのでどうにかしたほうがいいです。
僕が考えた最強のTemplateCustomView
基本的にはOverrideしたinvalidateの処理をゴニョゴニョしました。
今回は暫定で30ms間隔以上での更新しかsuper.invalidate();を呼ばないようにしています。
個人的な主観ですが、 30FPSくらいアレば違和感を感じないと思います。
また、一応onDetachedFromWindowが呼ばれた時には即効でreturnさせるようにしてほんの少しだけパフォーマンスに貢献したり。
onDetachedFromWindow()内の処理ではAnimatorのCancel処理を必須で入れるようにしています。
複雑な描画になればなるほど、onAnimationUpdateでする処理が重くなることもあるので必須と言えます。
と、まぁいろいろ考えて書いてはみたんですが、このViewが一番適してるのって、Handlerで回したinvalidateとどこかのAnimatorから呼ばれるinvalidate、その他なんか適当に呼ばれるinvalidateを制御するときに役に立ちます。
一つだけのAnimatorのinvalidateだと、30ms分の描画しないところが出てきたりするので、ViewCompat.postInvalidateOnAnimation(View)を使うのが良いかと。
ただ、postInvalidateOnAnimation(View)のためだけにsupport library導入するのが嫌だという人はもう少しいろいろ考えたほうがいいかなって思います。
ひとつのViewのパフォーマンスにこだわると
一つのViewが表示されている時間は一つの画面が表示されている時間より圧倒的に少ないです。
そのため、導入するときにパフォーマンスを見ることが少ないと思います。
ただ、もしかしたら使ってもらえるかもしれない、という状況に備えて今自分の持っている知識で最高のViewを作って公開することは非常に有意義だと思います。
最後に
結局 onDraw内でインスタンスを作成しないとかさらに当たり前のことをやれば確実に一定以上のパフォーマンスを担保出来といると思います。
そこでもう一歩踏み込んだメンテナンスをすることでイケメンなViewへと昇華できますね!!!!
おわり!!!!!!!!!
Path#opの闇にハマって抜け出せなかった
View作りに欠かせないPath。
API19から追加されたPath#opを触ってみたことについて。
Path#opで何が出来るか
個人的な感覚ですが、異なる2つのPathを引き算したり、UnionしたりXORしたりすることが出来る便利なやつです。
例えば、
このようにPathですべて描画しているものに対して、Bottle内の"中のものが減る"ということを計算して再度座標を計算し直すというのはかなり骨が折れます。
Path#cubicToなどベジェ曲線を使ってる場合はかなりの労力です。
そこで、Path同士の引き算を行うことによって簡単に実現するわけです。
今回の飲み物が増えたり減ったりするAnimationでは以下のようになっています。
mBottleBeerPath.op(mOpPath, Path.Op.DIFFERENCE); mGlassBeerPath.op(mOpPath, Path.Op.DIFFERENCE); canvas.drawPath(mGlassPath, mGlassPaint); canvas.drawPath(mGlassBeerPath, mBeerPaint); canvas.drawPath(mBottlePath, mBottlePaint); canvas.drawPath(mBottleBeerPath, mBeerPaint);
イメージ的には
こんな感じですね。
ハマって抜け出せない闇
Rectでopするとop対象のLinePathが勝手にCloseされて迷惑という話
— あみゅー (@amyu_san) June 23, 2015
コレです。
public class OpSampleView extends View { private Path mMainPath; private Path mOpPath; private Paint mMainPaint; public OpSampleView(Context context) { this(context, null, 0); } public OpSampleView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public OpSampleView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { setUpPaint(); setUpPath(); } private void setUpPaint() { mMainPaint = new Paint(); mMainPaint.setColor(Color.BLUE); mMainPaint.setStyle(Paint.Style.STROKE); mMainPaint.setStrokeWidth(20); } private void setUpPath() { mMainPath = new Path(); mOpPath = new Path(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { mMainPath.reset(); mMainPath.moveTo(w / 2, h / 2); mMainPath.lineTo(w / 2 - 300, h / 2 - 300); mMainPath.lineTo(w / 2 + 300, h / 2 - 300); mOpPath.addRect(0, 0, w / 2, h, Path.Direction.CCW); super.onSizeChanged(w, h, oldw, oldh); } @Override protected void onDraw(Canvas canvas) { //mMainPath.op(mOpPath, Path.Op.DIFFERENCE); canvas.drawPath(mMainPath, mMainPaint); } }
このように適当にCloseしてないPathのみで描画した線があります。
当然Closeしていないので三角形は出来上がりません。
別に三角形を作ることを望んでいないので当たり前です。
で、次から問題なことです。
@Override protected void onDraw(Canvas canvas) { mMainPath.op(mOpPath, Path.Op.DIFFERENCE); canvas.drawPath(mMainPath, mMainPaint); }
mMainPath.op(mOpPath, Path.Op.DIFFERENCE);からコメントを外し、左半分を選択しているmOpPathでopすると...
こんな感じに望んでいない形が描画されてしまっているんです...
勝手にCloseされて非常に困ってます。
うーん、コレほんとどうしよう。
これ理由とか解決方法知っている方いたら教えて下さい...
暫定な解決策
線は図形!!!!
がんばって1周させて図形とすればちゃんと描画されます。
最後に
Path#opはかなり便利ですが使えるのがAPI19からというかなり限られています。
そういう微妙に使いづらいところだったり、"op"というググりにくい単語だったりと非常に闇が深くなっています。
ほんと誰か知見ためたら教えて下さい。
コーラ1本上げます。
おわり
自然な感じの拡大縮小の Interpolator
減衰な Interpolator
前に作った減衰な Interpolator を。
多分使いドコロが結構ありそうだし、便利っぽいやつ。
個人的にyのMax値が高過ぎる気がしたり、もう少し強めに減衰させたほうが良いかなと思ったり。
式をちょこちょこ変えればイケるので適当に作ったらイケるはず。
最後に
Grapher使えば10分ぐらいでInterpolator作れて最強
TypeEvaluator を使った簡単な ValueAnimator
TypeEvaluatorがかなり便利だったのでソレについて
TypeEvaluatorとは
TypeEvaluator | Android Developers
コレです。
簡単に書くと...
ValueAnimator.ofFloat(0.f,1.f);
にしたValueAnimatorはセットしたDurationの間に0.fから1.fまでの値を受け取ることが出来ますね。
例えば上の状態だとsetDuration(1000)の場合、10msの時getAnimatorValuesで0.01fが取得できるみたいな(Interpolatorがy=xな時)
コレが、単一の数字同士なら楽なんですが、例えば
float[][] hoge
な多次元配列のものを引数に渡し、いい感じに値を受け取りたいという時にTypeEvaluatorが活躍します。
例えば
float[][] hoge = {{0,0},{10,10},{100,100}}; float[][] hoge2 = {{0,0},{100,100},{0,0}};
で1000msの間にhogeからhoge2に値を移動したいValueAnimatorの場合、提供されているValueAnimator.of~ では対応できません。
(500msの時に{{0,0},{55,55},{50,50}}な値を取得したい)
ので、TypeEvaluatorをそこの中身の処理を書くといった感じになっています。
とりあえず、使い方とかコード見れば即わかりすると思うのでサンプルをどうぞ。
で、TypeEvaluatorがここの部分
public static class WaveTypeEvaluator implements TypeEvaluator<float[][]> { @Override public float[][] evaluate(float v, float[][] start, float[][] end) { float[][] newValue = new float[start.length][start[0].length]; for (int i = 0; i < start.length; i++) { for (int j = 0; j < start[i].length; j++) { newValue[i][j] = start[i][j] + (end[i][j] - start[i][j]) * v; } } return newValue; } }
forの中にlengthを使うなとか言うツッコミは置いておいて。
結論書くと、自分で時間に対する値を設定できるという感じです。
オレオレTypeEvaluatorだったら基本的に何でも捌けて非常に良いですね!!!!!
例えば、今回は多次元配列を例にしていますが、作ったModelClassを引数に渡して
public static class WaveTypeEvaluator implements TypeEvaluator<MyPoint> { @Override public MyPoint evaluate(float v, MyPoint start, MyPoint end) { MyPoint newPoint = new MyPoint(); newPoint.setX(start.getX() + (end.getX() - start.getX()) * v); return newPaint; } }
こんな感じにイケるんですね。
便利。
はー素晴らしきTypeEvaluator。
また、ValueAnimatorと少しだけ仲良く慣れました。
最後に
TypeEvaluator、多次元配列だけじゃなくて、ここイジれば「インターポレイラー」も実はこの中に組むことが出来たりと、普通に便利でした。
もっと、Viewのカスタマイズしていって知見をためたい。
あと、内定欲しい。
ではではー
Interpolator を作ったリッチな View の Animation
Interpolatorが何かとかそういうのはググってください!
一番厄介なAnimationの変化率の式をどうやって作っていくかかなーと思うのでそこら辺を詳しく書いていきます。
Interpolatorの作成
android.view.animation.Interpolatorを実装すればInterpolatorになります。
で、 getInterpolation(float v) の中身を実装していく感じになります。
例えばBounceInterpolatorのコードでは
42 public float getInterpolation(float t) { 43 // _b(t) = t * t * 8 44 // bs(t) = _b(t) for t < 0.3535 45 // bs(t) = _b(t - 0.54719) + 0.7 for t < 0.7408 46 // bs(t) = _b(t - 0.8526) + 0.9 for t < 0.9644 47 // bs(t) = _b(t - 1.0435) + 0.95 for t <= 1.0 48 // b(t) = bs(t * 1.1226) 49 t *= 1.1226f; 50 if (t < 0.3535f) return bounce(t); 51 else if (t < 0.7408f) return bounce(t - 0.54719f) + 0.7f; 52 else if (t < 0.9644f) return bounce(t - 0.8526f) + 0.9f; 53 else return bounce(t - 1.0435f) + 0.95f; 54 }
こんな感じになっており、コレを見慣れた式に直すと
こんな感じになるわけです。
グラフと式みてもらえると分かるんですが、これ全部2次方程式なんですよ。
時間でMaxが1な横軸、縦軸が1で100%な感じ(2だと200%で倍になる感覚)でつかめると思います。
そう、基本的に高3までの数学で余裕でイケるんですね!!
素晴らしき高校数学。
高校数学の素晴らしさは置いといて、次にサクッとサンプル作ってみます。
拡大して、元に戻り、その後バウンドするようなInterpolator
BounceInterpolatorを少しパクらせてもらって、こんな感じのグラフを作成します。
数式はこんな感じで。
グラフの方をみてもらえれば分かるんですがx=0.5の時にy=1.3になるようになっています。
そのため、設定したDuration * 0.5経った頃に元の大きさから130%大きくなります。
で、その後は、まぁ、ボンボンとなるわけですね!!!!!
sin波も高校数学で、あー高校数学素晴らしい。
これ、実際どういう動きになるのかというと、www.youtube.com
コレの星の部分のAnimationになります。
一応今回作ったInterpolatorはこんな感じ。
gist.github.com
作ったグラフから式を書き写しただけですね。
最後に
Interpolatorの何が素晴らしいって、Interpolatorを極めればかなりAnimationをリッチに出来るというところです!!!!
かなり良いです!!!
Interpolatorを使いこなせてなかったころ、Viewの拡大縮小は、拡大のアニメーションが終わったあとに縮小のアニメーションを入れる、といった感じに実装していました。
しかし!! こんな簡単にInterpolator作ってAnimatorなどにセットするだけで、リッチな感を出したAnimationが作れるって良くないですか!!!!
大体が2次方程式から、正弦波など、ある程度は授業でやったグラフで作れるというのがほんとうに良いですね。
あと、サンプルで書いた動画のViewはそのうち全部コード公開すると思います。
今は闇の力が働いて公開できないので、待っててください!!!
Interpolator職人に俺はなる!!!!
おわり