1/* 2 * Copyright (C) 2013 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 18package android.support.v4.app; 19 20import android.app.Activity; 21import android.content.Context; 22import android.content.res.Configuration; 23import android.graphics.Canvas; 24import android.graphics.Rect; 25import android.graphics.drawable.Drawable; 26import android.graphics.drawable.InsetDrawable; 27import android.os.Build; 28import android.support.annotation.DrawableRes; 29import android.support.annotation.Nullable; 30import android.support.annotation.RequiresApi; 31import android.support.annotation.StringRes; 32import android.support.v4.content.ContextCompat; 33import android.support.v4.view.GravityCompat; 34import android.support.v4.view.ViewCompat; 35import android.support.v4.widget.DrawerLayout; 36import android.view.MenuItem; 37import android.view.View; 38 39/** 40 * This class provides a handy way to tie together the functionality of 41 * {@link DrawerLayout} and the framework <code>ActionBar</code> to implement the recommended 42 * design for navigation drawers. 43 * 44 * <p>To use <code>ActionBarDrawerToggle</code>, create one in your Activity and call through 45 * to the following methods corresponding to your Activity callbacks:</p> 46 * 47 * <ul> 48 * <li>{@link Activity#onConfigurationChanged(android.content.res.Configuration) onConfigurationChanged}</li> 49 * <li>{@link Activity#onOptionsItemSelected(android.view.MenuItem) onOptionsItemSelected}</li> 50 * </ul> 51 * 52 * <p>Call {@link #syncState()} from your <code>Activity</code>'s 53 * {@link Activity#onPostCreate(android.os.Bundle) onPostCreate} to synchronize the indicator 54 * with the state of the linked DrawerLayout after <code>onRestoreInstanceState</code> 55 * has occurred.</p> 56 * 57 * <p><code>ActionBarDrawerToggle</code> can be used directly as a 58 * {@link DrawerLayout.DrawerListener}, or if you are already providing your own listener, 59 * call through to each of the listener methods from your own.</p> 60 * 61 */ 62@Deprecated 63public class ActionBarDrawerToggle implements DrawerLayout.DrawerListener { 64 65 /** 66 * Allows an implementing Activity to return an {@link ActionBarDrawerToggle.Delegate} to use 67 * with ActionBarDrawerToggle. 68 * 69 * @deprecated Use ActionBarDrawerToggle.DelegateProvider in support-v7-appcompat. 70 */ 71 @Deprecated 72 public interface DelegateProvider { 73 74 /** 75 * @return Delegate to use for ActionBarDrawableToggles, or null if the Activity 76 * does not wish to override the default behavior. 77 */ 78 @Nullable 79 Delegate getDrawerToggleDelegate(); 80 } 81 82 /** 83 * @deprecated Use ActionBarDrawerToggle.DelegateProvider in support-v7-appcompat. 84 */ 85 @Deprecated 86 public interface Delegate { 87 /** 88 * @return Up indicator drawable as defined in the Activity's theme, or null if one is not 89 * defined. 90 */ 91 @Nullable 92 Drawable getThemeUpIndicator(); 93 94 /** 95 * Set the Action Bar's up indicator drawable and content description. 96 * 97 * @param upDrawable - Drawable to set as up indicator 98 * @param contentDescRes - Content description to set 99 */ 100 void setActionBarUpIndicator(Drawable upDrawable, @StringRes int contentDescRes); 101 102 /** 103 * Set the Action Bar's up indicator content description. 104 * 105 * @param contentDescRes - Content description to set 106 */ 107 void setActionBarDescription(@StringRes int contentDescRes); 108 } 109 110 private interface ActionBarDrawerToggleImpl { 111 Drawable getThemeUpIndicator(Activity activity); 112 Object setActionBarUpIndicator(Object info, Activity activity, 113 Drawable themeImage, int contentDescRes); 114 Object setActionBarDescription(Object info, Activity activity, int contentDescRes); 115 } 116 117 @RequiresApi(11) 118 private static class ActionBarDrawerToggleImplIcs implements ActionBarDrawerToggleImpl { 119 ActionBarDrawerToggleImplIcs() { 120 } 121 122 @Override 123 public Drawable getThemeUpIndicator(Activity activity) { 124 return ActionBarDrawerToggleIcs.getThemeUpIndicator(activity); 125 } 126 127 @Override 128 public Object setActionBarUpIndicator(Object info, Activity activity, 129 Drawable themeImage, int contentDescRes) { 130 return ActionBarDrawerToggleIcs.setActionBarUpIndicator(info, activity, 131 themeImage, contentDescRes); 132 } 133 134 @Override 135 public Object setActionBarDescription(Object info, Activity activity, int contentDescRes) { 136 return ActionBarDrawerToggleIcs.setActionBarDescription(info, activity, 137 contentDescRes); 138 } 139 } 140 141 @RequiresApi(18) 142 private static class ActionBarDrawerToggleImplJellybeanMR2 143 implements ActionBarDrawerToggleImpl { 144 ActionBarDrawerToggleImplJellybeanMR2() { 145 } 146 147 @Override 148 public Drawable getThemeUpIndicator(Activity activity) { 149 return ActionBarDrawerToggleJellybeanMR2.getThemeUpIndicator(activity); 150 } 151 152 @Override 153 public Object setActionBarUpIndicator(Object info, Activity activity, 154 Drawable themeImage, int contentDescRes) { 155 return ActionBarDrawerToggleJellybeanMR2.setActionBarUpIndicator(info, activity, 156 themeImage, contentDescRes); 157 } 158 159 @Override 160 public Object setActionBarDescription(Object info, Activity activity, int contentDescRes) { 161 return ActionBarDrawerToggleJellybeanMR2.setActionBarDescription(info, activity, 162 contentDescRes); 163 } 164 } 165 166 private static final ActionBarDrawerToggleImpl IMPL; 167 168 static { 169 if (Build.VERSION.SDK_INT >= 18) { 170 IMPL = new ActionBarDrawerToggleImplJellybeanMR2(); 171 } else { 172 IMPL = new ActionBarDrawerToggleImplIcs(); 173 } 174 } 175 176 /** Fraction of its total width by which to offset the toggle drawable. */ 177 private static final float TOGGLE_DRAWABLE_OFFSET = 1 / 3f; 178 179 // android.R.id.home as defined by public API in v11 180 private static final int ID_HOME = 0x0102002c; 181 182 final Activity mActivity; 183 private final Delegate mActivityImpl; 184 private final DrawerLayout mDrawerLayout; 185 private boolean mDrawerIndicatorEnabled = true; 186 private boolean mHasCustomUpIndicator; 187 188 private Drawable mHomeAsUpIndicator; 189 private Drawable mDrawerImage; 190 private SlideDrawable mSlider; 191 private final int mDrawerImageResource; 192 private final int mOpenDrawerContentDescRes; 193 private final int mCloseDrawerContentDescRes; 194 195 private Object mSetIndicatorInfo; 196 197 /** 198 * Construct a new ActionBarDrawerToggle. 199 * 200 * <p>The given {@link Activity} will be linked to the specified {@link DrawerLayout}. 201 * The provided drawer indicator drawable will animate slightly off-screen as the drawer 202 * is opened, indicating that in the open state the drawer will move off-screen when pressed 203 * and in the closed state the drawer will move on-screen when pressed.</p> 204 * 205 * <p>String resources must be provided to describe the open/close drawer actions for 206 * accessibility services.</p> 207 * 208 * @param activity The Activity hosting the drawer 209 * @param drawerLayout The DrawerLayout to link to the given Activity's ActionBar 210 * @param drawerImageRes A Drawable resource to use as the drawer indicator 211 * @param openDrawerContentDescRes A String resource to describe the "open drawer" action 212 * for accessibility 213 * @param closeDrawerContentDescRes A String resource to describe the "close drawer" action 214 * for accessibility 215 */ 216 public ActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout, 217 @DrawableRes int drawerImageRes, @StringRes int openDrawerContentDescRes, 218 @StringRes int closeDrawerContentDescRes) { 219 this(activity, drawerLayout, !assumeMaterial(activity), drawerImageRes, 220 openDrawerContentDescRes, closeDrawerContentDescRes); 221 } 222 223 private static boolean assumeMaterial(Context context) { 224 return context.getApplicationInfo().targetSdkVersion >= 21 225 && (Build.VERSION.SDK_INT >= 21); 226 } 227 228 /** 229 * Construct a new ActionBarDrawerToggle. 230 * 231 * <p>The given {@link Activity} will be linked to the specified {@link DrawerLayout}. 232 * The provided drawer indicator drawable will animate slightly off-screen as the drawer 233 * is opened, indicating that in the open state the drawer will move off-screen when pressed 234 * and in the closed state the drawer will move on-screen when pressed.</p> 235 * 236 * <p>String resources must be provided to describe the open/close drawer actions for 237 * accessibility services.</p> 238 * 239 * @param activity The Activity hosting the drawer 240 * @param drawerLayout The DrawerLayout to link to the given Activity's ActionBar 241 * @param animate True to animate the drawer indicator along with the drawer's position. 242 * Material apps should set this to false. 243 * @param drawerImageRes A Drawable resource to use as the drawer indicator 244 * @param openDrawerContentDescRes A String resource to describe the "open drawer" action 245 * for accessibility 246 * @param closeDrawerContentDescRes A String resource to describe the "close drawer" action 247 * for accessibility 248 */ 249 public ActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout, boolean animate, 250 @DrawableRes int drawerImageRes, @StringRes int openDrawerContentDescRes, 251 @StringRes int closeDrawerContentDescRes) { 252 mActivity = activity; 253 254 // Allow the Activity to provide an impl 255 if (activity instanceof DelegateProvider) { 256 mActivityImpl = ((DelegateProvider) activity).getDrawerToggleDelegate(); 257 } else { 258 mActivityImpl = null; 259 } 260 261 mDrawerLayout = drawerLayout; 262 mDrawerImageResource = drawerImageRes; 263 mOpenDrawerContentDescRes = openDrawerContentDescRes; 264 mCloseDrawerContentDescRes = closeDrawerContentDescRes; 265 266 mHomeAsUpIndicator = getThemeUpIndicator(); 267 mDrawerImage = ContextCompat.getDrawable(activity, drawerImageRes); 268 mSlider = new SlideDrawable(mDrawerImage); 269 mSlider.setOffset(animate ? TOGGLE_DRAWABLE_OFFSET : 0); 270 } 271 272 /** 273 * Synchronize the state of the drawer indicator/affordance with the linked DrawerLayout. 274 * 275 * <p>This should be called from your <code>Activity</code>'s 276 * {@link Activity#onPostCreate(android.os.Bundle) onPostCreate} method to synchronize after 277 * the DrawerLayout's instance state has been restored, and any other time when the state 278 * may have diverged in such a way that the ActionBarDrawerToggle was not notified. 279 * (For example, if you stop forwarding appropriate drawer events for a period of time.)</p> 280 */ 281 public void syncState() { 282 if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) { 283 mSlider.setPosition(1); 284 } else { 285 mSlider.setPosition(0); 286 } 287 288 if (mDrawerIndicatorEnabled) { 289 setActionBarUpIndicator(mSlider, mDrawerLayout.isDrawerOpen(GravityCompat.START) 290 ? mCloseDrawerContentDescRes : mOpenDrawerContentDescRes); 291 } 292 } 293 294 /** 295 * Set the up indicator to display when the drawer indicator is not 296 * enabled. 297 * <p> 298 * If you pass <code>null</code> to this method, the default drawable from 299 * the theme will be used. 300 * 301 * @param indicator A drawable to use for the up indicator, or null to use 302 * the theme's default 303 * @see #setDrawerIndicatorEnabled(boolean) 304 */ 305 public void setHomeAsUpIndicator(Drawable indicator) { 306 if (indicator == null) { 307 mHomeAsUpIndicator = getThemeUpIndicator(); 308 mHasCustomUpIndicator = false; 309 } else { 310 mHomeAsUpIndicator = indicator; 311 mHasCustomUpIndicator = true; 312 } 313 314 if (!mDrawerIndicatorEnabled) { 315 setActionBarUpIndicator(mHomeAsUpIndicator, 0); 316 } 317 } 318 319 /** 320 * Set the up indicator to display when the drawer indicator is not 321 * enabled. 322 * <p> 323 * If you pass 0 to this method, the default drawable from the theme will 324 * be used. 325 * 326 * @param resId Resource ID of a drawable to use for the up indicator, or 0 327 * to use the theme's default 328 * @see #setDrawerIndicatorEnabled(boolean) 329 */ 330 public void setHomeAsUpIndicator(int resId) { 331 Drawable indicator = null; 332 if (resId != 0) { 333 indicator = ContextCompat.getDrawable(mActivity, resId); 334 } 335 336 setHomeAsUpIndicator(indicator); 337 } 338 339 /** 340 * Enable or disable the drawer indicator. The indicator defaults to enabled. 341 * 342 * <p>When the indicator is disabled, the <code>ActionBar</code> will revert to displaying 343 * the home-as-up indicator provided by the <code>Activity</code>'s theme in the 344 * <code>android.R.attr.homeAsUpIndicator</code> attribute instead of the animated 345 * drawer glyph.</p> 346 * 347 * @param enable true to enable, false to disable 348 */ 349 public void setDrawerIndicatorEnabled(boolean enable) { 350 if (enable != mDrawerIndicatorEnabled) { 351 if (enable) { 352 setActionBarUpIndicator(mSlider, mDrawerLayout.isDrawerOpen(GravityCompat.START) 353 ? mCloseDrawerContentDescRes : mOpenDrawerContentDescRes); 354 } else { 355 setActionBarUpIndicator(mHomeAsUpIndicator, 0); 356 } 357 mDrawerIndicatorEnabled = enable; 358 } 359 } 360 361 /** 362 * @return true if the enhanced drawer indicator is enabled, false otherwise 363 * @see #setDrawerIndicatorEnabled(boolean) 364 */ 365 public boolean isDrawerIndicatorEnabled() { 366 return mDrawerIndicatorEnabled; 367 } 368 369 /** 370 * This method should always be called by your <code>Activity</code>'s 371 * {@link Activity#onConfigurationChanged(android.content.res.Configuration) onConfigurationChanged} 372 * method. 373 * 374 * @param newConfig The new configuration 375 */ 376 public void onConfigurationChanged(Configuration newConfig) { 377 // Reload drawables that can change with configuration 378 if (!mHasCustomUpIndicator) { 379 mHomeAsUpIndicator = getThemeUpIndicator(); 380 } 381 mDrawerImage = ContextCompat.getDrawable(mActivity, mDrawerImageResource); 382 syncState(); 383 } 384 385 /** 386 * This method should be called by your <code>Activity</code>'s 387 * {@link Activity#onOptionsItemSelected(android.view.MenuItem) onOptionsItemSelected} method. 388 * If it returns true, your <code>onOptionsItemSelected</code> method should return true and 389 * skip further processing. 390 * 391 * @param item the MenuItem instance representing the selected menu item 392 * @return true if the event was handled and further processing should not occur 393 */ 394 public boolean onOptionsItemSelected(MenuItem item) { 395 if (item != null && item.getItemId() == ID_HOME && mDrawerIndicatorEnabled) { 396 if (mDrawerLayout.isDrawerVisible(GravityCompat.START)) { 397 mDrawerLayout.closeDrawer(GravityCompat.START); 398 } else { 399 mDrawerLayout.openDrawer(GravityCompat.START); 400 } 401 return true; 402 } 403 return false; 404 } 405 406 /** 407 * {@link DrawerLayout.DrawerListener} callback method. If you do not use your 408 * ActionBarDrawerToggle instance directly as your DrawerLayout's listener, you should call 409 * through to this method from your own listener object. 410 * 411 * @param drawerView The child view that was moved 412 * @param slideOffset The new offset of this drawer within its range, from 0-1 413 */ 414 @Override 415 public void onDrawerSlide(View drawerView, float slideOffset) { 416 float glyphOffset = mSlider.getPosition(); 417 if (slideOffset > 0.5f) { 418 glyphOffset = Math.max(glyphOffset, Math.max(0.f, slideOffset - 0.5f) * 2); 419 } else { 420 glyphOffset = Math.min(glyphOffset, slideOffset * 2); 421 } 422 mSlider.setPosition(glyphOffset); 423 } 424 425 /** 426 * {@link DrawerLayout.DrawerListener} callback method. If you do not use your 427 * ActionBarDrawerToggle instance directly as your DrawerLayout's listener, you should call 428 * through to this method from your own listener object. 429 * 430 * @param drawerView Drawer view that is now open 431 */ 432 @Override 433 public void onDrawerOpened(View drawerView) { 434 mSlider.setPosition(1); 435 if (mDrawerIndicatorEnabled) { 436 setActionBarDescription(mCloseDrawerContentDescRes); 437 } 438 } 439 440 /** 441 * {@link DrawerLayout.DrawerListener} callback method. If you do not use your 442 * ActionBarDrawerToggle instance directly as your DrawerLayout's listener, you should call 443 * through to this method from your own listener object. 444 * 445 * @param drawerView Drawer view that is now closed 446 */ 447 @Override 448 public void onDrawerClosed(View drawerView) { 449 mSlider.setPosition(0); 450 if (mDrawerIndicatorEnabled) { 451 setActionBarDescription(mOpenDrawerContentDescRes); 452 } 453 } 454 455 /** 456 * {@link DrawerLayout.DrawerListener} callback method. If you do not use your 457 * ActionBarDrawerToggle instance directly as your DrawerLayout's listener, you should call 458 * through to this method from your own listener object. 459 * 460 * @param newState The new drawer motion state 461 */ 462 @Override 463 public void onDrawerStateChanged(int newState) { 464 } 465 466 Drawable getThemeUpIndicator() { 467 if (mActivityImpl != null) { 468 return mActivityImpl.getThemeUpIndicator(); 469 } 470 return IMPL.getThemeUpIndicator(mActivity); 471 } 472 473 void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes) { 474 if (mActivityImpl != null) { 475 mActivityImpl.setActionBarUpIndicator(upDrawable, contentDescRes); 476 return; 477 } 478 mSetIndicatorInfo = IMPL 479 .setActionBarUpIndicator(mSetIndicatorInfo, mActivity, upDrawable, contentDescRes); 480 } 481 482 void setActionBarDescription(int contentDescRes) { 483 if (mActivityImpl != null) { 484 mActivityImpl.setActionBarDescription(contentDescRes); 485 return; 486 } 487 mSetIndicatorInfo = IMPL 488 .setActionBarDescription(mSetIndicatorInfo, mActivity, contentDescRes); 489 } 490 491 private class SlideDrawable extends InsetDrawable implements Drawable.Callback { 492 private final boolean mHasMirroring = Build.VERSION.SDK_INT > 18; 493 private final Rect mTmpRect = new Rect(); 494 495 private float mPosition; 496 private float mOffset; 497 498 SlideDrawable(Drawable wrapped) { 499 super(wrapped, 0); 500 } 501 502 /** 503 * Sets the current position along the offset. 504 * 505 * @param position a value between 0 and 1 506 */ 507 public void setPosition(float position) { 508 mPosition = position; 509 invalidateSelf(); 510 } 511 512 public float getPosition() { 513 return mPosition; 514 } 515 516 /** 517 * Specifies the maximum offset when the position is at 1. 518 * 519 * @param offset maximum offset as a fraction of the drawable width, 520 * positive to shift left or negative to shift right. 521 * @see #setPosition(float) 522 */ 523 public void setOffset(float offset) { 524 mOffset = offset; 525 invalidateSelf(); 526 } 527 528 @Override 529 public void draw(Canvas canvas) { 530 copyBounds(mTmpRect); 531 canvas.save(); 532 533 // Layout direction must be obtained from the activity. 534 final boolean isLayoutRTL = ViewCompat.getLayoutDirection( 535 mActivity.getWindow().getDecorView()) == ViewCompat.LAYOUT_DIRECTION_RTL; 536 final int flipRtl = isLayoutRTL ? -1 : 1; 537 final int width = mTmpRect.width(); 538 canvas.translate(-mOffset * width * mPosition * flipRtl, 0); 539 540 // Force auto-mirroring if it's not supported by the platform. 541 if (isLayoutRTL && !mHasMirroring) { 542 canvas.translate(width, 0); 543 canvas.scale(-1, 1); 544 } 545 546 super.draw(canvas); 547 canvas.restore(); 548 } 549 } 550} 551