public class

CarDrawerController

extends java.lang.Object

 java.lang.Object

↳androidx.car.drawer.CarDrawerController

Gradle dependencies

compile group: 'androidx.car', name: 'car', version: '1.0.0-alpha7'

  • groupId: androidx.car
  • artifactId: car
  • version: 1.0.0-alpha7

Artifact androidx.car:car:1.0.0-alpha7 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.car:car com.android.support:car

Overview

A controller that will handle the set up of the navigation drawer. It will hook up the necessary buttons for up navigation, as well as expose methods to allow for a drill down navigation.

Summary

Constructors
publicCarDrawerController(DrawerLayout drawerLayout, ActionBarDrawerToggle drawerToggle)

Creates a CarDrawerController that will control the navigation of the drawer given by drawerLayout.

Methods
public voidaddDrawerListener(DrawerLayout.DrawerListener listener)

Sets a listener to be notified of Drawer events.

public voidcloseDrawer()

Close the drawer.

public voidonConfigurationChanged(Configuration newConfig)

Notify this controller that device configurations may have changed.

public booleanonOptionsItemSelected(MenuItem item)

An analog to an Activity's onOptionsItemSelected().

public voidopenDrawer()

Opens the drawer.

public final voidpushAdapter(CarDrawerAdapter adapter)

Switches to use the given CarDrawerAdapter as the one to supply the list to display in the navigation drawer.

public voidremoveDrawerListener(DrawerLayout.DrawerListener listener)

Removes a listener to be notified of Drawer events.

public voidscrollToPosition(int position)

Scroll to given position in the list.

public voidsetRootAdapter(CarDrawerAdapter rootAdapter)

Sets the CarDrawerAdapter that will function as the root adapter.

public voidshowLoadingProgressBar(boolean show)

Sets whether the loading progress bar is displayed in the navigation drawer.

public voidsyncState()

Synchronizes the display of the drawer with its linked DrawerLayout.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public CarDrawerController(DrawerLayout drawerLayout, ActionBarDrawerToggle drawerToggle)

Creates a CarDrawerController that will control the navigation of the drawer given by drawerLayout.

The given drawerLayout should either have a child view that is inflated from R.layout.car_drawer or ensure that its child views have the IDs expected in that layout. The ids expected can be configured in the theme by R.attr.drawerBackButtonId, R.attr.drawerListId, R.attr.drawerTitleId and R.attr.drawerProgressId.

Parameters:

drawerLayout: The top-level container for the window content that shows the interactive drawer.
drawerToggle: The ActionBarDrawerToggle that will open the drawer.

Methods

public void setRootAdapter(CarDrawerAdapter rootAdapter)

Sets the CarDrawerAdapter that will function as the root adapter. The contents of this root adapter are shown when the drawer is first opened. It is also the top-most level of navigation in the drawer.

Parameters:

rootAdapter: The adapter that will act as the root. If this value is null, then this method will do nothing.

public final void pushAdapter(CarDrawerAdapter adapter)

Switches to use the given CarDrawerAdapter as the one to supply the list to display in the navigation drawer. The title will also be updated from the adapter.

This switch is treated as a navigation to the next level in the drawer. Navigation away from this level will pop the given adapter off and surface contents of the previous adapter that was set via this method. If no such adapter exists, then the root adapter set by CarDrawerController.setRootAdapter(CarDrawerAdapter) will be used instead.

Parameters:

adapter: Adapter for next level of content in the drawer.

public void closeDrawer()

Close the drawer.

public void openDrawer()

Opens the drawer.

public void addDrawerListener(DrawerLayout.DrawerListener listener)

Sets a listener to be notified of Drawer events.

public void removeDrawerListener(DrawerLayout.DrawerListener listener)

Removes a listener to be notified of Drawer events.

public void showLoadingProgressBar(boolean show)

Sets whether the loading progress bar is displayed in the navigation drawer. If true, the progress bar is displayed and the navigation list is hidden and vice versa.

public void scrollToPosition(int position)

Scroll to given position in the list.

public void syncState()

Synchronizes the display of the drawer with its linked DrawerLayout.

