Heim > Java > javaLernprogramm > Benutzerdefinierte Android-Ansicht

Benutzerdefinierte Android-Ansicht

高洛峰
Freigeben: 2016-11-17 09:07:09
Original
1318 Leute haben es durchsucht

Vorwort

Die detaillierten Schritte der benutzerdefinierten Android-Ansicht sind Fähigkeiten, die jeder Android-Entwickler beherrschen muss, da wir während der Entwicklung immer auf die Notwendigkeit einer benutzerdefinierten Ansicht stoßen werden. Um mein technisches Niveau zu verbessern, habe ich es systematisch studiert und einige Erfahrungen hier niedergeschrieben. Ich hoffe, dass jeder rechtzeitig auf etwaige Mängel hinweist.

Prozess

In Android wird das Zeichnen der Layoutanforderung auf der Android-Framework-Ebene gestartet. Das Zeichnen beginnt am Wurzelknoten und misst und zeichnet den Layoutbaum. Erweitern Sie performTraversals in RootViewImpl. Was es tut, ist Messen (Messen der Größe der Ansicht), Layout (Bestimmen der Position der Ansicht) und Zeichnen (Zeichnen der Ansicht) auf der erforderlichen Ansicht. Das folgende Bild kann den Zeichenprozess der Ansicht gut zeigen:

Benutzerdefinierte Android-Ansicht

Wenn der Benutzer requestLayout aufruft, werden nur Maß und Layout ausgelöst, aber auch Zeichnen wird ausgelöst, wenn die Das System beginnt mit dem Aufruf.

Im Folgenden werden diese Prozesse im Detail vorgestellt.

measure

measure ist eine letzte Methode in View und kann nicht überschrieben werden. Es misst und berechnet die Größe der Ansicht, ruft jedoch die onMeasure-Methode zurück. Wenn wir also die Ansicht anpassen, können wir die onMeasure-Methode überschreiben, um die Ansicht nach Bedarf zu messen. Es verfügt über zwei Parameter widthMeasureSpec und heightMeasureSpec. Tatsächlich bestehen diese beiden Parameter aus zwei Teilen, nämlich Größe und Modus. Größe ist die gemessene Größe und Modus ist der Modus des Ansichtslayouts

Wir können ihn über den folgenden Code erhalten:

int widthSize = MeasureSpec.getSize(widthMeasureSpec); 
int heightSize = MeasureSpec.getSize(heightMeasureSpec); 
int widthMode = MeasureSpec.getMode(widthMeasureSpec); 
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
Nach dem Login kopieren

Die erhaltenen Modustypen sind in die folgenden drei Typen unterteilt:

Benutzerdefinierte Android-Ansicht

setMeasuredDimension

Ermitteln Sie die Breite und Höhe der Ansicht über die obige Logik und rufen Sie schließlich die Methode setMeasuredDimension auf, um die gemessene Breite und Höhe zu übergeben. Tatsächlich wird am Ende die Methode setMeasuredDimensionRaw aufgerufen, um dem übergebenen Wert Attribute zuzuweisen. Auch die Aufruflogik für den Aufruf von super.onMeasure() ist dieselbe.

Das Folgende ist ein Beispiel für das Anpassen einer Bestätigungscode-Ansicht. Die onMeasure-Methode lautet wie folgt:

@Override 
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
        int widthSize = MeasureSpec.getSize(widthMeasureSpec); 
        int heightSize = MeasureSpec.getSize(heightMeasureSpec); 
        int widthMode = MeasureSpec.getMode(widthMeasureSpec); 
        int heightMode = MeasureSpec.getMode(heightMeasureSpec); 
        if (widthMode == MeasureSpec.EXACTLY) { 
            //直接获取精确的宽度 
            width = widthSize; 
        } else if (widthMode == MeasureSpec.AT_MOST) { 
            //计算出宽度(文本的宽度+padding的大小) 
            width = bounds.width() + getPaddingLeft() + getPaddingRight(); 
        } 
        if (heightMode == MeasureSpec.EXACTLY) { 
            //直接获取精确的高度 
            height = heightSize; 
        } else if (heightMode == MeasureSpec.AT_MOST) { 
            //计算出高度(文本的高度+padding的大小) 
            height = bounds.height() + getPaddingBottom() + getPaddingTop(); 
        } 
        //设置获取的宽高 
        setMeasuredDimension(width, height); 
    }
