Android 自定义View——自绘控件

本文为学习完以下几位大神之后整理的学习笔记:

     huachao1001的专栏


       首先,在我的理解,自定义控件就是自己定义的控件,而相对的就是系统提供的控件,例如TextView,ImageView之类的,学习自定义View时,脑海中可对照着系统控件思考。


一、自定义控件的自定义属性

        首先我们需要在 res/values/attrs.xml文件(如果没有请自己新建)里面声明一个自定义属性:
1
2
3
4
5
6
7
8
<? xml  version = "1.0"  encoding = "utf-8" ?>
< resources >
    <!--此处为自定义属性集的名字,在代码实现中会用到,但xml布局中不会调用详情可看下文-->
     < declare-styleable  name = "CustomView" >
        <!--这些为自定义的属性-->
        <!--format为属性类型,共有:string,color,demension,integer,enum,reference,float,boolean,fraction,flag-->
         < attr  name = "titleText"  format = "string" />
         < attr  name = "titleTextColor"  format = "color" />
         < attr  name = "titleTextSize"  format = "dimension" />
     </ declare-styleable >
</ resources >

二、自定义控件的布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<? xml  version = "1.0"  encoding = "utf-8" ?>
< RelativeLayout  xmlns:android = "http://schemas.android.com/apk/res/android"
     xmlns:tools = "http://schemas.android.com/tools"
    <!--此句为添加命名空间,自定义属性调用时需引入,custom即为命名空间,可随心修改-->
    xmlns:custom = "http://schemas.android.com/apk/res-auto"
     android:layout_width = "match_parent"
     android:layout_height = "match_parent"
     tools:context = "com.eebbk.androidstudy.CustomView.CustomViewActivity" >
 
     < com.eebbk.androidstudy.CustomView.CustomView
         android:layout_width = "wrap_parent"
         android:layout_height = "wrap_content"
        <!--后面三句都是自定义属性的添加赋值,命名空间的作用在此体现了-->
         custom:titleText = "4324"
         custom:titleTextColor = "#ff0000"
         custom:titleTextSize = "40sp"  />
 
</ RelativeLayout >

三、自定义控件的实现

/自定义View效果图
        先上张效果图,后面读代码时方便理解。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public  class  CustomView  extends  View {
 
     private  String mTitleText;     //效果图中对应的文本,需在xml中自定义属性里取值
     private  int  mTitleTextColor;   //效果图中文本对应的颜色,需在xml中自定义属性里取值
     private  int  mTitleTextSize;    //效果图中文本对应的尺寸,需在xml中自定义属性里取值
     private  Rect mBound;           //效果图中文本对应的边界,需根据文本大小进行计算,后有细说
     private  Paint mPaint;          //画笔
 

     /*
    * 构造函数有三,他们分别对应不同的构造场景:
    * 1.对应View view = new View(context);
    * 2.对应在xml布局文件中使用View时,在inflate时调用
    * 3.与二类似,不同之处在于,三中添加了style属性设置
    * 本例中所有构造方法最后调用的都是3,避免代码冗余
    */
     public  CustomView(Context context) {
         this (context,  null );
     }
      
    public  CustomView(Context context, AttributeSet attrs) {
         this (context,  attrs 0 );
     }
 
     public  CustomView(Context context, AttributeSet attrs,  int  defStyle) {
         super (context, attrs);
 
        //此处是从xml中获取自定义属性的值,参数二就是自定义属性集的名字,在步骤一中定义
         TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomView, defStyle,  0 );
        //获取自定义属性时,键值应为R.styleable.自定义属性集_自定义属性
         mTitleText = a.getString(R.styleable.CustomView_titleText);
         mTitleTextColor = a.getColor(R.styleable.CustomView_titleTextColor, Color.BLACK);
         mTitleTextSize = a.getDimensionPixelSize(R.styleable.CustomView_titleTextSize,  100 );
        //获取完毕后,需添加此句,对TypedArray对象进行回收
         a.recycle();
 
         mPaint =  new  Paint();
         mPaint.setTextSize(mTitleTextSize);
         mBound =  new  Rect();
         mPaint.getTextBounds(mTitleText,  0 , mTitleText.length(), mBound);
     }
 
    //onMeasure顾名思义,就是对本View大小的测量,此处是坑,先有个印象吧
     protected  void  onMeasure( int  widthMeasureSpec,  int  heightMeasureSpec) {
         super .onMeasure(widthMeasureSpec, heightMeasureSpec);
     }
 
    //onDraw顾名思义,就是对本View的绘制
     @Override
     protected  void  onDraw(Canvas canvas) {
         mPaint.setColor(Color.YELLOW);
        //此处的getMeasuredWidth/Height是在onMeasure后才可调用的,对应的是控件的宽度和高度
         canvas.drawRect( 0 0 , getMeasuredWidth(), getMeasuredHeight(), mPaint);
 
         mPaint.setColor(mTitleTextColor);
         canvas.drawText(mTitleText, getWidth() /  2  - mBound.width() /  2 , getHeight() /  2  + mBound.height() /  2 , mPaint);
     }
}
        有了上文一二三步骤的实现,应该是可以大工告成了吧!确是如此,显示效果和上文效果图也如出一辙,但细心地朋友就会发现,我自定义View中设置的是
        android:layout_width = "wrap_content"
         android:layout_height = "wrap_content"
为何,出来的是满屏?秘密就在onMeasure中。

四、onMeasure解析

        首先了解下onMeasure的参数, widthMeasureSpec和heightMeasureSpec, 两个整型值。整型32位 = 2位模式 + 30位值。      

        1、模式共有三种:
             EXACTLY 模式: 准确的、精确的;这种模式,是最容易理解和处理的,可以理解为大小固定,比如在定义layout_width的时候,定义为固定大小 10dp,20dp,或者match_parent(此时父控件是固定的)这时候,获取出            来的mode就是EXACTLY 
            AT_MOST 模式: 最大的;这种模式稍微难处理些,不过也好理解,就是View的大小最大不能超过父控件,超过了,取父控件的大小,没有,则取自身大小,这种情况一般都是在layout_width设为warp_content时。                         
            UNSPECIFIED 模式:不指定大小,这种情况,我们几乎用不上,它是什么意思呢,就是View的大小想要多大,就给多大,不受父View的限制,几个例子就好理解了,ScrollView控件就是。

        2、值就是控件对应的宽高,此处为父类传递下来的
            当我们在xml中layout_width/layout_height设置成match_parent 或 wrap_content时,只都是match_parent的值,这也就是效果图产生的原因,至于为什么会如此,不明所以啊,所以这就是我们需要重新写onMeasure的原因了。
            当我们在xml中对layout_width/layout_height设置具体值时,widthMeasureSpec和heightMeasureSpec就是设置的值。
        所以只有当我们的控件设置的是wrap_content模式时,尺寸测量会出现问题

        有了以上认知之后,我们就可以重写onMeasure了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    protected  void  onMeasure( int  widthMeasureSpec,  int  heightMeasureSpec) {
         mPaint.setTextSize(mTitleTextSize);
         mPaint.getTextBounds(mTitleText,  0 , mTitleText.length(), mBound);
         float  textWidth = mBound.width();
         float  textHeight = mBound.height();
         int  desiredWidth = ( int ) (getPaddingLeft() + textWidth + getPaddingRight());
         int  desiredHeight = ( int ) (getPaddingTop() + textWidth + getPaddingBottom());
 
         setMeasuredDimension(getViewSize(desiredWidth, widthMeasureSpec),
                 getViewSize(desiredHeight, heightMeasureSpec));
     }
 
     //参数一为wrap_content模式时控件的尺寸,参数二为父类传入的尺寸
     public  static  int  getViewSize( int  desiredSize,  int  measureSpec) {
         int  specMode = MeasureSpec.getMode(measureSpec);
         int  specSize = MeasureSpec.getSize(measureSpec);
         int  result = desiredSize;
 
         switch  (specMode) {
             case  MeasureSpec.UNSPECIFIED:
                 result = desiredSize;
                 break ;
             case  MeasureSpec.AT_MOST:
                 result = Math.min(specSize, desiredSize);
                 break ;
             case  MeasureSpec.EXACTLY:
                 result = specSize;
                 break ;
         }
 
         return  result;
     }
  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值