美文网首页Android
自定义View系列(二)自定义属性及ScrollView嵌套Li

自定义View系列(二)自定义属性及ScrollView嵌套Li

作者: Ayres | 来源:发表于2017-07-12 11:46 被阅读29次

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

       <resources>
             // 自定义TextView
             <declare-styleable name="TextView">
            // name 是名称,format是格式  color(颜色),string(文本),dimension(sp,dp)...
           <attr name="textColor" format="color"/>
          <attr name="text" format="string"/>
          <attr name="textSize" format="dimension"/>
         </declare-styleable>
    </resources>
    

    在布局文件中使用

    <com.darren.view.TextView
        // app: 自定义属性
        app:text="自定义文本"
        app:textColor="@color/colorAccent"
        app:textSize="18sp"
        // android: 系统自带的属性
        android:layout_width="wrap_content"
        android:layout_height="match_parent" />
    

    代码中获取,我们可以去仿照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, 2<sup>31</sup>-1.
     */
    public static final int MAX_VALUE = 0x7FFFFFFF;
    

    所以右移2位拼接位32位

    相关文章

      网友评论

        本文标题:自定义View系列(二)自定义属性及ScrollView嵌套Li

        本文链接:https://www.haomeiwen.com/subject/kyavhxtx.html