Nach dem Login kopieren

Sie können verschiedene Attribute für die zu erreichende benutzerdefinierte Ansicht festlegen Bei unterschiedlichen Modustypen können Sie unterschiedliche Effekte sehen

measureChildren

Wenn Sie eine Ansicht anpassen, die ViewGroup erbt, müssen Sie beim Messen der eigenen Größe auch die Größe der Unteransicht messen. Im Allgemeinen wird die Größe der Unteransicht mit der Methode „measureChildren(int widthMeasureSpec, int heightMeasureSpec)“ gemessen.

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); 
            } 
        } 
    }
Nach dem Login kopieren

Anhand des obigen Quellcodes werden Sie feststellen, dass er tatsächlich jede Unteransicht durchläuft. Wenn die Unteransicht nicht ausgeblendet ist, wird die MeasureChild-Methode aufgerufen. Schauen Sie sich dann den MeasureChild-Quellcode an:

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); 
    }
Nach dem Login kopieren

Sie werden feststellen, dass zuerst die Methode getChildMeasureSpec aufgerufen wird, um die Breite bzw. Höhe zu ermitteln, und schließlich die Messmethode von View aufgerufen wird. Aus der vorherigen Analyse wissen wir bereits, dass die Größe berechnet wird der Aussicht. Die Parameter in Maßen werden über getChildMeasureSpec abgerufen. Werfen wir einen Blick auf den Quellcode:

public static int getChildMeasureSpec(int spec, int padding, int childDimension) { 
        int specMode = MeasureSpec.getMode(spec); 
        int specSize = MeasureSpec.getSize(spec); 
  
        int size = Math.max(0, specSize - padding); 
  
        int resultSize = 0; 
        int resultMode = 0; 
  
        switch (specMode) { 
        // Parent has imposed an exact size on us 
        case MeasureSpec.EXACTLY: 
            if (childDimension >= 0) { 
                resultSize = childDimension; 
                resultMode = MeasureSpec.EXACTLY; 
            } else if (childDimension == LayoutParams.MATCH_PARENT) { 
                // Child wants to be our size. So be it. 
                resultSize = size; 
                resultMode = MeasureSpec.EXACTLY; 
            } else if (childDimension == LayoutParams.WRAP_CONTENT) { 
                // Child wants to determine its own size. It can&#39;t be 
                // bigger than us. 
                resultSize = size; 
                resultMode = MeasureSpec.AT_MOST; 
            } 
            break; 
  
        // Parent has imposed a maximum size on us 
        case MeasureSpec.AT_MOST: 
            if (childDimension >= 0) { 
                // Child wants a specific size... so be it 
                resultSize = childDimension; 
                resultMode = MeasureSpec.EXACTLY; 
            } else if (childDimension == LayoutParams.MATCH_PARENT) { 
                // Child wants to be our size, but our size is not fixed. 
                // Constrain child to not be bigger than us. 
                resultSize = size; 
                resultMode = MeasureSpec.AT_MOST; 
            } else if (childDimension == LayoutParams.WRAP_CONTENT) { 
                // Child wants to determine its own size. It can&#39;t be 
                // bigger than us. 
                resultSize = size; 
                resultMode = MeasureSpec.AT_MOST; 
            } 
            break; 
  
        // Parent asked to see how big we want to be 
        case MeasureSpec.UNSPECIFIED: 
            if (childDimension >= 0) { 
                // Child wants a specific size... let him have it 
                resultSize = childDimension; 
                resultMode = MeasureSpec.EXACTLY; 
            } else if (childDimension == LayoutParams.MATCH_PARENT) { 
                // Child wants to be our size... find out how big it should 
                // be 
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; 
                resultMode = MeasureSpec.UNSPECIFIED; 
            } else if (childDimension == LayoutParams.WRAP_CONTENT) { 
                // Child wants to determine its own size.... find out how 
                // big it should be 
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; 
                resultMode = MeasureSpec.UNSPECIFIED; 
            } 
            break; 
        } 
        //noinspection ResourceType 
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode); 
    }
