美文网首页Android自定义View自定义view
自定义View简介 - onMeasure,onDraw,自定义

自定义View简介 - onMeasure,onDraw,自定义

作者: Peakmain | 来源:发表于2018-07-05 16:54 被阅读4次

    1.自定义View简介

    自定义View可以认为是继承自View或者ViewGroup。经常是处理一些系统没有的效果(如ImageView,TextView,Button实际也是自定义view)

    2.自定义View介绍常用属性

    三个构造函数

     /**
         * 构造函数会在new的时候调用
         */
        public TextView(Context context) {
            this(context, null);
        }
    
        /**
         * 在布局中使用
         */
        public TextView(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        /**
         * 布局layout中调用,但是会有style
         */
        public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
    

    onMeasure

     /**
         * 自定义view的测量方法
         */
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            //布局的宽高都是有这个方法指定
            //指定控件的宽高,需要测量
            //获取宽高的模式
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            /**
             * MeasureSpec.AT_MOST : 在布局中指定了wrap_content
             * MeasureSpec.EXACTLY : 在不居中指定了确切的值  100dp   match_parent  fill_parent
             * MeasureSpec.UNSPECIFIED : 尽可能的大,很少能用到,ListView , ScrollView 在测量子布局的时候会用UNSPECIFIED
             */
            //获取宽高的大小
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        }
    

    onDraw()

     /**
         * 用于绘制
         */
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            // 画文本        
            canvas.drawText();        
            // 画弧       
            canvas.drawArc();        
            // 画圆       
            canvas.drawCircle();
        }
    

    onTouchEvent:处理用户客户交互的

       /**
         * 处理用户客户交互的
         */
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    // 手指按下
                    Log.e("TAG", "手指按下");
                    break;
                case MotionEvent.ACTION_MOVE:
                    // 手指移动
                    Log.e("TAG", "手指移动");
                    break;
                case MotionEvent.ACTION_UP:
                    // 手指抬起
                    Log.e("TAG", "手指抬起");
                    break;
                default:
                    break;
            }
            return super.onTouchEvent(event);
        }
    

    自定义属性的一些细节

    自定义属性就是用来配置的,android:text="Peakmain" 是系统的一个自定义属性
    1.在res下的values下面新建attrs.xml

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <!--name 自定义View的名字 TextView-->
        <declare-styleable name="TextView">
            <!-- name 属性名称    format 格式:
             string 文字  color 颜色
             dimension 宽高 字体大小  integer 数字
             reference 资源(drawable)         -->
            <attr name="text" format="string" />
            <attr name="textColor" format="color" />
            <attr name="textSize" format="dimension" />
            <attr name="maxLength" format="integer" />
            <attr name="background" format="reference|color" />
            <!-- 枚举 -->
            <attr name="inputType">
                <enum name="number" value="1" />
                <enum name="text" value="2" />
                <enum name="password" value="3" />
            </attr>
        </declare-styleable>
    </resources>
    

    2.在布局中使用
    声明命名空间,然后在自己的自定义View中使用

     xmlns:app="http://schemas.android.com/apk/res-auto"
    
        <com.peakmain.view.TextView
            style="@style/defualt"
            android:text="Hello World!"
            app:text="@string/app_name"
            app:textColor="@color/colorAccent"/>
    

    在自定义View中获取属性

       // 获取自定义属性
            TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.TextView);
            mText = array.getString(R.styleable.TextView_text);
            // 15 15px 15sp
            mTextColor = array.getColor(R.styleable.TextView_textColor, mTextColor);
            mTextSize = array.getDimensionPixelSize(R.styleable.TextView_textSize, mTextSize);
            // 回收
            array.recycle();
    

    高级面试题:ScrollView+ListView显示不全

    首先看解决办法

      @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            // 解决显示不全的问题  32值
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(
                    Integer.MAX_VALUE>>2,MeasureSpec.AT_MOST);
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    

    分析:首先看ScrollView的源码
    ScrollView的onMeasure调用的实际是FrameLayout的onMeasure方法其中调用了measureChildWithMargins方法,然后ScrollView中复写了这个方法

     @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 usedTotal = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin +
                    heightUsed;
          //给子孩子设置的模式为UNSPECIFIED,即ListView设置的模式是UNSPECIFIED
            final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
                    Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - usedTotal),
                    MeasureSpec.UNSPECIFIED);
    
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    

    看ListView中的onMeasure中的heightMode

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

    从上面的我们可以看到高度被设置添加一个子孩子的高度,我们可以看下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);
            }
    

    这里就不看了,我们这时候就可以知道了为什么设置模式为AT_MOST模式,那么为什么设置大小为Integer的最大值向右移动2位

    public static final int AT_MOST     = 2 << MODE_SHIFT;//向左移动30位
    

    makeMeasureSpec源码分析

     public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                              @MeasureSpecMode int mode) {
                if (sUseBrokenMakeMeasureSpec) {
                     //大小+模式
                    return size + mode;
                } else {
                    return (size & ~MODE_MASK) | (mode & MODE_MASK);
                }
            }
    

    而我们知道Integer的MAX_VALUE的值是2的31次方

     @Native public static final int   MAX_VALUE = 0x7fffffff;
    

    此时如果向右移动2位即值是30位,所以此时height的值是AT_MOST+MAX_VALUE一共32位

    相关文章

      网友评论

        本文标题:自定义View简介 - onMeasure,onDraw,自定义

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