1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package androidx.car.drawer;
18
19import android.animation.ValueAnimator;
20import android.content.res.Configuration;
21import android.os.Bundle;
22import android.util.TypedValue;
23import android.view.LayoutInflater;
24import android.view.MenuItem;
25import android.view.View;
26import android.view.ViewGroup;
27
28import androidx.annotation.LayoutRes;
29import androidx.annotation.Nullable;
30import androidx.appcompat.app.ActionBarDrawerToggle;
31import androidx.appcompat.app.AppCompatActivity;
32import androidx.appcompat.widget.Toolbar;
33import androidx.car.R;
34import androidx.drawerlayout.widget.DrawerLayout;
35
36import com.google.android.material.appbar.AppBarLayout;
37
38/**
39 * Common base Activity for car apps that need to present a Drawer.
40 *
41 * <p>This Activity manages the overall layout. To use it, sub-classes need to:
42 *
43 * <ul>
44 *   <li>Provide the root-items for the drawer by calling {@link #getDrawerController()}.
45 *       {@link CarDrawerController#setRootAdapter(CarDrawerAdapter)}.
46 *   <li>Add their main content using {@link #setMainContent(int)} or {@link #setMainContent(View)}.
47 *       They can also add fragments to the main-content container by obtaining its id using
48 *       {@link #getContentContainerId()}
49 * </ul>
50 *
51 * <p>This class will take care of drawer toggling and display.
52 *
53 * <p>This Activity also exposes the ability to have its toolbar optionally hide if any content
54 * in its main view is scrolled. Be default, this ability is turned off. Call
55 * {@link #setToolbarCollapsible()} to enable this behavior. Additionally, a user can set elevation
56 * on this toolbar by calling the appropriate {@link #setToolbarElevation(float)} method. There is
57 * elevation on the toolbar by default.
58 *
59 * <p>The rootAdapter can implement nested-navigation, in its click-handling, by passing the
60 * CarDrawerAdapter for the next level to
61 * {@link CarDrawerController#pushAdapter(CarDrawerAdapter)}.
62 *
63 * <p>Any Activity's based on this class need to set their theme to
64 * {@code Theme.Car.Light.NoActionBar.Drawer} or a derivative.
65 */
66public class CarDrawerActivity extends AppCompatActivity {
67    private static final int ANIMATION_DURATION_MS = 100;
68
69    private CarDrawerController mDrawerController;
70    private AppBarLayout mAppBarLayout;
71    private Toolbar mToolbar;
72
73    @Override
74    protected void onCreate(Bundle savedInstanceState) {
75        super.onCreate(savedInstanceState);
76
77        setContentView(R.layout.car_drawer_activity);
78
79        mAppBarLayout = findViewById(R.id.appbar);
80        mAppBarLayout.setBackgroundColor(getThemeColorPrimary());
81        setToolbarElevation(getResources().getDimension(R.dimen.car_app_bar_default_elevation));
82
83        DrawerLayout drawerLayout = findViewById(R.id.drawer_layout);
84        ActionBarDrawerToggle drawerToggle = new ActionBarDrawerToggle(
85                /* activity= */ this,
86                drawerLayout,
87                R.string.car_drawer_open,
88                R.string.car_drawer_close);
89
90        mToolbar = findViewById(R.id.car_toolbar);
91        setSupportActionBar(mToolbar);
92
93        mDrawerController = new CarDrawerController(drawerLayout, drawerToggle);
94        CarDrawerAdapter rootAdapter = getRootAdapter();
95        if (rootAdapter != null) {
96            mDrawerController.setRootAdapter(rootAdapter);
97        }
98
99        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
100        getSupportActionBar().setHomeButtonEnabled(true);
101    }
102
103    /**
104     * Returns the {@link CarDrawerController} that is responsible for handling events relating
105     * to the drawer in this Activity.
106     *
107     * @return The {@link CarDrawerController} linked to this Activity. This value will be
108     * {@code null} if this method is called before {@code onCreate()} has been called.
109     */
110    @Nullable
111    protected CarDrawerController getDrawerController() {
112        return mDrawerController;
113    }
114
115    @Override
116    protected void onPostCreate(Bundle savedInstanceState) {
117        super.onPostCreate(savedInstanceState);
118        mDrawerController.syncState();
119    }
120
121    /**
122     * @return Adapter for root content of the Drawer.
123     * @deprecated Do not implement this, instead call {@link #getDrawerController}.
124     * {@link CarDrawerController#setRootAdapter(CarDrawerAdapter)} directly.
125     */
126    @Deprecated
127    @Nullable
128    protected CarDrawerAdapter getRootAdapter() {
129        return null;
130    }
131
132    /**
133     * Set main content to display in this Activity. It will be added to R.id.content_frame in
134     * car_drawer_activity.xml. NOTE: Do not use {@link #setContentView(View)}.
135     *
136     * @param view View to display as main content.
137     */
138    public void setMainContent(View view) {
139        ViewGroup parent = findViewById(getContentContainerId());
140        parent.addView(view);
141    }
142
143    /**
144     * Set main content to display in this Activity. It will be added to R.id.content_frame in
145     * car_drawer_activity.xml. NOTE: Do not use {@link #setContentView(int)}.
146     *
147     * @param resourceId Layout to display as main content.
148     */
149    public void setMainContent(@LayoutRes int resourceId) {
150        ViewGroup parent = findViewById(getContentContainerId());
151        LayoutInflater inflater = getLayoutInflater();
152        inflater.inflate(resourceId, parent, true);
153    }
154
155    /**
156     * Sets the elevation on the toolbar of this Activity.
157     *
158     * @param elevation The elevation to set.
159     */
160    public void setToolbarElevation(float elevation) {
161        // The AppBar's default animator needs to be set to null to manually change the elevation.
162        mAppBarLayout.setStateListAnimator(null);
163        mAppBarLayout.setElevation(elevation);
164    }
165
166    /**
167     * Sets the elevation of the toolbar and animate it from the current elevation value.
168     *
169     * @param elevation The elevation to set.
170     */
171    public void setToolbarElevationWithAnimation(float elevation) {
172        ValueAnimator elevationAnimator =
173                ValueAnimator.ofFloat(mAppBarLayout.getElevation(), elevation);
174        elevationAnimator
175                .setDuration(ANIMATION_DURATION_MS)
176                .addUpdateListener(animation -> setToolbarElevation(
177                        (float) animation.getAnimatedValue()));
178        elevationAnimator.start();
179    }
180
181    /**
182     * Sets the toolbar of this Activity as collapsible. When any content in the main view of the
183     * Activity is scrolled, the toolbar will collapse and show itself accordingly.
184     */
185    public void setToolbarCollapsible() {
186        AppBarLayout.LayoutParams params =
187                (AppBarLayout.LayoutParams) mToolbar.getLayoutParams();
188        params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL
189                | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS);
190    }
191
192    /**
193     * Sets the toolbar to always show even if content in the main view of the Activity has been
194     * scrolled. This is the default behavior.
195     */
196    public void setToolbarAlwaysShow() {
197        AppBarLayout.LayoutParams params =
198                (AppBarLayout.LayoutParams) mToolbar.getLayoutParams();
199        params.setScrollFlags(0);
200    }
201
202    /**
203     * Get the id of the main content Container which is a FrameLayout. Subclasses can add their own
204     * content/fragments inside here.
205     *
206     * @return Id of FrameLayout where main content of the subclass Activity can be added.
207     */
208    protected int getContentContainerId() {
209        return R.id.content_frame;
210    }
211
212    @Override
213    protected void onStop() {
214        super.onStop();
215        mDrawerController.closeDrawer();
216    }
217
218    @Override
219    public void onConfigurationChanged(Configuration newConfig) {
220        super.onConfigurationChanged(newConfig);
221        mDrawerController.onConfigurationChanged(newConfig);
222    }
223
224    @Override
225    public boolean onOptionsItemSelected(MenuItem item) {
226        return mDrawerController.onOptionsItemSelected(item) || super.onOptionsItemSelected(item);
227    }
228
229    /**
230     * Returns the color that has been set as {@code colorPrimary} on the current Theme of this
231     * Activity.
232     */
233    private int getThemeColorPrimary() {
234        TypedValue value = new TypedValue();
235        getTheme().resolveAttribute(android.R.attr.colorPrimary, value, true);
236        return value.data;
237    }
238}
239