Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android 自定義控件實現流式布局

android 自定義控件實現流式布局

編輯:關於Android編程

什麼是流式布局呢?也不知道哪個高手把它稱之為流失布局,叫什麼不重要,重要的是要知道怎麼實現,今天就實現下這個功能,先看下圖什麼就知道是什麼是流式布局了,做過電商的app或者網購的人都知道有一個什麼選擇規格(x,xl,ml)so,

\

當然這個用其他什麼gridview也能實現,如果大小是一樣的話,如果大小不一樣就不好搞定了,那麼如果使用今天講的流式布局就很好做了,那麼還是一開始並不是直接講這個效果怎麼實現,而是把相關的技術點盡自己的能力講清楚,如果這個懂了,說不定不僅這個流式布局懂了,也許你還懂了其他東西,這就是最好的,這就是為什麼不上來貼代碼的原因,而是花更多的時間把原理講清楚!要實現這個效果,就必須懂view的繪制流程,如圖:

\

這就是所謂的繪制流程三步驟,打個比方吧,你team叫你把一個控件放到手機屏幕上,那麼要問我要把一個多大的控件放在哪個位置啊,這裡就有二個詞很重要,多大,哪個位置,多大就是onMeasure(),哪個位置就是onLayout(),控件在屏幕上是顯示什麼,這就是內容了也就是onDraw(),

上面的圖說了onMeasure()方法也就是測量控件大小並不是最終的大小,又可能onLayout()方法中改變了view的大小,現在寫個小例子驗證下:

 



    
        
        
package com.example.flowlayout;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.LinearLayout;
/**
 * Created by admin on 2016/6/13.
 */
public class MyLinearLayout extends LinearLayout {
    public MyLinearLayout(Context context) {
        this(context,null);
    }
    public MyLinearLayout(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }
    public MyLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
    }
}
效果:

 

\

你會發現textview寬和高就是包裹內容,我現在在onLayout()方法中添加幾行代碼:

 

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    super.onLayout(changed, l, t, r, b);
    TextView tv = (TextView) getChildAt(0);//獲取MyLinearLayout控件的第一個子view,這個和xml布局是對應的
    tv.layout(0,0,300,300);
}
效果圖:

 

\

看到textview的寬和高變成了300,300了吧,和之前的內容包裹是不是不一樣了,因為在onLayout()方法中改變了子view的寬和高,按到底這是違背view繪制流程的,但是可以這麼做,我們知道android view有二種,一種是view比如TextView,Button,ImageView,就是不能通過addView(View view)添加子view的,另外還有一種View是ViewGroup,就是存儲view的容器,但是ViewGroup是繼承自View,所以你也可以說android上所有的控件就一種View,

onMeasure()---測量

