blob: 3d314770f181eea84276c187fa86051546521257 [file] [log] [blame]
/*
* 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:
* <ul>
* <li>the orientation is not horizontal</li>
* <li>any child has weights.</li>
* <li>more than one child has a non-null android:ellipsize.</li>
* </ul>
*
* 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.
* <ul>
* <li>|[text1]|[text2] |</li>
* <li>|[text1 text1]|[text2 text2] |</li>
* <li>|[text... ]|[text2 text2 text2]|</li>
* </ul>
*/
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)
}
}