子view宽度可变的自定义LinearLayout例子

最近项目设计的一个要求: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 可达到此效果

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您可以创建一个自定义LinearLayout并覆盖onMeasure方法来实现宽度可变的效果。在这个方法中,您可以通过调整视图的大小和位置来改变LinearLayout的大小。 以下是一个示例代码: ```java public class CustomLinearLayout extends LinearLayout { private int mMaxWidth = Integer.MAX_VALUE; // 默认最大宽度 public CustomLinearLayout(Context context) { super(context); } public CustomLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); } public void setMaxWidth(int maxWidth) { mMaxWidth = maxWidth; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int measuredWidth = 0, measuredHeight = 0, childWidth, childHeight; int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); // 测量视图 measureChildren(widthMeasureSpec, heightMeasureSpec); // 计算视图占用的空间 int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams(); childWidth = child.getMeasuredWidth() + params.leftMargin + params.rightMargin; childHeight = child.getMeasuredHeight() + params.topMargin + params.bottomMargin; measuredWidth += childWidth; measuredHeight = Math.max(measuredHeight, childHeight); } // 调整LinearLayout的大小 if (widthMode == MeasureSpec.EXACTLY) { measuredWidth = Math.min(measuredWidth, MeasureSpec.getSize(widthMeasureSpec)); } measuredWidth = Math.min(measuredWidth, mMaxWidth); setMeasuredDimension(measuredWidth, measuredHeight); } } ``` 您可以在布局文件中使用CustomLinearLayout并为其设置最大宽度: ```xml <com.example.CustomLinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" app:maxWidth="500dp"> <!-- 添加视图 --> </com.example.CustomLinearLayout> ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值