This should be called from the associated Activity's AppCompatActivity.onPostCreate(Bundle) method to synchronize after the DrawerLayout's instance state has been restored, and any other time when the state may have diverged in such a way that this controller's associated ActionBarDrawerToggle had not been notified.

public void onConfigurationChanged(Configuration newConfig)

Notify this controller that device configurations may have changed.

This method should be called from the associated Activity's onConfigurationChanged() method.

public boolean onOptionsItemSelected(MenuItem item)

An analog to an Activity's onOptionsItemSelected(). This method should be called when the Activity's method is called and will return true if the selection has been handled.

Returns:

true if the item processing was handled by this class.

Source

/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.car.drawer;

import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MenuItem;
import android.view.View;
import android.view.animation.AnimationUtils;
import android.widget.ProgressBar;
import android.widget.TextView;

import androidx.annotation.AnimRes;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.car.R;
import androidx.car.util.DropShadowScrollListener;
import androidx.car.widget.PagedListView;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayDeque;

/**
 * A controller that will handle the set up of the navigation drawer. It will hook up the
 * necessary buttons for up navigation, as well as expose methods to allow for a drill down
 * navigation.
 */
public class CarDrawerController {
    /** An animation for when a user navigates into a submenu. */
    @AnimRes
    private static final int DRILL_DOWN_ANIM = R.anim.fade_in_trans_right_layout_anim;

    /** An animation for when a user navigates up (when the back button is pressed). */
    @AnimRes
    private static final int NAVIGATE_UP_ANIM = R.anim.fade_in_trans_left_layout_anim;

    /**
     * A representation of the hierarchy of navigation being displayed in the list. The ordering of
     * this stack is the order that the user has visited each level. When the user navigates up,
     * the adapters are popped from this list.
     */
    private final ArrayDeque<CarDrawerAdapter> mAdapterStack = new ArrayDeque<>();

    private final Context mContext;

    private final TextView mTitleView;
    private final DrawerLayout mDrawerLayout;
    private final ActionBarDrawerToggle mDrawerToggle;

    private final PagedListView mDrawerList;
    private final ProgressBar mProgressBar;

    /**
     * Creates a {@link CarDrawerController} that will control the navigation of the drawer given by
     * {@code drawerLayout}.
     *
     * <p>The given {@code drawerLayout} should either have a child view that is inflated from
     * {@code R.layout.car_drawer} or ensure that its child views have the IDs expected in that
     * layout. The ids expected can be configured in the theme by {@code R.attr.drawerBackButtonId},
     * {@code R.attr.drawerListId}, {@code R.attr.drawerTitleId} and
     * {@code R.attr.drawerProgressId}.
     *
     * @param drawerLayout The top-level container for the window content that shows the
     *                     interactive drawer.
     * @param drawerToggle The {@link ActionBarDrawerToggle} that will open the drawer.
     * {@link R.attr#drawerBackButtonId}
     * {@link R.attr#drawerListId}
     * {@link R.attr#drawerProgressId}
     * {@link R.attr#drawerTitleId}
     */
    public CarDrawerController(@NonNull DrawerLayout drawerLayout,
            @NonNull ActionBarDrawerToggle drawerToggle) {
        mContext = drawerLayout.getContext();
        mDrawerToggle = drawerToggle;
        mDrawerLayout = drawerLayout;

        TypedValue outValue = new TypedValue();
        Resources.Theme theme = mContext.getTheme();

        mTitleView = drawerLayout.findViewById(
                theme.resolveAttribute(R.attr.drawerTitleId, outValue, true)
                        ? outValue.resourceId
                        : R.id.car_drawer_title);
        mProgressBar = drawerLayout.findViewById(
                theme.resolveAttribute(R.attr.drawerProgressId, outValue, true)
                        ? outValue.resourceId
                        : R.id.car_drawer_progress);

        mDrawerList = drawerLayout.findViewById(
                theme.resolveAttribute(R.attr.drawerListId, outValue, true)
                        ? outValue.resourceId
                        : R.id.car_drawer_list);
        mDrawerList.setMaxPages(PagedListView.UNLIMITED_PAGES);

        View toolbar = drawerLayout.findViewById(
                theme.resolveAttribute(R.attr.drawerToolbarId, outValue, true)
                        ? outValue.resourceId
                        : R.id.drawer_toolbar);
        mDrawerList.setOnScrollListener(new DropShadowScrollListener(toolbar));

        @IdRes int backButtonId = theme.resolveAttribute(R.attr.drawerBackButtonId, outValue, true)
                ? outValue.resourceId
                : R.id.car_drawer_back_button;

        drawerLayout.findViewById(backButtonId).setOnClickListener(v -> {
            if (!maybeHandleUpClick()) {
                closeDrawer();
            }
        });

        setupDrawerToggling();
    }

