一.自定义View学习笔记(持续更新中。。。。)

       自定义View的内容很多,原本只是想写一篇博客,现在觉得我需要新建一个自定义View的文集了,这一篇主要是讲什么是自定义View,以及自定义View中的自定义属性,其中的几个方法。接下来几篇会写几个自定义控件。此文主要参考:

http://blog.csdn.net/xmxkf/article/details/51490283

【openXu的博客】


注意:

1.在ListView中,条目中如果有Button之类带点击效果的控件,那么必须要处理一下,不然它会抢走ListView的焦点,使ListView的ItemOnclick事件不生效。解决方法:在条目布局根节点声明一个属性:descendantFocusability,指的是:该条目内部子控件获取焦点的方式

它可以指定三个值,分别是:

1)afterDescendants  在条目获取之后,子控件才获取焦点

2)beforeDescendants 在条目获取焦点之前,子控件获取焦点

3)blocksDescendants 以区块的方式获取焦点,只有在点击到子控件所在的区块,子控件才会获取焦点。


2.PagerAdapter中需要子类重写的4个方法:



3.自定义控件的绘制是在界面打开之后的onCreate()方法之后绘制。

4.在自定义控件中调用系统方法:invalidate() 方法,会调用onDraw方法,使整个控件重新绘制,界面更新。

5.在自定义View中获取上下文,一般使用getContext()



自定义View分为三种:

1.组合已有控件实现自定义控件

2.完全自定义控件

3.继承已有控件,扩展其功能



1.组合已有控件实现自定义控件

旋转菜单效果图:


旋转菜单效果图

点击home键和menu键分别让外层的相对布局转入和转出。

点击menu键,为第三层布局添加动画。第三层布局显示,将第三层转出去,第三层布局隐藏,转进来。使用补间动画

1.转出动画:

转出动画

2.转入动画:

转入动画
分析

1.旋转中心点:

相对于自己旋转,控件的长宽在坐标轴上均为1,中心点如上图红色点所示,点坐标是(0.5f,1f)

2.逆时针旋转,角度递减。顺时针旋转,角度递增。

3.点击home键,旋转第二层,点击menu键,旋转第三层。

在旋转时需要判断是转入,还是转出。如果布局显示,则转出,布局隐藏则转入。

4.点击home键时需要判断,第三层是否显示在屏幕上,如果第三层显示,先把第三层转出去。

这种情况,第二层布局需要添加延时执行动画,否则第二层第三层布局会同时转出。设置延时执行动画代码:

raOutAnimation.setStartOffset(100);//设置动画启动延时

在第二层旋转动画前加一个判断:如下:


5.设置动画执行延时,是否延时,要看外面那一级的菜单是否显示。设置一个变量,如果外面那层显示,将变量添加200毫秒

如果三级菜单显示,则delay+=200;如果三级菜单没显示,delay仍为0.



6.为了避免动画重复执行,设置一个变量,动画本身添加监听,动画开始执行时,将变量值++,动画执行结束,变量值- -,执行动画之前判断,如果变量值>0.则return;


7.手机键盘menu键,点击之后,三层布局全部执行转入转出动画。重写onKeyDown方法。



8.bug修复:

补间动画缺点,虽然布局转出去了,但是其实控件仍然在原来的位置,点击menu键原来的位置,第三层仍然会执行动画。

解决方法:转出动画时把按钮的点击事件屏蔽掉,转入动画再解除屏蔽。


屏蔽


启用



2.完全自定义控件:继承自View


绘制完全自定义控件步骤:

1.继承View,覆盖构造方法

2.自定义属性

3.重写onMeasure方法测量宽高

4.重写onDraw方法绘制控件


1.继承View,覆盖构造方法

因为View类不具有无参的构造函数,因此,自定义View需要重写其构造方法,一般重写三个构造方法,但此处,把第四个构造方法也详细的记录一下:

1)带一个参数的构造方法:从代码创建时走此构造方法,用于代码创建。如:newTextView(mContext);

源码中的解释:Simple constructor to use when creating a view from code.

2) 带两个参数的构造方法:在xml中使用时走此构造方法,可用于指定自定义属性。

源码中的解释:Constructor that is called when inflating a view from XML. This is called 

                            when a view is being constructed from an XML file, supplying attributes

                            that were specified in the XML file. This version uses a default style of

                           0, so the only attribute values applied are those in the Context's Theme

                           and the given AttributeSet.

