/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.util; import android.content.Context; import android.content.res.Resources; import android.view.View; import android.view.View.OnAttachStateChangeListener; /** * Utility class that handles view lifecycle events for View Controllers. * * Implementations should handle setup and teardown related activities inside of * {@link #onViewAttached()} and {@link #onViewDetached()}. Be sure to call {@link #init()} on * any child controllers that this uses. This can be done in {@link #onInit()} if the * controllers are injected, or right after creation time of the child controller. * * Tip: View "attachment" happens top down - parents are notified that they are attached before * any children. That means that if you call a method on a child controller in * {@link #onViewAttached()}, the child controller may not have had its onViewAttach method * called, so it may not be fully set up. * * As such, make sure that methods on your controller are safe to call _before_ its {@link #init()} * and {@link #onViewAttached()} methods are called. Specifically, if your controller must call * {@link View#findViewById(int)} on its root view to setup member variables, do so in its * constructor. Save {@link #onViewAttached()} for things that can happen post-construction - adding * listeners, dynamically changing content, or other runtime decisions. * * @param View class that this ViewController is for. */ public abstract class ViewController { protected final T mView; private boolean mInited; private OnAttachStateChangeListener mOnAttachStateListener = new OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { ViewController.this.onViewAttached(); } @Override public void onViewDetachedFromWindow(View v) { ViewController.this.onViewDetached(); } }; protected ViewController(T view) { mView = view; } /** * Call immediately after constructing Controller in order to handle view lifecycle events. * * Generally speaking, you don't want to override this method. Instead, override * {@link #onInit()} as a way to have an run-once idempotent method that you can use for * setup of your ViewController. */ public void init() { if (mInited) { return; } onInit(); mInited = true; if (isAttachedToWindow()) { mOnAttachStateListener.onViewAttachedToWindow(mView); } addOnAttachStateChangeListener(mOnAttachStateListener); } /** * Run once when {@link #init()} is called. * * Override this to perform idempotent, one-time setup that your controller needs. It will * be called before {@link #onViewAttached()}. */ protected void onInit() {} protected Context getContext() { return mView.getContext(); } protected Resources getResources() { return mView.getResources(); } public boolean isAttachedToWindow() { return mView != null && mView.isAttachedToWindow(); } /** Add an OnAttachStateListener to the view. Does nothing if the view is null. */ public void addOnAttachStateChangeListener(View.OnAttachStateChangeListener listener) { if (mView != null) { mView.addOnAttachStateChangeListener(listener); } } /** Destroy ViewController, removing any listeners. */ public void destroy() { mView.removeOnAttachStateChangeListener(mOnAttachStateListener); } /** * Called when the view is attached and a call to {@link #init()} has been made in either order. */ protected abstract void onViewAttached(); /** * Called when the view is detached. */ protected abstract void onViewDetached(); }