自定义View系列(二)自定义属性及ScrollView嵌套ListView显示不全讲解

自定义属性能够做到配置的灵活运用,比如在TextView中text配置什么就显示什么,textColor指定什么颜色就显示什么颜色等等,这些都是自定义属性。首先在res下的values目录下新建一个attrs.xml文件,其他名称可以吗?是可以的如attr.xml或者zzz.xml但是原则上一看attrs就知道是自定义属性。一切规范化。

   
         // 自定义TextView
         
        // name 是名称,format是格式  color(颜色),string(文本),dimension(sp,dp)...
       
      
      
     

在布局文件中使用


代码中获取,我们可以去仿照TextView的源码写

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

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

public TextView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    // 获取TypedArray
    TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.TextView);
    // 获取文本
    mText = typedArray.getText(R.styleable.TextView_text);
    // 获取文字颜色
    mTextColor = typedArray.getColorStateList(R.styleable.TextView_textColor);
    // 获取文字大小
    mTextSize = typedArray.getDimensionPixelSize(R.styleable.TextView_textSize,mTextSize);
    // 回收
    typedArray.recycle();
}
ScrollView嵌套ListView会出现显示不全的现象,怎么解决这个问题的?

在网上有很多解决办法,一搜就有,但是确不知道为啥,在面试中也经常问道,下面做下总结
解决问题的代码:

 @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  // TODO Auto-generated method stub
 int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
 MeasureSpec.AT_MOST);
 super.onMeasure(widthMeasureSpec, expandSpec);
 }

但是为啥呢,别急下面一一道来,没有看过我上篇文章的建议先看一下,才不至于懵逼

从ScrollView源码分析点进去(onMeasure -->measureChildWithMargins)你会发现ScrollView的measureChildWithMargins方法覆盖了父类的measureChildWithMargins方法,ScrollView的measureChildWithMargins方法如下

@Override
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 = MeasureSpec.makeSafeMeasureSpec(
            MeasureSpec.getSize(parentHeightMeasureSpec), MeasureSpec.UNSPECIFIED);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

况且这句代码:

     final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
            MeasureSpec.getSize(parentHeightMeasureSpec), MeasureSpec.UNSPECIFIED);

发现设计模式是:MeasureSpec.UNSPECIFIED

我们再看LIstView的onMeasure 方法
    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // Sets up mListPadding
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

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

    int childWidth = 0;
    int childHeight = 0;
    int childState = 0;

    mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
    if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED
            || heightMode == MeasureSpec.UNSPECIFIED)) {
        final View child = obtainView(0, mIsScrap);

        // Lay out child directly against the parent measure spec so that
        // we can obtain exected minimum width and height.
        measureScrapChild(child, 0, widthMeasureSpec, heightSize);

        childWidth = child.getMeasuredWidth();
        childHeight = child.getMeasuredHeight();
        childState = combineMeasuredStates(childState, child.getMeasuredState());

        if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
                ((LayoutParams) child.getLayoutParams()).viewType)) {
            mRecycler.addScrapView(child, 0);
        }
    }

    if (widthMode == MeasureSpec.UNSPECIFIED) {
        widthSize = mListPadding.left + mListPadding.right + childWidth +
                getVerticalScrollbarWidth();
    } else {
        widthSize |= (childState & MEASURED_STATE_MASK);
    }

    if (heightMode == MeasureSpec.UNSPECIFIED) {
        heightSize = mListPadding.top + mListPadding.bottom + childHeight +
                getVerticalFadingEdgeLength() * 2;
    }

    if (heightMode == MeasureSpec.AT_MOST) {
        // TODO: after first layout we should maybe start at the first visible position, not 0
        heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
    }

    setMeasuredDimension(widthSize, heightSize);

    mWidthMeasureSpec = widthMeasureSpec;
}