我們知道繪制流程第一步就是測量,從源碼中發現真正的測量是從measure()方法開始的,這個方法在view中而不是在ViewGroup中,所以剛才在自定義LinearLayout寫的onMeasure()方法也是繼承了View中的onMeasure()方法,那麼先看下View中的measure()方法:

 

 *
 * @param widthMeasureSpec Horizontal space requirements as imposed by the
 *        parent
 * @param heightMeasureSpec Vertical space requirements as imposed by the
 *        parent
 *
 * @see #onMeasure(int, int)
 */
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    boolean optical = isLayoutModeOptical(this);
    if (optical != isLayoutModeOptical(mParent)) {
        Insets insets = getOpticalInsets();
        int oWidth  = insets.left + insets.right;
        int oHeight = insets.top  + insets.bottom;
        widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
        heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
    }

    // Suppress sign extension for the low bytes
    long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
    if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

    if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
            widthMeasureSpec != mOldWidthMeasureSpec ||
            heightMeasureSpec != mOldHeightMeasureSpec) {

        // first clears the measured dimension flag
        mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

        resolveRtlPropertiesIfNeeded();

        int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
                mMeasureCache.indexOfKey(key);
        if (cacheIndex < 0 || sIgnoreMeasureCache) {
            // measure ourselves, this should set the measured dimension flag back
            onMeasure(widthMeasureSpec, heightMeasureSpec);//重點
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        } else {
            long value = mMeasureCache.valueAt(cacheIndex);
            // Casting a long to int drops the high 32 bits, no mask needed
            setMeasuredDimensionRaw((int) (value >> 32), (int) value);
            mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        // flag not set, setMeasuredDimension() was not invoked, we raise
        // an exception to warn the developer
        if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
            throw new IllegalStateException("View with id " + getId() + ": "
                    + getClass().getName() + "#onMeasure() did not set the"
                    + " measured dimension by calling"
                    + " setMeasuredDimension()");
        }

        mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
    }

    mOldWidthMeasureSpec = widthMeasureSpec;
    mOldHeightMeasureSpec = heightMeasureSpec;

    mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
            (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
從上面方法中的注釋標記了紅色,意思是說水平和豎直空間需要父view提供,記住這個,往下會用到,從上面的measure()方法看到這是用final修飾的,表示子類不能繼承它,也就是說Google讓你不想打破它的測量框架,上面有一個很重要的方法onMeasure(widthMeasureSpec, heightMeasureSpec);一般測量都是繼承這個方法,onMeasure()源碼:

 

 

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
其實onMeasure()方法中也就是調用了setMeasureDimension()方法,它也是接受2個形參,但是這二個形參確實調用了getDefaultSize()方法,

 

 

public static int getDefaultSize(int size, int measureSpec) {
    int result = size; //把size賦值給result
    int specMode = MeasureSpec.getMode(measureSpec);//獲取mode
    int specSize = MeasureSpec.getSize(measureSpec);//獲取size

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}
從上面的形參的字面意思知道第一個形參是大小,第二個形參是測量規范,我是從字面意思翻譯的,因為spec是規范意思,

 

所以onMeasure()方法中的2個參數就不是一個具體的值,比如不是什麼100,200之類的,其實這100,200是由大小和規范決定的,現在看下getDefaultSize()方法,其中

int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

MeasureSpec類源碼:

 

public static class MeasureSpec {
    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    public static final int EXACTLY     = 1 << MODE_SHIFT;

    public static final int AT_MOST     = 2 << MODE_SHIFT;
    public static int makeMeasureSpec(int size, int mode) {
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }

    public static int makeSafeMeasureSpec(int size, int mode) {
        if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
            return 0;
        }
        return makeMeasureSpec(size, mode);
    }

    public static int getMode(int measureSpec) {
        return (measureSpec & MODE_MASK);
    }

    public static int getSize(int measureSpec) {
        return (measureSpec & ~MODE_MASK);
    }

    static int adjust(int measureSpec, int delta) {
        final int mode = getMode(measureSpec);
        int size = getSize(measureSpec);
        if (mode == UNSPECIFIED) {
            // No need to adjust size for UNSPECIFIED mode.
            return makeMeasureSpec(size, UNSPECIFIED);
        }
        size += delta;
        if (size < 0) {
            Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
                    ") spec: " + toString(measureSpec) + " delta: " + delta);
            size = 0;
        }
        return makeMeasureSpec(size, mode);
    }

    public static String toString(int measureSpec) {
        int mode = getMode(measureSpec);
        int size = getSize(measureSpec);

        StringBuilder sb = new StringBuilder("MeasureSpec: ");

        if (mode == UNSPECIFIED)
            sb.append("UNSPECIFIED ");
        else if (mode == EXACTLY)
            sb.append("EXACTLY ");
        else if (mode == AT_MOST)
            sb.append("AT_MOST ");
        else
            sb.append(mode).append(" ");

        sb.append(size);
        return sb.toString();
    }
}
上面的幾個常量做一個簡單的介紹

 

UNSPECIFIED = 0 << MODE_SHIFT(=30)表示向左移30 最後的結果=0

EXACTLY = 1 << MODE_SHIFT表示左移30=1073741824

AT_MOST = 2 << MODE_SHIFT;表示左移30結果=-2147483648

MODE_MASK = 0x3 << MODE_SHIFT表示左移30結果-1073741824

現在看下getMode()的方法:

 

public static int getMode(int measureSpec) {
    return (measureSpec & MODE_MASK);
}
比如measureSpec=100,那麼getMode()最後返回的值為0,那麼就是UNSPECIFIED

 

 

public static int getSize(int measureSpec) {
    return (measureSpec & ~MODE_MASK);
}
getSize()最後的返回的值就是measureSpec傳入的值,

 

結合上面2個方法以及getDefaultSize()我們總結一個結論

測量最終的值=size+mode

現在講下上面涉及到的三個變量也就是mode,

UNSPECIFIED:

表示視圖按照自己的意願設置成任意的大小,沒有任何限制,這個一般用在ScollerView上

EXACTLY

這個exactly是精確的意思,意思是說父view傳遞給子view的大小是精度的,那麼子view就應該接受父view傳遞給它的值是多少就是多少

