自定义控件入门

作者: 豆沙包67 | 来源:发表于2015-09-11 20:33 被阅读286次

    自定义控件的优势

    • 对显示控件的完全控制权
    • 使视图层级结构平面化,减少视图层级结构,提升程序性能

    Demo

    第一个自定义View是上图下字;
    第二个自定义View是九宫格。

    Demo效果图

    自定义View步骤

    测量(Measurement)

    在控件被绘制之前,必须由父控件确定其规格。

    • 所有的Views都会在View层级结构上从上到下被测量
    • 父节点要求子节点更新规格时,字节点会调用onMeasure()方法
    • 根据父节点提供的约束条件,子节点通过自行测量确定大小
    • 最后调用setMeasureDimension()确定测量大小

    下面给出一个绝大部分情况下可用的测量Demo

    /**
     * 根据父控件提供的约束条件,测量控件的长宽
     *
     * @param measureSpec 约束条件
     * @param contentSize 期望大小
     * @return 实际大小
     */
    public static int getMeasurement(int measureSpec, int contentSize) {
        int result = 0;
        //获取约束条件
        int mode = View.MeasureSpec.getMode(measureSpec);
        //获取约束大小
        int size = View.MeasureSpec.getSize(measureSpec);
        switch (mode) {
            case View.MeasureSpec.EXACTLY:
    
                //只能被约束
                result = size;
                break;
            case View.MeasureSpec.AT_MOST:
                //最大也不能比约束条件大
                result = Math.min(size, contentSize);
                break;
            case View.MeasureSpec.UNSPECIFIED:
                //返回期望大小
                result = contentSize;
                break;
        }
        return result;
    }
    
      @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            //此处传递的参数就是父控件的约束条件,这里的期望值可以自定义
            setMeasuredDimension(MeasureUtils.getMeasurement(widthMeasureSpec, getDesiredWidth()), MeasureUtils.getMeasurement(heightMeasureSpec, getDesiredHeight()));
        }
    

    注意:当在xml声明
    android:layout_width="wrap_content" android:layout_height="wrap_content"
    时,就需要提供自定义getDesiredWidth()getDesiredHeight()方法求出期望宽高.

    绘画

    @Override
    protected void onDraw(Canvas canvas) {
        //此处传入的canvas大小和位置是根据以上测量结果来决定的
    }
    

    自定义属性

    res/value文件夹新建attrs.xml文件

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="MyImageView">
            <attr name="android:spacing"></attr>
            <attr name="android:textColor"></attr>
            <attr name="android:text"></attr>
            <attr name="android:textSize"></attr>
            <attr name="android:drawable"></attr>
        </declare-styleable>
    </resources>
    

    这里使用Android系统预定义的值,在布局文件中可以直接引用

    <com.chensl.customview.view.MyImageView
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:drawable="@drawable/abc_btn_rating_star_off_mtrl_alpha"
        android:textColor="@color/pink"
        android:text="@string/imageview_text"
        android:textSize="16sp"
        android:spacing="0dp" />
    

    在构造函数中读取到每个属性

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyImageView, 0, defStyleAttr);
        mSpace = a.getDimensionPixelSize(R.styleable.MyImageView_android_spacing, 0);
        mTextColor = a.getColor(R.styleable.MyImageView_android_textColor, 0);
        mText = a.getText(R.styleable.MyImageView_android_text);
        mTextSize = a.getDimensionPixelSize(R.styleable.MyImageView_android_textSize, 0);
        mDrawable = a.getDrawable(R.styleable.MyImageView_android_drawable);
        a.recycle();
    

    自定义ViewGroup

    测量

    • 计算ViewGroup自身大小

    • 通知子控件测量自身宽高

      @Override
      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      //获取默认宽高,父控件分配的最大宽高
      int width = getDefaultSize(0, widthMeasureSpec);
      int height = getDefaultSize(0, heightMeasureSpec);
      //取宽高中的小值
      int majorSize = Math.min(width, height);
      int blockSize = majorSize / CHILD_COUNT;
      int blockSpec = MeasureSpec.makeMeasureSpec(blockSize, MeasureSpec.EXACTLY);
      //通知子控件测量自身宽高
      measureChildren(blockSpec, blockSpec);
      //测量自身宽高
      setMeasuredDimension(majorSize, majorSize);
      }

    摆放控件

    根据需求将摆放子控件,这里是九宫格

       @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            int row, col, left, top, count;
            count = getChildCount();
            int gridWidth = getWidth() / CHILD_COUNT;
            int gridHeight = getHeight() / CHILD_COUNT;
            for (int i = 0; i < count; i++) {
                View child = getChildAt(i);
                if (child != null) {
                    row = i / CHILD_COUNT;
                    col = i % CHILD_COUNT;
                    left = gridWidth * col + child.getMeasuredWidth() / 2;
                    top = gridHeight * row;
                    child.layout(left, top, left + gridWidth, top + gridHeight);
                }
            }
        }
    

    绘画

    在ViewGroup中绘画有两个方法

      @Override
        protected void dispatchDraw(Canvas canvas) {
            super.dispatchDraw(canvas);
            //在子视图已经完成绘画后调用,所以显示在上方
            int lineHeight = getHeight() / CHILD_COUNT;
            int height = getHeight();
            for (int i = 0; i < height; i += lineHeight) {
                //画横线
                canvas.drawLine(0, i, getWidth(), i, mLinePaint);
            }
            int lineWidth = getWidth() / CHILD_COUNT;
            int width = getWidth();
            for (int i = 0; i < width; i += lineWidth) {
                //画竖线
                canvas.drawLine(i, 0, i, getHeight(), mLinePaint);
            }
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            //在子视图完成之后,所以显示在子视图下方,适合画动态背景等
            //记得初始化时setWillNotDraw(false);
        }
    

    总结

    自定义控件的优势就不过多解释了。

    值得说的是,自定义控件只要考虑那些有可能出现的情况做适配即可。

    像系统Relativelayout,LinearLayout这样需要考虑全方位的的适配对于我们来说是太过于浪费时间。

    而且,当可以继承已有控件的前提下,就不要逞能继承View或者ViewGroup

    相关文章

      网友评论

        本文标题:自定义控件入门

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