方法 final int heightMode = MeasureSpec.getMode(heightMeasureSpec);获取高度的设计模式,为ScrollView传进来的
MeasureSpec.UNSPECIFIED会走下面的方法

    if (heightMode == MeasureSpec.UNSPECIFIED) {
        heightSize = mListPadding.top + mListPadding.bottom + childHeight +
                getVerticalFadingEdgeLength() * 2;//表示高度为一个item高度
    }

为了让它走

 if (heightMode == MeasureSpec.AT_MOST) {
        // TODO: after first layout we should maybe start at the first visible position, not 0
        heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
    }

方法所以要自定义ListVIew,重写onMeasure 添加

int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
 MeasureSpec.AT_MOST);

那么为什么要这样写呢,下面讲解一下
重写之后进入该方法:

if (heightMode == MeasureSpec.AT_MOST) {
        // TODO: after first layout we should maybe start at the first visible position, not 0
        heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
    }

之后进入measureHeightOfChildren()方法

  final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
        int maxHeight, int disallowPartialChildPosition) {
    final ListAdapter adapter = mAdapter;
    if (adapter == null) {
        return mListPadding.top + mListPadding.bottom;
    }

    // Include the padding of the list
    int returnedHeight = mListPadding.top + mListPadding.bottom;
    final int dividerHeight = ((mDividerHeight > 0) && mDivider != null) ? mDividerHeight : 0;
    // The previous height value that was less than maxHeight and contained
    // no partial children
    int prevHeightWithoutPartialChild = 0;
    int i;
    View child;

    // mItemCount - 1 since endPosition parameter is inclusive
    endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;
    final AbsListView.RecycleBin recycleBin = mRecycler;
    final boolean recyle = recycleOnMeasure();
    final boolean[] isScrap = mIsScrap;

    for (i = startPosition; i <= endPosition; ++i) {
        child = obtainView(i, isScrap);

        measureScrapChild(child, i, widthMeasureSpec, maxHeight);

        if (i > 0) {
            // Count the divider for all but one child
            returnedHeight += dividerHeight;
        }

        // Recycle the view before we possibly return from the method
        if (recyle && recycleBin.shouldRecycleViewType(
                ((LayoutParams) child.getLayoutParams()).viewType)) {
            recycleBin.addScrapView(child, -1);
        }

        returnedHeight += child.getMeasuredHeight();

        if (returnedHeight >= maxHeight) {
            // We went over, figure out which height to return.  If returnedHeight > maxHeight,
            // then the i'th position did not fit completely.
            return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
                        && (i > disallowPartialChildPosition) // We've past the min pos
                        && (prevHeightWithoutPartialChild > 0) // We have a prev height
                        && (returnedHeight != maxHeight) // i'th child did not fit completely
                    ? prevHeightWithoutPartialChild
                    : maxHeight;
        }

        if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {
            prevHeightWithoutPartialChild = returnedHeight;
        }
    }

    // At this point, we went through the range of children, and they each
    // completely fit, so return the returnedHeight
    return returnedHeight;
}

我们传的为maxHeight参数:传的最大值使它进入不到方法

     if (returnedHeight >= maxHeight) {
            // We went over, figure out which height to return.  If returnedHeight > maxHeight,
            // then the i'th position did not fit completely.
            return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
                        && (i > disallowPartialChildPosition) // We've past the min pos
                        && (prevHeightWithoutPartialChild > 0) // We have a prev height
                        && (returnedHeight != maxHeight) // i'th child did not fit completely
                    ? prevHeightWithoutPartialChild
                    : maxHeight;
        }

正常返回returnedHeight;
heightMeasureSpec:包含两个信息 一个是模式;2位,一个是值:30位
为什么要右移两位Integer.MAX_VALUE >> 2,因为源码size加mode

  public static int makeMeasureSpec(int size, int mode) {
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }

Integer.MAX_VALUE 为32位

  /**
 * Constant for the maximum {@code int} value, 231-1.
 */
public static final int MAX_VALUE = 0x7FFFFFFF;

所以右移2位拼接位32位

你可能感兴趣的:(自定义View系列(二)自定义属性及ScrollView嵌套ListView显示不全讲解)