如:这样几行代码,只在带两个参数的构造方法中打印一段话,并且将其attrs也打印出来看看

public SwitchButtonView(Context context, @Nullable AttributeSet attrs){

                  super(context, attrs);

                  Log.i("带两个参数的构造方法", "SwitchButtonView: ");

                   for(int i=0;i<attrs.getAttributeCount();i++){

                                         Log.d("带两个参数的构造方法attrs", attrs.getAttributeName(i)+" : "+attrs.getAttributeValue(i));

                              }

             }

可以看到打印的结果:

可以看到,当我们直接在xml文件中使用,在类中绑定时,走的时带两个参数的构造方法,打印出的attrs,正是我们指定的属性,因此,在这个方法中,可以获取用户输入的自定义属性的值。

注意:如果xml中指定了样式,走的仍然是这个构造方法。也就是说,系统默认只会调用Custom View的前两个构造函数,至于第三个构造函数的调用,通常是我们自己在构造函数中主动调用的(例如,在第二个构造函数中调用第三个构造函数).

3)带三个和四个参数的构造方法:通常是在第二个构造函数中调用,一般用来获取用户的自定义属性。

获取自定义属性的代码:

public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) {

       super(context, attrs, defStyleAttr);

      TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView);

      String attr1 = ta.getString(R.styleable.MyCustomView_custom_attr1);

      String attr2 = ta.getString(R.styleable.MyCustomView_custom_attr2);

      String attr3 = ta.getString(R.styleable.MyCustomView_custom_attr3);

      String attr4 = ta.getString(R.styleable.MyCustomView_custom_attr4);

      Log.e("customview", "attr1=" + attr1);

       Log.e("customview", "attr2=" + attr2); Log.e("customview", "attr3=" + attr3); Log.e("customview", "attr4=" + attr4);

     ta.recycle();

}

使用TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView);这句代码获取自定义属性,通过对源码的追踪我们发现:




最终调用了Theme中的obtainStyledAttributes带有4个参数的构造方法:

1.AttributeSet set: 属性值的集合.

2.int[] attrs:  我们自定义属性集合在R类中生成的int型数组.这个数组中包含了自定义属性的资源ID.

3.int defStyleAttr:

这是当前Theme中的包含的一个指向style的引用.当我们没有给自定义View设置declare-styleable资源集合时,默认从这个集合里面查找布局文件中配置属性值.传入0表示不向该defStyleAttr中查找默认值.

4.int defStyleRes: 这个也是一个指向Style的资源ID,但是仅在defStyleAttr为0或者defStyleAttr不为0但Theme中没有为defStyleAttr属性赋值时起作用.

由于一个属性可以在很多地方对其进行赋值,包括: XML布局文件中、decalare-styleable、theme中等,它们之间是有优先级次序的,按照优先级从高到低排序如下:

属性赋值优先级次序表:

在布局xml中直接定义 > 在布局xml中通过style定义 > 自定义View所在的Activity的Theme中指定style引用 > 构造函数中defStyleRes指定的默认值

2.自定义属性

当我们自定义一个View时,可以通过自定义属性来更改View的一些如字体大小,背景图片等属性。那么自定义属性怎么用的呢?

1.首先,在attrs.xml文件中定义一个resource,其中可以填写任何我们想要设置的属性,format的意思是该属性的取值是什么类型(支持的类型有string,color,demension,integer,enum,reference,float,boolean,fraction,flag) 如图:


这是我在自定义toolbar中定义的自定义属性,其中rightButtonIcon代表的是右侧按钮图标,类型是reference,意思是引用类型。

format有11中类型,其中无法从字面直接获取其意思的几个详细记录一下:

reference:参考某一资源ID,如图片等的设置

dimension:尺寸值 如设置宽高

fraction:百分数

enum:枚举值 如线性布局中设置方向

flag:位或运算



注意,在xml文件中使用自定义属性是不要忘记命名空间。

xmlns:openxu="http://schemas.android.com/apk/res-auto"

2.在构造方法中获取用户输入的自定义属性。并在CustomView.java中编写相关方法,用来更改控件的属性。如同我们上面所讲的,在带有三个参数的构造方法中获取用户输入的自定义属性。如下图


上图以右侧按钮图标为例,在View中编写setRightButtonIcon()方法。setRightButtonIcon()方法是用来把用户输入的自定义属性设置到右侧按钮上的。


这样就完成了给控件设置自定义属性。

