コンダクターを理解する

Conductorライブラリは勢いを増していますが、ネットワークにはその使用に関する多くの情報がなく、公式ソースからののみが利用可能です。 この記事は、コンダクターの入門コースを提供し、パスからレーキを削除することを目的としています。 この記事は、すでにAndroid開発の経験がある人を対象としています。


コンダクターは、標準フラグメントの代替として配置されます。 主なアイデアは、ビューをラップして、ライフサイクルメソッドへのアクセスを提供することです。 Conductorには独自のライフサイクルがあり、フラグメントのライフサイクルよりもはるかに単純ですが、独自のトリックもあります(これについては後で詳しく説明します)。


Conductorの主な利点は次のとおりです。



また、ボックス内にあなたが受け取ります:



次に、ほとんどすべてのアプリケーションで見られるいくつかの典型的なユースケースを分析し、コントローラーのライフサイクルを理解しようとします。



パート1


公式サイトよりもさらに小さな単純な例から始めます 。 記事を読む前に、ページが大きくないことをよく理解することを強くお勧めします。


//File: MainActivity.java public class MainActivity extends AppCompatActivity { private Router router; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ViewGroup container = (ViewGroup) findViewById(R.id.controller_container); router = Conductor.attachRouter(this, container, savedInstanceState); if (!router.hasRootController()) { router.setRoot(RouterTransaction.with(new HomeController())); } } } //File: HomeController.java public class HomeController extends Controller { @Override protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) { return inflater.inflate(R.layout.controller_home, container, false); } } 

起動時に、 controller_homeレイアウトが表示される画面が表示されます。 大したことではありませんが、ここで何が起こるか見てみましょう。


コントローラのコードは非常にシンプルで、ビューを作成します。 ただし、初期化はやや複雑で、条件も含まれます。 アクティビティを作成するとき、 Conductor::attachRouterは、ルーターをアクティビティとそのライフサイクルにバインドします。 コンテキストとコンテナに加えて、保存された状態も転送することに注意してください。すべてが正しい場合、ルーターはすべてのコントローラーをスタックとその状態に保存します。 したがって、アクティビティが再度作成されずに復元された場合、ルートコントローラーのインストールは不要です。 保存された状態から再作成されました。


このチェックを削除することを妨げるものは何もありませんが、アクティビティを作成するたびに新しいコントローラーを作成し、保存されたすべてのデータを失います。


パート2


Conductorで状態を保存する方法と場所を詳しく見てみましょう。


これを行うには、サンプルを少し複雑にする必要があります。 私たちは無駄に時間を無駄にすることはありませんが、それと同時に、私たちが人生で誰がすべきことは木を成長させることです。 タプで育てましょう。


 //File: HomeController.java public class HomeController extends Controller { private View tree; @NonNull @Override protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) { View view = inflater.inflate(R.layout.controller_home, container, false); tree = view.findViewById(R.id.tree); view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { tree.setVisibility(View.VISIBLE); } }); return view; } @Override protected void onDestroyView(@NonNull View view) { super.onDestroyView(view); tree = null; } } 

これまでのところ、自然なことは何も起きていませんが、コントローラーが画面から削除されたときにビューがガベージコレクターによって収集されるように、 onDestroyViewツリーイメージへのリンクをリセットしていることに注意してください。


次に、反対側からツリーを見たい場合に何が起こるか見てみましょう。 画面の向きを変えましょう。


見て!

画像


残念ながら、ツリーは消えました。 私たちの仕事が無駄にならないようにするために何ができますか?
コントローラのライフサイクルは、アクティビティのフラグメントに関連付けられています。 フラグメントはConductor::attachRouter内に作成され、ライフサイクルメソッドへのすべての呼び出しがそのフラグメントと初期化が実行されたアクティビティにアタッチされます。 フラグメントにはsetRetainInstance(true)プロパティsetRetainInstance(true) 、これによりフラグメント、したがってすべてのコントローラーが構成の変更を体験できます。 したがって、私たちに必要なのは、コントローラーの変数に状態を保存することだけです。


 //File: HomeController.java public class HomeController extends Controller { private boolean isGrown = false; private View tree; @NonNull @Override protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) { View view = inflater.inflate(R.layout.controller_home, container, false); tree = view.findViewById(R.id.tree); view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { isGrown = true; update(); } }); return view; } @Override protected void onAttach(@NonNull View view) { super.onAttach(view); update(); } private void update() { tree.setVisibility(isGrown ? View.VISIBLE : View.GONE); } @Override protected void onDestroyView(@NonNull View view) { super.onDestroyView(view); tree = null; } } 

コントローラの状態を保存するisGrown変数と、ツリー画像の状態を更新するメソッドを追加しました。 繰り返しますが、問題はありません。


結果を見ます。


見て!

画像


