android开发自定义View的一些知识点总结

1,解决ScrollView和ListView冲突问题(1.3)

自定义一个MyListView继承自ListView,之后重写onMeasure()方法.具体的请看ScrollView源码和ListView源码


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        heightMeasureSpec=MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE>>2,MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

2,事件分发使用的是责任链模式(1.4)

重点关注ViewGroup的dispatchTouchEvent()方法,这个方法里面的while()循环。这里懂了就知道为什么必须返回onTouchEvent()必须要返回true才能响应后续事件。

 while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }

3,自定义属性的一些细节(1.5,第二节)

1,在value文件夹下面新建attrs文件(名字可以随便取)

<resources>
    <declare-styleable name="MyTextView">
        <attr name="text" format="string"/>
        <attr name="textSize" format="dimension"/>
        <attr name="textColor" format="color"/>
        <attr name="maxLength" format="integer"/>
        <attr name="background" format="reference|color"/>
        <!--枚举-->
        <attr name="inputType">
            <enum name="number" value="1"/>
            <enum name="text" value="2"/>
            <enum name="pasword" value="3"/>
        </attr>
    </declare-styleable>
</resources>

2,在xml布局总引用

<LinearLayout 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:orientation="vertical">

    <com.example.didi.myproject.MyTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:text="Draaen"
        app:textSize="18sp"
        app:textColor="@color/colorAccent"/>

</LinearLayout>

3,在MyTextView中得到属性

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.View;

public class MyTextView extends View {

    private String mText;
    private int mTextSize=15;//默认大小是15
    private int mTextColor=Color.BLACK;//默认是黑色

    public MyTextView(Context context) {
        this(context,null);
    }

    public MyTextView(Context context,AttributeSet attrs) {
        this(context, attrs,0);
    }

    public MyTextView(Context context,AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray=context.obtainStyledAttributes(attrs,R.styleable.MyTextView);

        mText=typedArray.getString(R.styleable.MyTextView_text);
        mTextSize=typedArray.getDimensionPixelSize(R.styleable.MyTextView_textSize,mTextSize);
        mTextColor=typedArray.getColor(R.styleable.MyTextView_textColor,mTextColor);
        typedArray.recycle();
    }
    
}

3,绘制自定义TextView

3.1,绘制宽高:有三种模式,根据不同的模式确定不同的宽高


public class MyTextView extends View {

    private String mText;
    private int mTextSize=15;//默认大小是15
    private int mTextColor=Color.BLACK;//默认是黑色

    private Paint mPaint;

    public MyTextView(Context context) {
        this(context,null);
    }

    public MyTextView(Context context,AttributeSet attrs) {
        this(context, attrs,0);
    }

    public MyTextView(Context context,AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray=context.obtainStyledAttributes(attrs,R.styleable.MyTextView);

        mText=typedArray.getString(R.styleable.MyTextView_ylytext);
        mTextSize=typedArray.getDimensionPixelSize(R.styleable.MyTextView_ylytextSize,mTextSize);
        mTextColor=typedArray.getColor(R.styleable.MyTextView_ylytextColor,mTextColor);
        typedArray.recycle();

        mPaint=new Paint();
        mPaint.setColor(mTextColor);
        mPaint.setTextSize(mTextSize);//抗锯齿
        mPaint.setAntiAlias(true);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec,heightMeasureSpec);

        int widthMode=MeasureSpec.getMode(widthMeasureSpec);
        int heightMode=MeasureSpec.getMode(heightMeasureSpec);

        //如果给的是确定值的话,不用计算,给的是多少就是多少
        int width=MeasureSpec.getSize(widthMeasureSpec);
        //如果给的是wrap_content的话就要计算
        if (widthMode==MeasureSpec.AT_MOST){
            //计算的火长度与字体的大小和字的长度有关,用画笔来测量
            Rect bounds=new Rect();
            mPaint.getTextBounds(mText,0,mText.length(),bounds);
            width=bounds.width();
            Log.e("yly","---"+width+"---");
        }

        //计算高度,与计算宽度的方法一样,因为高度也有可能是包裹内容
        int height=MeasureSpec.getSize(heightMeasureSpec);
        if (heightMode==MeasureSpec.AT_MOST){
            Rect bounds=new Rect();
            mPaint.getTextBounds(mText,0,mText.length(),bounds);
            height=bounds.height();
            Log.e("yly","---"+height+"---");
        }

        //设置控件的宽高
        setMeasuredDimension(width,height);
    }
    
}

3.2,使用onDraw()方法绘制文本

这一步最重要的是计算baseLine基线的位置


public class MyTextView extends View {

    private String mText;
    private int mTextSize=15;//默认大小是15
    private int mTextColor=Color.BLACK;//默认是黑色

    private Paint mPaint;

    public MyTextView(Context context) {
        this(context,null);
    }

    public MyTextView(Context context,AttributeSet attrs) {
        this(context, attrs,0);
    }

    public MyTextView(Context context,AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray=context.obtainStyledAttributes(attrs,R.styleable.MyTextView);

        mText=typedArray.getString(R.styleable.MyTextView_ylytext);
        mTextSize=typedArray.getDimensionPixelSize(R.styleable.MyTextView_ylytextSize,sp2px(mTextSize));
        mTextColor=typedArray.getColor(R.styleable.MyTextView_ylytextColor,mTextColor);
        typedArray.recycle();

        mPaint=new Paint();
        mPaint.setColor(mTextColor);
        mPaint.setTextSize(mTextSize);//抗锯齿
        mPaint.setAntiAlias(true);
    }

    //把sp转换成px
    private int sp2px(int sp){
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,sp,getResources().getDisplayMetrics());
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec,heightMeasureSpec);

        int widthMode=MeasureSpec.getMode(widthMeasureSpec);
        int heightMode=MeasureSpec.getMode(heightMeasureSpec);

        //如果给的是确定值的话,不用计算,给的是多少就是多少
        int width=MeasureSpec.getSize(widthMeasureSpec);
        //如果给的是wrap_content的话就要计算
        if (widthMode==MeasureSpec.AT_MOST){
            //计算的火长度与字体的大小和字的长度有关,用画笔来测量
            Rect bounds=new Rect();
            mPaint.getTextBounds(mText,0,mText.length(),bounds);
            width=bounds.width();
            Log.e("yly","---"+width+"---");
        }

        //计算高度,与计算宽度的方法一样,因为高度也有可能是包裹内容
        int height=MeasureSpec.getSize(heightMeasureSpec);
        if (heightMode==MeasureSpec.AT_MOST){
            Rect bounds=new Rect();
            mPaint.getTextBounds(mText,0,mText.length(),bounds);
            height=bounds.height();
            Log.e("yly","---"+height+"---");
        }

        //设置控件的宽高
        setMeasuredDimension(width,height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Paint.FontMetricsInt fontMetrics=mPaint.getFontMetricsInt();
        int dy= (fontMetrics.bottom-fontMetrics.top)/2-fontMetrics.bottom;
        int baseLine=getHeight()/2+dy;
        canvas.drawText(mText,0,baseLine,mPaint);
    }
}

3.3,设置padding值,首先在xml文件中设置padding,然后分别改变width,和height


public class MyTextView extends View {

    private String mText;
    private int mTextSize=15;//默认大小是15
    private int mTextColor=Color.BLACK;//默认是黑色

    private Paint mPaint;

    public MyTextView(Context context) {
        this(context,null);
    }

    public MyTextView(Context context,AttributeSet attrs) {
        this(context, attrs,0);
    }

    public MyTextView(Context context,AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray=context.obtainStyledAttributes(attrs,R.styleable.MyTextView);

        mText=typedArray.getString(R.styleable.MyTextView_ylytext);
        mTextSize=typedArray.getDimensionPixelSize(R.styleable.MyTextView_ylytextSize,sp2px(mTextSize));
        mTextColor=typedArray.getColor(R.styleable.MyTextView_ylytextColor,mTextColor);
        typedArray.recycle();

        mPaint=new Paint();
        mPaint.setColor(mTextColor);
        mPaint.setTextSize(mTextSize);//抗锯齿
        mPaint.setAntiAlias(true);
    }

    //把sp转换成px
    private int sp2px(int sp){
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,sp,getResources().getDisplayMetrics());
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec,heightMeasureSpec);

        int widthMode=MeasureSpec.getMode(widthMeasureSpec);
        int heightMode=MeasureSpec.getMode(heightMeasureSpec);

        //如果给的是确定值的话,不用计算,给的是多少就是多少
        int width=MeasureSpec.getSize(widthMeasureSpec);
        //如果给的是wrap_content的话就要计算
        if (widthMode==MeasureSpec.AT_MOST){
            //计算的火长度与字体的大小和字的长度有关,用画笔来测量
            Rect bounds=new Rect();
            mPaint.getTextBounds(mText,0,mText.length(),bounds);
            width=bounds.width()+getPaddingLeft()+getPaddingRight();
        }

        //计算高度,与计算宽度的方法一样,因为高度也有可能是包裹内容
        int height=MeasureSpec.getSize(heightMeasureSpec);
        if (heightMode==MeasureSpec.AT_MOST){
            Rect bounds=new Rect();
            mPaint.getTextBounds(mText,0,mText.length(),bounds);
            height=bounds.height()+getPaddingBottom()+getPaddingTop();
        }

        //设置控件的宽高
        setMeasuredDimension(width,height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Paint.FontMetricsInt fontMetrics=mPaint.getFontMetricsInt();
        int dy= (fontMetrics.bottom-fontMetrics.top)/2-fontMetrics.bottom;
        int baseLine=getHeight()/2+dy;
        Rect bounds =new Rect();
        
        mPaint.getTextBounds(mText,0,mText.length(),bounds);
        int dx=(getWidth()-bounds.width())/2;
        //还有一种方法计算:dx=getPaddingLeft()
        canvas.drawText(mText,dx,baseLine,mPaint);
    }
}

最后讲解一个面试题:

如果自定义的MyTextView继承自LinearLayout的话字体能够正常显示?答案是:如果MyTextView设置了背景色就会显示没如果不设置背景色就不会显示,下面解释为什么:

LinearLayout继承自ViewGroup,viewGroup继承自View,所以直接看View中的draw()方法,因为draw方法内部调用的是onDraw()。

在这个方法里主要关注三个方法:(这里使用到了模板设计模式)

// Step 3, draw the content最重要的还是画,
//可以看出只要dirtyOpaque为false就会执行onDraw()
if (!dirtyOpaque) onDraw(canvas);

dispatchDraw(canvas);
onDrawForeground(canvas);

那说了半天了直接继承自View就可以出来,但是继承自LinearLayout就不行了呢?

我们可以知道dirtyOpaque这个值是有下面决定的:
 

final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);

而privateFlags决定着整个,

final int privateFlags = mPrivateFlags;

直接找mPrivateFlgs在哪赋值就行了。在View构造方法中的最后一行调用了

computeOpaqueFlags()这个方法:
 protected void computeOpaqueFlags() {
        // Opaque if:
        //   - Has a background
        //   - Background is opaque
        //   - Doesn't have scrollbars or scrollbars overlay

        if (mBackground != null && mBackground.getOpacity() == PixelFormat.OPAQUE) {
            mPrivateFlags |= PFLAG_OPAQUE_BACKGROUND;
        } else {
            mPrivateFlags &= ~PFLAG_OPAQUE_BACKGROUND;
        }

        final int flags = mViewFlags;
        if (((flags & SCROLLBARS_VERTICAL) == 0 && (flags & SCROLLBARS_HORIZONTAL) == 0) ||
                (flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_INSIDE_OVERLAY ||
                (flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_OUTSIDE_OVERLAY) {
            mPrivateFlags |= PFLAG_OPAQUE_SCROLLBARS;
        } else {
            mPrivateFlags &= ~PFLAG_OPAQUE_SCROLLBARS;
        }
    }

下面再来看ViewGrou为什么出不来,主要是因为ViewGroup调用了

  private void initViewGroup() {
        // ViewGroup doesn't draw by default
        if (!debugDraw()) {
            setFlags(WILL_NOT_DRAW, DRAW_MASK);
        }

setFlags会导致mPrivateFlags会重新赋值。那么if (!dirtyOpaque) onDraw(canvas);就进不去了,

但是为什么有了北京就有进去了呢?

在view中有这样一个方法:

public void setBackgroundDrawable(Drawable background) {
    computeOpaqueFlags();

这里可以看到computeOpaqueFlags()又计算了一遍。所以就出来了

解决这个问题有以下三种方法:
1,设置透明背景色:

setBackgroundColor(Color.TRANSPARENT);

2,把onDraw()方法变为dispatchOnDraw()

3,改写setWillNotDraw()

wm.addView(decor,1)才开始把我们的DecorView加载到我们的Windows中去

这个时候才开始View的绘制流程

measure()——>layout()——>draw()流程才开始

对于View view=View.inflate(this,R.layout.activity,null/textView),这句话,是不可获取View的高度的,因为这只是把View实例化了而已,并没有把他加载到任何父布局.如果把第三个参数改成一个ViewGroup就能够获取到。

WindowManager是一个接口,其实现类是WindowMangerImpl,所以接下来就应该去WindowManagerImpl

wm.addView(decor, l);---->WindowManagerImpl.addView()---->mGlobal.addView()这就来到了WindowManagerGlobal的addView的方法中,首先关注一下:
ViewRootImpl root;是不是优点熟悉这个root,root到底是在哪里实例化的呢?

往下面看就能看到了:

root = new ViewRootImpl(view.getContext(), display);(ViewRootImpl类是继承自ViewParent,ViewParent是一个接口,其中requestLayout(),invalidateChildInParent()等方法均在这个接口里面)接着执行了下面三个,把他们加入集合:

在invalidate()源码的时候讲解了ViewRootImpl这个类

mViews.add(view);
mRoots.add(root);
mParams.add(wparams);

关键是下面一句话:

root.setView(view, wparams, panelParentView);接着会执行ViewRootImpl中的:
requestLayout();---->scheduleTraversals()---->postCallback(mTraversalRunnable)---->doTraversal()---->performTraversals()在这个方法中开始执行:
performMeasure()----->performLayout()------>perforDraw()

下面就开始讲activity_main.xml布局的绘制的流程:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.myproject.MainActivity">
    
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <TextView
            android:id="@+id/textView_1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"
             />
        <TextView
            android:id="@+id/textView_2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"
            />
        <TextView
            android:id="@+id/textView_3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"
            />
    </LinearLayout>
</LinearLayout>

在这个布局中三个TextView放在第二层LinearLayout中,在绘制View的时候首先执行performTraversals()中的performMeasure()方法:

performMeasure()调用:

mView.measure(childWidthMeasureSpec, childHeightMeasureSpec)

mView(其实也就是Decore)会通过measure会调用第一层布局LinearLayout的onMeasure,这个时候就应该来到LinearLayout的onMeasure()方法中:

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }
在这里进入measureVertical()之后,会执行measureChildBeforeLayoute(),之后执行measureChildWithMargins,
 protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
 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'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'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);
    }

由此可以看出,子View的大小和模式是父布局和子View共同决定的。

child.measure()方法会执行View中的measure()方法,在View的measure()方法中会执行onMeasure()方法,onMeasure()方法没有做什么特别的事情,主要就是赋值:

onMeasure---->setMeasuredDimension---->setMeasuredDimensionRaw在这个方法中进行了最终的赋值:

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }
至此View就能获取到高度和宽度了。

