最近项目设计的一个要求:TextView右面紧接着ImageView,并且当TextView的文字太长,使用ellipsize的单行效果,右面显示的图片不变。使用现有的布局,无论是线性布局、相对布局还是约束布局,都无法满足要求,于是想到自定义布局,代码如下:
/**
* Like a linearLayout but make child A with id==flexible_id flexible, that means,
* if there're other children after it and the A is large enough to fill parent,
* A's dimension will be changed to make sure other children can be seen;
* else it will perform just like a LinearLayout.
* Must declare 'flexible_id' in namespace <declare-styleable></declare-styleable>
*/
public class FlexLayout extends LinearLayout {
public static String flexableId = "flexible_id";//attr name, 在本布局标签下的自定义属性名
private int flexId;//自定义标签引用的子view的id
private boolean isHorizontal;
private List<View> otherChildList;//除了对应的flexableId之外的子view
public FlexLayout(Context context) {
this(context, null, 0);
}
public FlexLayout(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public FlexLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
if (attrs != null) {
flexId = attrs.getAttributeResourceValue("http://schemas.android.com/apk/res-auto", flexableId, 0);
isHorizontal = getOrientation() == HORIZONTAL;
if (flexId != 0) {
otherChildList = new ArrayList<>();
}
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (otherChildList == null) {
return;
}
int count = getChildCount();
int totalSize = 0;//所有子view的宽度(或高度,取决于布局方向)之和
View flexView = null;//需要调整的子view
otherChildList.clear();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (flexId == child.getId()) {
flexView = child;
} else {
otherChildList.add(child);
}
LinearLayout.LayoutParams params = (LayoutParams) child.getLayoutParams();
if (isHorizontal) {
totalSize += child.getMeasuredWidth() + params.leftMargin + params.rightMargin;
} else {
totalSize += child.getMeasuredHeight() + params.topMargin + params.bottomMargin;
}
}
if (flexView != null) {
if (isHorizontal) {
int dw = r - l - totalSize;
if (dw < 0) {//宽度之和超过布局的宽度,需要缩减flexView的宽度
flexView.layout(flexView.getLeft(), flexView.getTop(), flexView.getRight() + dw, flexView.getBottom());
if (flexView instanceof TextView) {//重设内容,否则文字省略效果可能有问题
TextView tv = (TextView) flexView;
tv.setText(tv.getText());
}
for (View v : otherChildList) {//其余子view需要向左平移
v.layout(v.getLeft() + dw, v.getTop(), v.getRight() + dw, v.getBottom());
}
}
} else {
int dh = b - t - totalSize;
if (dh < 0) {
flexView.layout(flexView.getLeft(), flexView.getTop(), flexView.getRight(), flexView.getBottom() + dh);
for (View v : otherChildList) {
v.layout(v.getLeft(), v.getTop() + dh, v.getRight(), v.getBottom() + dh);
}
}
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
//测量子控件的宽高,否则child.getMeasuredWidth()返回值为0
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
自定义的属性需要在values目录下新建xml资源文件,如attr.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="FlexLayout">
<attr name="flexible_id" format="reference"/>
</declare-styleable>
</resources>
在FlexLayout标签下需添加属性
app:flexible_id="@id/xxx
附:后来发现 com.google.android.flexbox.FlexboxLayout 可达到此效果