Nach dem Login kopieren

Ist er einfacher zu verstehen? Wie bereits erwähnt, wird die entsprechende Größe entsprechend dem Modustyp ermittelt. Der Modus, zu dem die Unteransicht gehört, wird anhand des Modustyps der übergeordneten Ansicht und des LayoutParams-Typs der Unteransicht bestimmt. Schließlich werden die erhaltene Größe und der erhaltene Modus integriert und über die Methode MeasureSpec.makeMeasureSpec zurückgegeben. Schließlich wird es an Maß übergeben, bei dem es sich um den Wert der beiden zuvor erwähnten Teile widthMeasureSpec und heightMeasureSpec handelt. Der gesamte Prozess ist MeasureChildren->measureChild->getChildMeasureSpec->measure->onMeasure->setMeasuredDimension, sodass die Unteransicht über MeasureChildren gemessen und berechnet werden kann.

layout

layout ist auch das gleiche. Die onLayout-Methode wird intern zurückgerufen, um die Zeichnungsposition der Unteransicht zu bestimmen, aber diese Methode ist eine abstrakte Methode in ViewGroup Wenn Sie also anpassen möchten, dass Ihre Ansicht ViewGroup erbt, müssen Sie diese Methode implementieren. Aber wenn Sie View erben, brauchen Sie es nicht. Es gibt eine leere Implementierung in View. Die Einstellung der Unteransichtsposition besteht darin, die berechneten Werte für links, oben, rechts und unten durch die Layoutmethode der Ansicht zu übergeben. Diese Werte werden im Allgemeinen mithilfe der Breite und Höhe der Ansicht berechnet Die Breite und Höhe der Ansicht können über die Methoden getMeasureWidth und GetMeasureHeight berechnet werden. Der mit diesen beiden Methoden erhaltene Wert ist der von setMeasuredDimension oben in onMeasure übergebene Wert, dh die von der Unteransicht gemessene Breite und Höhe.

getWidth, getHeight und getMeasureWidth, getMeasureHeight sind unterschiedlich. Ersteres ist ein Wert, der nur nach onLayout erhalten werden kann, während letzteres ein Wert ist, der nur nach onLayout erhalten werden kann erhalten nach onMeasure. Die mit diesen beiden Methoden erhaltenen Werte sind jedoch im Allgemeinen gleich. Achten Sie daher auf den Zeitpunkt des Aufrufs.

Das Folgende ist ein Beispiel für die Definition einer Ansicht, die Unteransichten an den vier Ecken der übergeordneten Ansicht platziert:

@Override 
    protected void onLayout(boolean changed, int l, int t, int r, int b) { 
        int count = getChildCount(); 
        MarginLayoutParams params; 
         
        int cl; 
        int ct; 
        int cr; 
        int cb; 
             
        for (int i = 0; i < count; i++) { 
            View child = getChildAt(i); 
            params = (MarginLayoutParams) child.getLayoutParams(); 
                 
            if (i == 0) { 
                //左上角 
                cl = params.leftMargin; 
                ct = params.topMargin; 
            } else if (i == 1) { 
                //右上角 
                cl = getMeasuredWidth() - params.rightMargin - child.getMeasuredWidth(); 
                ct = params.topMargin; 
            } else if (i == 2) { 
                //左下角 
                cl = params.leftMargin; 
                ct = getMeasuredHeight() - params.bottomMargin - child.getMeasuredHeight() 
                 - params.topMargin; 
            } else { 
                //右下角 
                cl = getMeasuredWidth() - params.rightMargin - child.getMeasuredWidth(); 
                ct = getMeasuredHeight() - params.bottomMargin - child.getMeasuredHeight() 
                 - params.topMargin; 
            } 
            cr = cl + child.getMeasuredWidth(); 
            cb = ct + child.getMeasuredHeight(); 
            //确定子视图在父视图中放置的位置 
            child.layout(cl, ct, cr, cb); 
        } 
    }