View的大小和模式确定之后接下来就要测量夫布局的宽高和模式了;总结起来测量可以这么说:

确定子View的尺寸的时候是从外往里走的:ViewRootImpl--->Decore--->ViewGroup--->子View

确定完成子View尺寸之后就要向外走了:ViewRootImpl<----Decore<----ViewGroup<----子View

下面讲解performLayout方法:会调用

host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());(在这里host肯定是最外层的Decor布局)

view类中的onLayout是一个空方法,谁需要实就去按照自己的标准去实现(用于摆放子View),在这里举个例子看看LinearLayout中的onLayout方法,

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

在LinearLayout的onLayout方法中首先判断布局是垂直还是水平,因为垂直和水平的时候测量的是不一样的,然后会调用setChildFrame方法,在这个方法中会调用child.layout()

接下来就要讲解performDraw方法了,它主要的作用就是用来绘制自己了子View(当然也包括绘制背景):

performDraw调用了View的draw()方法,在View的draw()方法中,利用了模板设计模式,在draw的时候会遵循一套流程:

draw  BackGround(画背景,如果设置的有背景,就要先绘制自己的背景,然后采取遍历子View绘制他们)

onDraw(canvas)画自己,ViewGroup默认不会调用这个方法

dispatchDraw(canvas)    画子View,肯定也是不断的循环调用子View的onDraw方法

然后dispatchDraw()这个方法中什么都没有写,这就是给了你一套模板,让你自己去实现,(模板设计模式在AsynTask中也使用到了)

ViewGroup默认情况下不会走onDraw方法,

总结一下:

第一步:performMeasure(),用于指定和测量layout中所有控件的宽高,对于ViewGroup要先去测量里面子孩子,然后根据子孩子的宽高再来计算和自定义自己的宽高,对于View,它的宽高是由自己和父布局决定的。

第二步:performLayout(),用于摆放子布局for循环所有子View,用child.layout()摆放ChildView

第三步:performDraw(),用于绘制自己还有子View,对于ViewGroup来说,首先绘制自己的背景,for循环绘制子view,调用子view的

draw()方法,对于View来说,只需要绘制自己的背景和绘制自己要显示的内容。

 
 
 
 
 
 
 
 
 
发布了75 篇原创文章 · 获赞 31 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/yaoyaoyao_123/article/details/90707414