From 30b1bcd3e61b059f43a970321554dc68331ba2f6 Mon Sep 17 00:00:00 2001 From: Yuichi Araki Date: Wed, 30 Nov 2016 00:47:51 +0000 Subject: [PATCH] am d3da39bb: am 2b9dabda: Merge "Initial implementation of NavigationDrawer" into lmp-mr1-ub-dev * commit 'd3da39bbe4b007d3ee2dadd318d55b0e06697b89': Initial implementation of NavigationDrawer GitOrigin-RevId=e06c9b9db020326166a5591c0483c03377418437 PiperOrigin-RevId: 140545176 --- res/layout/design_drawer_item.xml | 26 +++ res/layout/design_drawer_menu.xml | 22 +++ res/values/attrs.xml | 14 +- res/values/dimens.xml | 8 +- res/values/styles.xml | 14 +- .../internal/NavigationMenuItemView.java | 99 +++++++++++ .../internal/NavigationMenuPresenter.java | 160 ++++++++++++++++++ .../design/internal/NavigationMenuView.java | 52 ++++++ .../internal/ScrimInsetsFrameLayout.java | 130 ++++++++++++++ .../design/widget/NavigationDrawerView.java | 154 +++++++++++++++++ 10 files changed, 673 insertions(+), 6 deletions(-) create mode 100644 res/layout/design_drawer_item.xml create mode 100644 res/layout/design_drawer_menu.xml create mode 100644 src/android/support/design/internal/NavigationMenuItemView.java create mode 100644 src/android/support/design/internal/NavigationMenuPresenter.java create mode 100644 src/android/support/design/internal/NavigationMenuView.java create mode 100644 src/android/support/design/internal/ScrimInsetsFrameLayout.java create mode 100644 src/android/support/design/widget/NavigationDrawerView.java diff --git a/res/layout/design_drawer_item.xml b/res/layout/design_drawer_item.xml new file mode 100644 index 000000000..c1eb1cef6 --- /dev/null +++ b/res/layout/design_drawer_item.xml @@ -0,0 +1,26 @@ + + + diff --git a/res/layout/design_drawer_menu.xml b/res/layout/design_drawer_menu.xml new file mode 100644 index 000000000..e28eaf07b --- /dev/null +++ b/res/layout/design_drawer_menu.xml @@ -0,0 +1,22 @@ + + + diff --git a/res/values/attrs.xml b/res/values/attrs.xml index a6096ceb1..5102d1894 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -14,7 +14,6 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - @@ -36,4 +35,15 @@ - \ No newline at end of file + + + + + + + + + + + + diff --git a/res/values/dimens.xml b/res/values/dimens.xml index d16bb77d1..903399914 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -14,7 +14,6 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - 8dp @@ -23,4 +22,9 @@ 56dp 40dp - \ No newline at end of file + 320dp + 12dp + 32dp + 24dp + + diff --git a/res/values/styles.xml b/res/values/styles.xml index ad42108a8..0cb5ead5f 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -14,7 +14,6 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - - \ No newline at end of file + + + + + diff --git a/src/android/support/design/internal/NavigationMenuItemView.java b/src/android/support/design/internal/NavigationMenuItemView.java new file mode 100644 index 000000000..531372289 --- /dev/null +++ b/src/android/support/design/internal/NavigationMenuItemView.java @@ -0,0 +1,99 @@ +/* + * 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 android.support.design.internal; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.support.design.R; +import android.support.v4.widget.TextViewCompat; +import android.support.v7.internal.view.menu.MenuItemImpl; +import android.support.v7.internal.view.menu.MenuView; +import android.util.AttributeSet; +import android.view.View; +import android.widget.TextView; + +/** + * @hide + */ +public class NavigationMenuItemView extends TextView implements MenuView.ItemView { + + private MenuItemImpl mItemData; + + private int mIconSize; + + public NavigationMenuItemView(Context context) { + this(context, null); + } + + public NavigationMenuItemView(Context context, AttributeSet attrs) { + super(context, attrs); + mIconSize = context.getResources().getDimensionPixelSize(R.dimen.drawer_icon_size); + } + + @Override + public void initialize(MenuItemImpl itemData, int menuType) { + mItemData = itemData; + + setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE); + + setTitle(itemData.getTitle()); + setCheckable(itemData.isCheckable()); + setIcon(itemData.getIcon()); + setEnabled(itemData.isEnabled()); + } + + @Override + public MenuItemImpl getItemData() { + return mItemData; + } + + @Override + public void setTitle(CharSequence title) { + setText(title); + } + + @Override + public void setCheckable(boolean checkable) { + } + + @Override + public void setChecked(boolean checked) { + } + + @Override + public void setShortcut(boolean showShortcut, char shortcutKey) { + } + + @Override + public void setIcon(Drawable icon) { + if (icon != null) { + icon.setBounds(0, 0, mIconSize, mIconSize); + } + TextViewCompat.setCompoundDrawablesRelative(this, icon, null, null, null); + } + + @Override + public boolean prefersCondensedTitle() { + return false; + } + + @Override + public boolean showsIcon() { + return true; + } + +} diff --git a/src/android/support/design/internal/NavigationMenuPresenter.java b/src/android/support/design/internal/NavigationMenuPresenter.java new file mode 100644 index 000000000..b4c891e69 --- /dev/null +++ b/src/android/support/design/internal/NavigationMenuPresenter.java @@ -0,0 +1,160 @@ +/* + * 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 android.support.design.internal; + +import android.content.Context; +import android.os.Parcelable; +import android.support.design.R; +import android.support.v7.internal.view.menu.MenuBuilder; +import android.support.v7.internal.view.menu.MenuItemImpl; +import android.support.v7.internal.view.menu.MenuPresenter; +import android.support.v7.internal.view.menu.MenuView; +import android.support.v7.internal.view.menu.SubMenuBuilder; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.BaseAdapter; + +/** + * @hide + */ +public class NavigationMenuPresenter implements MenuPresenter, AdapterView.OnItemClickListener { + + private NavigationMenuView mMenuView; + + private Callback mCallback; + + private MenuBuilder mMenu; + + private int mId; + + private NavigationMenuAdapter mAdapter; + + private LayoutInflater mLayoutInflater; + + @Override + public void initForMenu(Context context, MenuBuilder menu) { + mLayoutInflater = LayoutInflater.from(context); + mMenu = menu; + } + + @Override + public MenuView getMenuView(ViewGroup root) { + if (mMenuView == null) { + mMenuView = (NavigationMenuView) mLayoutInflater.inflate( + R.layout.design_drawer_menu, root, false); + if (mAdapter == null) { + mAdapter = new NavigationMenuAdapter(); + } + mMenuView.setAdapter(mAdapter); + mMenuView.setOnItemClickListener(this); + } + return mMenuView; + } + + @Override + public void updateMenuView(boolean cleared) { + if (mAdapter != null) { + mAdapter.notifyDataSetChanged(); + } + } + + @Override + public void setCallback(Callback cb) { + mCallback = cb; + } + + @Override + public boolean onSubMenuSelected(SubMenuBuilder subMenu) { + return false; + } + + @Override + public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { + if (mCallback != null) { + mCallback.onCloseMenu(menu, allMenusAreClosing); + } + } + + @Override + public boolean flagActionItems() { + return false; + } + + @Override + public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) { + return false; + } + + @Override + public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) { + return false; + } + + @Override + public int getId() { + return mId; + } + + public void setId(int id) { + mId = id; + } + + @Override + public Parcelable onSaveInstanceState() { + return null; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + } + + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + mMenu.performItemAction(mAdapter.getItem(position), this, 0); + } + + private class NavigationMenuAdapter extends BaseAdapter { + + @Override + public int getCount() { + return mMenu.size(); + } + + @Override + public MenuItemImpl getItem(int position) { + return (MenuItemImpl) mMenu.getItem(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = mLayoutInflater.inflate(R.layout.design_drawer_item, parent, false); + } + MenuView.ItemView itemView = (MenuView.ItemView) convertView; + itemView.initialize(getItem(position), 0); + return convertView; + } + } + +} diff --git a/src/android/support/design/internal/NavigationMenuView.java b/src/android/support/design/internal/NavigationMenuView.java new file mode 100644 index 000000000..054b800c7 --- /dev/null +++ b/src/android/support/design/internal/NavigationMenuView.java @@ -0,0 +1,52 @@ +/* + * 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 android.support.design.internal; + +import android.content.Context; +import android.support.v7.internal.view.menu.MenuBuilder; +import android.support.v7.internal.view.menu.MenuView; +import android.util.AttributeSet; +import android.widget.ListView; + +/** + * @hide + */ +public class NavigationMenuView extends ListView implements MenuView { + + public NavigationMenuView(Context context) { + this(context, null); + } + + public NavigationMenuView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public NavigationMenuView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + public void initialize(MenuBuilder menu) { + + } + + @Override + public int getWindowAnimations() { + return 0; + } + +} diff --git a/src/android/support/design/internal/ScrimInsetsFrameLayout.java b/src/android/support/design/internal/ScrimInsetsFrameLayout.java new file mode 100644 index 000000000..246d5b322 --- /dev/null +++ b/src/android/support/design/internal/ScrimInsetsFrameLayout.java @@ -0,0 +1,130 @@ +/* + * 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 android.support.design.internal; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.support.design.R; +import android.support.v4.view.ViewCompat; +import android.support.v4.view.WindowInsetsCompat; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; + +/** + * @hide + */ +public class ScrimInsetsFrameLayout extends FrameLayout { + + private Drawable mInsetForeground; + + private Rect mInsets; + + private Rect mTempRect = new Rect(); + + public ScrimInsetsFrameLayout(Context context) { + this(context, null); + } + + public ScrimInsetsFrameLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ScrimInsetsFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + final TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.ScrimInsetsFrameLayout, defStyleAttr, + R.style.Widget_Design_ScrimInsetsFrameLayout); + mInsetForeground = a.getDrawable(R.styleable.ScrimInsetsFrameLayout_insetForeground); + a.recycle(); + setWillNotDraw(true); // No need to draw until the insets are adjusted + + ViewCompat.setOnApplyWindowInsetsListener(this, + new android.support.v4.view.OnApplyWindowInsetsListener() { + @Override + public WindowInsetsCompat onApplyWindowInsets(View v, + WindowInsetsCompat insets) { + if (null == mInsets) { + mInsets = new Rect(); + } + mInsets.set(insets.getSystemWindowInsetLeft(), + insets.getSystemWindowInsetTop(), + insets.getSystemWindowInsetRight(), + insets.getSystemWindowInsetBottom()); + setWillNotDraw(mInsets.isEmpty() || mInsetForeground == null); + ViewCompat.postInvalidateOnAnimation(ScrimInsetsFrameLayout.this); + return insets.consumeSystemWindowInsets(); + } + }); + } + + @Override + public void draw(@NonNull Canvas canvas) { + super.draw(canvas); + + int width = getWidth(); + int height = getHeight(); + if (mInsets != null && mInsetForeground != null) { + int sc = canvas.save(); + canvas.translate(getScrollX(), getScrollY()); + + // Top + mTempRect.set(0, 0, width, mInsets.top); + mInsetForeground.setBounds(mTempRect); + mInsetForeground.draw(canvas); + + // Bottom + mTempRect.set(0, height - mInsets.bottom, width, height); + mInsetForeground.setBounds(mTempRect); + mInsetForeground.draw(canvas); + + // Left + mTempRect.set(0, mInsets.top, mInsets.left, height - mInsets.bottom); + mInsetForeground.setBounds(mTempRect); + mInsetForeground.draw(canvas); + + // Right + mTempRect.set(width - mInsets.right, mInsets.top, width, height - mInsets.bottom); + mInsetForeground.setBounds(mTempRect); + mInsetForeground.draw(canvas); + + canvas.restoreToCount(sc); + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (mInsetForeground != null) { + mInsetForeground.setCallback(this); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (mInsetForeground != null) { + mInsetForeground.setCallback(null); + } + } + +} diff --git a/src/android/support/design/widget/NavigationDrawerView.java b/src/android/support/design/widget/NavigationDrawerView.java new file mode 100644 index 000000000..98505dd5a --- /dev/null +++ b/src/android/support/design/widget/NavigationDrawerView.java @@ -0,0 +1,154 @@ +/* + * 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 android.support.design.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.support.design.R; +import android.support.design.internal.NavigationMenuPresenter; +import android.support.design.internal.ScrimInsetsFrameLayout; +import android.support.v4.view.ViewCompat; +import android.support.v7.internal.view.menu.MenuBuilder; +import android.util.AttributeSet; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; + +/** + * Represents a standard navigation drawer for application. The drawer contents can be populated by + * a menu resource + * file. + *