AT_MOST
表示子view只能接受指定的大小,不能超過這個指定大小的范圍,就好像是LinearLayout的寬和高是100,而它的子view TextView只能接受最大值為100,不能超過這個100

現在看下之前自定義的LinearLayout中的onMeasure()方法

 

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int mode = MeasureSpec.getMode(widthMeasureSpec);
    Log.e(TAG,"mode------------------->"+mode);
}
log:

 

06-13 06:52:30.088 30004-30004/com.example.flowlayout E/MyLinearLayout: mode------------------->1073741824

把1073741824和上面的幾個分析的常量對比一下發現mode就是EXACTLY,哪為什麼是EXACTLY呢?看下布局文件:

 



    
        
        

發現MyLinearLayout寬和高都是match_parent,也就是填充父view的大小,它的父view就是RelativeLayout,而這個RelativeLayout的寬和高是讀取手機的屏幕賦值給RelativeLayout的,所以RelativeLayout的寬和高是一個定值,這就是為什麼mode為EXACTLY,如圖:

 

\

現在我把布局文件改變下,

 



    
        
            
        
    

現在打印下mode值為

 

06-13 07:22:11.258 23646-23646/com.example.flowlayout I/MyLinearLayout: mode------------------->-2147483648

這個是不是對應AT_MOST,因為LinearLayout的寬度為wrap_content,它的寬度取決於它孩子view的寬度,所以它不是固定的,那麼你MyLinearLayout就是最大取值反正不能超過父view的寬度就行,從上面的分析可以得出一般的結論:

1:當子view的寬和高設置為wrap_content,父view給它的mode為AT_MOST

2:當子view的高和寬設置為match_parent和確切的值的時候 父view給它的mode為EXACTLY

測量的最終是在setMeasuredDimension(int measuredWidth, int measuredHeight)方法中結束最後的測量過程,因為measuredWidth和measuredHeight都是最終的測量後的寬度和高度,從這個形參也知道後綴沒帶Spec這幾個字母,

在這裡我自定義一個View,

 

package com.example.flowlayout;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
/**
 * Created by admin on 2016/6/13.
 */
public class MyView extends View {
    public MyView(Context context) {
        super(context);
    }
    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        /**
         * 不調用父viewonMeasure()方法而是直接調用setMeasuredDimension()
         */
//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(200,200);
    }
}
布局文件

 

 



    
        
            
        
        
    

我布局文件設置的寬和高都是50px,效果:

 

\

發現被騙了一樣,是的布局文件是不能當作最終的view的寬和高,是因為我們在MyView的onMeasure()方法中設置了

 

setMeasuredDimension(200,200);
其實在ViewGroup類中還有一個測量子view的方法

 

 

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;//子view的總數
    final View[] children = mChildren;//記錄所有的子view(是一個數組)
    for (int i = 0; i < size; ++i) {//遍歷所有的子view
        final View child = children[i];//賦值某一個子view
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {//判斷這個View是不是Gone了也就是不可見
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}
現在看下measureChild()方法源碼:

 

 

protected void measureChild(View child, int parentWidthMeasureSpec,
        int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();

    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
上面通過一系列對父view傳遞進來的寬和高計算,最終調用的是子view的measure()方法來最終測量寬和高

 

\

在這提一個知識點,就是getMeasuredWidth() getMeasuredHeight()這二個方法,我們只要看其中一個方法源碼就行

 

public final int getMeasuredHeight() {
    return mMeasuredHeight & MEASURED_SIZE_MASK;
}
mMeasuredHeight這個值是在measure()方法中對進行賦值,而public static final int MEASURED_SIZE_MASK = 0x00ffffff;是一個定值,所以getMeasureHeight()方法是在測量後才能獲取到這個值,好了測量就講到這裡,現在接著講onLayout()方法

 

onLayout()

研究onLayout()方法首先要先研究下ViewGroup中的layout()方法開始

 

/**
 * {@inheritDoc}
 */
@Override
public final void layout(int l, int t, int r, int b) {
    if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
        if (mTransition != null) {
            mTransition.layoutChange(this);
        }
        super.layout(l, t, r, b);//調用父view的layout()方法也就是調用view的layout方法
    } else {
        // record the fact that we noop'd it; request layout when transition finishes
        mLayoutCalledWhileSuppressed = true;
    }
}

發現這個layout()方法也是final修飾的,所以子view不能繼承重寫這個layout()方法,現在看下view的layout()方法

 

public void layout(int l, int t, int r, int b) {
    if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
        onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);//在這裡調用了測量方法
        mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    }

    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;

    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);//給繼承了ViewGroup的子類讓它自己去控制view的位置
        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnLayoutChangeListeners != null) {
            ArrayList listenersCopy =
                    (ArrayList)li.mOnLayoutChangeListeners.clone();
            int numListeners = listenersCopy.size();
            for (int i = 0; i < numListeners; ++i) {
                listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
            }
        }
    }

    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
