blob: 1d90c171e83c5520a03c14d52b95197f6d05e438 [file] [log] [blame]
/*
* Copyright (C) 2015 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 com.android.car.app;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.support.car.ui.PagedListView;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import com.android.car.stream.ui.R;
import java.util.Stack;
/**
* Common base Activity for car apps that need to present a Drawer.
* <p>
* This Activity manages the overall layout. To use it sub-classes need to:
* <ul>
* <li>Provide the root-items for the Drawer by implementing {@link #getRootAdapter()}.</li>
* <li>Add their main content using {@link #setMainContent(int)} or
* {@link #setMainContent(View)}. They can also add fragments to the main-content container by
* obtaining its id using {@link #getContentContainerId()}</li>
* </ul>
* This class will take care of drawer toggling and display.
* <p>
* The rootAdapter can implement nested-navigation, in its click-handling, by passing the
* CarDrawerAdapter for the next level to {@link #switchToAdapter(CarDrawerAdapter)}. This
* activity will maintain a stack of such adapters. When the user navigates up, it will pop the top
* adapter off and display its contents again.
* <p>
* Any Activity's based on this class need to set their theme to CarDrawerActivityTheme or a
* derivative.
* <p>
* NOTE: This version is based on a regular Activity unlike car-support-lib's CarDrawerActivity
* which is based on CarActivity.
*/
public abstract class CarDrawerActivity extends AppCompatActivity {
private static final float COLOR_SWITCH_SLIDE_OFFSET = 0.25f;
private final Stack<CarDrawerAdapter> mAdapterStack = new Stack<>();
private DrawerLayout mDrawerLayout;
private PagedListView mDrawerList;
private ProgressBar mProgressBar;
private View mDrawerContent;
private Toolbar mToolbar;
private ActionBarDrawerToggle mDrawerToggle;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.car_drawer_activity);
mDrawerLayout = (DrawerLayout)findViewById(R.id.drawer_layout);
mDrawerContent = findViewById(R.id.drawer_content);
mDrawerList = (PagedListView)findViewById(R.id.drawer_list);
// Let drawer list show unlimited pages of items.
mDrawerList.setMaxPages(PagedListView.ItemCap.UNLIMITED);
mProgressBar = (ProgressBar)findViewById(R.id.drawer_progress);
mToolbar = (Toolbar) findViewById(R.id.main_toolbar);
setSupportActionBar(mToolbar);
// Init drawer adapter stack.
CarDrawerAdapter rootAdapter = getRootAdapter();
mAdapterStack.push(rootAdapter);
setToolbarTitleFrom(rootAdapter);
mDrawerList.setAdapter(rootAdapter);
setupDrawerToggling();
}
private void setToolbarTitleFrom(CarDrawerAdapter adapter) {
if (adapter.getTitle() != null) {
mToolbar.setTitle(adapter.getTitle());
} else {
throw new RuntimeException("CarDrawerAdapter subclass must supply title via " +
" setTitle()");
}
adapter.setTitleChangeListener(mToolbar::setTitle);
}
/**
* Set main content to display in this Activity. It will be added to R.id.content_frame in
* car_drawer_activity.xml. NOTE: Do not use {@link #setContentView(View)}.
*
* @param view View to display as main content.
*/
public void setMainContent(View view) {
ViewGroup parent = (ViewGroup) findViewById(getContentContainerId());
parent.addView(view);
}
/**
* Set main content to display in this Activity. It will be added to R.id.content_frame in
* car_drawer_activity.xml. NOTE: Do not use {@link #setContentView(int)}.
*
* @param resourceId Layout to display as main content.
*/
public void setMainContent(@LayoutRes int resourceId) {
ViewGroup parent = (ViewGroup) findViewById(getContentContainerId());
LayoutInflater inflater = getLayoutInflater();
inflater.inflate(resourceId, parent, true);
}
/**
* @return Adapter for root content of the Drawer.
*/
protected abstract CarDrawerAdapter getRootAdapter();
/**
* Used to pass in next level of items to display in the Drawer, including updated title. It is
* pushed on top of the existing adapter in a stack. Navigating up from this level later will
* pop this adapter off and surface contents of the next adapter at the top of the stack (and
* its title).
*
* @param adapter Adapter for next level of content in the drawer.
*/
public final void switchToAdapter(CarDrawerAdapter adapter) {
mAdapterStack.peek().setTitleChangeListener(null);
mAdapterStack.push(adapter);
setTitleAndSwitchToAdapter(adapter);
}
/**
* Close the drawer if open.
*/
public void closeDrawer() {
if (mDrawerLayout.isDrawerOpen(Gravity.LEFT)) {
mDrawerLayout.closeDrawer(Gravity.LEFT);
}
}
/**
* Used to open the drawer.
*/
public void openDrawer() {
if (!mDrawerLayout.isDrawerOpen(Gravity.LEFT)) {
mDrawerLayout.openDrawer(Gravity.LEFT);
}
}
/**
* @param listener Listener to be notified of Drawer events.
*/
public void addDrawerListener(@NonNull DrawerLayout.DrawerListener listener) {
mDrawerLayout.addDrawerListener(listener);
}
/**
* @param listener Listener to be notified of Drawer events.
*/
public void removeDrawerListener(@NonNull DrawerLayout.DrawerListener listener) {
mDrawerLayout.removeDrawerListener(listener);
}
/**
* Used to switch between the Drawer PagedListView and the "loading" progress-bar while the next
* level's adapter contents are being fetched.
*
* @param enable If true, the progress-bar is displayed. If false, the Drawer PagedListView is
* added.
*/
public void showLoadingProgressBar(boolean enable) {
mDrawerList.setVisibility(enable ? View.INVISIBLE : View.VISIBLE);
mProgressBar.setVisibility(enable ? View.VISIBLE : View.GONE);
}
/**
* Get the id of the main content Container which is a FrameLayout. Subclasses can add their own
* content/fragments inside here.
*
* @return Id of FrameLayout where main content of the subclass Activity can be added.
*/
protected int getContentContainerId() {
return R.id.content_frame;
}
private void setupDrawerToggling() {
mDrawerToggle = new ActionBarDrawerToggle(
this, /* host Activity */
mDrawerLayout, /* DrawerLayout object */
// The string id's below are for accessibility. However
// since they won't be used in cars, we just pass car_drawer_unused.
R.string.car_drawer_unused,
R.string.car_drawer_unused
);
mDrawerLayout.addDrawerListener(mDrawerToggle);
mDrawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
setTitleAndArrowColor(slideOffset >= COLOR_SWITCH_SLIDE_OFFSET);
}
@Override
public void onDrawerOpened(View drawerView) {}
@Override
public void onDrawerClosed(View drawerView) {
// If drawer is closed for any reason, revert stack/drawer to initial root state.
cleanupStackAndShowRoot();
mDrawerList.getRecyclerView().scrollToPosition(0);
}
@Override
public void onDrawerStateChanged(int newState) {}
});
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
}
private void setTitleAndArrowColor(boolean drawerOpen) {
// When drawer open, use car_title, which resolves to appropriate color depending on
// day-night mode. When drawer is closed, we always use light color.
int titleColorResId = drawerOpen ?
R.color.car_title : R.color.car_title_light;
int titleColor = getColor(titleColorResId);
mToolbar.setTitleTextColor(titleColor);
mDrawerToggle.getDrawerArrowDrawable().setColor(titleColor);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Sync the toggle state after onRestoreInstanceState has occurred.
mDrawerToggle.syncState();
// In case we're restarting after a config change (e.g. day, night switch), set colors
// again. Doing it here so that Drawer state is fully synced and we know if its open or not.
// NOTE: isDrawerOpen must be passed the second child of the DrawerLayout.
setTitleAndArrowColor(mDrawerLayout.isDrawerOpen(mDrawerContent));
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Pass any configuration change to the drawer toggls
mDrawerToggle.onConfigurationChanged(newConfig);
}
@Override
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).
if (mDrawerToggle.onOptionsItemSelected(item)) {
return true;
}
return super.onOptionsItemSelected(item);
}
private void setTitleAndSwitchToAdapter(CarDrawerAdapter adapter) {
setToolbarTitleFrom(adapter);
// NOTE: We don't use swapAdapter() since different levels in the Drawer may switch between
// car_menu_list_item_normal, car_menu_list_item_small and car_list_empty layouts.
mDrawerList.getRecyclerView().setAdapter(adapter);
mDrawerList.getRecyclerView().scrollToPosition(0);
}
private boolean maybeHandleUpClick() {
if (mAdapterStack.size() > 1) {
CarDrawerAdapter adapter = mAdapterStack.pop();
adapter.setTitleChangeListener(null);
adapter.cleanup();
setTitleAndSwitchToAdapter(mAdapterStack.peek());
return true;
}
return false;
}
/** Clears stack down to root adapter and switches to root adapter. */
private void cleanupStackAndShowRoot() {
while (mAdapterStack.size() > 1) {
CarDrawerAdapter adapter = mAdapterStack.pop();
adapter.setTitleChangeListener(null);
adapter.cleanup();
}
setTitleAndSwitchToAdapter(mAdapterStack.peek());
}
}