1. 程式人生 > >Android 自動換行的LinearLayout

Android 自動換行的LinearLayout

omv horizon most align 子控件 多次調用 append getc ins

在我們開發過程中會經常遇見一些客戶要求但是Android系統又不提供的效果,這時我們只能自己動手去實現它,或者從網絡上借鑒他人的資源,本著用別人不如自己會做的心態,在此我總結了一下Android中如何實現自動換行的LinearLayout。

在本文中,說是LinearLayout其實是繼承自GroupView,在這裏主要重寫了兩個方法,onMeasure、onLayout方法,下面我對此加以介紹。(代碼中使用了AttributeSet,由於時間問題不再予以介紹)。

1. onMeasure是幹什麽的?

在ViewGroup的創建過程中,onMeasure是在onLayout之前的,所以在此先對onMeasure進行介紹,onMeasure方法是計算子控件與父控件在屏幕中所占長寬大小的,onMeasure傳入兩個參數——widthMeasureSpec和heightMeasureSpec. 這兩個參數指明控件可獲得的空間以及關於這個空間描述的元數據.

int withMode = MeasureSpec.getMode(widthMeasureSpec);
int withSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);

Mode有3種模式分別是UNSPECIFIED, EXACTLY和AT_MOST,如果是AT_MOST,Size代表的是最大可獲得的空間;如果是EXACTLY,Size代表的是精確的尺寸;如果是UNSPECIFIED,就是你想要多少就有多少。經過代碼測試就知道,當我們設置width或height為fill_parent時,容器在布局時調用子 view的measure方法傳入的模式是EXACTLY,因為子view會占據剩余容器的空間,所以它大小是確定的。而當設置為 wrap_content時,容器傳進去的是AT_MOST, 表示子view的大小最多是多少,這樣子view會根據這個上限來設置自己的尺寸。當子view的大小設置為精確值時,容器傳入的是EXACTLY。

2. onLayout是幹什麽的?

與onMesaure相比,onLayout更加容易理解,它的作用就是調座位,就是把所有的子View根據不同的需要,通過View. layout(int l, int t, int r, int b)方法指定它所在的位置。

3. 解決問題

只要對onMeasure和onLayout加以理解,對於該篇所要實現的功能就不再難以實現,下面貼上代碼,並在代碼中講解。

WaroLinearLayout.java

public class WarpLinearLayout extends ViewGroup {
 
