2 * Copyright (C) 2016 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.android.internal.widget;
18 import android.content.Context;
19 import android.graphics.drawable.Drawable;
20 import android.graphics.Rect;
21 import android.util.AttributeSet;
22 import android.view.Gravity;
23 import android.view.View;
24 import android.view.ViewTreeObserver;
25 import android.widget.ListView;
26 import android.widget.FrameLayout;
28 import java.util.ArrayList;
32 * Layout for the decor for ListViews on watch-type devices with small screens.
34 * Supports one panel with the gravity set to top, and one panel with gravity set to bottom.
36 * Use with one ListView child. The top and bottom panels will track the ListView's scrolling.
37 * If there is no ListView child, it will act like a normal FrameLayout.
39 public class WatchListDecorLayout extends FrameLayout
40 implements ViewTreeObserver.OnScrollChangedListener {
42 private int mForegroundPaddingLeft = 0;
43 private int mForegroundPaddingTop = 0;
44 private int mForegroundPaddingRight = 0;
45 private int mForegroundPaddingBottom = 0;
47 private final ArrayList<View> mMatchParentChildren = new ArrayList<>(1);
49 /** Track the amount the ListView has to scroll up to account for padding change difference. */
50 private int mPendingScroll;
51 private View mBottomPanel;
52 private View mTopPanel;
53 private ListView mListView;
54 private ViewTreeObserver mObserver;
57 public WatchListDecorLayout(Context context, AttributeSet attrs) {
58 super(context, attrs);
61 public WatchListDecorLayout(Context context, AttributeSet attrs, int defStyleAttr) {
62 super(context, attrs, defStyleAttr);
65 public WatchListDecorLayout(
66 Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
67 super(context, attrs, defStyleAttr, defStyleRes);
71 protected void onAttachedToWindow() {
72 super.onAttachedToWindow();
76 for (int i = 0; i < getChildCount(); ++i) {
77 View child = getChildAt(i);
78 if (child instanceof ListView) {
79 if (mListView != null) {
80 throw new IllegalArgumentException("only one ListView child allowed");
82 mListView = (ListView) child;
84 mListView.setNestedScrollingEnabled(true);
85 mObserver = mListView.getViewTreeObserver();
86 mObserver.addOnScrollChangedListener(this);
88 int gravity = (((LayoutParams) child.getLayoutParams()).gravity
89 & Gravity.VERTICAL_GRAVITY_MASK);
90 if (gravity == Gravity.TOP && mTopPanel == null) {
92 } else if (gravity == Gravity.BOTTOM && mBottomPanel == null) {
100 public void onDetachedFromWindow() {
104 if (mObserver != null) {
105 if (mObserver.isAlive()) {
106 mObserver.removeOnScrollChangedListener(this);
112 private void applyMeasureToChild(View child, int widthMeasureSpec, int heightMeasureSpec) {
113 final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
115 final int childWidthMeasureSpec;
116 if (lp.width == LayoutParams.MATCH_PARENT) {
117 final int width = Math.max(0, getMeasuredWidth()
118 - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
119 - lp.leftMargin - lp.rightMargin);
120 childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
121 width, MeasureSpec.EXACTLY);
123 childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
124 getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
125 lp.leftMargin + lp.rightMargin,
129 final int childHeightMeasureSpec;
130 if (lp.height == LayoutParams.MATCH_PARENT) {
131 final int height = Math.max(0, getMeasuredHeight()
132 - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
133 - lp.topMargin - lp.bottomMargin);
134 childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
135 height, MeasureSpec.EXACTLY);
137 childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
138 getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
139 lp.topMargin + lp.bottomMargin,
143 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
146 private int measureAndGetHeight(View child, int widthMeasureSpec, int heightMeasureSpec) {
148 if (child.getVisibility() != GONE) {
149 applyMeasureToChild(mBottomPanel, widthMeasureSpec, heightMeasureSpec);
150 return child.getMeasuredHeight();
151 } else if (getMeasureAllChildren()) {
152 applyMeasureToChild(mBottomPanel, widthMeasureSpec, heightMeasureSpec);
159 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
160 int count = getChildCount();
162 final boolean measureMatchParentChildren =
163 MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
164 MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
165 mMatchParentChildren.clear();
171 for (int i = 0; i < count; i++) {
172 final View child = getChildAt(i);
173 if (getMeasureAllChildren() || child.getVisibility() != GONE) {
174 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
175 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
176 maxWidth = Math.max(maxWidth,
177 child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
178 maxHeight = Math.max(maxHeight,
179 child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
180 childState = combineMeasuredStates(childState, child.getMeasuredState());
181 if (measureMatchParentChildren) {
182 if (lp.width == LayoutParams.MATCH_PARENT ||
183 lp.height == LayoutParams.MATCH_PARENT) {
184 mMatchParentChildren.add(child);
190 // Account for padding too
191 maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
192 maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
194 // Check against our minimum height and width
195 maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
196 maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
198 // Check against our foreground's minimum height and width
199 final Drawable drawable = getForeground();
200 if (drawable != null) {
201 maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
202 maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
205 setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
206 resolveSizeAndState(maxHeight, heightMeasureSpec,
207 childState << MEASURED_HEIGHT_STATE_SHIFT));
209 if (mListView != null) {
210 if (mPendingScroll != 0) {
211 mListView.scrollListBy(mPendingScroll);
215 int paddingTop = Math.max(mListView.getPaddingTop(),
216 measureAndGetHeight(mTopPanel, widthMeasureSpec, heightMeasureSpec));
217 int paddingBottom = Math.max(mListView.getPaddingBottom(),
218 measureAndGetHeight(mBottomPanel, widthMeasureSpec, heightMeasureSpec));
220 if (paddingTop != mListView.getPaddingTop()
221 || paddingBottom != mListView.getPaddingBottom()) {
222 mPendingScroll += mListView.getPaddingTop() - paddingTop;
223 mListView.setPadding(
224 mListView.getPaddingLeft(), paddingTop,
225 mListView.getPaddingRight(), paddingBottom);
229 count = mMatchParentChildren.size();
231 for (int i = 0; i < count; i++) {
232 final View child = mMatchParentChildren.get(i);
233 if (mListView == null || (child != mTopPanel && child != mBottomPanel)) {
234 applyMeasureToChild(child, widthMeasureSpec, heightMeasureSpec);
241 public void setForegroundGravity(int foregroundGravity) {
242 if (getForegroundGravity() != foregroundGravity) {
243 super.setForegroundGravity(foregroundGravity);
245 // calling get* again here because the set above may apply default constraints
246 final Drawable foreground = getForeground();
247 if (getForegroundGravity() == Gravity.FILL && foreground != null) {
248 Rect padding = new Rect();
249 if (foreground.getPadding(padding)) {
250 mForegroundPaddingLeft = padding.left;
251 mForegroundPaddingTop = padding.top;
252 mForegroundPaddingRight = padding.right;
253 mForegroundPaddingBottom = padding.bottom;
256 mForegroundPaddingLeft = 0;
257 mForegroundPaddingTop = 0;
258 mForegroundPaddingRight = 0;
259 mForegroundPaddingBottom = 0;
264 private int getPaddingLeftWithForeground() {
265 return isForegroundInsidePadding() ? Math.max(mPaddingLeft, mForegroundPaddingLeft) :
266 mPaddingLeft + mForegroundPaddingLeft;
269 private int getPaddingRightWithForeground() {
270 return isForegroundInsidePadding() ? Math.max(mPaddingRight, mForegroundPaddingRight) :
271 mPaddingRight + mForegroundPaddingRight;
274 private int getPaddingTopWithForeground() {
275 return isForegroundInsidePadding() ? Math.max(mPaddingTop, mForegroundPaddingTop) :
276 mPaddingTop + mForegroundPaddingTop;
279 private int getPaddingBottomWithForeground() {
280 return isForegroundInsidePadding() ? Math.max(mPaddingBottom, mForegroundPaddingBottom) :
281 mPaddingBottom + mForegroundPaddingBottom;
285 public void onScrollChanged() {
286 if (mListView == null) {
290 if (mTopPanel != null) {
291 if (mListView.getChildCount() > 0) {
292 if (mListView.getFirstVisiblePosition() == 0) {
293 View firstChild = mListView.getChildAt(0);
294 setScrolling(mTopPanel,
295 firstChild.getY() - mTopPanel.getHeight() - mTopPanel.getTop());
297 // shift to hide the frame, last child is not the last position
298 setScrolling(mTopPanel, -mTopPanel.getHeight());
301 setScrolling(mTopPanel, 0); // no visible child, fallback to default behaviour
305 if (mBottomPanel != null) {
306 if (mListView.getChildCount() > 0) {
307 if (mListView.getLastVisiblePosition() >= mListView.getCount() - 1) {
308 View lastChild = mListView.getChildAt(mListView.getChildCount() - 1);
309 setScrolling(mBottomPanel,
310 lastChild.getY() + lastChild.getHeight() - mBottomPanel.getTop());
312 // shift to hide the frame, last child is not the last position
313 setScrolling(mBottomPanel, mBottomPanel.getHeight());
316 setScrolling(mBottomPanel, 0); // no visible child, fallback to default behaviour
321 /** Only set scrolling for the panel if there is a change in its translationY. */
322 private void setScrolling(View panel, float translationY) {
323 if (panel.getTranslationY() != translationY) {
324 panel.setTranslationY(translationY);