1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
|
/*
* Copyright (C) 2021 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.internal.widget;
import android.annotation.Nullable;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.RemoteViews;
import java.util.LinkedList;
import java.util.List;
/**
* This is a subclass of LinearLayout meant to be used in the Conversation header, to fix a bug
* when multiple user-provided strings are shown in the same conversation header. b/189723284
*
* This works around a deficiency in LinearLayout when shrinking views that it can't fully reduce
* all contents if any of the oversized views reaches zero.
*/
@RemoteViews.RemoteView
public class ConversationHeaderLinearLayout extends LinearLayout {
public ConversationHeaderLinearLayout(Context context) {
super(context);
}
public ConversationHeaderLinearLayout(Context context,
@Nullable AttributeSet attrs) {
super(context, attrs);
}
public ConversationHeaderLinearLayout(Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private int calculateTotalChildLength() {
final int count = getChildCount();
int totalLength = 0;
for (int i = 0; i < count; ++i) {
final View child = getChildAt(i);
if (child == null || child.getVisibility() == GONE) {
continue;
}
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
child.getLayoutParams();
totalLength += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
}
return totalLength + getPaddingLeft() + getPaddingRight();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int containerWidth = getMeasuredWidth();
final int contentsWidth = calculateTotalChildLength();
int excessContents = contentsWidth - containerWidth;
if (excessContents <= 0) {
return;
}
final int count = getChildCount();
float remainingWeight = 0;
List<ViewInfo> visibleChildrenToShorten = null;
// Find children which need to be shortened in order to ensure the contents fit.
for (int i = 0; i < count; ++i) {
final View child = getChildAt(i);
if (child == null || child.getVisibility() == View.GONE) {
continue;
}
final float weight = ((LayoutParams) child.getLayoutParams()).weight;
if (weight == 0) {
continue;
}
if (child.getMeasuredWidth() == 0) {
continue;
}
if (visibleChildrenToShorten == null) {
visibleChildrenToShorten = new LinkedList<>();
}
visibleChildrenToShorten.add(new ViewInfo(child));
remainingWeight += Math.max(0, weight);
}
if (visibleChildrenToShorten == null || visibleChildrenToShorten.isEmpty()) {
return;
}
balanceViewWidths(visibleChildrenToShorten, remainingWeight, excessContents);
remeasureChangedChildren(visibleChildrenToShorten);
}
/**
* Measure any child with a width that has changed.
*/
private void remeasureChangedChildren(List<ViewInfo> childrenInfo) {
for (ViewInfo info : childrenInfo) {
if (info.mWidth != info.mStartWidth) {
final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
Math.max(0, info.mWidth), MeasureSpec.EXACTLY);
final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
info.mView.getMeasuredHeight(), MeasureSpec.EXACTLY);
info.mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
/**
* Given a list of view, use the weights to remove width from each view proportionally to the
* weight (and ignoring the view's actual width), but do this iteratively whenever a view is
* reduced to zero width, because in that case other views need reduction.
*/
void balanceViewWidths(List<ViewInfo> viewInfos, float weightSum, int excessContents) {
boolean performAnotherPass = true;
// Loops only when all of the following are true:
// * `performAnotherPass` -- a view clamped to 0 width (or the first iteration)
// * `excessContents > 0` -- there is still horizontal space to allocate
// * `weightSum > 0` -- at least 1 view with nonzero width AND nonzero weight left
while (performAnotherPass && excessContents > 0 && weightSum > 0) {
int excessRemovedDuringThisPass = 0;
float weightSumForNextPass = 0;
performAnotherPass = false;
for (ViewInfo info : viewInfos) {
if (info.mWeight <= 0) {
continue;
}
if (info.mWidth <= 0) {
continue;
}
int newWidth = (int) (info.mWidth - (excessContents * (info.mWeight / weightSum)));
if (newWidth < 0) {
newWidth = 0;
performAnotherPass = true;
}
excessRemovedDuringThisPass += info.mWidth - newWidth;
info.mWidth = newWidth;
if (info.mWidth > 0) {
weightSumForNextPass += info.mWeight;
}
}
excessContents -= excessRemovedDuringThisPass;
weightSum = weightSumForNextPass;
}
}
/**
* A helper class for measuring children.
*/
static class ViewInfo {
final View mView;
final float mWeight;
final int mStartWidth;
int mWidth;
ViewInfo(View view) {
this.mView = view;
this.mWeight = ((LayoutParams) view.getLayoutParams()).weight;
this.mStartWidth = this.mWidth = view.getMeasuredWidth();
}
}
}
|