3.重写onMeasure方法测量宽高

关于View在官方文档中的解释:

View这个类代表用户界面组件的基本构建块。View在屏幕上占据一个矩形区域,并负责绘制和事件处理。View是用于创建交互式用户界面组件(按钮、文本等)的基础类。它的子类ViewGroup是所有布局的父类,它是一个可以包含其他view或者viewGroup并定义它们的布局属性的看不见的容器。 实现一个自定义View,你通常会覆盖一些framework层在所有view上调用的标准方法。你不需要重写所有这些方法。事实上,你可以只是重写onDraw(android.graphics.Canvas)。

Android界面的绘制流程:

View:重写以下方法

onMeasure()(该方法用于指定自己的宽高)---->onDraw()(该方法用于绘制自己的内容)

ViewGroup:重写以下方法

onMeasure()(该方法用于指定自己的宽高,子view的宽高)---->onLayout()(摆放所有的子View)----->onDraw()(绘制内容)

以上的这些方法,均在Activity或者Fragment、View等使用该控件的类中的onResume()方法之后执行。

好了,一个一个来解决:

onMeasure()方法:测量,也就是控制View的大小

测量:我们在写onMeasure方法时,通常会这么写:

 int widthMode = MeasureSpec.getMode(widthMeasureSpec);         

  int heightMode = MeasureSpec.getMode(heightMeasureSpec);         

  int widthSize = MeasureSpec.getSize(widthMeasureSpec);          

 int heightSize = MeasureSpec.getSize(heightMeasureSpec);  

用来获取宽高,那么MeasureSpec究竟是什么呢?跟踪一下源码,发现它是View中的一个静态内部类,是由尺寸和模式组合而成的一个值,用来描述父控件对子控件尺寸的约束,看看他的部分源码,一共有三种模式,然后提供了合成和分解的方法:


可以看到,其中有三种约束,UNSPECIFIED  EXACTLY  AT_MOST

当控件宽高设置为match_parent或者是具体宽高值的时候,模式为EXACTILY。

当控件宽高设置为warp_content时,模式为AT_MOST。

那么举个例子,来重写onMeasure方法:


http://blog.csdn.net/xmxkf/article/details/51490283

onMeasure()方法中调用了setMeasuredDimension(宽,高)方法,该方法时用来设置自定义控件的宽高,




完全自定义控件例子:自定义开关

1.绘制界面内容

2.响应触摸事件

3.接口监听

1.绘制界面内容

1)定义ToggleView继承View,重写View的三个构造方法

2).绘制界面内容,界面由两张图片组成,前景和背景。如下图:


自定义开关

在ToggleView类中,设置三个方法:

1.setToggleBackground(int background):设置开关背景

toggleBackground = BitmapFactory.decodeResource(getResources(), background);把用户传入的background转化成bitmap,通过onDraw方法画到控件上

2.setToggleForeground(int foreground):设置开关前景

toggleForeground = BitmapFactory.decodeResource(getResources(), foreground);同上

3.setToggleStatus(Boolean open):设置开关状态。通过用户输入的boolean值设置开关状态

3).重写onMeasure方法,设置控件的宽高

设置控件宽高,与背景图片一样宽、高:setMeasuredDimension(toggleBackground.getWidth(), toggleBackground.getHeight());

4).重写onDraw方法,把图片绘制到控件上,绘制的内容都会显示到控件上

//1.绘制背景

canvas.drawBitmap(toggleBackground,0,0,paint);//中间两个参数是距离控件原点(左上角坐标)的x、y轴距离

//2.根据开关状态绘制前景

if(isopen){

//开

    //获取前景移动距离

    int i =toggleBackground.getWidth() -toggleForeground.getWidth();

    canvas.drawBitmap(toggleForeground,i,0,paint);

}else{

//关

    canvas.drawBitmap(toggleForeground,0,0,paint);

}

控件内容绘制完成

2.响应触摸事件

重写View的onTouchEvent()方法:返回值改为true,控件才会消费用户的点击事件。

在触摸事件中,获取用户手指的X坐标,通过更新前景图标左上角的X坐标来更新控件。在onTouchEvent调用invalidate()方法,每次触摸都会重新绘制。在手指抬起时,根据前景图的坐标判断开关是什么状态。

@Override