看下onLayout()方法:

 

 

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
發現它是一個空方法,哪好了畫圖理解下

\

 

其實view的layout的四個參數其實就是2個坐標點而已,如圖:

\

 

 

package com.example.flowlayout;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.Window;
import android.widget.Button;

/**
 * Created by admin on 2016/6/13.
 */
public class MyView extends Button {
    private static final String TAG ="MyView" ;
    public MyView(Context context) {
        this(context,null);
    }
    public MyView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    private float downX = 0;
    private float downY = 0;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                downX = event.getX();
                downY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                float moveX = event.getX();
                float moveY = event.getY();
                int l = getLeft();
                int t = getTop();
                int r = getRight();
                int b =  getBottom();

                int newL = (int) (l+(moveX-downX));
                int newT = (int) (t+(moveY-downY));
                int newR = (int) (r+(moveX-downX));
                int newB = (int) (b+(moveY-downY));
                layout(newL,newT,newR,newB);
                downX = moveX;
                downY = moveY;
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return true;
    }
}
這個是實現在屏幕上隨意拖動

\

 

現在講下View的getwidth()和getHeight()方法,直接上源碼

 

@ViewDebug.ExportedProperty(category = "layout")
public final int getWidth() {
    return mRight - mLeft;
}
widht=mRight-mLeft,從這個簡單的算法中就知道要想一個view通過getWidth()獲取到寬度,必須是onLayout()方法執行後

 

在這裡忘記了講下onLayout(l,t,r,b)方法中四個參數,其實就是離父view的left,top,right,bottom

布局文件:

 



    


 

在onLayout()方法中打印log;

06-13 12:50:17.293 11451-11451/com.example.flowlayout E/MyLinearLayout: l=10t=10r=110b=110

看出來了吧從log日記中,如圖:

\

好吧,onLayout()方法就講到這裡了,現在講一個例子引出另外一個技術點

xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="阿裡巴巴"
android:background="#ff0000"
android:padding="10dp"
/>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="騰訊"
android:background="#ffff00"
android:padding="10dp"
/>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="百度"
android:background="#00ff00"
android:padding="10dp"
/>


 

 

package com.example.measureviewdemo;
import android.content.Context;
import android.util.AttributeSet;
import android.view.ViewGroup;
public class MyLinearLayout extends ViewGroup {
public MyLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyLinearLayout(Context context) {
super(context);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {

}
}

發現MyLinearLayout 是繼承了ViewGroup,裡面什麼邏輯代碼也沒寫,運行起來看有啥

\

發現叼都沒有,是因為沒有實現onMeasure()和onLayout()方法,因為我是繼承了ViewGroup,現在實現下這個二個方法中的邏輯

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = 0;
int height=0;
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);

int count = getChildCount();//獲取所有的子view
for (int i=0;i View view= getChildAt(i); //獲取某一個子view
measureChild(view, widthMeasureSpec, heightMeasureSpec); //測量子view
int childWidth = view.getMeasuredWidth(); //測量後獲取子view的寬度
int childHeight = view.getMeasuredHeight();//測量後獲取子view的高度
//得到最大寬度,並且累加高度
height = childHeight;
width+= Math.max(childWidth, width);
}
setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? widthSize: width, (heightMode == MeasureSpec.EXACTLY) ? heightSize: height);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = getChildCount();
int viewWidth = 0;//記錄每個子view的寬度累加
for (int i=0;i View child = getChildAt(i);
int childHeight = child.getMeasuredHeight();
int childWidth = child.getMeasuredWidth();
child.layout(viewWidth, 0, childWidth+viewWidth, childHeight);
viewWidth+=childWidth;
}
}

效果:

\

完成的把這三個子view顯示出來了,但是我現在布局文件中對這三個textview添加一個屬性android:layout_marginLeft="20px" 但是你發現運行起來的效果和上面的效果沒任何區別,按到底高度不變,寬度要加3*20也就是width+60呢?

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved