美文网首页
自定义View

自定义View

作者: jie啊 | 来源:发表于2019-01-24 21:58 被阅读6次

    继承已有的View,简单改写它们的尺寸:重写onMeasure()

    1. 重写onMeasure()
    2. 用getMeasuredWidth()和getMeasuredHeight()获取到测量出的尺寸
    3. 计算出最终尺寸
    4. 用setMeasuredDimension(w,h)把结果保存
     * 继承已有控件,重写onMeasure
     */
    public class SquareImageView extends android.support.v7.widget.AppCompatImageView {
        public SquareImageView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int measuredWidth = getMeasuredWidth();
            int measuredHeight = getMeasuredHeight();
            int size = Math.max(measuredWidth, measuredHeight);
            setMeasuredDimension(size, size); // 保存测得的尺寸
        }
    
    //
    //    @Override
    //    public void layout(int l, int t, int r, int b) {//直接在此指定宽高,样式虽说改变,但是没有经过测量,父view并不知晓控件宽高已经改写,
        //会导致父控件中的子view重叠
    //        int w = r - l;
    //        int h = b - t;
    //        int size = Math.min(w, h);
    //        super.layout(l, t, r + size, b + size);
    //
    //    }
    }
    

    对自定义View完全进行自定义尺寸计算:重写onMeasure()

    1. 重写onMeasure()
    2. 计算出自己的尺寸
    3. 用resolveSize()或者resolveSizeAndState()修正结果
    • resolveSize()/resolveSizeAndState()代码原理
      • ⾸先⽤ MeasureSpec.getMode(measureSpec) 和 MeasureSpec.getSize(measureSpec) 取出⽗ 对⾃⼰的尺⼨限制类型和具体限制
        尺⼨;
      • 如果 measure spec 的 mode 是 EXACTLY,表示⽗ View 对⼦ View 的尺⼨做出
        了精确限制,所以就放弃计算出的 size,直接选⽤ measure spec 的 size;
      • 如果 measure spec 的 mode 是 AT_MOST,表示⽗ View 对⼦ View 的尺⼨只限
        制了上限,需要看情况:
        • 如果计算出的 size 不⼤于 spec 中限制的 size,表示尺⼨没有超出限制,所
          以选⽤计算出的 size;
        • ⽽如果计算出的 size ⼤于 spec 中限制的 size,表示尺⼨超限了,所以选⽤
          spec 的 size,并且在 resolveSizeAndState() 中会添加标志
          MEASURED_STATE_TOO_SMALL(这个标志可以辅助⽗ View 做测量和布
          局的计算;
    • 如果 measure spec 的 mode 是 UNSPECIFIED,表示⽗ View 对⼦ View 没有任
      何尺⼨限制,所以直接选⽤计算出的 size,忽略 spec 中的 size。
    public class CircleView extends View {
        private static final int RADIUS = (int) Utils.dpToPixel(80);//应该在自定义属性值可动态设置
        private static final int PADDING = (int) Utils.dpToPixel(30);
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
    
        public CircleView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        public void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.drawColor(Color.RED);//画布
            canvas.drawCircle(RADIUS+PADDING, RADIUS+PADDING, RADIUS, p);
    
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int width = (PADDING + RADIUS) * 2; //圆的半径加padding
            int height = (PADDING + RADIUS) * 2;
    
            width = resolveSizeAndState(width, widthMeasureSpec, 0);////resolveSize()
            height = resolveSizeAndState(height, widthMeasureSpec, 0);//resolveSizeAndState是否压缩状态
            setMeasuredDimension(width, height);
    
        }
    }
    

    ⾃定义 Layout:重写 onMeasure() 和 onLayout():TagLayout

    1. 重写 onMeasure()
      1. 遍历每个⼦ View,⽤ measureChildWidthMargins() 测量⼦ View
        • 需要重写 generateLayoutParams() 并返回 MarginLayoutParams 才能使⽤
          measureChildWithMargins() ⽅法
      • 有些⼦ View 可能需要重新测量(⽐如换⾏处)
      • 测量完成后,得出⼦ View 的实际位置和尺⼨,并暂时保存
      • measureChildWidthMargins() 的内部实现(代码必读):
        通过 getChildMeasureSpec(int spec, int padding, int childDimension) ⽅
        法计算出⼦ View 的 widthMeasureSpec 和 heightMeasureSpec,然后调
        ⽤ child.measure() ⽅法来让⼦ View ⾃我测量;
    2. 重写 onLayout()
    public class TagLayout extends ViewGroup  {
        private static final String TAG = "TagLayout";
        List<Rect> childrenBounds = new ArrayList<>();//便于存放子view尺寸,容器以情况而定
    
        public TagLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            for (int i = 0; i < getChildCount(); i++) {
                final View child = getChildAt(i);
                Rect childBounds = childrenBounds.get(i);
                child.layout(childBounds.left, childBounds.top, childBounds.right, childBounds.bottom);
            }
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//widthMeasureSpec,heightMeasureSpec是TagLayout的测量模式
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int widthUsed = 0;
            int heightUsed = 0;
            int lineMaxHeight = 0;
            int lineWidthUsed = 0;
            int specWidth = MeasureSpec.getSize(widthMeasureSpec);
            int specMode = MeasureSpec.getMode(widthMeasureSpec);
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, heightUsed);//测量子view,0表示,最右边的view可以放下,避免被压缩
                if (specMode != MeasureSpec.UNSPECIFIED && lineWidthUsed + child.getMeasuredWidth() > specWidth) {
                    lineWidthUsed = 0;
                    heightUsed += lineMaxHeight;
                    lineMaxHeight = 0;
                    measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, heightUsed);//
                }
                Rect rect;
                if (childrenBounds.size() <= i) {
                    rect = new Rect();
                    childrenBounds.add(rect);
                } else {
                    rect = childrenBounds.get(i);
                }
                rect.set(lineWidthUsed, heightUsed,
                        lineWidthUsed + child.getMeasuredWidth(), heightUsed + child.getMeasuredHeight());
                lineWidthUsed += child.getMeasuredWidth();
                widthUsed = Math.max(widthUsed, lineWidthUsed);
                lineMaxHeight = Math.max(lineMaxHeight, child.getMeasuredHeight());
            }
            int w = widthUsed;
            int h = heightUsed + lineMaxHeight;
            setMeasuredDimension(w, h);//保存尺寸
        }
    
        //重写
        @Override
        public LayoutParams generateLayoutParams(AttributeSet p) {
            return new MarginLayoutParams(getContext(), p);
        }
    }
    

    学习自hencode课程

    相关文章

      网友评论

          本文标题:自定义View

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