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