構成が変更されると、アクティビティは破棄されますが、フラグメントは破棄されません。 しかし、構成を変更するプロセスではなく、システムによってまだアクティビティが破壊されている場合はどうでしょうか? [設定にアクティビティ保存しない]をオンにて、何が起こるかを確認します。


見て!

画像


奇跡は起こらず、すべてのデータを失いました。 この問題を解決するために、コンダクターはonRestoreInstanceStateonRestoreInstanceState複製しonRestoreInstanceState 。 それらを実装して、すべてが機能することを確認しましょう。


 //File: HomeController.java ... @Override protected void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean("isGrown", isGrown); } @Override protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); isGrown = savedInstanceState.getBoolean("isGrown"); } 

見て!

画像


やった! ツリーが安全で健全になったので、さらに複雑なものに進むことができます。


パート3


ツリーがいくつのコーンをもたらすことができるかを調べましょう。 これを行うには、入力として円錐の数を取り、それらを表示するコントローラーを作成します。


 //File: ConeController.java public class ConeController extends Controller { private int conesCount = 0; private TextView textField; public ConeController(int conesCount) { this.conesCount = conesCount; } @NonNull @Override protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) { View view = inflater.inflate(R.layout.controller_cone, container, false); textField = (TextView) view.findViewById(R.id.textField); return view; } @Override protected void onAttach(@NonNull View view) { super.onAttach(view); textField.setText("Cones: " + conesCount); } @Override protected void onDestroyView(@NonNull View view) { super.onDestroyView(view); textField = null; } } 

しかし、このコードはコンパイルされません。 そして、彼は別のコンストラクターがないことを誓います-彼が必要な理由を見てみましょう。


フラグメントと同様に、ここにはトリックがあります。 フラグメントの場合、パラメーターなしのコンストラクターがリフレクションを介して呼び出されると、Bundleパラメーターを持つコンストラクターがここで呼び出されます。


コンストラクタを追加するだけの場合は。 コントローラーが破壊された場合、コンダクターはそれを再作成し、バンプに関する情報を失います。 これを回避するには、データをargsに書き込む必要があります。 コントローラーが破壊されると、コンダクターによってArgsが保存されます。


 //File: ConeController.java public ConeController(int conesCount) { this.conesCount = conesCount; getArgs().putInt("conesCount", conesCount); } public ConeController(@Nullable Bundle args) { super(args); conesCount = args.getInt("conesCount"); } 

BundleBuilderたよりエレガントなオプション- を見ることができます。


コントローラーに呼び出しを追加し、コーンの数を保存することは残っています。


 //File: HomeController.java public class HomeController extends Controller { private boolean isGrown = false; private int conesCount = 42; private View tree; @NonNull @Override protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) { View view = inflater.inflate(R.layout.controller_home, container, false); tree = view.findViewById(R.id.tree); view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (isGrown) { getRouter().pushController(RouterTransaction.with(new ConeController(conesCount))); } else { isGrown = true; update(); } } }); return view; } } 

最初のタパでは木を成長させ、次のタパではその上の円錐の数を示します。 最後に、別のコントローラーへの切り替えは、ルーターでpushControllerを呼び出すだけでpushControllerします。これにより、コントローラーがバックスタックに追加され、画面に表示されます。


見て!

画像


すべては問題ありませんが、アプリケーションを再起動せずに戻ることはできません。修正しましょう。


 //File: MainActivity.java @Override public void onBackPressed() { if (!router.handleBack()) { super.onBackPressed(); } } 

このコードは、アプリケーションで問題なく機能します。 ただし、 onBackPressed内部onBackPressed詳しく見てみましょう。


最後のジャンプバックでアプリケーションを非表示にしたくないとします。 最初にしたいことは、 router.handleBack()呼び出しのみを残すことです。 ただし、 handleBackがfalseを返す場合、これは作業が行われたことを意味するものではありません。つまり、最後のコントローラーが破棄され、アクティビティ(またはルーターを含む他のコンテナー)の存在を停止する必要があることを意味します。 ルートコントローラーが削除されても、そのビューはシーンから削除されないという事実に注意する価値があります。 これは、アクティビティを終了するときに「高品質」の終了アニメーションを見ることができるように残されています。


handleBackがfalseを返す場合、このルーターの所有者をhandleBack必要があるという規則にhandleBackます。


この動作は、 falseパラメーターを指定してsetPopsLastViewメソッドを呼び出すことで変更できます。


パート4


今、果物を選ぶ時間です。 多くの場合、前のコントローラーにデータを戻すタスクがあります。 ツリーからいくつかのコーンを収集してみましょう。


リスナーをコントローラーに渡すだけでもかまいませんが、Androidエコシステムはノーと言います。 コントローラーが破壊された場合はどうなりますか? リスナーへのリンクを単に復元することはできません。 この問題を解決する最も簡単な方法は、 setTargetControllerメソッドを使用することですsetTargetControllerこれにより、別のコントローラーへのリンクを記憶し、コントローラーの再作成時にリンクを復元できます。