public boolean onTouchEvent(MotionEvent event) {

switch (event.getAction()){

case MotionEvent.ACTION_DOWN:

//按下

            /**

            * 按下时,将isTouchMode改为true,更改currentX

*/

            currentX = event.getX();

            isTouchMode =true;

                        break;

        case MotionEvent.ACTION_MOVE:

                         //移动

            currentX = event.getX();

            /**

            * 移动时,更改currentX,通过调用invalidate()重绘界面

            */

                             break;

        case MotionEvent.ACTION_UP:

                    //抬起

              currentX = event.getX();

            /**

            * 抬起时,将isTouchMode设置为false

*/

               boolean state = false;

                 if(currentX < center){

                         //在中间值左边 关

                              state = false;

                              }else if(currentX > center){

                               state = true;}

                              isopen = state;//把state的值赋值给开关状态

                     isTouchMode =false;

                             break;

    }

              invalidate();//调用该方法,每次触摸时都重新绘制控件

                return true;//必须更改为true,控件才能消费掉用户的触摸事件

}

在onDraw()方法中绘制界面:如果是触摸模式,根据触摸坐标来绘制界面;否则,根据开关状态绘制界面。设置左右边界,不允许前景图片超过边界,

@Override

protected void onDraw(Canvas canvas) {

//1.绘制背景

    canvas.drawBitmap(toggleBackground,0,0,paint);

    //根据用户触摸坐标来绘制画面

    if(isTouchMode){

currentX =currentX -toggleForeground.getWidth()/2.0f;//移动的坐标需要比手指点下的坐标左移半个前景图片大小,这样看起来就是点击在开关中间

        //容错处理:设置左右边界

        float maxLeft =toggleBackground.getWidth() -toggleForeground.getWidth();//开关能移到右侧的最大值

        if(currentX<0){

currentX =0;

        }else if(currentX>maxLeft){

currentX = maxLeft;

        }

canvas.drawBitmap(toggleForeground,currentX,0,paint);

    }else{

//根据开关状态绘制前景

        if(isopen){

//开

            //获取前景移动距离

            int i =toggleBackground.getWidth() -toggleForeground.getWidth();

            canvas.drawBitmap(toggleForeground,i,0,paint);

        }else{

//关

            canvas.drawBitmap(toggleForeground,0,0,paint);

        }}}

3.接口监听

当用户操作自定义控件时,自定义控件内部需要通知外部(界面,程序)我的状态改变了,并把状态的boolean变量传出去。

// 1. 声明接口对象

public interface OnSwitchStateUpdateListener{

// 状态回调, 把当前状态传出去

void onStateUpdate(boolean state);

}

// 2. 添加设置接口对象的方法, 外部进行调用

public void setOnSwitchStateUpdateListener(

OnSwitchStateUpdateListener onSwitchStateUpdateListener) {

this.onSwitchStateUpdateListener = onSwitchStateUpdateListener;

}

// 3. 在合适的位置.执行接口的方法

onSwitchStateUpdateListener.onStateUpdate(state);

// 4. 界面/外部, 收到事件.

sbv.setOnSwitchStatusChangeListener(new SwitchButtonView.OnSwitchStatusChangeListener(){

@Override

    public void onStatusChange(boolean status) {

Toast.makeText(mContext,"开关状态为"+status,Toast.LENGTH_SHORT).show();

    }

});


手指抬起时,如果state的值改变了,那么说明开关状态改变了。因为state的值最后是赋值给了isopen,在赋值之前判断,如果两者不同,就说明开关状态改变,那么在此时执行3中的方法

if(state!=isopen&&onSwitchStatusChangeListener!=null){

onSwitchStatusChangeListener.onStatusChange(state);//这句代码执行时,外部(界面上该控件的该接口的监听被调用)

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,799评论 25 707
  • A:听说最近你有点不开心? B:哦是的。 A:为什么? B:因为钱,最近几年挣钱越来越少了,感觉心里有些不安全感。...
    想要飞飞阅读 95评论 0 2
  • 周六日作业 但是自己的 想不懂
    华域说说阅读 214评论 0 0
  • 宣宜心录:带孩,是一门以孩子为导师,把孩子的行为反馈作为起点,以孩子的未来为目标的实操课。和孩子一起互动,同孩子一...
    心灵育儿妈妈阅读 279评论 0 0
  • 星期六 晴 每日一我 起来和妹去吃牛杂粉。然后乘地铁,小火车和小哈吃午饭。 黄牛馆的涮牛肉超好吃。晚上吃川菜。外面...
    sophietyl阅读 153评论 0 0