NavigationDrawer needs to be placed inside a {@link android.support.v4.widget.DrawerLayout}. + *

+ *
+ * <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ *     xmlns:app="http://schemas.android.com/apk/res-auto"
+ *     android:id="@+id/drawer_layout"
+ *     android:layout_width="match_parent"
+ *     android:layout_height="match_parent"
+ *     android:fitsSystemWindows="true">
+ *
+ *     <!-- Your contents -->
+ *
+ *     <android.support.design.widget.NavigationDrawerView
+ *         android:id="@+id/navigation_drawer"
+ *         android:layout_width="wrap_content"
+ *         android:layout_height="match_parent"
+ *         android:layout_gravity="start" />
+ * </android.support.v4.widget.DrawerLayout>
+ * 
+ */ +public class NavigationDrawerView extends ScrimInsetsFrameLayout { + + private OnNavigationItemSelectedListener mListener; + + private MenuBuilder mMenu; + + private int mMaxWidth; + + public NavigationDrawerView(Context context) { + this(context, null); + } + + public NavigationDrawerView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public NavigationDrawerView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + // Custom attributes + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.NavigationDrawerView, defStyleAttr, + R.style.Widget_Design_NavigationDrawerView); + + //noinspection deprecation + setBackgroundDrawable(a.getDrawable(R.styleable.NavigationDrawerView_android_background)); + ViewCompat.setElevation(this, + a.getDimensionPixelSize(R.styleable.NavigationDrawerView_android_elevation, 0)); + ViewCompat.setFitsSystemWindows(this, + a.getBoolean(R.styleable.NavigationDrawerView_android_fitsSystemWindows, false)); + mMaxWidth = a.getDimensionPixelSize(R.styleable.NavigationDrawerView_android_maxWidth, 0); + a.recycle(); + + // Set up the menu + mMenu = new MenuBuilder(context); + MenuBuilder.Callback menuCallback = new MenuBuilder.Callback() { + @Override + public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { + return mListener != null && mListener.onNavigationItemSelected(item); + } + + @Override + public void onMenuModeChange(MenuBuilder menu) { + + } + }; + mMenu.setCallback(menuCallback); + NavigationMenuPresenter presenter = new NavigationMenuPresenter(); + presenter.initForMenu(context, mMenu); + addView((View) presenter.getMenuView(this)); + } + + /** + * Set a listener that will be notified when a menu item is clicked. + * + * @param listener The listener to notify + */ + public void setNavigationItemSelectedListener(OnNavigationItemSelectedListener listener) { + mListener = listener; + } + + @Override + protected void onMeasure(int widthSpec, int heightSpec) { + switch (MeasureSpec.getMode(widthSpec)) { + case MeasureSpec.EXACTLY: + // Nothing to do + break; + case MeasureSpec.AT_MOST: + widthSpec = MeasureSpec.makeMeasureSpec( + Math.min(MeasureSpec.getSize(widthSpec), mMaxWidth), MeasureSpec.EXACTLY); + break; + case MeasureSpec.UNSPECIFIED: + widthSpec = MeasureSpec.makeMeasureSpec(mMaxWidth, MeasureSpec.EXACTLY); + break; + } + // Let super sort out the height + super.onMeasure(widthSpec, heightSpec); + } + + /** + * @return The {@link Menu} associated with this NavigationDrawerView. + */ + public Menu getMenu() { + return mMenu; + } + + /** + * Listener for handling events on navigation drawer items. + */ + public interface OnNavigationItemSelectedListener { + + /** + * Called when an item in the navigation drawer is selected. + * + * @param item The selected item + */ + public boolean onNavigationItemSelected(MenuItem item); + } + +}