Bring AlertDialog up to date with N framework
Test: all existing tests pass
Change-Id: I8fb64f443ad8e0b742e2242dec7fc50f992d6e18
diff --git a/api/current.txt b/api/current.txt
index 4ea0b76..a000fdf 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9755,6 +9755,7 @@
method public int getBaselineAlignedChildIndex();
method public android.graphics.drawable.Drawable getDividerDrawable();
method public int getDividerPadding();
+ method public int getGravity();
method public int getOrientation();
method public int getShowDividers();
method public float getWeightSum();
diff --git a/v7/appcompat/res/layout/abc_alert_dialog_button_bar_material.xml b/v7/appcompat/res/layout/abc_alert_dialog_button_bar_material.xml
index 08adfd1..f747278 100644
--- a/v7/appcompat/res/layout/abc_alert_dialog_button_bar_material.xml
+++ b/v7/appcompat/res/layout/abc_alert_dialog_button_bar_material.xml
@@ -15,44 +15,50 @@
limitations under the License.
-->
-<android.support.v7.widget.ButtonBarLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/buttonPanel"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layoutDirection="locale"
- android:orientation="horizontal"
- android:paddingLeft="12dp"
- android:paddingRight="12dp"
- android:paddingTop="4dp"
- android:paddingBottom="4dp"
- android:gravity="bottom"
- style="?attr/buttonBarStyle">
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/buttonPanel"
+ style="?attr/buttonBarStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fillViewport="true"
+ android:scrollIndicators="top|bottom">
- <Button
- android:id="@android:id/button3"
- style="?attr/buttonBarNeutralButtonStyle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
+ <android.support.v7.widget.ButtonBarLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="bottom"
+ android:layoutDirection="locale"
+ android:orientation="horizontal"
+ android:paddingBottom="4dp"
+ android:paddingLeft="12dp"
+ android:paddingRight="12dp"
+ android:paddingTop="4dp">
- <android.support.v4.widget.Space
- android:id="@+id/spacer"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:visibility="invisible" />
+ <Button
+ android:id="@android:id/button3"
+ style="?attr/buttonBarNeutralButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
- <Button
- android:id="@android:id/button2"
- style="?attr/buttonBarNegativeButtonStyle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
+ <android.support.v4.widget.Space
+ android:id="@+id/spacer"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:visibility="invisible"/>
- <Button
- android:id="@android:id/button1"
- style="?attr/buttonBarPositiveButtonStyle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
+ <Button
+ android:id="@android:id/button2"
+ style="?attr/buttonBarNegativeButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
-</android.support.v7.widget.ButtonBarLayout>
+ <Button
+ android:id="@android:id/button1"
+ style="?attr/buttonBarPositiveButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ </android.support.v7.widget.ButtonBarLayout>
+
+</ScrollView>
\ No newline at end of file
diff --git a/v7/appcompat/res/layout/abc_alert_dialog_material.xml b/v7/appcompat/res/layout/abc_alert_dialog_material.xml
index 3237533..40aee7f 100644
--- a/v7/appcompat/res/layout/abc_alert_dialog_material.xml
+++ b/v7/appcompat/res/layout/abc_alert_dialog_material.xml
@@ -15,113 +15,85 @@
limitations under the License.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/parentPanel"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
+<android.support.v7.widget.AlertDialogLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/parentPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="start|left|top"
+ android:orientation="vertical">
- <LinearLayout
- android:id="@+id/topPanel"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <LinearLayout
- android:id="@+id/title_template"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:gravity="center_vertical"
- android:paddingLeft="?attr/dialogPreferredPadding"
- android:paddingRight="?attr/dialogPreferredPadding"
- android:paddingTop="@dimen/abc_dialog_padding_top_material">
-
- <ImageView
- android:id="@android:id/icon"
- android:layout_width="32dip"
- android:layout_height="32dip"
- android:scaleType="fitCenter"
- android:src="@null"
- style="@style/RtlOverlay.Widget.AppCompat.DialogTitle.Icon"/>
-
- <android.support.v7.widget.DialogTitle
- android:id="@+id/alertTitle"
- style="?attr/android:windowTitleStyle"
- android:singleLine="true"
- android:ellipsize="end"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAlignment="viewStart" />
-
- </LinearLayout>
- <!-- If the client uses a customTitle, it will be added here. -->
- </LinearLayout>
+ <include layout="@layout/abc_alert_dialog_title_material"/>
<FrameLayout
- android:id="@+id/contentPanel"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:minHeight="48dp">
+ android:id="@+id/contentPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="48dp">
<View android:id="@+id/scrollIndicatorUp"
- android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_gravity="top"
- android:background="?attr/colorControlHighlight"/>
+ android:background="?attr/colorControlHighlight"
+ android:visibility="gone"/>
<android.support.v4.widget.NestedScrollView
- android:id="@+id/scrollView"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:clipToPadding="false">
+ android:id="@+id/scrollView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipToPadding="false">
<LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <android.support.v4.widget.Space
+ android:id="@+id/textSpacerNoTitle"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
+ android:layout_height="@dimen/abc_dialog_padding_top_material"
+ android:visibility="gone"/>
<TextView
- android:id="@android:id/message"
- style="@style/TextAppearance.AppCompat.Subhead"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingLeft="?attr/dialogPreferredPadding"
- android:paddingTop="@dimen/abc_dialog_padding_top_material"
- android:paddingRight="?attr/dialogPreferredPadding"/>
+ android:id="@android:id/message"
+ style="@style/TextAppearance.AppCompat.Subhead"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="?attr/dialogPreferredPadding"
+ android:paddingRight="?attr/dialogPreferredPadding"/>
- <View
- android:id="@+id/textSpacerNoButtons"
- android:visibility="gone"
- android:layout_width="0dp"
- android:layout_height="@dimen/abc_dialog_padding_top_material"/>
+ <android.support.v4.widget.Space
+ android:id="@+id/textSpacerNoButtons"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/abc_dialog_padding_top_material"
+ android:visibility="gone"/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
<View android:id="@+id/scrollIndicatorDown"
- android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_gravity="bottom"
- android:background="?attr/colorControlHighlight"/>
+ android:background="?attr/colorControlHighlight"
+ android:visibility="gone"/>
</FrameLayout>
<FrameLayout
- android:id="@+id/customPanel"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:minHeight="48dp">
+ android:id="@+id/customPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="48dp">
<FrameLayout
- android:id="@+id/custom"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
+ android:id="@+id/custom"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
</FrameLayout>
- <include layout="@layout/abc_alert_dialog_button_bar_material" />
+ <include layout="@layout/abc_alert_dialog_button_bar_material"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
-</LinearLayout>
\ No newline at end of file
+</android.support.v7.widget.AlertDialogLayout>
\ No newline at end of file
diff --git a/v7/appcompat/res/layout/abc_alert_dialog_title_material.xml b/v7/appcompat/res/layout/abc_alert_dialog_title_material.xml
new file mode 100644
index 0000000..0b8b14e
--- /dev/null
+++ b/v7/appcompat/res/layout/abc_alert_dialog_title_material.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/topPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <!-- If the client uses a customTitle, it will be added here. -->
+
+ <LinearLayout
+ android:id="@+id/title_template"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical|start|left"
+ android:orientation="horizontal"
+ android:paddingLeft="?attr/dialogPreferredPadding"
+ android:paddingRight="?attr/dialogPreferredPadding"
+ android:paddingTop="@dimen/abc_dialog_padding_top_material">
+
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="32dip"
+ android:layout_height="32dip"
+ android:layout_marginEnd="8dip"
+ android:layout_marginRight="8dip"
+ android:scaleType="fitCenter"
+ android:src="@null"/>
+
+ <android.support.v7.widget.DialogTitle
+ android:id="@+id/alertTitle"
+ style="?android:attr/windowTitleStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:singleLine="true"
+ android:textAlignment="viewStart"/>
+
+ </LinearLayout>
+
+ <android.support.v4.widget.Space
+ android:id="@+id/titleDividerNoCustom"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/abc_dialog_title_divider_material"
+ android:visibility="gone"/>
+</LinearLayout>
diff --git a/v7/appcompat/res/layout/abc_select_dialog_material.xml b/v7/appcompat/res/layout/abc_select_dialog_material.xml
index 12bcbf1..ae4b268 100644
--- a/v7/appcompat/res/layout/abc_select_dialog_material.xml
+++ b/v7/appcompat/res/layout/abc_select_dialog_material.xml
@@ -20,16 +20,19 @@
This layout file is inflated and used as the ListView to display the items.
Assign an ID so its state will be saved/restored.
-->
-<ListView xmlns:android="http://schemas.android.com/apk/res/android"
+<view xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/select_dialog_listview"
+ style="@style/Widget.AppCompat.ListView"
+ class="android.support.v7.app.AlertController$RecycleListView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:cacheColorHint="@null"
- android:divider="?attr/listDividerAlertDialog"
- android:scrollbars="vertical"
- android:overScrollMode="ifContentScrolls"
- android:fadingEdge="none"
- android:paddingTop="@dimen/abc_dialog_list_padding_vertical_material"
- android:paddingBottom="@dimen/abc_dialog_list_padding_vertical_material"
android:clipToPadding="false"
- style="@style/Widget.AppCompat.ListView" />
\ No newline at end of file
+ android:divider="?attr/listDividerAlertDialog"
+ android:fadingEdge="none"
+ android:overScrollMode="ifContentScrolls"
+ android:scrollbars="vertical"
+ android:textAlignment="viewStart"
+ app:paddingBottomNoButtons="@dimen/abc_dialog_list_padding_bottom_no_buttons"
+ app:paddingTopNoTitle="@dimen/abc_dialog_list_padding_top_no_title"/>
\ No newline at end of file
diff --git a/v7/appcompat/res/values/attrs.xml b/v7/appcompat/res/values/attrs.xml
index f9990db..03ddeec 100644
--- a/v7/appcompat/res/values/attrs.xml
+++ b/v7/appcompat/res/values/attrs.xml
@@ -1038,6 +1038,7 @@
<attr name="multiChoiceItemLayout" format="reference" />
<attr name="singleChoiceItemLayout" format="reference" />
<attr name="listItemLayout" format="reference" />
+ <attr name="showTitle" format="boolean" />
</declare-styleable>
<!-- @hide -->
@@ -1101,4 +1102,11 @@
<attr name="android:textAppearance" />
</declare-styleable>
+ <declare-styleable name="RecycleListView">
+ <!-- Bottom padding to use when no buttons are present. -->
+ <attr name="paddingBottomNoButtons" format="dimension" />
+ <!-- Top padding to use when no title is present. -->
+ <attr name="paddingTopNoTitle" format="dimension" />
+ </declare-styleable>
+
</resources>
diff --git a/v7/appcompat/res/values/dimens.xml b/v7/appcompat/res/values/dimens.xml
index 7467beb..e055eed 100644
--- a/v7/appcompat/res/values/dimens.xml
+++ b/v7/appcompat/res/values/dimens.xml
@@ -72,13 +72,13 @@
<dimen name="abc_dialog_padding_material">24dp</dimen>
<dimen name="abc_dialog_padding_top_material">18dp</dimen>
+ <dimen name="abc_dialog_title_divider_material">8dp</dimen>
+ <dimen name="abc_dialog_list_padding_top_no_title">8dp</dimen>
+ <dimen name="abc_dialog_list_padding_bottom_no_buttons">8dp</dimen>
<!-- Dialog button bar height -->
<dimen name="abc_alert_dialog_button_bar_height">48dp</dimen>
- <!-- Padding above and below selection dialog lists. -->
- <dimen name="abc_dialog_list_padding_vertical_material">8dp</dimen>
-
<!-- Dialog padding minus control padding, used to fix alignment. -->
<dimen name="abc_select_dialog_padding_start_material">20dp</dimen>
diff --git a/v7/appcompat/res/values/styles_base.xml b/v7/appcompat/res/values/styles_base.xml
index b8c1f29..a760b78 100644
--- a/v7/appcompat/res/values/styles_base.xml
+++ b/v7/appcompat/res/values/styles_base.xml
@@ -468,7 +468,6 @@
<style name="Base.Widget.AppCompat.Button.ButtonBar.AlertDialog" parent="Widget.AppCompat.Button.Borderless.Colored">
<item name="android:minWidth">64dp</item>
- <item name="android:maxLines">2</item>
<item name="android:minHeight">@dimen/abc_alert_dialog_button_bar_height</item>
</style>
diff --git a/v7/appcompat/res/values/themes_base.xml b/v7/appcompat/res/values/themes_base.xml
index df3473d..92e7bce 100644
--- a/v7/appcompat/res/values/themes_base.xml
+++ b/v7/appcompat/res/values/themes_base.xml
@@ -272,10 +272,10 @@
<!-- Define these here; ContextThemeWrappers around themes that define them should
always clear these values. -->
- <item name="windowFixedWidthMajor">0dp</item>
- <item name="windowFixedWidthMinor">0dp</item>
- <item name="windowFixedHeightMajor">0dp</item>
- <item name="windowFixedHeightMinor">0dp</item>
+ <item name="windowFixedWidthMajor">@null</item>
+ <item name="windowFixedWidthMinor">@null</item>
+ <item name="windowFixedHeightMajor">@null</item>
+ <item name="windowFixedHeightMinor">@null</item>
</style>
<!-- Base platform-dependent theme providing an action bar in a light-themed activity. -->
@@ -433,10 +433,10 @@
<!-- Define these here; ContextThemeWrappers around themes that define them should
always clear these values. -->
- <item name="windowFixedWidthMajor">0dp</item>
- <item name="windowFixedWidthMinor">0dp</item>
- <item name="windowFixedHeightMajor">0dp</item>
- <item name="windowFixedHeightMinor">0dp</item>
+ <item name="windowFixedWidthMajor">@null</item>
+ <item name="windowFixedWidthMinor">@null</item>
+ <item name="windowFixedHeightMajor">@null</item>
+ <item name="windowFixedHeightMinor">@null</item>
</style>
<style name="Base.Theme.AppCompat" parent="Base.V7.Theme.AppCompat">
@@ -675,6 +675,11 @@
<item name="listPreferredItemPaddingRight">24dip</item>
<item name="android:listDivider">@null</item>
+
+ <item name="windowFixedWidthMajor">@null</item>
+ <item name="windowFixedWidthMinor">@null</item>
+ <item name="windowFixedHeightMajor">@null</item>
+ <item name="windowFixedHeightMinor">@null</item>
</style>
</resources>
diff --git a/v7/appcompat/src/android/support/v7/app/AlertController.java b/v7/appcompat/src/android/support/v7/app/AlertController.java
index c7aad2d..ee7ea8e 100644
--- a/v7/appcompat/src/android/support/v7/app/AlertController.java
+++ b/v7/appcompat/src/android/support/v7/app/AlertController.java
@@ -29,7 +29,9 @@
import android.support.v4.widget.NestedScrollView;
import android.support.v7.appcompat.R;
import android.text.TextUtils;
+import android.util.AttributeSet;
import android.util.TypedValue;
+import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
@@ -109,6 +111,8 @@
int mSingleChoiceItemLayout;
int mListItemLayout;
+ private boolean mShowTitle;
+
private int mButtonPanelLayoutHint = AlertDialog.LAYOUT_HINT_NONE;
Handler mHandler;
@@ -163,6 +167,12 @@
}
}
+ private static boolean shouldCenterSingleButton(Context context) {
+ final TypedValue outValue = new TypedValue();
+ context.getTheme().resolveAttribute(R.attr.alertDialogCenterButtons, outValue, true);
+ return outValue.data != 0;
+ }
+
public AlertController(Context context, AppCompatDialog di, Window window) {
mContext = context;
mDialog = di;
@@ -180,6 +190,7 @@
mSingleChoiceItemLayout = a
.getResourceId(R.styleable.AlertDialog_singleChoiceItemLayout, 0);
mListItemLayout = a.getResourceId(R.styleable.AlertDialog_listItemLayout, 0);
+ mShowTitle = a.getBoolean(R.styleable.AlertDialog_showTitle, true);
a.recycle();
@@ -486,6 +497,29 @@
if (mScrollView != null) {
mScrollView.setClipToPadding(true);
}
+
+ // Only show the divider if we have a title.
+ View divider = null;
+ if (mMessage != null || mListView != null || hasCustomPanel) {
+ if (!hasCustomPanel) {
+ divider = topPanel.findViewById(R.id.titleDividerNoCustom);
+ }
+ }
+
+ if (divider != null) {
+ divider.setVisibility(View.VISIBLE);
+ }
+ } else {
+ if (contentPanel != null) {
+ final View spacer = contentPanel.findViewById(R.id.textSpacerNoTitle);
+ if (spacer != null) {
+ spacer.setVisibility(View.VISIBLE);
+ }
+ }
+ }
+
+ if (mListView instanceof RecycleListView) {
+ ((RecycleListView) mListView).setHasDecor(hasTopPanel, hasButtonPanel);
}
// Update scroll indicators as needed.
@@ -640,7 +674,7 @@
mIconView = (ImageView) mWindow.findViewById(android.R.id.icon);
final boolean hasTextTitle = !TextUtils.isEmpty(mTitle);
- if (hasTextTitle) {
+ if (hasTextTitle && mShowTitle) {
// Display the title if a title is supplied, else hide it.
mTitleView = (TextView) mWindow.findViewById(R.id.alertTitle);
mTitleView.setText(mTitle);
@@ -751,12 +785,63 @@
whichButtons = whichButtons | BIT_BUTTON_NEUTRAL;
}
+ if (shouldCenterSingleButton(mContext)) {
+ /*
+ * If we only have 1 button it should be centered on the layout and
+ * expand to fill 50% of the available space.
+ */
+ if (whichButtons == BIT_BUTTON_POSITIVE) {
+ centerButton(mButtonPositive);
+ } else if (whichButtons == BIT_BUTTON_NEGATIVE) {
+ centerButton(mButtonNegative);
+ } else if (whichButtons == BIT_BUTTON_NEUTRAL) {
+ centerButton(mButtonNeutral);
+ }
+ }
+
final boolean hasButtons = whichButtons != 0;
if (!hasButtons) {
buttonPanel.setVisibility(View.GONE);
}
}
+ private void centerButton(Button button) {
+ LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) button.getLayoutParams();
+ params.gravity = Gravity.CENTER_HORIZONTAL;
+ params.weight = 0.5f;
+ button.setLayoutParams(params);
+ }
+
+ public static class RecycleListView extends ListView {
+ private final int mPaddingTopNoTitle;
+ private final int mPaddingBottomNoButtons;
+
+ public RecycleListView(Context context) {
+ this(context, null);
+ }
+
+ public RecycleListView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ final TypedArray ta = context.obtainStyledAttributes(
+ attrs, R.styleable.RecycleListView);
+ mPaddingBottomNoButtons = ta.getDimensionPixelOffset(
+ R.styleable.RecycleListView_paddingBottomNoButtons, -1);
+ mPaddingTopNoTitle = ta.getDimensionPixelOffset(
+ R.styleable.RecycleListView_paddingTopNoTitle, -1);
+ }
+
+ public void setHasDecor(boolean hasTitle, boolean hasButtons) {
+ if (!hasButtons || !hasTitle) {
+ final int paddingLeft = getPaddingLeft();
+ final int paddingTop = hasTitle ? getPaddingTop() : mPaddingTopNoTitle;
+ final int paddingRight = getPaddingRight();
+ final int paddingBottom = hasButtons ? getPaddingBottom() : mPaddingBottomNoButtons;
+ setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
+ }
+ }
+ }
+
public static class AlertParams {
public final Context mContext;
public final LayoutInflater mInflater;
@@ -877,7 +962,8 @@
}
private void createListView(final AlertController dialog) {
- final ListView listView = (ListView) mInflater.inflate(dialog.mListLayout, null);
+ final RecycleListView listView =
+ (RecycleListView) mInflater.inflate(dialog.mListLayout, null);
final ListAdapter adapter;
if (mIsMultiChoice) {
diff --git a/v7/appcompat/src/android/support/v7/widget/AlertDialogLayout.java b/v7/appcompat/src/android/support/v7/widget/AlertDialogLayout.java
new file mode 100644
index 0000000..e7600e9
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/AlertDialogLayout.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2016 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.v7.widget;
+
+import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.appcompat.R;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Special implementation of linear layout that's capable of laying out alert
+ * dialog components.
+ * <p>
+ * A dialog consists of up to three panels. All panels are optional, and a
+ * dialog may contain only a single panel. The panels are laid out according
+ * to the following guidelines:
+ * <ul>
+ * <li>topPanel: exactly wrap_content</li>
+ * <li>contentPanel OR customPanel: at most fill_parent, first priority for
+ * extra space</li>
+ * <li>buttonPanel: at least minHeight, at most wrap_content, second
+ * priority for extra space</li>
+ * </ul>
+ *
+ * @hide
+ */
+@RestrictTo(GROUP_ID)
+public class AlertDialogLayout extends LinearLayoutCompat {
+
+ public AlertDialogLayout(@Nullable Context context) {
+ super(context);
+ }
+
+ public AlertDialogLayout(@Nullable Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (!tryOnMeasure(widthMeasureSpec, heightMeasureSpec)) {
+ // Failed to perform custom measurement, let superclass handle it.
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+
+ private boolean tryOnMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ View topPanel = null;
+ View buttonPanel = null;
+ View middlePanel = null;
+
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+
+ final int id = child.getId();
+ if (id == R.id.topPanel) {
+ topPanel = child;
+ } else if (id == R.id.buttonPanel) {
+ buttonPanel = child;
+ } else if (id == R.id.contentPanel || id == R.id.customPanel) {
+ if (middlePanel != null) {
+ // Both the content and custom are visible. Abort!
+ return false;
+ }
+ middlePanel = child;
+ } else {
+ // Unknown top-level child. Abort!
+ return false;
+ }
+ }
+
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+
+ int childState = 0;
+ int usedHeight = getPaddingTop() + getPaddingBottom();
+
+ if (topPanel != null) {
+ topPanel.measure(widthMeasureSpec, MeasureSpec.UNSPECIFIED);
+
+ usedHeight += topPanel.getMeasuredHeight();
+ childState = ViewCompat.combineMeasuredStates(childState,
+ ViewCompat.getMeasuredState(topPanel));
+ }
+
+ int buttonHeight = 0;
+ int buttonWantsHeight = 0;
+ if (buttonPanel != null) {
+ buttonPanel.measure(widthMeasureSpec, MeasureSpec.UNSPECIFIED);
+ buttonHeight = resolveMinimumHeight(buttonPanel);
+ buttonWantsHeight = buttonPanel.getMeasuredHeight() - buttonHeight;
+
+ usedHeight += buttonHeight;
+ childState = ViewCompat.combineMeasuredStates(childState,
+ ViewCompat.getMeasuredState(buttonPanel));
+ }
+
+ int middleHeight = 0;
+ if (middlePanel != null) {
+ final int childHeightSpec;
+ if (heightMode == MeasureSpec.UNSPECIFIED) {
+ childHeightSpec = MeasureSpec.UNSPECIFIED;
+ } else {
+ childHeightSpec = MeasureSpec.makeMeasureSpec(
+ Math.max(0, heightSize - usedHeight), heightMode);
+ }
+
+ middlePanel.measure(widthMeasureSpec, childHeightSpec);
+ middleHeight = middlePanel.getMeasuredHeight();
+
+ usedHeight += middleHeight;
+ childState = ViewCompat.combineMeasuredStates(childState,
+ ViewCompat.getMeasuredState(middlePanel));
+ }
+
+ int remainingHeight = heightSize - usedHeight;
+
+ // Time for the "real" button measure pass. If we have remaining space,
+ // make the button pane bigger up to its target height. Otherwise,
+ // just remeasure the button at whatever height it needs.
+ if (buttonPanel != null) {
+ usedHeight -= buttonHeight;
+
+ final int heightToGive = Math.min(remainingHeight, buttonWantsHeight);
+ if (heightToGive > 0) {
+ remainingHeight -= heightToGive;
+ buttonHeight += heightToGive;
+ }
+
+ final int childHeightSpec = MeasureSpec.makeMeasureSpec(
+ buttonHeight, MeasureSpec.EXACTLY);
+ buttonPanel.measure(widthMeasureSpec, childHeightSpec);
+
+ usedHeight += buttonPanel.getMeasuredHeight();
+ childState = ViewCompat.combineMeasuredStates(childState,
+ ViewCompat.getMeasuredState(buttonPanel));
+ }
+
+ // If we still have remaining space, make the middle pane bigger up
+ // to the maximum height.
+ if (middlePanel != null && remainingHeight > 0) {
+ usedHeight -= middleHeight;
+
+ final int heightToGive = remainingHeight;
+ remainingHeight -= heightToGive;
+ middleHeight += heightToGive;
+
+ // Pass the same height mode as we're using for the dialog itself.
+ // If it's EXACTLY, then the middle pane MUST use the entire
+ // height.
+ final int childHeightSpec = MeasureSpec.makeMeasureSpec(
+ middleHeight, heightMode);
+ middlePanel.measure(widthMeasureSpec, childHeightSpec);
+
+ usedHeight += middlePanel.getMeasuredHeight();
+ childState = ViewCompat.combineMeasuredStates(childState,
+ ViewCompat.getMeasuredState(middlePanel));
+ }
+
+ // Compute desired width as maximum child width.
+ int maxWidth = 0;
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != View.GONE) {
+ maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
+ }
+ }
+
+ maxWidth += getPaddingLeft() + getPaddingRight();
+
+ final int widthSizeAndState = ViewCompat.resolveSizeAndState(
+ maxWidth, widthMeasureSpec, childState);
+ final int heightSizeAndState = ViewCompat.resolveSizeAndState(
+ usedHeight, heightMeasureSpec, 0);
+ setMeasuredDimension(widthSizeAndState, heightSizeAndState);
+
+ // If the children weren't already measured EXACTLY, we need to run
+ // another measure pass to for MATCH_PARENT widths.
+ if (widthMode != MeasureSpec.EXACTLY) {
+ forceUniformWidth(count, heightMeasureSpec);
+ }
+
+ return true;
+ }
+
+ /**
+ * Remeasures child views to exactly match the layout's measured width.
+ *
+ * @param count the number of child views
+ * @param heightMeasureSpec the original height measure spec
+ */
+ private void forceUniformWidth(int count, int heightMeasureSpec) {
+ // Pretend that the linear layout has an exact size.
+ final int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(
+ getMeasuredWidth(), MeasureSpec.EXACTLY);
+
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (lp.width == LayoutParams.MATCH_PARENT) {
+ // Temporarily force children to reuse their old measured
+ // height.
+ final int oldHeight = lp.height;
+ lp.height = child.getMeasuredHeight();
+
+ // Remeasure with new dimensions.
+ measureChildWithMargins(child, uniformMeasureSpec, 0, heightMeasureSpec, 0);
+ lp.height = oldHeight;
+ }
+ }
+ }
+ }
+
+ /**
+ * Attempts to resolve the minimum height of a view.
+ * <p>
+ * If the view doesn't have a minimum height set and only contains a single
+ * child, attempts to resolve the minimum height of the child view.
+ *
+ * @param v the view whose minimum height to resolve
+ * @return the minimum height
+ */
+ private static int resolveMinimumHeight(View v) {
+ final int minHeight = ViewCompat.getMinimumHeight(v);
+ if (minHeight > 0) {
+ return minHeight;
+ }
+
+ if (v instanceof ViewGroup) {
+ final ViewGroup vg = (ViewGroup) v;
+ if (vg.getChildCount() == 1) {
+ return resolveMinimumHeight(vg.getChildAt(0));
+ }
+ }
+
+ return 0;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ final int paddingLeft = getPaddingLeft();
+
+ // Where right end of child should go
+ final int width = right - left;
+ final int childRight = width - getPaddingRight();
+
+ // Space available for child
+ final int childSpace = width - paddingLeft - getPaddingRight();
+
+ final int totalLength = getMeasuredHeight();
+ final int count = getChildCount();
+ final int gravity = getGravity();
+ final int majorGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
+ final int minorGravity = gravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK;
+
+ int childTop;
+ switch (majorGravity) {
+ case Gravity.BOTTOM:
+ // totalLength contains the padding already
+ childTop = getPaddingTop() + bottom - top - totalLength;
+ break;
+
+ // totalLength contains the padding already
+ case Gravity.CENTER_VERTICAL:
+ childTop = getPaddingTop() + (bottom - top - totalLength) / 2;
+ break;
+
+ case Gravity.TOP:
+ default:
+ childTop = getPaddingTop();
+ break;
+ }
+
+ final Drawable dividerDrawable = getDividerDrawable();
+ final int dividerHeight = dividerDrawable == null ?
+ 0 : dividerDrawable.getIntrinsicHeight();
+
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child != null && child.getVisibility() != GONE) {
+ final int childWidth = child.getMeasuredWidth();
+ final int childHeight = child.getMeasuredHeight();
+
+ final LinearLayoutCompat.LayoutParams lp =
+ (LinearLayoutCompat.LayoutParams) child.getLayoutParams();
+
+ int layoutGravity = lp.gravity;
+ if (layoutGravity < 0) {
+ layoutGravity = minorGravity;
+ }
+ final int layoutDirection = ViewCompat.getLayoutDirection(this);
+ final int absoluteGravity = GravityCompat.getAbsoluteGravity(
+ layoutGravity, layoutDirection);
+
+ final int childLeft;
+ switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+ case Gravity.CENTER_HORIZONTAL:
+ childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ + lp.leftMargin - lp.rightMargin;
+ break;
+
+ case Gravity.RIGHT:
+ childLeft = childRight - childWidth - lp.rightMargin;
+ break;
+
+ case Gravity.LEFT:
+ default:
+ childLeft = paddingLeft + lp.leftMargin;
+ break;
+ }
+
+ if (hasDividerBeforeChildAt(i)) {
+ childTop += dividerHeight;
+ }
+
+ childTop += lp.topMargin;
+ setChildFrame(child, childLeft, childTop, childWidth, childHeight);
+ childTop += childHeight + lp.bottomMargin;
+ }
+ }
+ }
+
+ private void setChildFrame(View child, int left, int top, int width, int height) {
+ child.layout(left, top, left + width, top + height);
+ }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/widget/ButtonBarLayout.java b/v7/appcompat/src/android/support/v7/widget/ButtonBarLayout.java
index 4f08f90..6c97ee1 100644
--- a/v7/appcompat/src/android/support/v7/widget/ButtonBarLayout.java
+++ b/v7/appcompat/src/android/support/v7/widget/ButtonBarLayout.java
@@ -37,12 +37,15 @@
*/
@RestrictTo(GROUP_ID)
public class ButtonBarLayout extends LinearLayout {
- // Whether to allow vertically stacked button bars. This is disabled for
- // configurations with a small (e.g. less than 320dp) screen height. -->
+ /** Minimum screen height required for button stacking. */
private static final int ALLOW_STACKING_MIN_HEIGHT_DP = 320;
+ /** Amount of the second button to "peek" above the fold when stacked. */
+ private static final int PEEK_BUTTON_DP = 16;
+
/** Whether the current configuration allows stacking. */
private boolean mAllowStacking;
+
private int mLastWidthSize = -1;
public ButtonBarLayout(Context context, AttributeSet attrs) {
@@ -69,26 +72,33 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+
if (mAllowStacking) {
if (widthSize > mLastWidthSize && isStacked()) {
// We're being measured wider this time, try un-stacking.
setStacked(false);
}
+
mLastWidthSize = widthSize;
}
+
boolean needsRemeasure = false;
+
// If we're not stacked, make sure the measure spec is AT_MOST rather
// than EXACTLY. This ensures that we'll still get TOO_SMALL so that we
// know to stack the buttons.
final int initialWidthMeasureSpec;
if (!isStacked() && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) {
initialWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST);
+
// We'll need to remeasure again to fill excess space.
needsRemeasure = true;
} else {
initialWidthMeasureSpec = widthMeasureSpec;
}
+
super.onMeasure(initialWidthMeasureSpec, heightMeasureSpec);
+
if (mAllowStacking && !isStacked()) {
final boolean stack;
@@ -113,18 +123,54 @@
needsRemeasure = true;
}
}
+
if (needsRemeasure) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
+
+ // Compute minimum height such that, when stacked, some portion of the
+ // second button is visible.
+ int minHeight = 0;
+ final int firstVisible = getNextVisibleChildIndex(0);
+ if (firstVisible >= 0) {
+ final View firstButton = getChildAt(firstVisible);
+ final LayoutParams firstParams = (LayoutParams) firstButton.getLayoutParams();
+ minHeight += getPaddingTop() + firstButton.getMeasuredHeight()
+ + firstParams.topMargin + firstParams.bottomMargin;
+ if (isStacked()) {
+ final int secondVisible = getNextVisibleChildIndex(firstVisible + 1);
+ if (secondVisible >= 0) {
+ minHeight += getChildAt(secondVisible).getPaddingTop()
+ + PEEK_BUTTON_DP * getResources().getDisplayMetrics().density;
+ }
+ } else {
+ minHeight += getPaddingBottom();
+ }
+ }
+
+ if (ViewCompat.getMinimumHeight(this) != minHeight) {
+ setMinimumHeight(minHeight);
+ }
+ }
+
+ private int getNextVisibleChildIndex(int index) {
+ for (int i = index, count = getChildCount(); i < count; i++) {
+ if (getChildAt(i).getVisibility() == View.VISIBLE) {
+ return i;
+ }
+ }
+ return -1;
}
private void setStacked(boolean stacked) {
setOrientation(stacked ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL);
setGravity(stacked ? Gravity.RIGHT : Gravity.BOTTOM);
+
final View spacer = findViewById(R.id.spacer);
if (spacer != null) {
spacer.setVisibility(stacked ? View.GONE : View.INVISIBLE);
}
+
// Reverse the child order. This is specific to the Material button
// bar's layout XML and will probably not generalize.
final int childCount = getChildCount();
diff --git a/v7/appcompat/src/android/support/v7/widget/LinearLayoutCompat.java b/v7/appcompat/src/android/support/v7/widget/LinearLayoutCompat.java
index eacd17d..ce6f140 100644
--- a/v7/appcompat/src/android/support/v7/widget/LinearLayoutCompat.java
+++ b/v7/appcompat/src/android/support/v7/widget/LinearLayoutCompat.java
@@ -1690,6 +1690,16 @@
}
}
+ /**
+ * Returns the current gravity. See {@link android.view.Gravity}
+ *
+ * @return the current gravity.
+ * @see #setGravity
+ */
+ public int getGravity() {
+ return mGravity;
+ }
+
public void setHorizontalGravity(int horizontalGravity) {
final int gravity = horizontalGravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK;
if ((mGravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK) != gravity) {