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 17 package androidx.car.drawer; 18 19 import android.animation.ValueAnimator; 20 import android.content.res.Configuration; 21 import android.os.Bundle; 22 import android.util.TypedValue; 23 import android.view.LayoutInflater; 24 import android.view.MenuItem; 25 import android.view.View; 26 import android.view.ViewGroup; 27 28 import androidx.annotation.LayoutRes; 29 import androidx.annotation.Nullable; 30 import androidx.appcompat.app.ActionBarDrawerToggle; 31 import androidx.appcompat.app.AppCompatActivity; 32 import androidx.appcompat.widget.Toolbar; 33 import androidx.car.R; 34 import androidx.drawerlayout.widget.DrawerLayout; 35 36 import 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 */ 66 public 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