コントローラのタイプを直接指定するのではなく、継承する必要があるインターフェースのみを示すことをお勧めします。


 //File: ConeController.java public class ConeController extends Controller { private int conesCount = 0; private TextView textField; public <T extends Controller & ConeListener> ConeController(int conesCount, T listener) { this.conesCount = conesCount; getArgs().putInt("conesCount", conesCount); setTargetController(listener); } public ConeController(@Nullable Bundle args) { super(args); conesCount = args.getInt("conesCount"); } @NonNull @Override protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) { View view = inflater.inflate(R.layout.controller_cone, container, false); textField = (TextView) view.findViewById(R.id.textField); view.findViewById(R.id.collectConeButton) .setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (getTargetController() != null) { conesCount--; getArgs().putInt("conesCount", conesCount); update(); } } }); return view; } @Override protected void onAttach(@NonNull View view) { super.onAttach(view); update(); } @Override public boolean handleBack() { ((ConeListener) getTargetController()).conesLeft(conesCount); return super.handleBack(); } private void update() { textField.setText("Cones: " + conesCount); } @Override protected void onDestroyView(@NonNull View view) { super.onDestroyView(view); textField = null; } public interface ConeListener { void conesLeft(int count); } } //HomeController.java public class HomeController extends Controller implements ConeController.ConeListener { .... @NonNull @Override protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) { View view = inflater.inflate(R.layout.controller_home, container, false); tree = view.findViewById(R.id.tree); view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (isGrown) { getRouter().pushController(RouterTransaction.with(new ConeController(conesCount, HomeController.this))); } else { isGrown = true; update(); } } }); return view; } @Override public void conesLeft(int count) { conesCount = count; } 

コンストラクターで、 Controllerの子孫になるパラメーターを渡し、リスナーのインターフェイスを実装します。 setTargetControllerメソッドを呼び出して覚えてください。


コントローラーを離れるとき、リスナーでconesLeft(...)を呼び出すことHomeControllerHomeControllerのコーンの数を更新します。


そして結果!


見て!

画像


パート5


美しさをもたらすために残っています。 手首を軽くたたくと、トランジションアニメーションが追加され、コントローラーの表示と非表示が切り替わります。 ボックスには基本的なアニメーションのセットがありますが、 ControllerChangeHandlerまたはそれから継承されたクラスをオーバーライドすることで、独自のアニメーションを簡単に実装することもできます。


 //File: MainActivity.java getRouter().pushController(RouterTransaction.with( new ConeController(conesCount, HomeController.this)) .popChangeHandler(new FadeChangeHandler()) .pushChangeHandler(new FadeChangeHandler()) ); 

パート6


Conductorは、公式ドキュメントの図に示されている以外のいくつかのライフサイクルメソッドがあり、通常とは異なる動作を実装する場合に役立ちます。 それらを見てみましょう。


onAttach-コントローラーが画面に表示されるときに呼び出されます
onDetach-コントローラーが画面から削除されると呼び出されます
onDestroyView-コントローラーにバインドされたビューが破棄されると呼び出されます
onCreateView-コントローラーのビューを作成するときに呼び出されます
onDestroy-コントローラーが破棄される前に呼び出されます


以下に説明するメソッドもライフサイクルに参加しますが、 ViewクラスとActivityクラスの対応するメソッドの呼び出しを本質的に複製します。


onSaveViewState
onRestoreViewState
onSaveInstanceState
onRestoreInstanceState


次のメソッドはライフサイクル中に呼び出されますが、実際にはそれに割り当てることはできません。 呼び出しの順序は、遷移アニメーションに依存する場合があります。 そして、何も当てにしないで、アニメーションを処理するだけの価値はありません。
onChangeStarted-アニメーションの開始前に呼び出されます
onChangeEnded-アニメーションの完了時に呼び出されます


ライフサイクルを構築するとき、すべてが公式ページのようにバラ色ではありませんでした。 最終的にコントローラーの場合、2つのライフサイクルが特徴的です。 1つは、コントローラーを切り替えるときに機能する独自のサイクルと、アクティビティを破棄して作成するときに機能する2つ目のサイクルです。 それらは異なっており、それがチャートが少し成長した理由です。


画像


ペアのメソッドが強調表示されます。 太字の矢印のアクションは、ユーザーによって開始されます。 また、パラメーターなしまたはBundleパラメーター付きのコンストラクターのみが自動的に呼び出されることにも注意してください。 他のコンストラクタは、手動でのみ呼び出すことができます。


チュートリアルコード
指揮者
ロシア語版


ウラジミール・ファラフォノフのおかげで、準備にご協力いただきありがとうございます。



Source: https://habr.com/ru/post/J329532/


All Articles