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