技术控

    今日:27| 主题:49507
收藏本版 (1)
最新软件应用技术尽在掌握

[其他] View 的工作原理下 View 的 layout 和 draw 过程 (Android 开发艺术探索读书笔记) ...

[复制链接]
我在等我的永恒- 发表于 2016-10-5 19:19:22
84 3

立即注册CoLaBug.com会员,免费获得投稿人的专业资料,享用更多功能,玩转个人品牌!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
本次介绍的主要内容是 View 的工作原理下 View 的 layout 和 draw 过程,同时介绍自定义 View 的注意事项并结合一个小的 Demo 进行说明,其中涉及到的 onMeasre 测量部分知识可以看上一篇文章    View 的工作原理上 View 绘制流程梳理及 Measure 过程详解,以下开始正文:  
  二、 layout 过程详解  

  layout 的作用是 ViewGroup 来确定子元素的位置,当 ViewGroup 的位置被确定后,在 layout 中会调用 onLayout ,在 onLayout 中会遍历所有的子元素并调用子元素的 layout 方法,在子元素的 layout 方法中 onLayout 方法又会被调用,layout 方法是确定 View 本身在屏幕上显示的具体位置,即在代码中设置其成员变量 mLeft,mTop,mRight,mBottom 的值,这几个值是在屏幕上构成矩形区域的四个坐标点,就是该 View 显示的位置,不过这里的具体位置都是相对与父视图的位置而言,而 onLayout 方法则会确定所有子元素位置,ViewGroup 在 onLayout 函数中通过调用其 children 的 layout 函数来设置子视图相对与父视图中的位置,具体位置由函数 layout 的参数决定。下面我们先看 View 的layout 方法如下:
  1. /*
  2. [email protected] l view 左边缘相对于父布局左边缘距离
  3. [email protected] t view 上边缘相对于父布局上边缘位置
  4. [email protected] r view 右边缘相对于父布局左边缘距离
  5. [email protected] b view 下边缘相对于父布局上边缘距离
  6. */
  7. publicvoidlayout(intl,intt,intr,intb){
  8. if((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) !=0) {
  9. onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
  10. mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
  11. }
  12. //记录 view 原始位置
  13. intoldL = mLeft;
  14. intoldT = mTop;
  15. intoldB = mBottom;
  16. intoldR = mRight;
  17. //第1步,调用 setFrame 方法 设置新的 mLeft、mTop、mBottom、mRight 值,
  18. //设置 View 本身四个顶点位置
  19. //并返回 changed 用于判断 view 布局是否改变
  20. booleanchanged = isLayoutModeOptical(mParent) ?
  21. setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
  22. //第二步,如果 view 位置改变那么调用 onLayout 方法设置子 view 位置
  23. if(changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
  24. //调用 onLayout
  25. onLayout(changed, l, t, r, b);
  26. mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
  27. ListenerInfo li = mListenerInfo;
  28. if(li !=null&& li.mOnLayoutChangeListeners !=null) {
  29. ArrayList<OnLayoutChangeListener> listenersCopy =
  30. (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
  31. intnumListeners = listenersCopy.size();
  32. for(inti =0; i < numListeners; ++i) {
  33. listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
  34. }
  35. }
  36. }
复制代码
layout 方法大致流程:先通过上面代码第一步调用 setFrame 设置 view 本身四个顶点位置,其中 setOpticalFrame 内部也是调用 setFrame 方法来完成设置的,即为 View 的4个成员变量(mLeft,mTop,mRight,mBottom)赋值,View 的四个顶点一旦确定,那么 View 在父容器中的位置就确定了,接着进行第二步,调用 onLayout 方法,这个方法用途是父容器确定子 View 位置,和 onMeasure 方法类似, onLayout 方法的具体实现同样和具体布局有关,所以 View 和 ViewGroup 中都没有真正实现 onLayout 方法,都是一个空方法。
  先看一下 View 的 onLayout 方法:
            void onLayout(boolean changed, int left, int top, int right, int bottom) {}```                              
  1. 那么对于我们自定义的 View 是继承自 View 的情况下,我们一般不需要重写 onLayout 方法,因为 这个方法用途是父容器确定子 View 位置,对于 View 来说是没有子 View 的,所以一般不需要重写。
  2. 再看一下 ViewGroup 的 onLayout 方法:
  3. ```java
  4. protected abstract void onLayout(boolean changed,
  5. int l, int t, int r, int b);
复制代码
                     相对于 view 来说,ViewGroup 中 onLayout 多了关键字abstract的修饰,也就说对于继承自 ViewGroup 的自定义 View 必须要重写 onLayout 方法,而重载 onLayout 的目的就是安排其子元素在父视图中的具体位置,为了更好的理解,接下来我们看一下 LinearLayout 的 onLayout 方法:
  1. @Override
  2. protectedvoidonLayout(booleanchanged,intl,intt,intr,intb){
  3. if(mOrientation == VERTICAL) {
  4. layoutVertical(l, t, r, b);
  5. } else{
  6. layoutHorizontal(l, t, r, b);
  7. }
  8. }
复制代码
和 onMeasure 类似,这里也是分为竖直方向和水平方向的布局安排,二者原理一样,我们选择竖直方向的 layoutVertical 来进行分析,这里给出主要代码如下:
  1. voidlayoutVertical(intleft,inttop,intright,intbottom){
  2. finalintpaddingLeft = mPaddingLeft;
  3. //记录子 View 上边缘相对于父容器上边缘距离
  4. intchildTop;
  5. //记录子 View 左边缘相对于父容器左边缘距离
  6. intchildLeft;
  7. //第1步,主要是根据不同的 gravity 属性来确定子元素的 child 的位置
  8. switch(majorGravity) {
  9. caseGravity.BOTTOM:
  10. // mTotalLength contains the padding already
  11. childTop = mPaddingTop + bottom - top - mTotalLength;
  12. break;
  13. // mTotalLength contains the padding already
  14. caseGravity.CENTER_VERTICAL:
  15. childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
  16. break;
  17. caseGravity.TOP:
  18. default:
  19. childTop = mPaddingTop;
  20. break;
  21. }
  22. ...............................
  23. //第2步,循环遍历子 view
  24. for(inti =0; i < count; i++) {
  25. //获取指定位置 view
  26. finalView child = getVirtualChildAt(i);
  27. if(child ==null) {
  28. childTop += measureNullChild(i);
  29. } elseif(child.getVisibility() != GONE) {
  30. //第2.1步,如果 view 可见,获取 view 的测量宽/高
  31. finalintchildWidth = child.getMeasuredWidth();
  32. finalintchildHeight = child.getMeasuredHeight();
  33. //获取 view 的 LayoutParams 参数
  34. finalLinearLayout.LayoutParams lp =
  35. (LinearLayout.LayoutParams) child.getLayoutParams();
  36. .............
  37. if(hasDividerBeforeChildAt(i)) {
  38. childTop += mDividerHeight;
  39. }
  40. childTop += lp.topMargin;
  41. //第3步,设置子 view 位置
  42. setChildFrame(child, childLeft, childTop + getLocationOffset(child),
  43. childWidth, childHeight);
  44. //第4步,重新计算子 view 的 顶部 top 位置,也就是每增加一个子 view
  45. //下一个子 view 的 top 顶部位置就会相应的增加
  46. childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
  47. i += getChildrenSkipCount(child, i);
  48. }
  49. }
  50. }
复制代码
简单梳理下整个流程,此方法会遍历所有子 view ,并调用 setChildFrame 方法来设定子元素位置,然后重新计算 childTop ,childTop 随着子元素的遍历而逐渐增大,这就意味着后面的子元素会被放置在当前子元素的下方,这正是我们平时使用竖直方向 LinearLayout 的特性。这里我们看一下第三步执行的 setChildFrame 方法类设置子元素位置方法代码:
  1. privatevoidsetChildFrame(View child,intleft,inttop,intwidth,intheight){
  2. child.layout(left, top, left + width, top + height);
  3. }
复制代码
可以发现,这个方法只是调用子元素的 layout 方法而已,这样父元素在自己的 layout 方法中完成自己的定位之后,通过 onLayout 方法去调用了子元素的 layout 方法,子元素又会通过自己的 layout 方法完成自己的位置设定,这样一层一层的传递下去就完成了整个 view 数的 layout 过程。
  这里我们注意到在第三步调用 setChildFrame 方法中的 传入的参数 childWidth 和 childHeight 是上面第2.1步获取的子元素的测量宽/高,而在 layout 过程中会通过 setFrame 方法设置子元素四个顶点位置,这样子元素的位置就确定了,在 setFrame 中有如下赋值语句:
  1. mLeft = left;
  2. mTop = top;
  3. mRight = right;
  4. mBottom = bottom;
复制代码
也就是说在 LinearLayout 中其子视图显示的宽和高由 measure 过程来决定的,因此 measure 过程的意义就是为 layout 过程提供视图显示范围的参考值。为什么说是提供参考值呢?因为 layout 过程中的4个参数 left, top, iwidth, height 完全可以由视图设计者任意指定,而最终视图的布局位置和大小完全由这4个参数决定,measure 过程得到的mMeasuredWidth 和 mMeasuredHeight 提供了视图大小的测量值,只是提供一个参考一般情况下我们使用这个参考值,但我们完全可以不使用这两个值,而自己在 layout 过程中去设定一个值,可见 measure 过程并不是必须的。
  说到这里就不得说一下 getWidth() 、getHeight() 和 getMeasuredWidth()、getMeasuredHeight() 这两对函数之间的区别,即 View 的测量宽/高和最终显示宽/高之间的区别。首先我们看一下 getWith() 和 getHeight() 方法的具体实现:
  1. publicfinalintgetWidth(){
  2. returnmRight - mLeft;
  3. }
  4. publicfinalintgetHeight(){
  5. returnmBottom - mTop;
  6. }
复制代码
通过 getWith() 和 getHeight() 源码和上面 setChildFrame(View child, int left, int top, int width, int height) 方法设置子元素四个顶点位置的四个变量 mLeft、mTop、mRight、mBottom 的赋值过程来看,默认情况下 getWidth() 、getHeight() 方法返回的值正好就是 view 的测量宽/高,只不过 view 的测量宽/高形成于 view 的measure 过程,而最终宽/高形成于 view 的 layout 方法中,但是对于特殊情况,两者的值是不相等的,就是我们在 layout 过程中不按默认常规套路出牌,即不使用 measure 过程得到的 mMeasuredWidth 和 mMeasuredHeight ,而是人为的去自己根据需要设定的一个值的情况,例如以下代码,重写 view 的 layout 方法:
  1. publicvoidlayout(intl,intt,intr,intb){
  2. //在得到的测量值基础上加100
  3. super.layout(intl,intt,intr+100,intb+100);
  4. }
复制代码
上面代码会导致在任何情况下 view 的最终宽/高总会比测量宽高大100px。
  三、 draw 过程详解  

  draw 的作用是将 view 绘制到屏幕上,view 的绘制过程准守以下几个步骤:
  
       
  • 绘制背景:      background.draw(canvas);   
  • 绘制自己:      onDraw();   
  • 绘制 children:      dispatchDraw;   
  •       绘制装饰:        onDrawScrollBars。      
          通过源码可以看出来,部分源码如下:
      
  1. publicvoiddraw(Canvas canvas){
  2. finalintprivateFlags = mPrivateFlags;
  3. finalbooleandirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
  4. (mAttachInfo == null|| !mAttachInfo.mIgnoreDirtyState);
  5. mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
  6. /*
  7. * Draw traversal performs several drawing steps which must be executed
  8. * in the appropriate order:
  9. *
  10. * 1. Draw the background
  11. * 2. If necessary, save the canvas' layers to prepare for fading
  12. * 3. Draw view's content
  13. * 4. Draw children
  14. * 5. If necessary, draw the fading edges and restore layers
  15. * 6. Draw decorations (scrollbars for instance)
  16. */
  17. // Step 1, draw the background, if needed
  18. //绘制背景
  19. intsaveCount;
  20. if(!dirtyOpaque) {
  21. drawBackground(canvas);
  22. }
  23. // skip step 2 & 5 if possible (common case)
  24. finalintviewFlags = mViewFlags;
  25. booleanhorizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) !=0;
  26. booleanverticalEdges = (viewFlags & FADING_EDGE_VERTICAL) !=0;
  27. if(!verticalEdges && !horizontalEdges) {
  28. // Step 3, draw the content
  29. //调用 onDraw 方法,绘制自己本身内容,这个方法是个空方法,没有具体实现,
  30. //因为每个视图的内容部分肯定都是各不相同的,这部分的功能交给子类来去实现,
  31. //如果要自定义 view ,需要重载该方法完成绘制工作
  32. if(!dirtyOpaque) onDraw(canvas);
  33. // Step 4, draw the children
  34. //绘制子视图
  35. //View 中的 dispatchDraw()方法也是一个空方法,因为 view 本身没有子视图,所以不需要,
  36. //而 ViewGroup 的 dispatchDraw() 方法中就会有具体的绘制代码,来实现子视图的绘制工作
  37. dispatchDraw(canvas);
  38. // Overlay is part of the content and draws beneath Foreground
  39. if(mOverlay !=null&& !mOverlay.isEmpty()) {
  40. mOverlay.getOverlayView().dispatchDraw(canvas);
  41. }
  42. // Step 6, draw decorations (foreground, scrollbars)
  43. //绘制装饰
  44. //对视图的滚动条进行绘制,其实任何一个视图都是有滚动条的,只是一般情况下都没有让它显示出来,
  45. //而例如像 ListView 等控件是进行了显示而已。
  46. onDrawForeground(canvas);
  47. // we're done...
  48. return;
  49. }
复制代码
通过上面代码可以发现,View 绘制过程的传递是通过 dispatchDraw() 方法完成,这个方法会遍历调用所有子视图的 draw ()方法,这样事件就一层一层的传递下去了。其中 View 中有一个特殊方法 setWillNotDraw ,源码如下:
  1. /**
  2. * If this view doesn't do any drawing on its own, set this flag to
  3. * allow further optimizations. By default, this flag is not set on
  4. * View, but could be set on some View subclasses such as ViewGroup.
  5. *
  6. * Typically, if you override [email protected]#onDraw(android.graphics.Canvas)}
  7. * you should clear this flag.
  8. *
  9. * @paramwillNotDraw whether or not this View draw on its own
  10. */
  11. publicvoidsetWillNotDraw(booleanwillNotDraw){
  12. setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
  13. }
复制代码
看注释部分大概意思是,如果一个 View 不需要绘制任何内容,那么设置这个标记位为 true 后,系统会进行相应的优化,默认情况下 View 没有启动这个默认标记位,但 viewGroup 默认启用这个标记位,这个标记位对实际开发的意义是:当我们的自定义的控件继承自 viewGroup 并且本身不具备绘制功能的时候,就可以开启这个标记位,从而便于系统进行后续的优化工作,当我们明确知道 viewGrop 需要通过 onDraw 来绘制本身内容时,需要我们去关闭 WILL_NOT_DRAW 这个标记位。
  四、自定义 View  

  4.1 自定义 View 分类  

  
       
  •       继承自 View 重写 ondraw 方法

          这种方法主要用于实现一些不规则的效果,即想要达到的 View 效果无法使用已有的 View 通过布局组合的方式来实现,所以需要我们自己去绘制去画一个出来,即重写 onDraw 方法,采用这种方式需要注意处理自定义的 View 支持 wrap_content ,并且 padding 也需要自己处理。
       
  •       继承自 ViewGroup 实现特殊的 Layout 容器

          主要实现除了 LinearLayout 、 RelativeLayout 等系统已有的 View 容器之外的特殊 View 容器,需要处理 ViewGroup 的测量 onMeasure 和布局 onLayout 这两个方法,并同时处理子元素的测量和布局。
       
  •       继承自 Android 系统本身已有的特定 View (如 TextView)

          这种方法是要是为拓展某个已有 View 的功能,在已有的 View 的基础上添加一些功能,方便我们重复使用,这种方法不需要我们进行特殊的处理。
       
  •       继承自 Android 系统本身已有的特定的 ViewGroup (如 LinearLayout)

          这种方法主要是为了实现将几个 View 组合在一起形成一个特定的组合模块,来方便我们后续进行使用,例如我们想要一个特定的 TitleBar ,我们可以可以将几个 TextView 和 Button 放在一个 LinearLayout 布局中组合成一个自定义的控件,采用这种方式不需要进行特殊的处理。
      
  4.2 自定义 View 须知  

  自定义 View 过程中需要注意一些事项,如果这些问题处理不好,可能会影响 View 的正常使用和性能。
  
       
  •       让 View 支持 wrap_content

          在自定义 View 时,如果是直接继承自 View 或者 View Group ,并且不在 onMeasure 中对 wrap_content 做特殊处理,那么在我们使用这个自定义的 View 的 wrap_content 属性时,就无法达到预期效果,而是和使用 match_parent 属性效果一样。
       
  •       如果有必要,让自定义的 View 支持 padding 属性

          在自定义 View 时,如果是直接继承自 View ,不在 onDraw 方法中处理 padding ,那么该自定义的 View padding属性将失效;如果是直接继承自 ViewGrop 需要在 onMeasure 和 onLayout 中考虑 padding 和 margin 对其造成的影响,否则将导致自定义的控件 padding 和子元素的 margin 属性失效。
       
  •       尽量不要在 View 中使用 Handler ,没必要

          View 本身内部提供了一些列的 post 方法,完全可以替代 Handler 作用。
       
  •       View 中如果有线程或者动画需要在特定生命周期进行停止

          当包含此 View 的 Activity 退出或者当前 View 被 remove 掉时,View 的 onDetachedFromWindow() 方法会被调用,所以如果有需要停止的线程或者动画可以在这个方法中执行,和此方法相对应的是 onAttachedToWindow() 方法,当包含该 View 的 Activity 启动的时候,该方法就会被调用。同时当 View 变得不可见时,我们需要及时停止线程和动画,否则可能造成内存泄露。
       
  •       View 带有滑动嵌套情形时,需要处理好滑动冲突

          如果有滑动冲突需要合适的进行处理。如果要处理好滑动处理可以看一下View 事件的分发机制      
      
  4.3 自定义 View 示例  

  4.3.1 继承自 View 重写 onDraw 方法  

  这种方法一般为了实现一些不规则的效果,需要我们自己去绘制去画一个出来 View 出来,即重写 onDraw 方法,采用这种方式需要考虑 View 四周的空白即处理 padding 值,而 margin 值是受父容器控制所以不需要进行处理,并且需要注意处理自定义的 View 支持 wrap_content ,即重写 onMeasure 方法,如果不进行处理那么当在 xml 文件中使用 wrap_content 属性的时候,就相当于 match_parent 属性,这里为了更详细的说明问题,我们一起来实现一个简单的自定义 View ,只简单的画一个圆出来,这里给出关键地方的代码,先看看 onMeasure 部分代码如下:
  1. 那么对于我们自定义的 View 是继承自 View 的情况下,我们一般不需要重写 onLayout 方法,因为 这个方法用途是父容器确定子 View 位置,对于 View 来说是没有子 View 的,所以一般不需要重写。
  2. 再看一下 ViewGroup 的 onLayout 方法:
  3. ```java
  4. protected abstract void onLayout(boolean changed,
  5. int l, int t, int r, int b);
  6. 0
复制代码
以上代码就解决了让 自定义的 View 支持 wrap_content 的问题。下面在看看在 onDraw 方法中进行绘制的时候处理 padding 值的问题,代码如下:
  1. 那么对于我们自定义的 View 是继承自 View 的情况下,我们一般不需要重写 onLayout 方法,因为 这个方法用途是父容器确定子 View 位置,对于 View 来说是没有子 View 的,所以一般不需要重写。
  2. 再看一下 ViewGroup 的 onLayout 方法:
  3. ```java
  4. protected abstract void onLayout(boolean changed,
  5. int l, int t, int r, int b);
  6. 1
复制代码
以上代码解决了让自定义的 View 支持 padding 属性。
  4.3.2 继承自 ViewGroup 实现特殊的 Layout 容器  

  这种自定义的 ViewGroup 需要处理 onMeasure 测量和 onLayout 布局两个过程,同时需要处理子元素的测量和布局过程。采用这种方法实现一个规范的自定义 View 是相当复杂的,通过前面分析的 LinearLayout 代码就可以发现,因为要考虑如何摆放子视图以及各种细节的处理,Android 开发艺术探索书中给出了一个相对规范(不完全规范)的自定义的 HorizontalScrollViewEx 视图容器,实现了一个类似 ViewPaper 的控件,内部子视图可以水平方向滑动,并且子视图的内部子元素可以实现竖直方向滑动,很显然这个控件解决了水平方向和竖直方向滑动冲突的问题,该部分知识可以看一下    View 的事件分发机制(Android 开发艺术探索读书笔记)这篇文章从源码角度分析了事件分发机制,理解了就可以解决滑动冲突的问题。下面一起看一下关键部分代码,先看 onMeasure 部分代码如下:  
  1. 那么对于我们自定义的 View 是继承自 View 的情况下,我们一般不需要重写 onLayout 方法,因为 这个方法用途是父容器确定子 View 位置,对于 View 来说是没有子 View 的,所以一般不需要重写。
  2. 再看一下 ViewGroup 的 onLayout 方法:
  3. ```java
  4. protected abstract void onLayout(boolean changed,
  5. int l, int t, int r, int b);
  6. 2
复制代码
以上代码实现了让自定义的 View 支持 wrap_content 属性。
  说明,这里为了方便处理有几处不规范的地方如下:

  
       
  • 假设了所有子视图的高度和宽度都相等,而实际应用中这是不可能的,所以计算起来会更复杂。   
  • 没有子元素的时候不应该直接设置宽/高为 0,而是应该根据 LayoutParams 的宽/高来做相应的处理,因为当使用 padding 属性的时候,虽然没有子视图,但 padding 值也会占据一定空间,你可以设置 LinearLayout 子视图个数为 0,然后给定一个 padding 值去试试。   
  • 在测量 HorizontalScrollViewEx 的高/宽的时候没有考虑它的 padding 值和子视图的 margin 值,因为自己的 padding 值和子视图的 margin 值都是占据空间的。  
  下面再看一下 onLayout 代码如下:
  1. 那么对于我们自定义的 View 是继承自 View 的情况下,我们一般不需要重写 onLayout 方法,因为 这个方法用途是父容器确定子 View 位置,对于 View 来说是没有子 View 的,所以一般不需要重写。
  2. 再看一下 ViewGroup 的 onLayout 方法:
  3. ```java
  4. protected abstract void onLayout(boolean changed,
  5. int l, int t, int r, int b);
  6. 3
复制代码
以上代码实现了摆放子视图的功能,从代码可以看出放置子视图是从左至右依次摆放。
  说明,以上代码不规范之处:

  在摆放子视图的过程中,没有考虑自身的 padding 和子视图的 margin 值。
  4.3.3 自定义 View 的总结  

  到这里,关于 View 的基础知识基本学习完毕,笔者写到这里也完全不能写出了一个牛逼的自定义控件(一个基友曾经这样问我:你学完了这些知识,还不徒手撸出一个牛逼的自定义 View 啊——–阿风 ,我的回答当然不能。因为自定义 View 是一个综合的知识体系,需要灵活的运用各种知识和经验,这里我们只是学习了一下基础理论知识,知其原理,懂其思路,如果我们想自定义 View,首先要掌握基本功,比如View 的弹性滑动,滑动冲突,绘制原理等,这些都是自定义 View 所必须知识点,再复杂的自定义 View 也是离不开这些知识点,尤其是那些看起来很炫酷的自定义 View,往往对这些技术点要求更高,只有熟悉掌握这些基础知识点以后,在面对新的自定义 View 时,才能够根据需求情况选择合适的实现思路,实现大体方法就是 4.1 节中介绍的四种分类,另外还需要学习一下 Canvas 这个类的用法才能画出想要的 View 。  
  最后,文章中的 Demo 会上传在 github,    链接地址,这里只是一个简单的 Demo ,分析一下原理,(这些 Demo 源码出自 Android 开发艺术探索)如果想撸出更炫酷的 View 还需要掌握 Canvas 这个类的用法,后面需要续学习。  
  以上就是本次的笔记内容,如有错误,希望指出,谢谢!!!
友荐云推荐




上一篇:Juggling Databases Between Datacenters
下一篇:Staged vs Stageless Handlers
酷辣虫提示酷辣虫禁止发表任何与中华人民共和国法律有抵触的内容!所有内容由用户发布,并不代表酷辣虫的观点,酷辣虫无法对用户发布内容真实性提供任何的保证,请自行验证并承担风险与后果。如您有版权、违规等问题,请通过"联系我们"或"违规举报"告知我们处理。

yugangmail 发表于 2016-10-5 21:57:41
广告位,坐下看看
回复 支持 反对

使用道具 举报

就想爱你九年 发表于 2016-10-8 16:50:34
为楼主点个赞!
回复 支持 反对

使用道具 举报

侯金翠 发表于 2016-11-19 13:36:23
放假前的节奏
回复 支持 反对

使用道具 举报

*滑动验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

我要投稿

推荐阅读

扫码访问 @iTTTTT瑞翔 的微博
回页顶回复上一篇下一篇回列表手机版
手机版/CoLaBug.com ( 粤ICP备05003221号 | 文网文[2010]257号 )|网站地图 酷辣虫

© 2001-2016 Comsenz Inc. Design: Dean. DiscuzFans.

返回顶部 返回列表