    private
Type mType; private List<WarpLine> mWarpLineGroup; public WarpLinearLayout(Context context) { this(context, null); } public WarpLinearLayout(Context context, AttributeSet attrs) { this(context, attrs, R.style.WarpLinearLayoutDefault); } public WarpLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mType = new Type(context, attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int withMode = MeasureSpec.getMode(widthMeasureSpec); int withSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int with = 0; int height = 0; int childCount = getChildCount(); /** * 在調用childView。getMeasre之前必須先調用該行代碼,用於對子View大小的測量 */ measureChildren(widthMeasureSpec, heightMeasureSpec); /** * 計算寬度 */ switch (withMode) { case MeasureSpec.EXACTLY: with = withSize; break; case MeasureSpec.AT_MOST: for (int i = 0; i < childCount; i++) { if (i != 0) { with += mType.horizontal_Space; } with += getChildAt(i).getMeasuredWidth(); } with += getPaddingLeft() + getPaddingRight(); with = with > withSize ? withSize : with; break; case MeasureSpec.UNSPECIFIED: for (int i = 0; i < childCount; i++) { if (i != 0) { with += mType.horizontal_Space; } with += getChildAt(i).getMeasuredWidth(); } with += getPaddingLeft() + getPaddingRight(); break; default: with = withSize; break; } /** * 根據計算出的寬度,計算出所需要的行數 */ WarpLine warpLine = new WarpLine(); /** * 不能夠在定義屬性時初始化,因為onMeasure方法會多次調用 */ mWarpLineGroup = new ArrayList<WarpLine>(); for (int i = 0; i < childCount; i++) { if (warpLine.lineWidth + getChildAt(i).getMeasuredWidth() + mType.horizontal_Space > with) { if (warpLine.lineView.size() == 0) { warpLine.addView(getChildAt(i)); mWarpLineGroup.add(warpLine); warpLine = new WarpLine(); } else { mWarpLineGroup.add(warpLine); warpLine = new WarpLine(); warpLine.addView(getChildAt(i)); } } else { warpLine.addView(getChildAt(i)); } } /** * 添加最後一行 */ if (warpLine.lineView.size() > 0 && !mWarpLineGroup.contains(warpLine)) { mWarpLineGroup.add(warpLine); } /** * 計算寬度 */ height = getPaddingTop() + getPaddingBottom(); for (int i = 0; i < mWarpLineGroup.size(); i++) { if (i != 0) { height += mType.vertical_Space; } height += mWarpLineGroup.get(i).height; } switch (heightMode) { case MeasureSpec.EXACTLY: height = heightSize; break; case MeasureSpec.AT_MOST: height = height > heightSize ? heightSize : height; break; case MeasureSpec.UNSPECIFIED: break; default: break; } setMeasuredDimension(with, height); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { t = getPaddingTop(); for (int i = 0; i < mWarpLineGroup.size(); i++) { int left = getPaddingLeft(); WarpLine warpLine = mWarpLineGroup.get(i); int lastWidth = getMeasuredWidth() - warpLine.lineWidth; for (int j = 0; j < warpLine.lineView.size(); j++) { View view = warpLine.lineView.get(j); if (isFull()) {//需要充滿當前行時 view.layout(left, t, left + view.getMeasuredWidth() + lastWidth / warpLine.lineView.size(), t + view.getMeasuredHeight()); left += view.getMeasuredWidth() + mType.horizontal_Space + lastWidth / warpLine.lineView.size(); } else { switch (getGrivate()) { case 0://右對齊 view.layout(left + lastWidth, t, left + lastWidth + view.getMeasuredWidth(), t + view.getMeasuredHeight()); break; case 2://居中對齊 view.layout(left + lastWidth / 2, t, left + lastWidth / 2 + view.getMeasuredWidth(), t + view.getMeasuredHeight()); break; default://左對齊 view.layout(left, t, left + view.getMeasuredWidth(), t + view.getMeasuredHeight()); break; } left += view.getMeasuredWidth() + mType.horizontal_Space; } } t += warpLine.height + mType.vertical_Space; } } /** * 用於存放一行子View */ private final class WarpLine { private List<View> lineView = new ArrayList<View>(); /** * 當前行中所需要占用的寬度 */ private int lineWidth = getPaddingLeft() + getPaddingRight(); /** * 該行View中所需要占用的最大高度 */ private int height = 0; private void addView(View view) { if (lineView.size() != 0) { lineWidth += mType.horizontal_Space; } height = height > view.getMeasuredHeight() ? height : view.getMeasuredHeight(); lineWidth += view.getMeasuredWidth(); lineView.add(view); } } /** * 對樣式的初始化 */ private final static class Type { /* *對齊方式 right 0,left 1,center 2 */ private int grivate; /** * 水平間距,單位px */ private float horizontal_Space; /** * 垂直間距,單位px */ private float vertical_Space; /** * 是否自動填滿 */ private boolean isFull; Type(Context context, AttributeSet attrs) { if (attrs == null) { return; } TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.WarpLinearLayout); grivate = typedArray.getInt(R.styleable.WarpLinearLayout_grivate, grivate); horizontal_Space = typedArray.getDimension(R.styleable.WarpLinearLayout_horizontal_Space, horizontal_Space); vertical_Space = typedArray.getDimension(R.styleable.WarpLinearLayout_vertical_Space, vertical_Space); isFull = typedArray.getBoolean(R.styleable.WarpLinearLayout_isFull, isFull); } } public int getGrivate() { return mType.grivate; } public float getHorizontal_Space() { return mType.horizontal_Space; } public float getVertical_Space() { return mType.vertical_Space; } public boolean isFull() { return mType.isFull; } public void setGrivate(int grivate) { mType.grivate = grivate; } public void setHorizontal_Space(float horizontal_Space) { mType.horizontal_Space = horizontal_Space; } public void setVertical_Space(float vertical_Space) { mType.vertical_Space = vertical_Space; } public void setIsFull(boolean isFull) { mType.isFull = isFull; } /** * 每行子View的對齊方式 */ public final static class Gravite { public final static int RIGHT = 0; public final static int LEFT = 1; public final static int CENTER = 2; } }

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="WarpLinearLayout">
        <attr name="grivate" format="enum"><!--對齊方式 !-->
            <enum name="right" value="0"></enum>
            <enum name="left" value="1"></enum>
            <enum name="center" value="2"></enum>
        </attr>
        <attr name="horizontal_Space" format="dimension"></attr>
        <attr name="vertical_Space" format="dimension"></attr>
        <attr name="isFull" format="boolean"></attr>
    </declare-styleable>
</resources>
WarpLinearLayoutDefault
<style name="WarpLinearLayoutDefault">
        <item name="grivate">left</item>
        <item name="horizontal_Space">20dp</item>
        <item name="vertical_Space">20dp</item>
        <item name="isFull">false</item>
</style>

MainActivity.java

public class MainActivity extends Activity {
    private Button btn;
    private WarpLinearLayout warpLinearLayout;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn = (Button) findViewById(R.id.btn);
        warpLinearLayout = (WarpLinearLayout) findViewById(R.id.warpLinearLayout);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int n = new Random().nextInt(10) + 5;
                StringBuffer stringBuffer = new StringBuffer();
                Random random = new Random();
                Log.i("WarpLinearLayout","n="+n);
                for (int i = 0; i < n; i++) {
                    stringBuffer.append((char)(65+random.nextInt(26)));
                    Log.i("WarpLinearLayout", "StringBuffer=" + stringBuffer.toString());
                }
                TextView tv = new TextView(MainActivity.this);
                tv.setText(stringBuffer.toString()+"000");
                tv.setBackgroundResource(R.drawable.radius_backgroup_yellow);
                tv.setPadding(10,10,10,10);
                warpLinearLayout.addView(tv);
            }
        });
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">
 
    <Button
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="add"
        android:textSize="20dp" />
 
    <com.example.customview.viewgroup.WarpLinearLayout
        android:id="@+id/warpLinearLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/btn"
        android:background="#FF00FF00"
        android:padding="10dp"
        app:grivate="right"
        app:horizontal_Space="10dp"
        app:isFull="false"
        app:vertical_Space="10dp"></com.example.customview.viewgroup.WarpLinearLayout>
</RelativeLayout>

運行效果圖如下:

技術分享圖片

Android 自動換行的LinearLayout