Nach dem Login kopieren

Was den Implementierungsquellcode von onMeasure betrifft, werde ich ihn später verlinken , wenn Sie den Effekt sehen möchten. Wenn es sich um ein Bild handelt, werde ich es später veröffentlichen, ebenso wie den vorherigen Bestätigungscode

Zeichnung

draw是由dispatchDraw发动的,dispatchDraw是ViewGroup中的方法,在View是空实现。自定义View时不需要去管理该方法。而draw方法只在View中存在,ViewGoup做的只是在dispatchDraw中调用drawChild方法,而drawChild中调用的就是View的draw方法。那么我们来看下draw的源码:

public void draw(Canvas canvas) { 
        final int privateFlags = mPrivateFlags; 
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && 
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState); 
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN; 
          
        /* 
         * Draw traversal performs several drawing steps which must be executed 
         * in the appropriate order: 
         * 
         *      1. Draw the background 
         *      2. If necessary, save the canvas&#39; layers to prepare for fading 
         *      3. Draw view&#39;s content 
         *      4. Draw children 
         *      5. If necessary, draw the fading edges and restore layers 
         *      6. Draw decorations (scrollbars for instance) 
         */ 
           
        // Step 1, draw the background, if needed 
        int saveCount; 
  
        if (!dirtyOpaque) { 
            drawBackground(canvas); 
        } 
          
        // skip step 2 & 5 if possible (common case) 
        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); 
              
            // Overlay is part of the content and draws beneath Foreground 
            if (mOverlay != null && !mOverlay.isEmpty()) { 
                            mOverlay.getOverlayView().dispatchDraw(canvas); 
            } 
                          
            // Step 6, draw decorations (foreground, scrollbars) 
            onDrawForeground(canvas); 
                        
            // we&#39;re done... 
            return; 
        } 
        //省略2&5的情况 
        .... 
}
Nach dem Login kopieren

源码已经非常清晰了draw总共分为6步;

绘制背景

如果需要的话,保存layers

绘制自身文本

绘制子视图

如果需要的话,绘制fading edges

绘制scrollbars

其中 第2步与第5步不是必须的。在第3步调用了onDraw方法来绘制自身的内容,在View中是空实现,这就是我们为什么在自定义View时必须要重写该方法。而第4步调用了dispatchDraw对子视图进行绘制。还是以验证码为例:

@Override 
    protected void onDraw(Canvas canvas) { 
        //绘制背景 
        mPaint.setColor(getResources().getColor(R.color.autoCodeBg)); 
        canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint); 
 
        mPaint.getTextBounds(autoText, 0, autoText.length(), bounds); 
        //绘制文本 
        for (int i = 0; i < autoText.length(); i++) { 
             mPaint.setColor(getResources().getColor(colorRes[random.nextInt(6)])); 
            canvas.drawText(autoText, i, i + 1, getWidth() / 2 - bounds.width() / 2 + i * bounds.width() / autoNum 
                    , bounds.height() + random.nextInt(getHeight() - bounds.height()) 
                    , mPaint); 
        } 
  
        //绘制干扰点 
        for (int j = 0; j < 250; j++) { 
             canvas.drawPoint(random.nextInt(getWidth()), random.nextInt(getHeight()), pointPaint); 
        } 
  
        //绘制干扰线 
        for (int k = 0; k < 20; k++) { 
            int startX = random.nextInt(getWidth()); 
            int startY = random.nextInt(getHeight()); 
            int stopX = startX + random.nextInt(getWidth() - startX); 
            int stopY = startY + random.nextInt(getHeight() - startY); 
             linePaint.setColor(getResources().getColor(colorRes[random.nextInt(6)])); 
            canvas.drawLine(startX, startY, stopX, stopY, linePaint); 
        } 
    }
Nach dem Login kopieren

图,与源码链接

示例图

Benutzerdefinierte Android-Ansicht

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage