Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android編程入門 >> 深入了解View的繪制流程

深入了解View的繪制流程

編輯:Android編程入門

1.  ViewRoot

    ViewRoot是連接WindowManager與DecorView的紐帶,View的整個繪制流程的三大步(measure、layout、draw)都是通過ViewRoot完成的。當Activity對象被創建完畢後,會將DecorView添加到Window中(Window是對窗口的抽象,DecorView是一個窗口的頂級容器View,其本質是一個FrameLayout),同時會創建ViewRootImpl(ViewRoot的實現類)對象,並將ViewRootImpl與DecorView建立關聯。關於ViewRoot,我們只需要知道它是聯系GUI管理系統和GUI呈現系統的紐帶。View的繪制流程從ViewRoot的performTraversals方法開始,經過measure、layout、draw三大過程完成對一個View的繪制工作。peformTraversal方法內部會調用measure、layout、draw這三個方法,這三個方法內部又分別調用onMeasure、onLayout、onDraw方法。

    在onMeasure方法中View會對其所有的子元素執行measure過程,此時measure過程就從父容器"傳遞"到了子元素中,接著子元素會遞歸的對其子元素進行measure過程,如此反復完成對整個View樹的遍歷。onLayout與onDraw過程的執行流程與此類似。

    measure過程決定了View的測量寬高,這個過程結束後,就可以通過getMeasuredHeight和getMeasuredWidth獲得View的測量寬高了;

    layout過程決定了View在父容器中的位置和View的最終顯示寬高,getTop等方法可獲取View的top等四個位置參數(View的左上角頂點的坐標為(left, top), 右下角頂點坐標為(right, bottom)),getWidth和getHeight可獲得View的最終顯示寬高(width = right - left;height = bottom - top)。 

    draw過程決定了View最終顯示出來的樣子,此過程完成後,View才會在屏幕上顯示出來。

   

2. MeasureSpec

    MeasureSpec為一個32位的int值,高2位代表SpecMode,低30位代表SpecSize,前者指測量模式,後者指某種測量模式下的規格大小。在一個View的measure過程中,系統會將該View的LayoutParams結合父容器的“要求”生成一個MeasureSpec,這個MeasureSpec說明了應該怎樣測量這個View。

(1)三種 SpecMode:

    UNSPECIFIED父容器不對View作任何要求,通常用於系統內部,表示一種測量的狀態。

    EXACTLY父容器已經檢測出View所需要的精確大小,這種測量模式下View的測量值就是SpecSize的值。這個SpecMode對應於LayoutParams中的match_parent和給出具體大小這兩種模式。

    AT_MOST父容器指定了一個可用大小即SpecSize,View的大小不能大於此值,可用大小取決於不同View的具體實現。這個SpecMode對應於LayoutParams中的wrap_content。

(2)對於DecorView,它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同確定;對於普通View,他的MeasureSpec由父容器的MeasureSpec和其自身的LayoutParams共同確定。

 

3. View的具體繪制流程

(1)measure過程

a. DecorView的measure過程

    前面我們提到過,DecorView是一個應用窗口的根容器,它本質上是一個FrameLayout。DecorView有唯一一個子View,它是一個垂直LinearLayout,這個垂直線性布局管理器包含兩個子元素,一個是TitleView(ActionBar的容器),另一個是ContentView(窗口內容的容器)。關於ContentView,它是一個FrameLayout(android.R.id.content),我們平常用的setContentView就是設置它的子View。如下圖中,我們為TilteView中添加了一個ActionBar,為ContentView中添加了一個RelativeLayout(通過setContentView方法)。

    前面提到,DecorView的MeasureSpec由窗口的尺寸和自身的LayoutParams共同決定。在ViewRootImpl的measureHierarchy方法中,完成了創建DecorView的MeasureSpec的過程,相應的代碼片段如下:

1 childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

    以上代碼片段中的childXxxMeasureSpec即為DecorView的MeasureSpec,lp.width和lp.height被系統賦值為MATCH_PARENT。getRootMeasureSpec的代碼如下:

 private int getRootMeasureSpec(int windowSize, int rootDimension) {  
    int measureSpec;  
    switch (rootDimension) {  
        case ViewGroup.LayoutParams.MATCH_PARENT:  
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);  
            break;  
        case ViewGroup.LayoutParams.WRAP_CONTENT:  
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);  
            break;  
        default:  
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);  
            break;  
    }  
    return measureSpec;  
} 

    上述代碼中調用了makeMeasureSpec方法來獲取measureSpec,而傳入的rootDimension參數即為lp.width或lp.height,值為MATCH_PARENT,由此可得DecorView的MeasureSpec,其中SpecMode為EXACTLY,SpecSize為windowSize。

 

 

