1 /* 2 * Copyright (C) 2021 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.systemui.util 18 19 import android.content.Context 20 import android.content.res.Configuration 21 import android.util.AttributeSet 22 import android.util.DisplayMetrics 23 import android.util.TypedValue 24 import android.widget.LinearLayout 25 import android.widget.TextView 26 import com.android.systemui.R 27 28 /** 29 * Horizontal [LinearLayout] to contain some text. 30 * 31 * The height of this container can alternate between two different heights, depending on whether 32 * the text takes one line or more. 33 * 34 * When the text takes multiple lines, it will use the values in the regular attributes (`padding`, 35 * `layout_height`). The single line behavior must be set in XML. 36 * 37 * XML attributes for single line behavior: 38 * * `systemui:textViewId`: set the id for the [TextView] that determines the height of the 39 * container 40 * * `systemui:singleLineHeight`: sets the height of the view when the text takes up only one line. 41 * By default, it will use [getMinimumHeight]. 42 * * `systemui:singleLineVerticalPadding`: sets the padding (top and bottom) when then text takes up 43 * only one line. By default, it is 0. 44 * 45 * All dimensions are updated when configuration changes. 46 */ 47 class DualHeightHorizontalLinearLayout @JvmOverloads constructor( 48 context: Context, 49 attrs: AttributeSet? = null, 50 defStyleAttrs: Int = 0, 51 defStyleRes: Int = 0 52 ) : LinearLayout(context, attrs, defStyleAttrs, defStyleRes) { 53 54 private val singleLineHeightValue: TypedValue? 55 private var singleLineHeightPx = 0 56 57 private val singleLineVerticalPaddingValue: TypedValue? 58 private var singleLineVerticalPaddingPx = 0 59 60 private val textViewId: Int 61 private var textView: TextView? = null 62 63 private val displayMetrics: DisplayMetrics 64 get() = context.resources.displayMetrics 65 66 private var initialPadding = mPaddingTop // All vertical padding is the same 67 68 init { 69 if (orientation != HORIZONTAL) { 70 throw IllegalStateException("This view should always have horizontal orientation") 71 } 72 73 val ta = context.obtainStyledAttributes( 74 attrs, 75 R.styleable.DualHeightHorizontalLinearLayout, defStyleAttrs, defStyleRes 76 ) 77 78 val tempHeight = TypedValue() 79 singleLineHeightValue = if ( 80 ta.hasValue(R.styleable.DualHeightHorizontalLinearLayout_singleLineHeight) 81 ) { 82 ta.getValue(R.styleable.DualHeightHorizontalLinearLayout_singleLineHeight, tempHeight) 83 tempHeight 84 } else { 85 null 86 } 87 88 val tempPadding = TypedValue() 89 singleLineVerticalPaddingValue = if ( 90 ta.hasValue(R.styleable.DualHeightHorizontalLinearLayout_singleLineVerticalPadding) 91 ) { 92 ta.getValue( 93 R.styleable.DualHeightHorizontalLinearLayout_singleLineVerticalPadding, 94 tempPadding 95 ) 96 tempPadding 97 } else { 98 null 99 } 100 101 textViewId = ta.getResourceId(R.styleable.DualHeightHorizontalLinearLayout_textViewId, 0) 102 103 ta.recycle() 104 } 105 106 init { 107 updateResources() 108 } 109 110 override fun setPadding(left: Int, top: Int, right: Int, bottom: Int) { 111 super.setPadding(left, top, right, bottom) 112 initialPadding = top 113 } 114 115 override fun setPaddingRelative(start: Int, top: Int, end: Int, bottom: Int) { 116 super.setPaddingRelative(start, top, end, bottom) 117 initialPadding = top 118 } 119 120 override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 121 super.onMeasure(widthMeasureSpec, heightMeasureSpec) 122 textView?.let { tv -> 123 if (tv.lineCount < 2) { 124 setMeasuredDimension(measuredWidth, singleLineHeightPx) 125 mPaddingBottom = 0 126 mPaddingTop = 0 127 } else { 128 mPaddingBottom = initialPadding 129 mPaddingTop = initialPadding 130 } 131 } 132 } 133 134 override fun onFinishInflate() { 135 super.onFinishInflate() 136 textView = findViewById(textViewId) 137 } 138 139 override fun onConfigurationChanged(newConfig: Configuration?) { 140 super.onConfigurationChanged(newConfig) 141 updateResources() 142 } 143 144 override fun setOrientation(orientation: Int) { 145 if (orientation == VERTICAL) { 146 throw IllegalStateException("This view should always have horizontal orientation") 147 } 148 super.setOrientation(orientation) 149 } 150 151 private fun updateResources() { 152 updateDimensionValue(singleLineHeightValue, minimumHeight, ::singleLineHeightPx::set) 153 updateDimensionValue(singleLineVerticalPaddingValue, 0, ::singleLineVerticalPaddingPx::set) 154 } 155 156 private inline fun updateDimensionValue( 157 tv: TypedValue?, 158 defaultValue: Int, 159 propertySetter: (Int) -> Unit 160 ) { 161 val value = tv?.let { 162 if (it.resourceId != 0) { 163 context.resources.getDimensionPixelSize(it.resourceId) 164 } else { 165 it.getDimension(displayMetrics).toInt() 166 } 167 } ?: defaultValue 168 propertySetter(value) 169 } 170 }