Android中怎么自定义View

本篇文章给大家分享的是有关Android中怎么自定义View,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。

成都创新互联公司专注为客户提供全方位的互联网综合服务,包含不限于网站设计制作、成都网站设计、台州网络推广、成都小程序开发、台州网络营销、台州企业策划、台州品牌公关、搜索引擎seo、人物专访、企业宣传片、企业代运营等,从售前售中售后,我们都将竭诚为您服务,您的肯定,是我们最大的嘉奖;成都创新互联公司为所有大学生创业者提供台州建站搭建服务,24小时服务热线:18982081108,官方网址:www.cdcxhl.com

流程

在Android中对于布局的请求绘制是在Android  framework层开始处理的。绘制是从根节点开始,对布局树进行measure与draw。在RootViewImpl中的performTraversals展开。它所做的就是对需要的视图进行measure(测量视图大小)、layout(确定视图的位置)与draw(绘制视图)。下面的图能很好的展现视图的绘制流程:  

Android中怎么自定义View

当用户调用requestLayout时,只会触发measure与layout,但系统开始调用时还会触发draw

下面来详细介绍这几个流程。

measure

measure是View中的final型方法不可以进行重写。它是对视图的大小进行测量计算,但它会回调onMeasure方法,所以我们在自定义View的时候可以重写onMeasure方法来对View进行我们所需要的测量。它有两个参数widthMeasureSpec与heightMeasureSpec。其实这两个参数都包含两部分,分别为size与mode。size为测量的大小而mode为视图布局的模式

我们可以通过以下代码分别获取:

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

获取到的mode种类分为以下三种:

MODEEXPLAIN
UNSPECIFiED父视图不对子视图进行约束,子视图大小可以是任意大小,一般是对ListViewScrollView等进行自定义,一般用不到
EXACTLY父视图对子视图设定了一个精确的尺寸,子视图不超过该尺寸,一般为精确的值例如200dp或者使用了match_parent
AT_MOST父视图对子视图指定了一***的尺寸,确保子视图的所以内容都刚好能在该尺寸中显示出来,一般为wrap_content,这种父视图不能获取子视图的大小,只能由子视图自己去计算尺寸,这也是我们测量要实现的逻辑情况

setMeasuredDimension

通过以上逻辑获取视图的宽高,***要调用setMeasuredDimension方法将测量好的宽高进行传递出去。其实最终是调用setMeasuredDimensionRaw方法对传过来的值进行属性赋值。调用super.onMeasure()的调用逻辑也是一样的。

下面以自定义一个验证码的View为例,它的onMeasure方法如下:

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

可以对自定义View的layout_width与layout_height进行设置不同的属性,达到不同的mode类型,就可以看到不同的效果

measureChildren

如果你是对继承ViewGroup的自定义View那么在进行测量自身的大小时还要测量子视图的大小。一般通过measureChildren(int  widthMeasureSpec, int heightMeasureSpec)方法来测量子视图的大小。

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方法,那么来看下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);     }

会发现它首先调用了getChildMeasureSpec方法来分别获取宽高,***再调用的就是View的measure方法,而通过前面的分析我们已经知道它做的就是对视图大小的计算。而对于measure中的参数是通过getChildMeasureSpec获取,再来看下其源码:

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

是不是容易理解了点呢。它做的就是前面所说的根据mode的类型,获取相应的size。根据父视图的mode类型与子视图的LayoutParams类型来决定子视图所属的mode,***再将获取的size与mode通过MeasureSpec.makeMeasureSpec方法整合返回。***传递到measure中,这就是前面所说的widthMeasureSpec与heightMeasureSpec中包含的两部分的值。整个过程为measureChildren->measureChild->getChildMeasureSpec->measure->onMeasure->setMeasuredDimension,所以通过measureChildren就可以对子视图进行测量计算。

layout

layout也是一样的内部会回调onLayout方法,该方法是用来确定子视图的绘制位置,但这个方法在ViewGroup中是个抽象方法,所以如果要自定义的View是继承ViewGroup的话就必须实现该方法。但如果是继承View的话就不需要了,View中有一个空实现。而对子视图位置的设置是通过View的layout方法通过传递计算出来的left、top、right与bottom值,而这些值一般都要借助View的宽高来计算,视图的宽高则可以通过getMeasureWidth与getMeasureHeight方法获取,这两个方法获取的值就是上面onMeasure中setMeasuredDimension传递的值,即子视图测量的宽高。

getWidth、getHeight与getMeasureWidth、getMeasureHeight是不同的,前者是在onLayout之后才能获取到的值,分别为left-right与top-bottom;而后者是在onMeasure之后才能获取到的值。只不过这两种获取的值一般都是相同的,所以要注意调用的时机。

下面以定义一个把子视图放置于父视图的四个角的View为例:

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

至于onMeasure的实现源码我后面会给链接,如果要看效果图的话,我后面也会贴出来,前面的那个验证码的也是一样

draw

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' layers to prepare for fading          *      3. Draw view'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're done...             return;         }         //省略2&5的情况         .... }

源码已经非常清晰了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);         }     }

以上就是Android中怎么自定义View,小编相信有部分知识点可能是我们日常工作会见到或用到的。希望你能通过这篇文章学到更多知识。更多详情敬请关注创新互联行业资讯频道。

网站栏目:Android中怎么自定义View
标题链接:https://www.cdcxhl.com/article22/jcggcc.html

成都网站建设公司_创新互联,为您提供网站排名用户体验面包屑导航云服务器软件开发营销型网站建设

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联

h5响应式网站建设