b. 普通View(非ViewGroup)的measure過程:

    非ViewGroup的View的特點是不能有子元素,因此只需測量好自身就行。普通View的measure通過measure方法來完成:

1 public final void measure(int widthMeasureSpec, int heightMeasureSpec) {  
    //....  
  
    //回調onMeasure()方法    
    onMeasure(widthMeasureSpec, heightMeasureSpec);  
     
    //more  
}

    普通View的measure方法是由ViewGroup在measureChild方法中調用的(即完成了measure過程從ViewGroup到子View的傳遞),ViewGroup調用其子View的measure時即傳入了該子View的widthMeasureSpec和heightMeasureSpec。注意到measure是一個final方法,因此要實現自定義的measure過程,需要重寫onMeasure方法:

1 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

    setMeasureDimension方法用於設置View的測量寬高,如果不重寫此方法,默認是直接調用getDefaultSize獲取尺寸的:

 public static int getDefaultSize(int size, int measureSpec) {  
    int result = size;  
    int specMode = MeasureSpec.getMode(measureSpec);  
    int specSize = MeasureSpec.getSize(measureSpec);  
    switch (specMode) {  
        case MeasureSpec.UNSPECIFIED:  
            result = size;  
            break;  
        case MeasureSpec.AT_MOST:  
        case MeasureSpec.EXACTLY:  
            result = specSize;  
            break;  
    }  
    return result;  
}  

    由以上代碼可知,正常情況下(SpecMode為AT_MOST或EXACTLY),getDefaultSize獲取的尺寸大小即為specSize。由以上代碼還可知道,直接繼承View的自定義控件需要重寫onMeasure方法並設置wrap_content時的自身大小,否則在布局中使用wrap_content就相當於使用match_parent的效果。示例如下:

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
    if (widthSpecMode == MeasureSpec.AT_MOST  && heightSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(mWidth, mHeight);
    } else if (widthSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(mWidth, heightSpecSize);
    } else if (heightSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(widthSpecSize, mHeight);
    }
}

    上述示例代碼中的mWidth,mHeight是為wrap_content時設定的默認寬高。這個默認寬高可根據實際需要自行設置,比如TextView在wrap_content時的默認寬高是根據其中的所有文字的寬度來設定的,從而實現正好“包裹”文字內容的效果。

 

c. ViewGroup的measure過程:

    ViewGroup需要先完成子View的measure過程,才能完成自身的measure過程,ViewGroup的onMeasure方法根據不同的布局管理器類(LinearLayout、RelativeLayout等等)有不同的實現,比如LinearLayout的onMeasure方法代碼如下:

1 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (mOriention == VERTICAL) {
        measureVertical(widthMeasureSpec, heightMeasureSpec);
    } else {
        measureHorizontal(widthMeasureSpec, heightMeasureSpec);
    }
}

    measureVertical中測量子元素的主要代碼如下:

 //See how tall everyone is. Also remember max width.
for (int i = 0; i < count; ++i) {
    final View child = getVirtualChildAt(i);
    . . .
    //Determine how big this child would like to be. If this or previous children have given a weight, 

//then we allow it to use all available space (and we will shrink things later if needed). measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec, totalHeight == 0 ? mTotalLength : 0); if (oldHeight != Integer.MIN_VALUE) { lp.height = oldHeight; } final int childLength = child.getMeasuredHeight(); final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength+childHeight+lp.topMargin+lp.bottomMargin+getNextLocationOffset(child)); }

    由上述代碼可以知道,在measureVertical方法中會對每個LinearLayout中的子元素進行遍歷並通過measureChildBeforeLayout方法對每個子元素執行measure過程。在measureChildBeforeLayout方法內部會調用子元素的measure方法,這樣會依次讓每個子元素進入measure過程。mTotalLength表示LinearLayout在豎直方向上的尺寸,每完成一個子元素的measure過程,它的值也會相應增加。測量完子元素後,LinearLayout會測量自身的大小。measureVertical中測量LinearLayout自身的主要代碼如下:

 //Add in our padding.
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
//Check against our minimum height
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
//Reconcile our calculated size with the heightMeasureSpec
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
. . .
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), heightSizeAndState);

    對垂直的LinearLayout來說,它在水平方向的測量過程與普通View的measure過程一樣,在豎直方向的measure過程如下:若該垂直LinearLayout的layout_height為match_parent或具體數值,它的measure過程與普通View一樣;若該垂直LinearLayout的layout_height為wrap_content,則它豎直方向的高度為所有子元素占用的高度之和,但不能超過父容器的可用空間大小,最終高度還要考慮到其豎直方向的padding,相關的代碼如下:

 public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);
    switch (speczMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
            if (specSize < size) {
                result = specSize | MEASURED_STATE_TOO_SMALL;
            } else {
                result = size;
            }
            break;
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
    }
    return result | (childMeasuredState & MEASURED_STATE_MASK);
}

   

    ViewGroup主要通過其measureChildren方法完成其子View的measure過程,上面垂直LinearLayout中調用的measureChildBeforeLayout可以看做是measureChildren的一個“變種”,measureChildren方法代碼如下:

 protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {  
    final int size = mChildrenCount;  
    final View[] children = mChildren;  
    for (int i = 0; i < size; ++i) {  
            final View child = children[i];  
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { 
                measureChild(child, widthMeasureSpec, heightMeasureSpec);  
            }  
    }  
}  

    其中,measureChild方法完成對子View的measure過程:

