Add top and bottom panel overlay to lists in AlertDialog for watch.

For watch devices, AlertDialogs added the title and button bar as header and
footer views in the ListView. This broke compatibility, hence a solution to
overlay the panels instead with a wrapper layout.

Bug: 27482353
Bug: 30075032
Bug: 29833395
Bug: 29277843

Change-Id: I2ecbe56ae8f7d7e99c7ca2dad2a2092499212199
This commit is contained in:
Michael Kwan
2016-07-22 12:17:04 -07:00
parent c3bdde9790
commit 55e4030f77
4 changed files with 358 additions and 52 deletions

View File

@@ -178,11 +178,6 @@ public class AlertController {
return outValue.data != 0;
}
private static boolean isWatch(Context context) {
return (context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_TYPE_WATCH)
== Configuration.UI_MODE_TYPE_WATCH;
}
public static final AlertController create(Context context, DialogInterface di, Window window) {
final TypedArray a = context.obtainStyledAttributes(
null, R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
@@ -892,14 +887,8 @@ public class AlertController {
listView.setAdapter(mAdapter);
final int checkedItem = mCheckedItem;
if (checkedItem > -1) {
// TODO: Remove temp watch specific code
if (isWatch(mContext)) {
listView.setItemChecked(checkedItem + listView.getHeaderViewsCount(), true);
listView.setSelection(checkedItem + listView.getHeaderViewsCount());
} else {
listView.setItemChecked(checkedItem, true);
listView.setSelection(checkedItem);
}
listView.setItemChecked(checkedItem, true);
listView.setSelection(checkedItem);
}
}
}
@@ -1078,13 +1067,7 @@ public class AlertController {
if (mCheckedItems != null) {
boolean isItemChecked = mCheckedItems[position];
if (isItemChecked) {
// TODO: Remove temp watch specific code
if (isWatch(mContext)) {
listView.setItemChecked(
position + listView.getHeaderViewsCount(), true);
} else {
listView.setItemChecked(position, true);
}
listView.setItemChecked(position, true);
}
}
return view;
@@ -1105,16 +1088,9 @@ public class AlertController {
public void bindView(View view, Context context, Cursor cursor) {
CheckedTextView text = (CheckedTextView) view.findViewById(R.id.text1);
text.setText(cursor.getString(mLabelIndex));
// TODO: Remove temp watch specific code
if (isWatch(mContext)) {
listView.setItemChecked(
cursor.getPosition() + listView.getHeaderViewsCount(),
cursor.getInt(mIsCheckedIndex) == 1);
} else {
listView.setItemChecked(
cursor.getPosition(),
cursor.getInt(mIsCheckedIndex) == 1);
}
listView.setItemChecked(
cursor.getPosition(),
cursor.getInt(mIsCheckedIndex) == 1);
}
@Override
@@ -1157,10 +1133,6 @@ public class AlertController {
listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
// TODO: Remove temp watch specific code
if (isWatch(mContext)) {
position -= listView.getHeaderViewsCount();
}
mOnClickListener.onClick(dialog.mDialogInterface, position);
if (!mIsSingleChoice) {
dialog.mDialogInterface.dismiss();
@@ -1171,10 +1143,6 @@ public class AlertController {
listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
// TODO: Remove temp watch specific code
if (isWatch(mContext)) {
position -= listView.getHeaderViewsCount();
}
if (mCheckedItems != null) {
mCheckedItems[position] = listView.isItemChecked(position);
}

View File

@@ -18,12 +18,15 @@ package com.android.internal.app;
import android.content.Context;
import android.content.DialogInterface;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.AbsListView;
import com.android.internal.app.AlertController;
import com.android.internal.R;
@@ -52,30 +55,38 @@ public class MicroAlertController extends AlertController {
contentPanel.removeView(mMessageView);
if (mListView != null) {
// has ListView, swap ScrollView with ListView
// has ListView, swap scrollView with ListView
// move topPanel into header of ListView
// move topPanel into top of scrollParent
View topPanel = mScrollView.findViewById(R.id.topPanel);
((ViewGroup) topPanel.getParent()).removeView(topPanel);
topPanel.setLayoutParams(
new AbsListView.LayoutParams(topPanel.getLayoutParams()));
mListView.addHeaderView(topPanel, null, false);
FrameLayout.LayoutParams topParams =
new FrameLayout.LayoutParams(topPanel.getLayoutParams());
topParams.gravity = Gravity.TOP;
topPanel.setLayoutParams(topParams);
// move buttonPanel into footer of ListView
// move buttonPanel into bottom of scrollParent
View buttonPanel = mScrollView.findViewById(R.id.buttonPanel);
((ViewGroup) buttonPanel.getParent()).removeView(buttonPanel);
buttonPanel.setLayoutParams(
new AbsListView.LayoutParams(buttonPanel.getLayoutParams()));
mListView.addFooterView(buttonPanel, null, false);
FrameLayout.LayoutParams buttonParams =
new FrameLayout.LayoutParams(buttonPanel.getLayoutParams());
buttonParams.gravity = Gravity.BOTTOM;
buttonPanel.setLayoutParams(buttonParams);
// swap ScrollView w/ ListView
// remove scrollview
final ViewGroup scrollParent = (ViewGroup) mScrollView.getParent();
final int childIndex = scrollParent.indexOfChild(mScrollView);
scrollParent.removeViewAt(childIndex);
scrollParent.addView(mListView, childIndex,
// add list view
scrollParent.addView(mListView,
new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
// add top and button panel
scrollParent.addView(topPanel);
scrollParent.addView(buttonPanel);
} else {
// no content, just hide everything
contentPanel.setVisibility(View.GONE);

View File

@@ -0,0 +1,327 @@
/*
* 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 com.android.internal.widget;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.ListView;
import android.widget.FrameLayout;
import java.util.ArrayList;
/**
* Layout for the decor for ListViews on watch-type devices with small screens.
* <p>
* Supports one panel with the gravity set to top, and one panel with gravity set to bottom.
* <p>
* Use with one ListView child. The top and bottom panels will track the ListView's scrolling.
* If there is no ListView child, it will act like a normal FrameLayout.
*/
public class WatchListDecorLayout extends FrameLayout
implements ViewTreeObserver.OnScrollChangedListener {
private int mForegroundPaddingLeft = 0;
private int mForegroundPaddingTop = 0;
private int mForegroundPaddingRight = 0;
private int mForegroundPaddingBottom = 0;
private final ArrayList<View> mMatchParentChildren = new ArrayList<>(1);
/** Track the amount the ListView has to scroll up to account for padding change difference. */
private int mPendingScroll;
private View mBottomPanel;
private View mTopPanel;
private ListView mListView;
private ViewTreeObserver mObserver;
public WatchListDecorLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public WatchListDecorLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public WatchListDecorLayout(
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mPendingScroll = 0;
for (int i = 0; i < getChildCount(); ++i) {
View child = getChildAt(i);
if (child instanceof ListView) {
if (mListView != null) {
throw new IllegalArgumentException("only one ListView child allowed");
}
mListView = (ListView) child;
mListView.setNestedScrollingEnabled(true);
mObserver = mListView.getViewTreeObserver();
mObserver.addOnScrollChangedListener(this);
} else {
int gravity = (((LayoutParams) child.getLayoutParams()).gravity
& Gravity.VERTICAL_GRAVITY_MASK);
if (gravity == Gravity.TOP && mTopPanel == null) {
mTopPanel = child;
} else if (gravity == Gravity.BOTTOM && mBottomPanel == null) {
mBottomPanel = child;
}
}
}
}
@Override
public void onDetachedFromWindow() {
mListView = null;
mBottomPanel = null;
mTopPanel = null;
if (mObserver != null) {
if (mObserver.isAlive()) {
mObserver.removeOnScrollChangedListener(this);
}
mObserver = null;
}
}
private void applyMeasureToChild(View child, int widthMeasureSpec, int heightMeasureSpec) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}
final int childHeightMeasureSpec;
if (lp.height == LayoutParams.MATCH_PARENT) {
final int height = Math.max(0, getMeasuredHeight()
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
- lp.topMargin - lp.bottomMargin);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
height, MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height);
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
private int measureAndGetHeight(View child, int widthMeasureSpec, int heightMeasureSpec) {
if (child != null) {
if (child.getVisibility() != GONE) {
applyMeasureToChild(mBottomPanel, widthMeasureSpec, heightMeasureSpec);
return child.getMeasuredHeight();
} else if (getMeasureAllChildren()) {
applyMeasureToChild(mBottomPanel, widthMeasureSpec, heightMeasureSpec);
}
}
return 0;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (getMeasureAllChildren() || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
// Account for padding too
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Check against our foreground's minimum height and width
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
if (mListView != null) {
if (mPendingScroll != 0) {
mListView.scrollListBy(mPendingScroll);
mPendingScroll = 0;
}
int paddingTop = Math.max(mListView.getPaddingTop(),
measureAndGetHeight(mTopPanel, widthMeasureSpec, heightMeasureSpec));
int paddingBottom = Math.max(mListView.getPaddingBottom(),
measureAndGetHeight(mBottomPanel, widthMeasureSpec, heightMeasureSpec));
if (paddingTop != mListView.getPaddingTop()
|| paddingBottom != mListView.getPaddingBottom()) {
mPendingScroll += mListView.getPaddingTop() - paddingTop;
mListView.setPadding(
mListView.getPaddingLeft(), paddingTop,
mListView.getPaddingRight(), paddingBottom);
}
}
count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
if (mListView == null || (child != mTopPanel && child != mBottomPanel)) {
applyMeasureToChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
}
@Override
public void setForegroundGravity(int foregroundGravity) {
if (getForegroundGravity() != foregroundGravity) {
super.setForegroundGravity(foregroundGravity);
// calling get* again here because the set above may apply default constraints
final Drawable foreground = getForeground();
if (getForegroundGravity() == Gravity.FILL && foreground != null) {
Rect padding = new Rect();
if (foreground.getPadding(padding)) {
mForegroundPaddingLeft = padding.left;
mForegroundPaddingTop = padding.top;
mForegroundPaddingRight = padding.right;
mForegroundPaddingBottom = padding.bottom;
}
} else {
mForegroundPaddingLeft = 0;
mForegroundPaddingTop = 0;
mForegroundPaddingRight = 0;
mForegroundPaddingBottom = 0;
}
}
}
private int getPaddingLeftWithForeground() {
return isForegroundInsidePadding() ? Math.max(mPaddingLeft, mForegroundPaddingLeft) :
mPaddingLeft + mForegroundPaddingLeft;
}
private int getPaddingRightWithForeground() {
return isForegroundInsidePadding() ? Math.max(mPaddingRight, mForegroundPaddingRight) :
mPaddingRight + mForegroundPaddingRight;
}
private int getPaddingTopWithForeground() {
return isForegroundInsidePadding() ? Math.max(mPaddingTop, mForegroundPaddingTop) :
mPaddingTop + mForegroundPaddingTop;
}
private int getPaddingBottomWithForeground() {
return isForegroundInsidePadding() ? Math.max(mPaddingBottom, mForegroundPaddingBottom) :
mPaddingBottom + mForegroundPaddingBottom;
}
@Override
public void onScrollChanged() {
if (mListView == null) {
return;
}
if (mTopPanel != null) {
if (mListView.getChildCount() > 0) {
if (mListView.getFirstVisiblePosition() == 0) {
View firstChild = mListView.getChildAt(0);
setScrolling(mTopPanel,
firstChild.getY() - mTopPanel.getHeight() - mTopPanel.getTop());
} else {
// shift to hide the frame, last child is not the last position
setScrolling(mTopPanel, -mTopPanel.getHeight());
}
} else {
setScrolling(mTopPanel, 0); // no visible child, fallback to default behaviour
}
}
if (mBottomPanel != null) {
if (mListView.getChildCount() > 0) {
if (mListView.getLastVisiblePosition() >= mListView.getCount() - 1) {
View lastChild = mListView.getChildAt(mListView.getChildCount() - 1);
setScrolling(mBottomPanel,
lastChild.getY() + lastChild.getHeight() - mBottomPanel.getTop());
} else {
// shift to hide the frame, last child is not the last position
setScrolling(mBottomPanel, mBottomPanel.getHeight());
}
} else {
setScrolling(mBottomPanel, 0); // no visible child, fallback to default behaviour
}
}
}
/** Only set scrolling for the panel if there is a change in its translationY. */
private void setScrolling(View panel, float translationY) {
if (panel.getTranslationY() != translationY) {
panel.setTranslationY(translationY);
}
}
}

View File

@@ -14,7 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
<FrameLayout
<com.android.internal.widget.WatchListDecorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/parentPanel"
android:layout_width="match_parent"
@@ -104,4 +104,4 @@
</FrameLayout>
</LinearLayout>
</ScrollView>
</FrameLayout>
</com.android.internal.widget.WatchListDecorLayout>