AlertDialog + Dialog fixes & improvements
- Brought AlertController up to date with M
- Fix min width on tablets
- Add scroll indicators
- Added OnScrollChangeListener to NestedScrollView
to enable scroll indicators
- Add scroll indicators shim to ViewCompat
BUG: 19673703
BUG: 20961957
Change-Id: I02802d5299cb8554ff16de4ca689dd44325f465f
diff --git a/v4/api/current.txt b/v4/api/current.txt
index 669b4a4..9f8a03c 100644
--- a/v4/api/current.txt
+++ b/v4/api/current.txt
@@ -2408,6 +2408,7 @@
method public static float getRotationY(android.view.View);
method public static float getScaleX(android.view.View);
method public static float getScaleY(android.view.View);
+ method public static int getScrollIndicators(android.view.View);
method public static java.lang.String getTransitionName(android.view.View);
method public static float getTranslationX(android.view.View);
method public static float getTranslationY(android.view.View);
@@ -2468,6 +2469,8 @@
method public static void setSaveFromParentEnabled(android.view.View, boolean);
method public static void setScaleX(android.view.View, float);
method public static void setScaleY(android.view.View, float);
+ method public static void setScrollIndicators(android.view.View, int);
+ method public static void setScrollIndicators(android.view.View, int, int);
method public static void setTransitionName(android.view.View, java.lang.String);
method public static void setTranslationX(android.view.View, float);
method public static void setTranslationY(android.view.View, float);
@@ -2500,6 +2503,12 @@
field public static final int SCROLL_AXIS_HORIZONTAL = 1; // 0x1
field public static final int SCROLL_AXIS_NONE = 0; // 0x0
field public static final int SCROLL_AXIS_VERTICAL = 2; // 0x2
+ field public static final int SCROLL_INDICATOR_BOTTOM = 2; // 0x2
+ field public static final int SCROLL_INDICATOR_END = 32; // 0x20
+ field public static final int SCROLL_INDICATOR_LEFT = 4; // 0x4
+ field public static final int SCROLL_INDICATOR_RIGHT = 8; // 0x8
+ field public static final int SCROLL_INDICATOR_START = 16; // 0x10
+ field public static final int SCROLL_INDICATOR_TOP = 1; // 0x1
}
public class ViewConfigurationCompat {
@@ -3243,11 +3252,16 @@
method public void onAttachedToWindow();
method public boolean pageScroll(int);
method public void setFillViewport(boolean);
+ method public void setOnScrollChangeListener(android.support.v4.widget.NestedScrollView.OnScrollChangeListener);
method public void setSmoothScrollingEnabled(boolean);
method public final void smoothScrollBy(int, int);
method public final void smoothScrollTo(int, int);
}
+ public static abstract interface NestedScrollView.OnScrollChangeListener {
+ method public abstract void onScrollChange(android.support.v4.widget.NestedScrollView, int, int, int, int);
+ }
+
public class PopupMenuCompat {
method public static android.view.View.OnTouchListener getDragToOpenListener(java.lang.Object);
}
diff --git a/v4/api23/android/support/v4/view/ViewCompatMarshmallow.java b/v4/api23/android/support/v4/view/ViewCompatMarshmallow.java
new file mode 100644
index 0000000..16d3ae8
--- /dev/null
+++ b/v4/api23/android/support/v4/view/ViewCompatMarshmallow.java
@@ -0,0 +1,33 @@
+/*
+ * 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.v4.view;
+
+import android.view.View;
+
+class ViewCompatMarshmallow {
+ public static void setScrollIndicators(View view, int indicators) {
+ view.setScrollIndicators(indicators);
+ }
+
+ public static void setScrollIndicators(View view, int indicators, int mask) {
+ view.setScrollIndicators(indicators, mask);
+ }
+
+ public static int getScrollIndicators(View view) {
+ return view.getScrollIndicators();
+ }
+}
diff --git a/v4/java/android/support/v4/view/ViewCompat.java b/v4/java/android/support/v4/view/ViewCompat.java
index b5dfd9f8..b88d7e5 100644
--- a/v4/java/android/support/v4/view/ViewCompat.java
+++ b/v4/java/android/support/v4/view/ViewCompat.java
@@ -27,6 +27,7 @@
import android.support.annotation.FloatRange;
import android.support.annotation.IdRes;
import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat;
@@ -276,6 +277,73 @@
*/
public static final int SCROLL_AXIS_VERTICAL = 1 << 1;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true,
+ value = {
+ SCROLL_INDICATOR_TOP,
+ SCROLL_INDICATOR_BOTTOM,
+ SCROLL_INDICATOR_LEFT,
+ SCROLL_INDICATOR_RIGHT,
+ SCROLL_INDICATOR_START,
+ SCROLL_INDICATOR_END,
+ })
+ public @interface ScrollIndicators {}
+
+ /**
+ * Scroll indicator direction for the top edge of the view.
+ *
+ * @see #setScrollIndicators(int)
+ * @see #setScrollIndicators(int, int)
+ * @see #getScrollIndicators()
+ */
+ public static final int SCROLL_INDICATOR_TOP = 0x1;
+
+ /**
+ * Scroll indicator direction for the bottom edge of the view.
+ *
+ * @see #setScrollIndicators(int)
+ * @see #setScrollIndicators(int, int)
+ * @see #getScrollIndicators()
+ */
+ public static final int SCROLL_INDICATOR_BOTTOM = 0x2;
+
+ /**
+ * Scroll indicator direction for the left edge of the view.
+ *
+ * @see #setScrollIndicators(int)
+ * @see #setScrollIndicators(int, int)
+ * @see #getScrollIndicators()
+ */
+ public static final int SCROLL_INDICATOR_LEFT = 0x4;
+
+ /**
+ * Scroll indicator direction for the right edge of the view.
+ *
+ * @see #setScrollIndicators(int)
+ * @see #setScrollIndicators(int, int)
+ * @see #getScrollIndicators()
+ */
+ public static final int SCROLL_INDICATOR_RIGHT = 0x8;
+
+ /**
+ * Scroll indicator direction for the starting edge of the view.
+ *
+ * @see #setScrollIndicators(View, int)
+ * @see #setScrollIndicators(View, int, int)
+ * @see #getScrollIndicators(View)
+ */
+ public static final int SCROLL_INDICATOR_START = 0x10;
+
+ /**
+ * Scroll indicator direction for the ending edge of the view.
+ *
+ * @see #setScrollIndicators(int)
+ * @see #setScrollIndicators(int, int)
+ * @see #getScrollIndicators()
+ */
+ public static final int SCROLL_INDICATOR_END = 0x20;
+
interface ViewCompatImpl {
public boolean canScrollHorizontally(View v, int direction);
public boolean canScrollVertically(View v, int direction);
@@ -385,6 +453,9 @@
public float getZ(View view);
public boolean isAttachedToWindow(View view);
public boolean hasOnClickListeners(View view);
+ public void setScrollIndicators(View view, int indicators);
+ public void setScrollIndicators(View view, int indicators, int mask);
+ public int getScrollIndicators(View view);
}
static class BaseViewCompatImpl implements ViewCompatImpl {
@@ -969,6 +1040,21 @@
public boolean hasOnClickListeners(View view) {
return false;
}
+
+ @Override
+ public int getScrollIndicators(View view) {
+ return 0;
+ }
+
+ @Override
+ public void setScrollIndicators(View view, int indicators) {
+ // no-op
+ }
+
+ @Override
+ public void setScrollIndicators(View view, int indicators, int mask) {
+ // no-op
+ }
}
static class EclairMr1ViewCompatImpl extends BaseViewCompatImpl {
@@ -1542,10 +1628,29 @@
}
}
+ static class MarshmallowViewCompatImpl extends LollipopViewCompatImpl {
+ @Override
+ public void setScrollIndicators(View view, int indicators) {
+ ViewCompatMarshmallow.setScrollIndicators(view, indicators);
+ }
+
+ @Override
+ public void setScrollIndicators(View view, int indicators, int mask) {
+ ViewCompatMarshmallow.setScrollIndicators(view, indicators, mask);
+ }
+
+ @Override
+ public int getScrollIndicators(View view) {
+ return ViewCompatMarshmallow.getScrollIndicators(view);
+ }
+ }
+
static final ViewCompatImpl IMPL;
static {
final int version = android.os.Build.VERSION.SDK_INT;
- if (version >= 21) {
+ if (version >= 23) {
+ IMPL = new MarshmallowViewCompatImpl();
+ } else if (version >= 21) {
IMPL = new LollipopViewCompatImpl();
} else if (version >= 19) {
IMPL = new KitKatViewCompatImpl();
@@ -3109,4 +3214,67 @@
public static boolean hasOnClickListeners(View view) {
return IMPL.hasOnClickListeners(view);
}
+
+ /**
+ * Sets the state of all scroll indicators.
+ * <p>
+ * See {@link #setScrollIndicators(View, int, int)} for usage information.
+ *
+ * @param indicators a bitmask of indicators that should be enabled, or
+ * {@code 0} to disable all indicators
+ *
+ * @see #setScrollIndicators(View, int, int)
+ * @see #getScrollIndicators(View)
+ */
+ public static void setScrollIndicators(@NonNull View view, @ScrollIndicators int indicators) {
+ IMPL.setScrollIndicators(view, indicators);
+ }
+
+ /**
+ * Sets the state of the scroll indicators specified by the mask. To change
+ * all scroll indicators at once, see {@link #setScrollIndicators(View, int)}.
+ * <p>
+ * When a scroll indicator is enabled, it will be displayed if the view
+ * can scroll in the direction of the indicator.
+ * <p>
+ * Multiple indicator types may be enabled or disabled by passing the
+ * logical OR of the desired types. If multiple types are specified, they
+ * will all be set to the same enabled state.
+ * <p>
+ * For example, to enable the top scroll indicatorExample: {@code setScrollIndicators}
+ *
+ * @param indicators the indicator direction, or the logical OR of multiple
+ * indicator directions. One or more of:
+ * <ul>
+ * <li>{@link #SCROLL_INDICATOR_TOP}</li>
+ * <li>{@link #SCROLL_INDICATOR_BOTTOM}</li>
+ * <li>{@link #SCROLL_INDICATOR_LEFT}</li>
+ * <li>{@link #SCROLL_INDICATOR_RIGHT}</li>
+ * <li>{@link #SCROLL_INDICATOR_START}</li>
+ * <li>{@link #SCROLL_INDICATOR_END}</li>
+ * </ul>
+ *
+ * @see #setScrollIndicators(View, int)
+ * @see #getScrollIndicators(View)
+ */
+ public static void setScrollIndicators(@NonNull View view, @ScrollIndicators int indicators,
+ @ScrollIndicators int mask) {
+ IMPL.setScrollIndicators(view, indicators, mask);
+ }
+
+ /**
+ * Returns a bitmask representing the enabled scroll indicators.
+ * <p>
+ * For example, if the top and left scroll indicators are enabled and all
+ * other indicators are disabled, the return value will be
+ * {@code ViewCompat.SCROLL_INDICATOR_TOP | ViewCompat.SCROLL_INDICATOR_LEFT}.
+ * <p>
+ * To check whether the bottom scroll indicator is enabled, use the value
+ * of {@code (ViewCompat.getScrollIndicators(view) & ViewCompat.SCROLL_INDICATOR_BOTTOM) != 0}.
+ *
+ * @return a bitmask representing the enabled scroll indicators
+ */
+ public static int getScrollIndicators(@NonNull View view) {
+ return IMPL.getScrollIndicators(view);
+ }
}
diff --git a/v4/java/android/support/v4/widget/NestedScrollView.java b/v4/java/android/support/v4/widget/NestedScrollView.java
index 583c728..2e30e08 100644
--- a/v4/java/android/support/v4/widget/NestedScrollView.java
+++ b/v4/java/android/support/v4/widget/NestedScrollView.java
@@ -68,6 +68,28 @@
private static final String TAG = "NestedScrollView";
+ /**
+ * Interface definition for a callback to be invoked when the scroll
+ * X or Y positions of a view change.
+ *
+ * <p>This version of the interface works on all versions of Android, back to API v4.</p>
+ *
+ * @see #setOnScrollChangeListener(OnScrollChangeListener)
+ */
+ public interface OnScrollChangeListener {
+ /**
+ * Called when the scroll position of a view changes.
+ *
+ * @param v The view whose scroll position has changed.
+ * @param scrollX Current horizontal scroll origin.
+ * @param scrollY Current vertical scroll origin.
+ * @param oldScrollX Previous horizontal scroll origin.
+ * @param oldScrollY Previous vertical scroll origin.
+ */
+ void onScrollChange(NestedScrollView v, int scrollX, int scrollY,
+ int oldScrollX, int oldScrollY);
+ }
+
private long mLastScroll;
private final Rect mTempRect = new Rect();
@@ -153,6 +175,8 @@
private float mVerticalScrollFactor;
+ private OnScrollChangeListener mOnScrollChangeListener;
+
public NestedScrollView(Context context) {
this(context, null);
}
@@ -376,6 +400,19 @@
}
/**
+ * Register a callback to be invoked when the scroll X or Y positions of
+ * this view change.
+ * <p>This version of the method works on all versions of Android, back to API v4.</p>
+ *
+ * @param l The listener to notify when the scroll X or Y position changes.
+ * @see android.view.View#getScrollX()
+ * @see android.view.View#getScrollY()
+ */
+ public void setOnScrollChangeListener(OnScrollChangeListener l) {
+ mOnScrollChangeListener = l;
+ }
+
+ /**
* @return Returns true this ScrollView can be scrolled
*/
private boolean canScroll() {
@@ -430,6 +467,15 @@
}
@Override
+ protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+ super.onScrollChanged(l, t, oldl, oldt);
+
+ if (mOnScrollChangeListener != null) {
+ mOnScrollChangeListener.onScrollChange(this, l, t, oldl, oldt);
+ }
+ }
+
+ @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
diff --git a/v7/appcompat/api/current.txt b/v7/appcompat/api/current.txt
index fcb8a82..cba4c81 100644
--- a/v7/appcompat/api/current.txt
+++ b/v7/appcompat/api/current.txt
@@ -649,6 +649,10 @@
field public static int abc_control_corner_material;
field public static int abc_control_inset_material;
field public static int abc_control_padding_material;
+ field public static int abc_dialog_fixed_height_major;
+ field public static int abc_dialog_fixed_height_minor;
+ field public static int abc_dialog_fixed_width_major;
+ field public static int abc_dialog_fixed_width_minor;
field public static int abc_dialog_list_padding_vertical_material;
field public static int abc_dialog_min_width_major;
field public static int abc_dialog_min_width_minor;
@@ -687,10 +691,6 @@
field public static int abc_text_size_subtitle_material_toolbar;
field public static int abc_text_size_title_material;
field public static int abc_text_size_title_material_toolbar;
- field public static int dialog_fixed_height_major;
- field public static int dialog_fixed_height_minor;
- field public static int dialog_fixed_width_major;
- field public static int dialog_fixed_width_minor;
field public static int disabled_alpha_material_dark;
field public static int disabled_alpha_material_light;
field public static int highlight_alpha_material_colored;
@@ -833,6 +833,8 @@
field public static int progress_horizontal;
field public static int radio;
field public static int screen;
+ field public static int scrollIndicatorDown;
+ field public static int scrollIndicatorUp;
field public static int scrollView;
field public static int search_badge;
field public static int search_bar;
diff --git a/v7/appcompat/res/layout/abc_alert_dialog_material.xml b/v7/appcompat/res/layout/abc_alert_dialog_material.xml
index 01ff885..c14cb0a 100644
--- a/v7/appcompat/res/layout/abc_alert_dialog_material.xml
+++ b/v7/appcompat/res/layout/abc_alert_dialog_material.xml
@@ -65,7 +65,14 @@
android:layout_weight="1"
android:minHeight="48dp">
- <ScrollView
+ <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.support.v4.widget.NestedScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -91,7 +98,14 @@
android:layout_width="0dp"
android:layout_height="@dimen/abc_dialog_padding_top_material"/>
</LinearLayout>
- </ScrollView>
+ </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"/>
</FrameLayout>
diff --git a/v7/appcompat/res/values-large/dimens.xml b/v7/appcompat/res/values-large/dimens.xml
index de1cefc..274379b 100644
--- a/v7/appcompat/res/values-large/dimens.xml
+++ b/v7/appcompat/res/values-large/dimens.xml
@@ -24,15 +24,18 @@
<!-- The platform's desired fixed width for a dialog along the major axis
(the screen is in landscape). This may be either a fraction or a dimension.-->
- <item type="dimen" name="dialog_fixed_width_major">60%</item>
+ <item type="dimen" name="abc_dialog_fixed_width_major">60%</item>
<!-- The platform's desired fixed width for a dialog along the minor axis
(the screen is in portrait). This may be either a fraction or a dimension.-->
- <item type="dimen" name="dialog_fixed_width_minor">90%</item>
+ <item type="dimen" name="abc_dialog_fixed_width_minor">90%</item>
<!-- The platform's desired fixed height for a dialog along the major axis
(the screen is in portrait). This may be either a fraction or a dimension.-->
- <item type="dimen" name="dialog_fixed_height_major">60%</item>
+ <item type="dimen" name="abc_dialog_fixed_height_major">60%</item>
<!-- The platform's desired fixed height for a dialog along the minor axis
(the screen is in landscape). This may be either a fraction or a dimension.-->
- <item type="dimen" name="dialog_fixed_height_minor">90%</item>
+ <item type="dimen" name="abc_dialog_fixed_height_minor">90%</item>
+
+ <item type="dimen" name="abc_dialog_min_width_major">55%</item>
+ <item type="dimen" name="abc_dialog_min_width_minor">80%</item>
</resources>
diff --git a/v7/appcompat/res/values-xlarge/dimens.xml b/v7/appcompat/res/values-xlarge/dimens.xml
index 3eb2962..3410da2 100644
--- a/v7/appcompat/res/values-xlarge/dimens.xml
+++ b/v7/appcompat/res/values-xlarge/dimens.xml
@@ -26,15 +26,18 @@
<!-- The platform's desired fixed width for a dialog along the major axis
(the screen is in landscape). This may be either a fraction or a dimension.-->
- <item type="dimen" name="dialog_fixed_width_major">50%</item>
+ <item type="dimen" name="abc_dialog_fixed_width_major">50%</item>
<!-- The platform's desired fixed width for a dialog along the minor axis
(the screen is in portrait). This may be either a fraction or a dimension.-->
- <item type="dimen" name="dialog_fixed_width_minor">70%</item>
+ <item type="dimen" name="abc_dialog_fixed_width_minor">70%</item>
<!-- The platform's desired fixed height for a dialog along the major axis
(the screen is in portrait). This may be either a fraction or a dimension.-->
- <item type="dimen" name="dialog_fixed_height_major">60%</item>
+ <item type="dimen" name="abc_dialog_fixed_height_major">60%</item>
<!-- The platform's desired fixed height for a dialog along the minor axis
(the screen is in landscape). This may be either a fraction or a dimension.-->
- <item type="dimen" name="dialog_fixed_height_minor">90%</item>
+ <item type="dimen" name="abc_dialog_fixed_height_minor">90%</item>
+
+ <item type="dimen" name="abc_dialog_min_width_major">45%</item>
+ <item type="dimen" name="abc_dialog_min_width_minor">72%</item>
</resources>
diff --git a/v7/appcompat/res/values/dimens.xml b/v7/appcompat/res/values/dimens.xml
index 06f1a06..f44ebaf 100644
--- a/v7/appcompat/res/values/dimens.xml
+++ b/v7/appcompat/res/values/dimens.xml
@@ -47,16 +47,16 @@
<!-- The platform's desired fixed width for a dialog along the major axis
(the screen is in landscape). This may be either a fraction or a dimension.-->
- <item type="dimen" name="dialog_fixed_width_major">320dp</item>
+ <item type="dimen" name="abc_dialog_fixed_width_major">320dp</item>
<!-- The platform's desired fixed width for a dialog along the minor axis
(the screen is in portrait). This may be either a fraction or a dimension.-->
- <item type="dimen" name="dialog_fixed_width_minor">320dp</item>
+ <item type="dimen" name="abc_dialog_fixed_width_minor">320dp</item>
<!-- The platform's desired fixed height for a dialog along the major axis
(the screen is in portrait). This may be either a fraction or a dimension.-->
- <item type="dimen" name="dialog_fixed_height_major">80%</item>
+ <item type="dimen" name="abc_dialog_fixed_height_major">80%</item>
<!-- The platform's desired fixed height for a dialog along the minor axis
(the screen is in landscape). This may be either a fraction or a dimension.-->
- <item type="dimen" name="dialog_fixed_height_minor">100%</item>
+ <item type="dimen" name="abc_dialog_fixed_height_minor">100%</item>
<dimen name="abc_button_inset_vertical_material">6dp</dimen>
<dimen name="abc_button_inset_horizontal_material">@dimen/abc_control_inset_material</dimen>
diff --git a/v7/appcompat/res/values/themes_base.xml b/v7/appcompat/res/values/themes_base.xml
index 1b3d8c5..bb4d308 100644
--- a/v7/appcompat/res/values/themes_base.xml
+++ b/v7/appcompat/res/values/themes_base.xml
@@ -510,17 +510,17 @@
</style>
<style name="Base.Theme.AppCompat.Dialog.FixedSize">
- <item name="windowFixedWidthMajor">@dimen/dialog_fixed_width_major</item>
- <item name="windowFixedWidthMinor">@dimen/dialog_fixed_width_minor</item>
- <item name="windowFixedHeightMajor">@dimen/dialog_fixed_height_major</item>
- <item name="windowFixedHeightMinor">@dimen/dialog_fixed_height_minor</item>
+ <item name="windowFixedWidthMajor">@dimen/abc_dialog_fixed_width_major</item>
+ <item name="windowFixedWidthMinor">@dimen/abc_dialog_fixed_width_minor</item>
+ <item name="windowFixedHeightMajor">@dimen/abc_dialog_fixed_height_major</item>
+ <item name="windowFixedHeightMinor">@dimen/abc_dialog_fixed_height_minor</item>
</style>
<style name="Base.Theme.AppCompat.Light.Dialog.FixedSize">
- <item name="windowFixedWidthMajor">@dimen/dialog_fixed_width_major</item>
- <item name="windowFixedWidthMinor">@dimen/dialog_fixed_width_minor</item>
- <item name="windowFixedHeightMajor">@dimen/dialog_fixed_height_major</item>
- <item name="windowFixedHeightMinor">@dimen/dialog_fixed_height_minor</item>
+ <item name="windowFixedWidthMajor">@dimen/abc_dialog_fixed_width_major</item>
+ <item name="windowFixedWidthMinor">@dimen/abc_dialog_fixed_width_minor</item>
+ <item name="windowFixedHeightMajor">@dimen/abc_dialog_fixed_height_major</item>
+ <item name="windowFixedHeightMinor">@dimen/abc_dialog_fixed_height_minor</item>
</style>
<!-- We're not large, so redirect to Theme.AppCompat -->
diff --git a/v7/appcompat/src/android/support/v7/app/AlertController.java b/v7/appcompat/src/android/support/v7/app/AlertController.java
index 76c1670..5f8eb4b 100644
--- a/v7/appcompat/src/android/support/v7/app/AlertController.java
+++ b/v7/appcompat/src/android/support/v7/app/AlertController.java
@@ -21,20 +21,25 @@
import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.os.Handler;
import android.os.Message;
+import android.support.annotation.Nullable;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.widget.NestedScrollView;
import android.support.v7.appcompat.R;
-import android.support.v7.internal.widget.TintTypedArray;
import android.text.TextUtils;
import android.util.TypedValue;
-import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
+import android.view.ViewParent;
+import android.view.ViewStub;
import android.view.Window;
import android.view.WindowManager;
+import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
@@ -46,7 +51,6 @@
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
-import android.widget.ScrollView;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
@@ -84,7 +88,7 @@
private CharSequence mButtonNeutralText;
private Message mButtonNeutralMessage;
- private ScrollView mScrollView;
+ private NestedScrollView mScrollView;
private int mIconId = 0;
private Drawable mIcon;
@@ -159,19 +163,13 @@
}
}
- private static boolean shouldCenterSingleButton(Context context) {
- 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;
mWindow = window;
mHandler = new ButtonHandler(di);
- TypedArray a = context.obtainStyledAttributes(null, R.styleable.AlertDialog,
+ final TypedArray a = context.obtainStyledAttributes(null, R.styleable.AlertDialog,
R.attr.alertDialogStyle, 0);
mAlertDialogLayout = a.getResourceId(R.styleable.AlertDialog_android_layout, 0);
@@ -211,7 +209,6 @@
public void installContent() {
/* We use a custom title so never request a window title */
mDialog.supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
-
final int contentView = selectContentView();
mDialog.setContentView(contentView);
setupView();
@@ -368,6 +365,7 @@
/**
* @param attrId the attributeId of the theme-specific drawable
* to resolve the resourceId for.
+ *
* @return resId the resourceId of the theme-specific drawable
*/
public int getIconAttributeResId(int attrId) {
@@ -403,26 +401,194 @@
return mScrollView != null && mScrollView.executeKeyEvent(event);
}
- private void setupView() {
- final ViewGroup contentPanel = (ViewGroup) mWindow.findViewById(R.id.contentPanel);
- setupContent(contentPanel);
- final boolean hasButtons = setupButtons();
+ /**
+ * Resolves whether a custom or default panel should be used. Removes the
+ * default panel if a custom panel should be used. If the resolved panel is
+ * a view stub, inflates before returning.
+ *
+ * @param customPanel the custom panel
+ * @param defaultPanel the default panel
+ * @return the panel to use
+ */
+ @Nullable
+ private ViewGroup resolvePanel(@Nullable View customPanel, @Nullable View defaultPanel) {
+ if (customPanel == null) {
+ // Inflate the default panel, if needed.
+ if (defaultPanel instanceof ViewStub) {
+ defaultPanel = ((ViewStub) defaultPanel).inflate();
+ }
- final ViewGroup topPanel = (ViewGroup) mWindow.findViewById(R.id.topPanel);
- final TintTypedArray a = TintTypedArray.obtainStyledAttributes(mContext,
- null, R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
- final boolean hasTitle = setupTitle(topPanel);
+ return (ViewGroup) defaultPanel;
+ }
- final View buttonPanel = mWindow.findViewById(R.id.buttonPanel);
- if (!hasButtons) {
- buttonPanel.setVisibility(View.GONE);
- final View spacer = mWindow.findViewById(R.id.textSpacerNoButtons);
- if (spacer != null) {
- spacer.setVisibility(View.VISIBLE);
+ // Remove the default panel entirely.
+ if (defaultPanel != null) {
+ final ViewParent parent = defaultPanel.getParent();
+ if (parent instanceof ViewGroup) {
+ ((ViewGroup) parent).removeView(defaultPanel);
}
}
- final FrameLayout customPanel = (FrameLayout) mWindow.findViewById(R.id.customPanel);
+ // Inflate the custom panel, if needed.
+ if (customPanel instanceof ViewStub) {
+ customPanel = ((ViewStub) customPanel).inflate();
+ }
+
+ return (ViewGroup) customPanel;
+ }
+
+ private void setupView() {
+ final View parentPanel = mWindow.findViewById(R.id.parentPanel);
+ final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel);
+ final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel);
+ final View defaultButtonPanel = parentPanel.findViewById(R.id.buttonPanel);
+
+ // Install custom content before setting up the title or buttons so
+ // that we can handle panel overrides.
+ final ViewGroup customPanel = (ViewGroup) parentPanel.findViewById(R.id.customPanel);
+ setupCustomContent(customPanel);
+
+ final View customTopPanel = customPanel.findViewById(R.id.topPanel);
+ final View customContentPanel = customPanel.findViewById(R.id.contentPanel);
+ final View customButtonPanel = customPanel.findViewById(R.id.buttonPanel);
+
+ // Resolve the correct panels and remove the defaults, if needed.
+ final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel);
+ final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);
+ final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel);
+
+ setupContent(contentPanel);
+ setupButtons(buttonPanel);
+ setupTitle(topPanel);
+
+ final boolean hasCustomPanel = customPanel != null
+ && customPanel.getVisibility() != View.GONE;
+ final boolean hasTopPanel = topPanel != null
+ && topPanel.getVisibility() != View.GONE;
+ final boolean hasButtonPanel = buttonPanel != null
+ && buttonPanel.getVisibility() != View.GONE;
+
+ // Only display the text spacer if we don't have buttons.
+ if (!hasButtonPanel) {
+ if (contentPanel != null) {
+ final View spacer = contentPanel.findViewById(R.id.textSpacerNoButtons);
+ if (spacer != null) {
+ spacer.setVisibility(View.VISIBLE);
+ }
+ }
+ }
+
+ if (hasTopPanel) {
+ // Only clip scrolling content to padding if we have a title.
+ if (mScrollView != null) {
+ mScrollView.setClipToPadding(true);
+ }
+ }
+
+ // Update scroll indicators as needed.
+ if (!hasCustomPanel) {
+ final View content = mListView != null ? mListView : mScrollView;
+ if (content != null) {
+ final int indicators = (hasTopPanel ? ViewCompat.SCROLL_INDICATOR_TOP : 0)
+ | (hasButtonPanel ? ViewCompat.SCROLL_INDICATOR_BOTTOM : 0);
+ setScrollIndicators(contentPanel, content, indicators,
+ ViewCompat.SCROLL_INDICATOR_TOP | ViewCompat.SCROLL_INDICATOR_BOTTOM);
+ }
+ }
+
+ final ListView listView = mListView;
+ if (listView != null && mAdapter != null) {
+ listView.setAdapter(mAdapter);
+ final int checkedItem = mCheckedItem;
+ if (checkedItem > -1) {
+ listView.setItemChecked(checkedItem, true);
+ listView.setSelection(checkedItem);
+ }
+ }
+ }
+
+ private void setScrollIndicators(ViewGroup contentPanel, View content,
+ final int indicators, final int mask) {
+ // Set up scroll indicators (if present).
+ View indicatorUp = mWindow.findViewById(R.id.scrollIndicatorUp);
+ View indicatorDown = mWindow.findViewById(R.id.scrollIndicatorDown);
+
+ if (Build.VERSION.SDK_INT >= 23) {
+ // We're on Marshmallow so can rely on the View APIs
+ ViewCompat.setScrollIndicators(content, indicators, mask);
+ // We can also remove the compat indicator views
+ if (indicatorUp != null) {
+ contentPanel.removeView(indicatorUp);
+ }
+ if (indicatorDown != null) {
+ contentPanel.removeView(indicatorDown);
+ }
+ } else {
+ // First, remove the indicator views if we're not set to use them
+ if (indicatorUp != null && (indicators & ViewCompat.SCROLL_INDICATOR_TOP) == 0) {
+ contentPanel.removeView(indicatorUp);
+ indicatorUp = null;
+ }
+ if (indicatorDown != null && (indicators & ViewCompat.SCROLL_INDICATOR_BOTTOM) == 0) {
+ contentPanel.removeView(indicatorDown);
+ indicatorDown = null;
+ }
+
+ if (indicatorUp != null || indicatorDown != null) {
+ final View top = indicatorUp;
+ final View bottom = indicatorDown;
+
+ if (mMessage != null) {
+ // We're just showing the ScrollView, set up listener.
+ mScrollView.setOnScrollChangeListener(
+ new NestedScrollView.OnScrollChangeListener() {
+ @Override
+ public void onScrollChange(NestedScrollView v, int scrollX,
+ int scrollY,
+ int oldScrollX, int oldScrollY) {
+ manageScrollIndicators(v, top, bottom);
+ }
+ });
+ // Set up the indicators following layout.
+ mScrollView.post(new Runnable() {
+ @Override
+ public void run() {
+ manageScrollIndicators(mScrollView, top, bottom);
+ }
+ });
+ } else if (mListView != null) {
+ // We're just showing the AbsListView, set up listener.
+ mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {}
+
+ @Override
+ public void onScroll(AbsListView v, int firstVisibleItem,
+ int visibleItemCount, int totalItemCount) {
+ manageScrollIndicators(v, top, bottom);
+ }
+ });
+ // Set up the indicators following layout.
+ mListView.post(new Runnable() {
+ @Override
+ public void run() {
+ manageScrollIndicators(mListView, top, bottom);
+ }
+ });
+ } else {
+ // We don't have any content to scroll, remove the indicators.
+ if (top != null) {
+ contentPanel.removeView(top);
+ }
+ if (bottom != null) {
+ contentPanel.removeView(bottom);
+ }
+ }
+ }
+ }
+ }
+
+ private void setupCustomContent(ViewGroup customPanel) {
final View customView;
if (mView != null) {
customView = mView;
@@ -454,23 +620,9 @@
} else {
customPanel.setVisibility(View.GONE);
}
-
- final ListView listView = mListView;
- if (listView != null && mAdapter != null) {
- listView.setAdapter(mAdapter);
- final int checkedItem = mCheckedItem;
- if (checkedItem > -1) {
- listView.setItemChecked(checkedItem, true);
- listView.setSelection(checkedItem);
- }
- }
-
- a.recycle();
}
- private boolean setupTitle(ViewGroup topPanel) {
- boolean hasTitle = true;
-
+ private void setupTitle(ViewGroup topPanel) {
if (mCustomTitleView != null) {
// Add the custom title view directly to the topPanel layout
LayoutParams lp = new LayoutParams(
@@ -512,18 +664,17 @@
titleTemplate.setVisibility(View.GONE);
mIconView.setVisibility(View.GONE);
topPanel.setVisibility(View.GONE);
- hasTitle = false;
}
}
- return hasTitle;
}
private void setupContent(ViewGroup contentPanel) {
- mScrollView = (ScrollView) mWindow.findViewById(R.id.scrollView);
+ mScrollView = (NestedScrollView) mWindow.findViewById(R.id.scrollView);
mScrollView.setFocusable(false);
+ mScrollView.setNestedScrollingEnabled(false);
// Special case for users that only want to display a String
- mMessageView = (TextView) mWindow.findViewById(android.R.id.message);
+ mMessageView = (TextView) contentPanel.findViewById(android.R.id.message);
if (mMessageView == null) {
return;
}
@@ -546,12 +697,23 @@
}
}
- private boolean setupButtons() {
+ private static void manageScrollIndicators(View v, View upIndicator, View downIndicator) {
+ if (upIndicator != null) {
+ upIndicator.setVisibility(
+ ViewCompat.canScrollVertically(v, -1) ? View.VISIBLE : View.INVISIBLE);
+ }
+ if (downIndicator != null) {
+ downIndicator.setVisibility(
+ ViewCompat.canScrollVertically(v, 1) ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+
+ private void setupButtons(ViewGroup buttonPanel) {
int BIT_BUTTON_POSITIVE = 1;
int BIT_BUTTON_NEGATIVE = 2;
int BIT_BUTTON_NEUTRAL = 4;
int whichButtons = 0;
- mButtonPositive = (Button) mWindow.findViewById(android.R.id.button1);
+ mButtonPositive = (Button) buttonPanel.findViewById(android.R.id.button1);
mButtonPositive.setOnClickListener(mButtonHandler);
if (TextUtils.isEmpty(mButtonPositiveText)) {
@@ -562,7 +724,7 @@
whichButtons = whichButtons | BIT_BUTTON_POSITIVE;
}
- mButtonNegative = (Button) mWindow.findViewById(android.R.id.button2);
+ mButtonNegative = (Button) buttonPanel.findViewById(android.R.id.button2);
mButtonNegative.setOnClickListener(mButtonHandler);
if (TextUtils.isEmpty(mButtonNegativeText)) {
@@ -574,7 +736,7 @@
whichButtons = whichButtons | BIT_BUTTON_NEGATIVE;
}
- mButtonNeutral = (Button) mWindow.findViewById(android.R.id.button3);
+ mButtonNeutral = (Button) buttonPanel.findViewById(android.R.id.button3);
mButtonNeutral.setOnClickListener(mButtonHandler);
if (TextUtils.isEmpty(mButtonNeutralText)) {
@@ -586,28 +748,10 @@
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);
}
-
- return whichButtons != 0;
- }
-
- 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 AlertParams {
@@ -661,7 +805,6 @@
/**
* Called before the ListView is bound to an adapter.
- *
* @param listView The ListView that will be shown in the dialog.
*/
void onPrepareListView(ListView listView);
@@ -732,7 +875,7 @@
private void createListView(final AlertController dialog) {
final ListView listView = (ListView) mInflater.inflate(dialog.mListLayout, null);
- ListAdapter adapter;
+ final ListAdapter adapter;
if (mIsMultiChoice) {
if (mCursor == null) {
@@ -763,8 +906,8 @@
@Override
public void bindView(View view, Context context, Cursor cursor) {
- CheckedTextView text = (CheckedTextView) view
- .findViewById(android.R.id.text1);
+ CheckedTextView text = (CheckedTextView) view.findViewById(
+ android.R.id.text1);
text.setText(cursor.getString(mLabelIndex));
listView.setItemChecked(cursor.getPosition(),
cursor.getInt(mIsCheckedIndex) == 1);
@@ -779,14 +922,20 @@
};
}
} else {
- int layout = mIsSingleChoice
- ? dialog.mSingleChoiceItemLayout : dialog.mListItemLayout;
- if (mCursor == null) {
- adapter = (mAdapter != null) ? mAdapter
- : new CheckedItemAdapter(mContext, layout, android.R.id.text1, mItems);
+ final int layout;
+ if (mIsSingleChoice) {
+ layout = dialog.mSingleChoiceItemLayout;
} else {
- adapter = new SimpleCursorAdapter(mContext, layout,
- mCursor, new String[]{mLabelColumn}, new int[]{android.R.id.text1});
+ layout = dialog.mListItemLayout;
+ }
+
+ if (mCursor != null) {
+ adapter = new SimpleCursorAdapter(mContext, layout, mCursor,
+ new String[] { mLabelColumn }, new int[] { android.R.id.text1 });
+ } else if (mAdapter != null) {
+ adapter = mAdapter;
+ } else {
+ adapter = new CheckedItemAdapter(mContext, layout, android.R.id.text1, mItems);
}
}