1 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);  
}  

    注意在這裡,在執行child.measure方法前,就已經通過getChildMeasureSpec獲取了子View的MeasureSpec。getChildMeasureSpec根據子View的LayoutParams和父容器的MeasureSpec來決定子View的MeasureSpec,getChildMeasureSpec的代碼如下:

 public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    //這裡傳入的spec為ViewGroup的MeasureSpec
    //specMode和specSize即為父容器的MeasureSpec
    int specMode = MeasureSpec.getMode(spec);
    int specSize = MeasureSpec.getSize(spec);
    //padding為父容器中已使用的空間大小,size為父容器可用空間大小
    int size = Math.max(0, specSize - padding);
    int resultSize = 0;
    int resultMode = 0;

    switch (specMode) {
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                //子View想要和父容器一樣大
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                //子View想自己決定它的大小,但不能比父容器大
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
        
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } 
            break;
        
        //Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
    

    以上函數的工作過程可總結如下:

    a. 當childLayoutParams指定為為具體的大小時:若parentSpecMode為EXACTLY,則childSpecMode為EXACTLY,childSpecSize為childSize(layout_width和layout_height中指定的具體大小);若parentSpecMode為AT_MOST,則childSpecMode和childSpecSize分別為EXACTLY和childSize。

    b. 當childLayoutParams為match_parent時:若parentSpecMode為EXACTLY,則childSpecMode和childSpecSize分別為EXACTLY和parentSize(父容器中可用的大小);若parentSpecMode為AT_MOST,則childSpecMode和childSpecSize分別為AT_MOST和parentSize。

    c. 當childLayoutParams為wrap_content時:若parentSpecMode為EXACTLY,則childSpecMode和childSpecSize分別為AT_MOST和parentSize;若parentSpecMode為AT_MOST,則childSpecMode和childSpecSize分別為AT_MOST和parentSize。

     還有一點需要注意的是,View的measure過程和Activity生命周期的回調方法不是同步的,也就是不能保證在某個生命周期的回調方法中measure過程已經執行完畢。

 

(2)layout過程

    layout過程用來確定View在父容器中的位置,因而是由父容器獲取子View的位置參數後,調用child.layout方法並傳入已獲取的位置參數,從而完成對子View的layout。當ViewGroup的位置被確定後,它在onLayout中會遍歷所有子元素並調用其layout方法,在layout方法中子元素的onLayout又會被調用。layout方法確定先View本身的位置,再調用onLayout方法確定所有子元素的位置。layout方法如下:

1 public void layout(int l, int t, int r, int b) {  
 2     ……
    int oldL = mLeft;  
    int oldT = mTop;  
    int oldB = mBottom;  
    int oldR = mRight;  
    boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : set(l, t, r, b);
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {  
 9         onLayout(changed, l, t, r, b);  
        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; 
        ListenerInfo li = mListenerInfo; 
        if (li != null && li.mOnLayoutChangeListeners != null) {  
            ArrayList<OnLayoutChangeListener> listenersCopy =  
                (ArrayList<OnLayoutChangeListener>) 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);  
18             }  
19         }  
20     }          
    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; 
    mPrivateFlags3 |= PFLAGS3_IS_LAID_OUT; 
} 

    layout方法的大致流程:首先通過setFrame方法設定View的四個位置參數,即用傳來的l、t、r、b四個參數初始化mLeft、mTop、mRight、mBottom這四個值,從而確定了該View在父容器中的位置。若位置發生改變就調用onLayout方法,onLayout方法在View類中為空,因為對子元素布局的工作只有容器View才需要做。在ViewGroup中,onLayout是一個抽象方法,因為對於不同的布局管理器類,對子元素的布局方式是不同的。比如,LinearLayout的onLayout方法如下:

