Android 自定义view(三) 继承ViewGroup

来源:互联网 发布:集成电路设计软件 编辑:程序博客网 时间:2024/05/01 11:37

前言:

本篇博客讲解内容主要是来自 鸿洋的博客Android 自定义ViewGroup 实战篇 -> 实现FlowLayout

上篇文章 自定义view(二),继承view。今天来看自定义View的第二种情况,继承自viewgroup,虽然viewGroup也是继承view控件,但是ViewGroup和View还是有很多方法区别的,顾名思义,这是一个控件的集合控件。

1.

我们来通过自定义viewGroup来实现瀑布流的效果。效果图如下:
这里写图片描述

2.

继承viewGroup主要来重写onMearsure和onLayout,剩下的onDraw更多是用来绘图的,所以不需要来重写。所以我们来看看代码

onMeasure

    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)    {        //获取测量模式和测量大小        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);            /**以下代码主要是针对测量模式是wrap_content时需要动态的计算,如果是一些具体的大小或者match_parent时根本不不需要写onMearsure            */        //整个自定义viewgroup的宽,高的最大值进行记录        int width = 0;        int height = 0;        //判断是否要换行时使用        int lineWidth = 0;        int lineHeight = 0;        //计算子view的个数        int cCount = getChildCount();        //for循环计算每个子view在viewgroup中的大小        for (int i = 0; i < cCount; i++)        {            View child = getChildAt(i);            //measureChild这个方法来计算子view的大小和mode,这个viewGroup定义的一个方法,是个很重要的方法            measureChild(child, widthMeasureSpec, heightMeasureSpec);            // 得到child的lp,MarginLayoutParams可以得到Margin数据。            MarginLayoutParams lp = (MarginLayoutParams) child                    .getLayoutParams();            //measureChild方法过后,child.getMeasuredWidth()才能计算得到值,不然会为0            int childWidth = child.getMeasuredWidth() + lp.leftMargin                    + lp.rightMargin;            int childHeight = child.getMeasuredHeight() + lp.topMargin                    + lp.bottomMargin;            //判断是否需要换行            if (lineWidth + childWidth > sizeWidth - getPaddingLeft() - getPaddingRight())            {                //需要换行了,记录宽的最大值                width = Math.max(width, lineWidth);                //换行后的新的lineWidth等于控件的宽                lineWidth = childWidth;                height += lineHeight;                lineHeight = childHeight;            } else            {   //不需要换行                lineWidth += childWidth;                lineHeight = Math.max(lineHeight, childHeight);            }            //最后一个view            if (i == cCount - 1)            {                width = Math.max(lineWidth, width);                height += lineHeight;            }        }        //不同的mode进行一个简单的判断,viewGroup的真正的大小实际上就是通过这个方法进行设置的        setMeasuredDimension(                //                modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width + getPaddingLeft() + getPaddingRight(),                modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height + getPaddingTop() + getPaddingBottom()//        );    }

上面的注释其实也是十分的详细了,上面有个很关键的方法,是进行测试子view的
measureChild(child, widthMeasureSpec, heightMeasureSpec);这个viewgroup的方法,我们来看下源码:

    /**     * Ask one of the children of this view to measure itself, taking into     * account both the MeasureSpec requirements for this view and its padding.     * The heavy lifting is done in getChildMeasureSpec.     *     * @param child The child to measure     * @param parentWidthMeasureSpec The width requirements for this view     * @param parentHeightMeasureSpec The height requirements for this view     */    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()和measure()这两个方法的源码大家可以自己看看,主要是对子view进行测量。

3.

测量好了大小后,我们来看看是如何布局的,此时需要重写onLayout方法:

    //记录所有的子view,并且再按行来进行一个区别记录    private List<List<View>> mAllViews = new ArrayList<List<View>>();      //记录没行的最大高度     private List<Integer> mLineHeight = new ArrayList<Integer>();      @Override      protected void onLayout(boolean changed, int l, int t, int r, int b)      {          mAllViews.clear();          mLineHeight.clear();          int width = getWidth();          int lineWidth = 0;          int lineHeight = 0;          // 存储每一行所有的childView          List<View> lineViews = new ArrayList<View>();          int cCount = getChildCount();          // 遍历所有的孩子          for (int i = 0; i < cCount; i++)          {              View child = getChildAt(i);              MarginLayoutParams lp = (MarginLayoutParams) child                      .getLayoutParams();              int childWidth = child.getMeasuredWidth();              int childHeight = child.getMeasuredHeight();              // 如果已经需要换行              if (childWidth + lp.leftMargin + lp.rightMargin + lineWidth > width)              {                  // 记录这一行所有的View以及最大高度                  mLineHeight.add(lineHeight);                  // 将当前行的childView保存,然后开启新的ArrayList保存下一行的childView                  mAllViews.add(lineViews);                  lineWidth = 0;// 重置行宽                  lineViews = new ArrayList<View>();              }              /**              * 如果不需要换行,则累加              */              lineWidth += childWidth + lp.leftMargin + lp.rightMargin;              lineHeight = Math.max(lineHeight, childHeight + lp.topMargin                      + lp.bottomMargin);              lineViews.add(child);          }          // 记录最后一行          mLineHeight.add(lineHeight);          mAllViews.add(lineViews);          int left = 0;          int top = 0;          // 得到总行数          int lineNums = mAllViews.size();          for (int i = 0; i < lineNums; i++)          {              // 每一行的所有的views              lineViews = mAllViews.get(i);              // 当前行的最大高度              lineHeight = mLineHeight.get(i);              // 遍历当前行所有的View              for (int j = 0; j < lineViews.size(); j++)              {                  View child = lineViews.get(j);                  if (child.getVisibility() == View.GONE)                  {                      continue;                  }                  MarginLayoutParams lp = (MarginLayoutParams) child                          .getLayoutParams();                  //计算childView的left,top,right,bottom                  int lc = left + lp.leftMargin;                  int tc = top + lp.topMargin;                  int rc =lc + child.getMeasuredWidth();                  int bc = tc + child.getMeasuredHeight();                  //以上所有的计算都是为了获取这四个参数,来进行子view的布局。                child.layout(lc, tc, rc, bc);                  left += child.getMeasuredWidth() + lp.rightMargin                          + lp.leftMargin;              }              left = 0;              top += lineHeight;          }      } 

代码也不是很难,细心一点。

4.

实际上,我们可以发现,该自定义控件实现的逻辑挺简单的,主要是:
(1)onMeasure中,通过计算每个子view的大小才确定viewgroup的大小。
(1)onLayout中,动态的计算子view的位置坐标然后进行一个布局。
由此也可以发现,自定义viewgroup最重要的也就是这两个方法,但是,其中很多小的细节也是需要我们注意到的,比如:
(a)只有子view调用了measure方法,才能调用getMeasureSpec获取到值,不然为0.
(b)用MarginLayoutParams来支持margin属性。
代码:

@Override  public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs)  {      return new MarginLayoutParams(getContext(), attrs);  } 

以上代码就可以简单实现自定义view的流式布局了。

最近搞了个Android技术分享的公众号,欢迎关注投稿。
这里写图片描述

0 0