    /**
     * Sets the {@link CarDrawerAdapter} that will function as the root adapter. The contents of
     * this root adapter are shown when the drawer is first opened. It is also the top-most level of
     * navigation in the drawer.
     *
     * @param rootAdapter The adapter that will act as the root. If this value is {@code null}, then
     *                    this method will do nothing.
     */
    public void setRootAdapter(@Nullable CarDrawerAdapter rootAdapter) {
        if (rootAdapter == null) {
            return;
        }

        // The root adapter is always the last item in the stack.
        if (!mAdapterStack.isEmpty()) {
            mAdapterStack.removeLast();
        }
        mAdapterStack.addLast(rootAdapter);
        setDisplayAdapter(rootAdapter);
    }

    /**
     * Switches to use the given {@link CarDrawerAdapter} as the one to supply the list to display
     * in the navigation drawer. The title will also be updated from the adapter.
     *
     * <p>This switch is treated as a navigation to the next level in the drawer. Navigation away
     * from this level will pop the given adapter off and surface contents of the previous adapter
     * that was set via this method. If no such adapter exists, then the root adapter set by
     * {@link #setRootAdapter(CarDrawerAdapter)} will be used instead.
     *
     * @param adapter Adapter for next level of content in the drawer.
     */
    public final void pushAdapter(CarDrawerAdapter adapter) {
        mAdapterStack.peek().setTitleChangeListener(null);
        mAdapterStack.push(adapter);
        setDisplayAdapter(adapter);
        runLayoutAnimation(DRILL_DOWN_ANIM);
    }

    /** Close the drawer. */
    public void closeDrawer() {
        if (mDrawerLayout.isDrawerOpen(Gravity.LEFT)) {
            mDrawerLayout.closeDrawer(Gravity.LEFT);
        }
    }

    /** Opens the drawer. */
    public void openDrawer() {
        if (!mDrawerLayout.isDrawerOpen(Gravity.LEFT)) {
            mDrawerLayout.openDrawer(Gravity.LEFT);
        }
    }

    /** Sets a listener to be notified of Drawer events. */
    public void addDrawerListener(@NonNull DrawerLayout.DrawerListener listener) {
        mDrawerLayout.addDrawerListener(listener);
    }

    /** Removes a listener to be notified of Drawer events. */
    public void removeDrawerListener(@NonNull DrawerLayout.DrawerListener listener) {
        mDrawerLayout.removeDrawerListener(listener);
    }

    /**
     * Sets whether the loading progress bar is displayed in the navigation drawer. If {@code true},
     * the progress bar is displayed and the navigation list is hidden and vice versa.
     */
    public void showLoadingProgressBar(boolean show) {
        mDrawerList.setVisibility(show ? View.INVISIBLE : View.VISIBLE);
        mProgressBar.setVisibility(show ? View.VISIBLE : View.GONE);
    }

    /** Scroll to given position in the list. */
    public void scrollToPosition(int position) {
        mDrawerList.getRecyclerView().smoothScrollToPosition(position);
    }

    /**
     * Retrieves the title from the given {@link CarDrawerAdapter} and set its as the title of this
     * controller's internal Toolbar.
     */
    private void setToolbarTitleFrom(CarDrawerAdapter adapter) {
        mTitleView.setText(adapter.getTitle());
        adapter.setTitleChangeListener(mTitleView::setText);
    }

