1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 
17 package com.android.deskclock.stopwatch;
18 
19 import android.content.Context;
20 import android.util.AttributeSet;
21 import android.view.View;
22 import android.view.ViewGroup;
23 
24 import com.android.deskclock.R;
25 
26 import static android.view.View.MeasureSpec.AT_MOST;
27 import static android.view.View.MeasureSpec.EXACTLY;
28 import static android.view.View.MeasureSpec.UNSPECIFIED;
29 
30 /**
31  * Dynamically apportions size the stopwatch circle depending on the preferred width of the laps
32  * list and the container size. Layouts fall into two different buckets:
33  *
34  * When the width of the laps list is less than half the container width, the laps list and
35  * stopwatch display are each centered within half the container.
36  * <pre>
37  *     ---------------------------------------------------------------------------
38  *     |                                    |               Lap 5                |
39  *     |                                    |               Lap 4                |
40  *     |             21:45.67               |               Lap 3                |
41  *     |                                    |               Lap 2                |
42  *     |                                    |               Lap 1                |
43  *     ---------------------------------------------------------------------------
44  * </pre>
45  *
46  * When the width of the laps list is greater than half the container width, the laps list is
47  * granted all of the space it requires and the stopwatch display is centered within the remaining
48  * container width.
49  * <pre>
50  *     ---------------------------------------------------------------------------
51  *     |               |                          Lap 5                          |
52  *     |               |                          Lap 4                          |
53  *     |   21:45.67    |                          Lap 3                          |
54  *     |               |                          Lap 2                          |
55  *     |               |                          Lap 1                          |
56  *     ---------------------------------------------------------------------------
57  * </pre>
58  */
59 public class StopwatchLandscapeLayout extends ViewGroup {
60 
61     private View mLapsListView;
62     private View mStopwatchView;
63 
StopwatchLandscapeLayout(Context context)64     public StopwatchLandscapeLayout(Context context) {
65         super(context);
66     }
67 
StopwatchLandscapeLayout(Context context, AttributeSet attrs)68     public StopwatchLandscapeLayout(Context context, AttributeSet attrs) {
69         super(context, attrs);
70     }
71 
StopwatchLandscapeLayout(Context context, AttributeSet attrs, int defStyleAttr)72     public StopwatchLandscapeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
73         super(context, attrs, defStyleAttr);
74     }
75 
76     @Override
onFinishInflate()77     protected void onFinishInflate() {
78         super.onFinishInflate();
79 
80         mLapsListView = findViewById(R.id.laps_list);
81         mStopwatchView = findViewById(R.id.stopwatch_time_wrapper);
82     }
83 
84     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)85     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
86         final int height = MeasureSpec.getSize(heightMeasureSpec);
87         final int width = MeasureSpec.getSize(widthMeasureSpec);
88         final int halfWidth = width / 2;
89 
90         final int minWidthSpec = MeasureSpec.makeMeasureSpec(width, UNSPECIFIED);
91         final int maxHeightSpec = MeasureSpec.makeMeasureSpec(height, AT_MOST);
92 
93         // First determine the width of the laps list.
94         final int lapsListWidth;
95         if (mLapsListView != null && mLapsListView.getVisibility() != GONE) {
96             // Measure the intrinsic size of the laps list.
97             mLapsListView.measure(minWidthSpec, maxHeightSpec);
98 
99             // Actual laps list width is the larger of half the container and its intrinsic width.
100             lapsListWidth = Math.max(mLapsListView.getMeasuredWidth(), halfWidth);
101             final int lapsListWidthSpec = MeasureSpec.makeMeasureSpec(lapsListWidth, EXACTLY);
102             mLapsListView.measure(lapsListWidthSpec, maxHeightSpec);
103         } else {
104             lapsListWidth = 0;
105         }
106 
107         // Stopwatch timer consumes the remaining width of container not granted to laps list.
108         final int stopwatchWidth = width - lapsListWidth;
109         final int stopwatchWidthSpec = MeasureSpec.makeMeasureSpec(stopwatchWidth, EXACTLY);
110         mStopwatchView.measure(stopwatchWidthSpec, maxHeightSpec);
111 
112         // Record the measured size of this container.
113         setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
114     }
115 
116     @Override
onLayout(boolean changed, int l, int t, int r, int b)117     protected void onLayout(boolean changed, int l, int t, int r, int b) {
118         // Compute the space available for layout.
119         final int left = getPaddingLeft();
120         final int top = getPaddingTop();
121         final int right = getWidth() - getPaddingRight();
122         final int bottom = getHeight() - getPaddingBottom();
123         final int width = right - left;
124         final int height = bottom - top;
125         final int halfHeight = height / 2;
126         final boolean isLTR = getLayoutDirection() == LAYOUT_DIRECTION_LTR;
127 
128         final int lapsListWidth;
129         if (mLapsListView != null && mLapsListView.getVisibility() != GONE) {
130             // Layout the laps list, centering it vertically.
131             lapsListWidth = mLapsListView.getMeasuredWidth();
132             final int lapsListHeight = mLapsListView.getMeasuredHeight();
133             final int lapsListTop = top + halfHeight - (lapsListHeight / 2);
134             final int lapsListBottom = lapsListTop + lapsListHeight;
135             final int lapsListLeft;
136             final int lapsListRight;
137             if (isLTR) {
138                 lapsListLeft = right - lapsListWidth;
139                 lapsListRight = right;
140             } else {
141                 lapsListLeft = left;
142                 lapsListRight = left + lapsListWidth;
143             }
144 
145             mLapsListView.layout(lapsListLeft, lapsListTop, lapsListRight, lapsListBottom);
146         } else {
147             lapsListWidth = 0;
148         }
149 
150         // Layout the stopwatch, centering it horizontally and vertically.
151         final int stopwatchWidth = mStopwatchView.getMeasuredWidth();
152         final int stopwatchHeight = mStopwatchView.getMeasuredHeight();
153         final int stopwatchTop = top + halfHeight - (stopwatchHeight / 2);
154         final int stopwatchBottom = stopwatchTop + stopwatchHeight;
155         final int stopwatchLeft;
156         final int stopwatchRight;
157         if (isLTR) {
158             stopwatchLeft = left + ((width - lapsListWidth - stopwatchWidth) / 2);
159             stopwatchRight = stopwatchLeft + stopwatchWidth;
160         } else {
161             stopwatchRight = right - ((width - lapsListWidth - stopwatchWidth) / 2);
162             stopwatchLeft = stopwatchRight - stopwatchWidth;
163         }
164 
165         mStopwatchView.layout(stopwatchLeft, stopwatchTop, stopwatchRight, stopwatchBottom);
166     }
167 }