181 lines
6.7 KiB
Java
181 lines
6.7 KiB
Java
/*
|
|
* 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.ArrayList;
|
|
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 ArrayList<>(count);
|
|
}
|
|
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();
|
|
}
|
|
}
|
|
}
|