自定义控件测量

自定义 View 测量

自定义 Layout 测量

第一种情况 MeasureSpec.AT_MOST

wrap_content

第二种情况 MeasureSpec.EXACTLY

match_parent
xxdp

第三种情况 MeasureSpec.UNSPECIFIED

NestedScrollView 测量子 View 高度时

当 MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.UNSPECIFIED 时, MeasureSpec.getSize(widthMeasureSpec) 获取到的尺寸都是最大尺寸, 就算设置了相关 layout 属性还是一样. 为了避免这种情况可以通过 LayoutParams 获取 layout 属性, 根据实际情况去测量计算

  1. // ShadowLayout 部分代码
  2. @Override
  3. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  4. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  5. if (getChildCount() < 1) {
  6. return;
  7. }
  8. // ======================================================
  9. // 通过 MeasureSpec.getMode 获取模式存在一个问题:
  10. // 当获取 MeasureSpec.getMode == MeasureSpec.UNSPECIFIED 时,
  11. // 即使 xml 宽高设置的是具体值, MeasureSpec.getSize 获取到也还是推荐的值.
  12. // ======================================================
  13. // 下面的代码就是为了解决这种情况:
  14. // 为了避免 MeasureSpec.UNSPECIFIED 模式下获取不到 xml 设置的具体宽高值, 直接从 LayoutParams 中获取
  15. int width = getLayoutParams().width;
  16. int height = getLayoutParams().height;
  17. // 计算剩余空间
  18. int widthSize = MeasureSpec.getSize(widthMeasureSpec);
  19. int useWidth = (width > 0 ? width : widthSize) - mLeftPadding - mRightPadding;
  20. int childWidthSpec = MeasureSpec.makeMeasureSpec(useWidth, MeasureSpec.EXACTLY);
  21. int heightSize = MeasureSpec.getSize(heightMeasureSpec);
  22. int useHeight = (height > 0 ? height : heightSize) - mTopPadding - mBottomPadding;
  23. int childHeightSpec = MeasureSpec.makeMeasureSpec(useHeight, MeasureSpec.EXACTLY);
  24. View child = getChildAt(0);
  25. measureChild(child, childWidthSpec, childHeightSpec);
  26. // MATCH_PARENT
  27. if (width == ViewGroup.LayoutParams.MATCH_PARENT) {
  28. width = widthSize;
  29. }
  30. if (height == ViewGroup.LayoutParams.MATCH_PARENT) {
  31. height = heightSize;
  32. }
  33. // WRAP_CONTENT
  34. MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
  35. if (width == ViewGroup.LayoutParams.WRAP_CONTENT) {
  36. width = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin + mLeftPadding + mRightPadding;
  37. }
  38. if (height == ViewGroup.LayoutParams.WRAP_CONTENT) {
  39. height = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin + mTopPadding + mBottomPadding;
  40. }
  41. setMeasuredDimension(width, height);
  42. }

自定义 layout 测量踩坑记录

java.lang.ClassCastException: android.view.ViewGroup$LayoutParams cannot be cast to android.view.ViewGroup$MarginLayoutParams

如果自定义 layout 是直接继承的 ViewGroup , childView 的默认 LayoutParams 就是 ViewGroup.LayoutParams
如果想要转换成 MarginLayoutParams 需要重写如下方法:

  1. override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
  2. return MarginLayoutParams(context, attrs)
  3. }
  4. override fun generateLayoutParams(lp: LayoutParams?): LayoutParams {
  5. return MarginLayoutParams(lp)
  6. }
  7. override fun generateDefaultLayoutParams(): LayoutParams {
  8. return MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
  9. }

参考链接

踩坑记录

自定义 Layout 中, findViewById 获得的 Viewnull

原因: 大概因为布局未加载完成
解决方案:

  1. override fun onFinishInflate() {
  2. // 将 findViewById 相关的逻辑挪到此处即可
  3. }