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