/* * Copyright (C) 2020 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.deskclock.widget import android.content.Context import android.util.AttributeSet import android.view.View import android.widget.LinearLayout import android.widget.TextView /** * When this layout is in the Horizontal orientation and one and only one child is a TextView with a * non-null android:ellipsize, this layout will reduce android:maxWidth of that TextView to ensure * the siblings are not truncated. This class is useful when that ellipsize-text-view "starts" * before other children of this view group. This layout has no effect if: * * * The purpose of this horizontal-linear-layout is to ensure that when the sum of widths of the * children are greater than this parent, the maximum width of the ellipsize-text-view, is reduced * so that no siblings are truncated. * * * For example: Given Text1 has android:ellipsize="end" and Text2 has android:ellipsize="none", * as Text1 and/or Text2 grow in width, both will consume more width until Text2 hits the end * margin, then Text1 will cease to grow and instead shrink to accommodate any further growth in * Text2. * */ class EllipsizeLayout @JvmOverloads constructor( context: Context?, attrs: AttributeSet? = null ) : LinearLayout(context, attrs) { /** * This override only acts when the LinearLayout is in the Horizontal orientation and is in it's * final measurement pass(MeasureSpec.EXACTLY). In this case only, this class * * * Identifies the one TextView child with the non-null android:ellipsize. * * Re-measures the needed width of all children (by calling measureChildWithMargins with * the width measure specification to MeasureSpec.UNSPECIFIED.) * * Sums the children's widths. * * Whenever the sum of the children's widths is greater than this parent was allocated, * the maximum width of the one TextView child with the non-null android:ellipsize is * reduced. * * * @param widthMeasureSpec horizontal space requirements as imposed by the parent * @param heightMeasureSpec vertical space requirements as imposed by the parent */ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { if (orientation == HORIZONTAL && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) { var totalLength = 0 // If any of the constraints of this class are exceeded, outOfSpec becomes true // and the no alterations are made to the ellipsize-text-view. var outOfSpec = false var ellipsizeView: TextView? = null val count = childCount val parentWidth = MeasureSpec.getSize(widthMeasureSpec) val queryWidthMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.UNSPECIFIED) var ii = 0 while (ii < count && !outOfSpec) { val child = getChildAt(ii) if (child != null && child.visibility != View.GONE) { // Identify the ellipsize view if (child is TextView) { val tv = child if (tv.ellipsize != null) { if (ellipsizeView == null) { ellipsizeView = tv // Clear the maximum width on ellipsizeView before measurement ellipsizeView.maxWidth = Int.MAX_VALUE } else { // TODO: support multiple android:ellipsize outOfSpec = true } } } // Ask the child to measure itself measureChildWithMargins(child, queryWidthMeasureSpec, 0, heightMeasureSpec, 0) // Get the layout parameters to check for a weighted width and to add the // child's margins to the total length. val layoutParams = child.layoutParams as LayoutParams? if (layoutParams != null) { outOfSpec = outOfSpec or (layoutParams.weight > 0f) totalLength += (child.measuredWidth + layoutParams.leftMargin + layoutParams.rightMargin) } else { outOfSpec = true } } ++ii } // Last constraint test outOfSpec = outOfSpec or (ellipsizeView == null || totalLength == 0) if (!outOfSpec && totalLength > parentWidth) { var maxWidth = ellipsizeView!!.measuredWidth - (totalLength - parentWidth) // TODO: Respect android:minWidth (easy with @TargetApi(16)) val minWidth = 0 if (maxWidth < minWidth) { maxWidth = minWidth } ellipsizeView.maxWidth = maxWidth } } super.onMeasure(widthMeasureSpec, heightMeasureSpec) } }