Android技术点

自定义View

2016-06-14  本文已影响137人  四月一号

View的继承关系

一张图描述 View 的继承关系:


Paste_Image.png

View的绘制流程

画多大 --> 考虑怎么画 --> 开始动手画


自定义控件的分类


自定义控件的具体实现步骤

控件的初始化


View的测量 -measure

前面说了那么多,其实值说明了一个问题:View的测量规格是由父控件的测量规格和自身的LayoutParams共同决定的

Paste_Image.png

在计算子控件的尺寸时,不管父View的specMode是MeasureSpec.AT_MOST还是MeasureSpec.EXACTLY对于子View而言系统给它设置的specMode都是MeasureSpec.AT_MOST,并且其大小都是parentLeftSize即父View目前剩余的可用空间。这时wrap_content就失去了原本的意义,变成了match_parent一样了.
所以自定义控件默认不支持 wrap_content
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec , heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpceSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec);
int heightSpceSize=MeasureSpec.getSize(heightMeasureSpec);
int wrapWidth,wrapHight;//根据逻辑计算自己的尺寸
if(widthSpecMode==MeasureSpec.AT_MOST&&heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth, mHeight);
}else if(widthSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth, heightSpceSize);
}else if(heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpceSize, mHeight);
}
}


ViewGroup 的测量


View的 layout

//l, t, r, b分别表示子View相对于父View的左、上、右、下的坐标
 public void layout(int l, int t, int r, int b) {
     if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) { 
          onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec); 
          mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; 
      } 
      int oldL = mLeft; 
      int oldT = mTop; 
      int oldB = mBottom; 
      int oldR = mRight; 
      boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); 
      if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { 
          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); 
                 }
           }
       } 
       mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; 
       mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; 
  }

...代码没看懂,引用一段网上的解释

确定该View在其父View中的位置,把l,t, r, b分别与之前的mLeft,mTop,mRight,mBottom一一作比较,假若其中任意一个值发生了变化,那么就判定该View的位置发生了变化 ,若View的位置发生了变化则调用onLayout()方法

前面说过 onLayout 才是自定义控件时需要关注的方法

/** 
  * Called from layout when this view should 
  * assign a size and position to each of its children. 
  * 
  * Derived classes with children should override 
  * this method and call layout on each of 
  * their children. 
  * @param changed This is a new size or position for this view 
  * @param left Left position, relative to parent 
  * @param top Top position, relative to parent 
  * @param right Right position, relative to parent 
  * @param bottom Bottom position, relative to parent 
  */
  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}

这是个空方法(为什么是空方法?)

Called from layout when this view should assign a size and position to each of its children.
在layout方法中调用该方法,用于指定子View的大小和位置。

我们知道,只有ViewGroup才有子控件,也就是说, 自定义View是不需要考虑该方法的.再看看 该方法在ViewGroup中的实现

protected abstract void onLayout(boolean changed,int l, int t, int r, int b);

是个抽象方法,其实很好理解,因为每种布局都有其自己的逻辑,比如:FrameLayou,LinearLayout,RelativeLayout等对 onLayout的实现肯定就不一样.进一步说,自定义ViewGroup时,系统肯定是不知道我们想要什么样的逻辑,必须要自己根据需求去实现.

简单的模仿一个垂直方向线性布局的需求

@Override 
protected void onLayout(boolean changed, int l, int t, int r, int b) { 
      int childCount=getChildCount(); 
      int height = 0;
      if(childCount>0){ 
          View child=getChildAt(i);
           int childHeight=child.getMeasuredHeight(); 
           if(child.getVisibility != View.GONE){
               child.layout(l,height,r,height += childHeight); 
           }
      }
 }

大部分时候,如果可能,尽量避免直接继承ViewGroup,而是继承LinearLayout,RelativeLayout等系统已有的布局来简化这些步骤。


View 的绘制 -draw

draw()的源码就不看了(实在太长了),我们真正关注的是onDraw()方法

/** 
 * Implement this to do your drawing. 
 * 需要由具体的子View去实现各自不同的需求
 * @param canvas the canvas on which the background will be drawn 
 */
 protected void onDraw(Canvas canvas) {}

结论:
一般来说,自定义ViewGroup不需要实现该方法(布局容器,更关注的应该是子控件的测量和摆放);
自定义View 则需要根据具体的需求实现该方法.

关于使用画布和画笔绘制图形,API很多,不一一列举,只介绍一些常用的

 // 画背景色
 canvas.drawColor(Color.BLUE);
 canvas.drawRGB(255, 255, 0);   
 // 画点
 void drawPoint (float x, float y, Paint paint)
 void drawPoints (float[] pts, Paint paint)
 void drawPoints (float[] pts, int offset, int count, Paint paint)
 // 画线
 void drawLine (float startX, float startY, float stopX, float stopY, Paint paint)
 void drawLines (float[] pts, Paint paint)
 void drawLines (float[] pts, int offset, int count, Paint paint)
 // 画矩形
 void drawRect (float left, float top, float right, float bottom, Paint paint)
 void drawRect (RectF rect, Paint paint)
 void drawRect (Rect r, Paint paint)
 // 圆角矩形
 void drawRoundRect (RectF rect, float rx, float ry, Paint paint)
 // 圆形
 void drawCircle (float cx, float cy, float radius, Paint paint)
 // 椭圆
 void drawOval (RectF oval, Paint paint)
 // 文字
 void drawText (String text, float x, float y, Paint paint)
 void drawText (CharSequence text, int start, int end, float x, float y, Paint paint)
 void drawText (String text, int start, int end, float x, float y, Paint paint)
 void drawText (char[] text, int index, int count, float x, float y, Paint paint)

关于控件的绘制,这里只列举了一些简单的,常用的API,实际上控件的绘制非常复杂,涉及到的知识点也很多,推荐一个大神的博客,非常值得学习.
http://blog.csdn.net/harvic880925/article/details/50995268

关于自定义控件的主要流程总结下来就是 :

初始化 --> onMeasure --> onLayout --> onDraw

关于自定义控件还有更多的东西需要学习:控件的滑动,事件的分发...

上一篇 下一篇

猜你喜欢

热点阅读