    /**
     * Sets up the necessary listeners for {@link DrawerLayout} so that the navigation drawer
     * hierarchy is properly displayed.
     */
    private void setupDrawerToggling() {
        mDrawerLayout.addDrawerListener(mDrawerToggle);
        mDrawerLayout.addDrawerListener(
                new DrawerLayout.DrawerListener() {
                    @Override
                    public void onDrawerSlide(View drawerView, float slideOffset) {}

                    @Override
                    public void onDrawerClosed(View drawerView) {
                        // If drawer is closed, revert stack/drawer to initial root state.
                        cleanupStackAndShowRoot();
                        scrollToPosition(0);
                    }

                    @Override
                    public void onDrawerOpened(View drawerView) {}

                    @Override
                    public void onDrawerStateChanged(int newState) {}
                });
    }

    /**
     * Synchronizes the display of the drawer with its linked {@link DrawerLayout}.
     *
     * <p>This should be called from the associated Activity's
     * {@link androidx.appcompat.app.AppCompatActivity#onPostCreate(Bundle)} method to synchronize
     * after the DrawerLayout's instance state has been restored, and any other time when the
     * state may have diverged in such a way that this controller's associated
     * {@link ActionBarDrawerToggle} had not been notified.
     */
    public void syncState() {
        mDrawerToggle.syncState();
    }

    /**
     * Notify this controller that device configurations may have changed.
     *
     * <p>This method should be called from the associated Activity's
     * {@code onConfigurationChanged()} method.
     */
    public void onConfigurationChanged(Configuration newConfig) {
        // Pass any configuration change to the drawer toggle.
        mDrawerToggle.onConfigurationChanged(newConfig);
    }

    /**
     * Sets the given adapter as the one displaying the current contents of the drawer.
     *
     * <p>The drawer's title will also be derived from the given adapter.
     */
    private void setDisplayAdapter(CarDrawerAdapter adapter) {
        setToolbarTitleFrom(adapter);
        // NOTE: We don't use swapAdapter() since different levels in the Drawer may switch between
        // car_drawer_list_item_normal, car_drawer_list_item_small and car_list_empty layouts.
        mDrawerList.getRecyclerView().setAdapter(adapter);
    }

    /**
     * An analog to an Activity's {@code onOptionsItemSelected()}. This method should be called
     * when the Activity's method is called and will return {@code true} if the selection has
     * been handled.
     *
     * @return {@code true} if the item processing was handled by this class.
     */
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle home-click and see if we can navigate up in the drawer.
        if (item != null && item.getItemId() == android.R.id.home && maybeHandleUpClick()) {
            return true;
        }

        // DrawerToggle gets next chance to handle up-clicks (and any other clicks).
        return mDrawerToggle.onOptionsItemSelected(item);
    }

    /**
     * Switches to the previous level in the drawer hierarchy if the current list being displayed
     * is not the root adapter. This is analogous to a navigate up.
     *
     * @return {@code true} if a navigate up was possible and executed. {@code false} otherwise.
     */
    private boolean maybeHandleUpClick() {
        // Check if already at the root level.
        if (mAdapterStack.size() <= 1) {
            return false;
        }

        CarDrawerAdapter adapter = mAdapterStack.pop();
        adapter.setTitleChangeListener(null);
        adapter.cleanup();
        setDisplayAdapter(mAdapterStack.peek());
        runLayoutAnimation(NAVIGATE_UP_ANIM);
        return true;
    }

    /** Clears stack down to root adapter and switches to root adapter. */
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    void cleanupStackAndShowRoot() {
        while (mAdapterStack.size() > 1) {
            CarDrawerAdapter adapter = mAdapterStack.pop();
            adapter.setTitleChangeListener(null);
            adapter.cleanup();
        }
        setDisplayAdapter(mAdapterStack.peek());
        runLayoutAnimation(NAVIGATE_UP_ANIM);
    }

    /**
     * Runs the given layout animation on the PagedListView. Running this animation will also
     * refresh the contents of the list.
     */
    private void runLayoutAnimation(@AnimRes int animation) {
        RecyclerView recyclerView = mDrawerList.getRecyclerView();
        recyclerView.setLayoutAnimation(AnimationUtils.loadLayoutAnimation(mContext, animation));
        recyclerView.getAdapter().notifyDataSetChanged();
        recyclerView.scheduleLayoutAnimation();
    }
}