自定义 View 之总结

3,521 阅读7分钟

文章来自:Android程序员日记

作者:贤榆的鱼

参考阅读时间:5 min 15s

导读语:自定义控件只看这一篇,是不够的!

在之前我先后写了"自定义View之扩展式"、"自定义View之复合式"、"自定义View之完全自定义",在这三篇文章中我都分别给出了一个例子。当然了,例子其实是相对比较简单的,主要原因可能是个人水平有限吧!但尽管如此,我也尽可能把我要讲的内容表述的更清晰一些!那么为了更好的在我们的脑海里构建这个知识框架(这句话太专业,让我写起来都感到了满满的压力!其实就是让某些重要的或是会用到的内容,能够在我们大脑里留下个更深刻的印象,并让他们之间有更多的联系。)以便于在我们需要的时候,更容易提取出来用!为此,写个总结也是一个不错的方法!这个观点是来自《暗时间》!

我们暂且将自定义View分为了扩展式、复合式和完全自定义三种类型(当然我们也可以按别的分)!他们看起来确实有一定的从易到难的梯度关系!不管怎样,我们将这前面三遍中的知识点和自定义View的一些知识点做一个总结!好处就是——可能会有跟多人关注我的“Android程序员日记”的公众号吧!

都说程序员都是从零开始计数的,那我们开始吧!

[ 0 ]关于自定义View的三个构造方法

  • 一个参数的构造方法:在用代码动态的添加我们的自定义view时调用。
  • 两个参数的构造方法:在使用xml +inflate的方法添加控件时会调用,里面多了一个AttributeSet类型的值
  • 三个参数的构造方法:这个构造方法系统是不调用的,需要我们显示调用并给defStyleAttr传值,多了一个defStyleAttr参数,这是这个view引用style资源的属性参数,也就是我们可以在style中为自定义View定义一个默认的属性样式然后添加进来!

[ 1 ]关于三种自定义View

  • 扩展式:

    扩展式自定义View继承自Android原生特定的View如:TextView,ImageView等等。我们通过重写onDrow()等回调方法对其进行扩展!使其实现我们想要的更能或样式!

    注:该方法实现的自定义View控件不需要自己支持wrap_content和padding。

  • 组合式:

    组合式自定义View继承自ViewGrop的子View如:LinearLayout、RelativieLayout等。当某种效果看起来像几种View组合在一起的时候,都可以使用这种方式实现。

    注:该方式实现自定义View不需要自己处理ViewGroup的测量和布局这两个过程。

  • 完全自定义:
    完全自定义View继承自View(android中所有控件的基类),通常实现一些不方便布局的组合方式来达到的,需要静态或动态地显示一些不规则的控件或图形!

    注:该方法实现的自定义View控件需要自己支持wrap_content和padding。

[ 2 ]常用的回调方法

  • onFinishInflate():加载完XML组件后回调
  • onSizeChanged():组件大小改变时回调
  • onMeasure():回调该方法来进行测量(在该方法中实现对wrap_content支持的代码)
  • onLayout():回调该方法来显示位置
  • onTouchEvent():监听到触摸事件回调,也是实现交互非常重要的回调方法
  • onDraw():回调该方法对我们的控件进行绘制

[ 3 ]为自定义View添加并使用自定义属性的过程

Step 1 : 在Values下创建attrs.xml(当然也可以以别的名字命名,无限制),然后在该文件中添加自定义View的自定义属性!

Step 2 : 在自定义View的构造方法中获取自定义属性值,并将值配予相应的位置!

Step 3 :在xml中使用自定义控件及配置其自定义属性

注:在xml使用自定义控件时一定要加 :

xmlns:custom="schemas.android.com/apk/res-aut…"

当然你也可以写成

xmlns:custom="schemas.android.com/apk/res/com…)

这两种方式没有本质上的区别。至于custom随便你喜欢命名什么都可以。

[ 4 ]完全自定义控件中我们自己支持wrap_content和padding的代码

  • MeasureSpec

    • 简介:MeasureSpec代表一个32位int值,高2位代表SpecMode(测量模式),低30位代表SpecSize(指定模式下的规格大小)。
    • 三种SpecMode与SpecSize

      1.UNSPECIFIED:父容器自容器无限制,要多大给多大,这种情况一般用于系统内部。一般我们不关注盖模式。

      2.EXACTLY:父容器已检测出了View所需要的精确大小,这时V接我的最终大小就是SpecSize所给定的值。它对应于LayoutParams中的match_parent和具体的数值两种模式

      3.AT_MOST:父容器制定了一个可用大小即SpecSize,View的大小不能大于这个值,具体值要看不同View的具体实现。它对应于LayoutParams中的wrap_content。

    • MeasureSpec和LayoutParams的对应关系
      普通的View(即非顶层View)的MeasureSpec由父容器的MeasureSpec和自身的LyoutParams来共同决定的,MeasureSpec一旦确定后,onMeasure中就可以确定View的测量宽高。但对于顶层View(即)

  • 对wrap_content的支持

    在onMeasure()方法中实现对wrap_content的支持

      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
          super.onMeasure(widthMeasureSpec, heightMeasureSpec);
          int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
          int widthSpecSize= MeasureSpec.getSize(widthMeasureSpec);
          int heightSpectMode = MeasureSpec.getMode(heightMeasureSpec);
          int heightSpecSize=MeasureSpec.getSize(heightMeasureSpec);
          //这里就是对wrap_content的支持
          if(widthSpecMode==MeasureSpec.AT_MOST&&heightSpectMode==MeasureSpec.AT_MOST){
              //这里设定的根据你自己自定义View的情况而定
              setMeasuredDimension(200,200);
          }else if(widthSpecMode==MeasureSpec.AT_MOST){
              setMeasuredDimension(200,heightSpecSize);
          }else if (heightSpectMode==MeasureSpec.AT_MOST){
              setMeasuredDimension(widthSpecSize,200);
          }
      }
  • 对padding的支持

    在onDraw()方法中实现对padding的支持,其实就是在或控件时考虑到就padding就好了。如果不自己实现那么你对该自定义View设置padding将是无效的!
    
      @Override
         protected void onDraw(Canvas canvas) {
          super.onDraw(canvas);
          //这里是对画一个圆形的View的padding支持
          final int paddingLeft = getPaddingLeft();
          final int paddingRight = getPaddingRight();
          final int paddingTop = getPaddingTop();
          final int paddingBottom=getPaddingBottom();
          int width = getWidth()-paddingLeft-paddingRight;
          int height = getHeight()-paddingBottom-paddingTop;
          int radius = Math.min(width,height)/2;
          canvas.drawCircle(paddingLeft+width/2,paddingTop+height/2,radius,mPaint_while);
      }

[ 5 ]为自定义View定义并实现接口回调的过程

本段示例代码来自"自定义View之复合式",为了总结的需要,做了一些注释上的修改,并简化为实现一个回调方法!

Step 1: 定义回调接口(自定义View类中操作)

public interface TopBarClickListener {
    void leftClick();
}

Step 2: 暴露回调接口(自定义View类中操作)

public void setOnTopbarClickListener(TopBarClickListener mListener){
    this.mListener=mListener;
}

Step 3: 调用回调接口(自定义View类中操作)

private void bindEvents() {
    mLeftButton.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            if(mListener!=null){
                mListener.leftClick();
            }
        }
    });
}

Step 4: 实现回调回调接口及方法(在实例化自定义View对象的类中)

mtopBar.setOnTopbarClickListener(new TopBar.TopBarClickListener() {
    @Override
    public void leftClick() {
        Toast.makeText(MainActivity.this,"上一张",Toast.LENGTH_SHORT).show();
        if (index<=0){
            index=3;
        }else{
            index--;
        }
        iv_image.setImageResource(images.get(index));
    }

后记

好了,这篇总结就这么多了!我必须再次强调一次,本文最最重要的一句话,就是放在最前面的导读语——自定义控件只看这一篇,是不够的!这句话我没开玩笑,你不信也没关系,反正我是不会在哪我的美貌做赌注了!

其实,不只是我对自己写的东西不自信,而是无论是我这一篇,还是某个大牛写的某一篇。我都觉得自定义View看某一篇是不够的!对于初学者或新手而言更是如此。因为正如我在我在前言中写道的一样,我们都在构造自己的知识架构。我们需要从更多的角度,去理解我们学到的或是要学习的内容!这样才更有助于我们去理解和记忆!也才更有助于我们去实际应用!

最后总结一句话吧——“我们学习的任何事物”看“某一篇”都是不够的!

喜欢可以关注我微信公众号
分享绝不止于Android!


喜欢请关注公众Android程序员日记