1 protected void onLayout(boolean changed, int l, int t, int r, int b) {
    if (mOriention == VERTIVAL) {
        layoutVertical(l, t, r, b);
    } else {
        layoutHorizontal(l, t, r, b);
    }
}

    以上代碼會根據LinearLayout的orientation為水平或垂直調用相應的函數來完成布局過程,這裡以layoutVertical為例分析一下垂直線性布局管理器的布局過程,layoutVertical的主要代碼如下:

 void layoutVertical(int left, int top, int right, int bottom) {
    . . .
    final int count = getVirtualChildCount();
    for (int i = 0; i < count; i++) {
        final View child = getVirtualChildAt(i);
        if (child == null) {
            childTop += measureNullChild(i);
        } else if (child.getVisibility() != GONE) {
            final int childWidth = child.getMeasuredWidth();
            final int childHeight = child.getMeasuredHeight();
            
            final int LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
            . . .
            if (hasDividerBeforeChildAt(i)) {
                childTop += mDividerHeight;
            }
 
            childTop += lp.topMargin;
            setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight);
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
            
            i += getChildrenSkipCount(child, i);
        }
    }
}

      以上代碼中,LinearLayout會遍歷它的所有子View,並調用setChildFrame方法設置子View的位置,代碼中的childTop代表當前子View的top位置參數。setChildFrame方法的代碼如下:    

1 private void setChildFrame(View child, int left, int top, int width, int  height) {
    child.layout(left, top, left + width, top + height);
}

      也就是說,在父容器(這裡為LinearLayout)完成了對子元素位置參數(top、left、right、bottom)的獲取後,會調用子元素的layout方法,並把獲取到的子元素位置參數傳入,從而完成對子元素的layout過程。子元素在自己的layout方法中,也會先完成對自己的布局(確定四個位置參數),再調用onLayout方法完成對其子View的布局,這樣layout過程就沿著View樹一層層傳了下去。

      layout過程完成後,便可以通過getWidth和getHeight方法獲取View的最終顯示寬高,這倆方法源碼如下:

1 public final int getWidth() {
    return mRight – mLeft;
}    
1 public final int getHeight() {
    return mBottom – mTop;
}

     由此便可以知道,通過getMeasuredWidth/getMeasuredHeight方法獲取的測量寬高與通過getWidth/getHeight方法獲取的最終顯示寬高的區別:即最終顯示寬高是通過View的位置參數相減得到的,正常情況下應該與測量寬高相等。但如果我們重寫View的layout方法如下:

1 public void layout(int l, int t, int r, int b) {
    super.layout(l, t, r, b, r + 100, b + 100);
}

    這樣就會導致最終顯示寬高比測量寬高大100。(除非你很明確的知道自己想要干啥,否則不應該這樣做)

 

(3)draw過程

    主要分為以下六步:
    a. 繪制背景;
    b. 如果要視圖顯示漸變框,這裡會做一些准備工作;
    c. 繪制視圖本身,即調用onDraw()函數。在View中onDraw()是個空函數,也就是說具體的視圖都要override該函數來實現自己的顯示,而對於ViewGroup則不需要實現該函數,因為作為容器是“沒有內容“的,其包含了多個子view,而子view已經實現了自己的繪制方法,因此只需要告訴子view繪制自己就可以了,也就是下面的dispatchDraw()方法;
    d. 繪制子視圖,即dispatchDraw()函數。在View中這是個空函數,具體的視圖不需要實現該方法,它是專門為容器類准備的,也就是容器類必須實現該方法;
    e. 如果需要, 開始繪制漸變框;
    f. 繪制滾動條;

    draw方法的代碼如下:   

 public void draw(Canvas canvas) {  
    final int privateFlags = mPrivateFlags;  
    final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&  
        (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);  
    mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;  
    // Step 1, draw the background, if needed  
    int saveCount;  
    if (!dirtyOpaque) {  
        drawBackground(canvas);
    }  
    //step 2 & 5 
    final int viewFlags = mViewFlags;  
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;  
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;  
    if (!verticalEdges && !horizontalEdges) {  
        // Step 3, draw the content  
        if (!dirtyOpaque) onDraw(canvas);
        // Step 4, draw the children  
        dispatchDraw(canvas);
        // Step 6, draw decorations (scrollbars)  
        onDrawScrollBars(canvas);  
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
            // we're done...
            return;  
        }
    }  
}  

   View的draw過程的傳遞通過diapatchDraw來實現,dispatchDraw會遍歷調用所有子View的draw方法,這樣draw事件就一層層傳了下去。重寫View的onDraw方法可以定制View繪制出來的樣子,例如實現一些特殊的圖形和動畫。

   View有個名為setWillNotDraw的方法,若一個View不需要繪制任何內容,可通過這個方法將相應標記設為true,系統會進行相應優化。ViewGroup默認開啟這個標記,View默認不開啟。

 

以上是我學習View的繪制流程後的簡單總結,很多地方敘述的還不夠清晰准確,如有問題歡迎大家在評論區一起討論 :)

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