Android自定义控件开发系列(一)——第一次动手做自定义控件

  转载自:"_程序猿大人_"http://blog.csdn.net/a_running_wolf

 Android系统提供的控件多种多样,以至于很多初学者经常忘了还有这样那样的控件没用过甚至没听过。尽管如此,但是系统控件大多比较死板,而且不够美观,很多多样化的显示或是交互方式都没法完成。每每遇到这种情况,就需要我们来开发我们自己的控件了——所谓的“自定义控件”。接下来我们就一步一步扎扎实实的从头开始Android自定义控件的开发。

        废话少说,开始吧:

        一、实现自定义控件的3种主要方式

        (1)修改已有控件——继承已有控件,重写其显示、响应等;

        (2)组合已有控件——将已有的系统控件组合成一个独特的控件(接下来的示例中就是这种演示);

        (3)开发全新的控件——一般继承View或SurfaceView。他们都提供一个Canvas(画布)和一系列的画的方法,还有Paint(画笔)。使用它们去创建一个自定义的UI。你可以重写事件,包括屏幕接触或者按键按下等等,用来提供与用户交互。这种方式比较高阶,需要熟悉View的工作原理并熟悉其各个回调方法。

        二、为自定义控件增加属性的两种方法:

        (1)在自定义类中定义属性字段

         本文通过开发一个可以显示文字和图片组合的控件来加以说明。

       通过构造函数中引入的AttributeSet 去查找XML布局的属性名称,然后找到它对应引用的资源ID去找值:

[java]  view plain  copy
  1. public class MyView1 extends View {  
  2.     private String mtext;  
  3.     private int msrc;  
  4.   
  5.     public MyView1(Context context) {  
  6.         super(context);  
  7.     }  
  8.   
  9.     public MyView1(Context context, AttributeSet attrs) {  
  10.         super(context, attrs);  
  11.   
  12.         //方法attrs.getAttributeResourceValue(String nameSpace, String attriName, int default)的nameSpace参数传null即可————  
  13.         int textId = attrs.getAttributeResourceValue(null"Text"0);  
  14.         int imgId = attrs.getAttributeResourceValue(null"Img"0);  
  15.   
  16.         //根据传来的id找出字符串,比如示例代码中传入的是@stirng/hello_world  
  17.         //这里也可以直接在xml文件中设置字符串的参数但是获取属性值的方法就要相应变成  
  18.         //mtext = attrs.getAttributeValue(null, "Text")直接获取传入的字符串  
  19.         mtext = getResources().getText(textId).toString();  
  20.         msrc = imgId;  
  21.     }  
  22.   
  23.     @Override  
  24.     protected void onDraw(Canvas canvas) {  
  25.         Paint paint = new Paint();  
  26.         paint.setColor(Color.RED);  
  27.         paint.setTextSize(30);  
  28.   
  29.         InputStream is = getResources().openRawResource(msrc);  
  30.         Bitmap bitmap = BitmapFactory.decodeStream(is);  
  31.         int imgWid = bitmap.getWidth();  
  32.         int imgHei = bitmap.getHeight();  
  33.         canvas.drawBitmap(bitmap, 00, paint);  
  34.         canvas.drawText(mtext,imgWid/3, imgHei/2, paint);  
  35.     }  
  36. }  

        然后在布局文件中使用我们自定义的控件(控件名要用包名+类名):

[html]  view plain  copy
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:layout_width="match_parent"  
  3.     android:layout_height="match_parent">  
  4.   
  5.     <com.wangj.cusview.MyView1  
  6.         android:layout_width="wrap_content"  
  7.         android:layout_height="wrap_content"  
  8.         Img="@drawable/xx"  
  9.         Text="@string/hello_world" />  
  10. </RelativeLayout>  

         运行一下如下图:

        

        到这里,可能会有人问,布局文件里自定义控件的属性设置直接用Img="@drawable/xx"和Text="@string/hello_world",怎么不像系统控件的属性那样的写法android:layout_height="wrap_content"呢?这里就需要回顾一下上边我们在上边MyView1类中用attrs.getAttributeResourceValue(String nameSpace, String attriName, int default)方法时的nameSpace参数传了个null,没有使用命名空间

        ----------------------------------------------好了,上边这种方法完成了,下边的和上边的一样,只是更规范而已----------------------------------------------

        什么是命名空间呢?呵呵,你就先理解成一个归属吧,就像你们学校有好多个叫“王小二”的同学,校长要叫王二小同学来办公室聊聊,这是就要指出是“哪个班的王二小”了,命名空间就相当于这个班级的作用。我们看布局文件,根节点属性中都会有xmlns:android="http://schemas.android.com/apk/res/android"这一句,这就是声明一个命名空间,“命名空间的名字”就叫android,接下来就可以用android:layout_height="wrap_content"这种写法了。

        接下来我们也将上边的布局文件改成这种方式(我们也自己给一个命名空间)(这里需要说明一下,有人认为系统组件的命名空间必须要用“android”,其实不是的。“android”只是命名空间的名称,“http://schemas.android.com/apk/res/android”才是命名空间,你把xmlns:android="http://schemas.android.com/apk/res/android"改成xmlns:abcd="http://schemas.android.com/apk/res/android",在系统组件属性用abcd:layout_width也是没有问题的):   

[html]  view plain  copy
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:myview="http://blog.csdn.net/a_running_wolf"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent">  
  5.     <!--这里第二行xmlns:my="http://blog.csdn.net/a_running_wolf"就是自己定义的命名空间-->  
  6.     <!--***注意这里的http://blog.csdn.net/a_running_wolf要和自定义类中的nameSpace一致,否则属性找不出来的-->  
  7.   
  8.     <com.wangj.cusview.MyView1  
  9.         android:layout_width="wrap_content"  
  10.         android:layout_height="wrap_content"  
  11.         myview:Img="@drawable/xx"  
  12.         myview:Text="@string/hello_world" />  <!--上边定义了命名空间,这里就可以像系统控件的属性一样的写法了-->  
  13.   
  14. </RelativeLayout>  

        相应的,在MyView1中就应该使用我们声明的这个命名空间了,直接看代码(这个类应该先于布局文件创建,不然在布局文件中怎么会有自己的控件可以用呢?这里放在后边主要是让大家重点看“命名空间”)

[java]  view plain  copy
  1. public class MyView1 extends View {  
  2.     //要和布局文件中声明的命名空间一致  
  3.     private final String nameSpace = "http://blog.csdn.net/a_running_wolf";  
  4.     private String mtext;  
  5.     private int msrc;  
  6.   
  7.     public MyView1(Context context) {  
  8.         super(context);  
  9.     }  
  10.   
  11.     public MyView1(Context context, AttributeSet attrs) {  
  12.         super(context, attrs);  
  13.         int textId = attrs.getAttributeResourceValue(nameSpace, "Text"0);   //参数nameSpace用我们的命名空间  
  14.         int imgId = attrs.getAttributeResourceValue(nameSpace, "Img"0);  
  15.         mtext = getResources().getText(textId).toString();  
  16.         msrc = imgId;  
  17.     }  
  18.   
  19.     //onDraw方法和上边的一样  
  20.     .......  
  21.  }  
         改造完毕,运行一下和上边一致:

        

        显然这种方式能实现相应的效果,但是效果很不好,文字显示在图片的什么地方、如果文字很多图片很小呢、如果我要改文字位置还要手动改drawText的位置参数,那么我们来看更规范的做法——往下看。

        (2)通过XML资源文件为自定义控件注册属性(和Android系统提供属性的方式一致)
        通过XML资源文件为控件注册属性,并使用命名空间的方式就是Android系统控件的属性提供方法,所以这种方式是推荐的自定义控件开发方法。首先在value目录下创建attrs.xml文件(有的话就不用创建了,直接往进写),写入如下的属性字段和相应的值(当然这里是根据自己的需要写,如下只是示例):

[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.     <declare-styleable name="MyView2">  
  4.         <attr name="Text" format="reference|string" />  
  5.         <attr name="Oriental">  
  6.             <enum name="Herizontal" value="1" />  
  7.             <enum name="Vertical" value="0" />  
  8.         </attr>  
  9.         <attr name="Img" format="reference|integer" />  
  10.     </declare-styleable>  
  11. </resources>  

        创建好attrs.xml后就可以用这些属性字段了(前提是你先创建好MyView2,否则控件啊!可以先创建一个空类,构造方法具体实现先不写,等后边在具体实现):

[html]  view plain  copy
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:TV="http://schemas.android.com/apk/res-auto"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:padding="@dimen/activity_vertical_margin">  
  6.     <!-- 有的博客上说这里自己的命名空间要用“http://schemas.android.com/apk/包名”,但是我在AndroidStudio1.3.2、Android5.0时  
  7.     用“http://schemas.android.com/apk/res-auto”更智能一点 -->  
  8.   
  9.     <com.wangj.cusview.MyView2  
  10.         android:layout_width="wrap_content"  
  11.         android:layout_height="wrap_content"  
  12.         TV:Text="@string/app_name"  
  13.         TV:Img="@drawable/xx"  
  14.         TV:Oriental="Herizontal"/>  
  15. </RelativeLayout>  

       附上MyView2的实现类: 

[java]  view plain  copy
  1. public class MyView2 extends LinearLayout {   //为了简单,这里继承LinearLayout  
  2.     public MyView2(Context context) {  
  3.         super(context);  
  4.     }  
  5.   
  6.     public MyView2(Context context, AttributeSet attrs) {  
  7.         super(context, attrs);  
  8.         int resourceId = -1;  
  9.           
  10.         //获取布局文件中对应控件的属性  
  11.         TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyView2);  
  12.         ImageView iv = new ImageView(context);  
  13.         TextView tv = new TextView(context);  
  14.         int N = typedArray.getIndexCount();  
  15.         for (int i = 0; i < N; i++){  
  16.             int attr = typedArray.getIndex(i);  
  17.             switch (attr){  
  18.                 case R.styleable.MyView2_Text:  
  19.                     resourceId = typedArray.getResourceId(R.styleable.MyView2_Text, 0);  
  20.                     tv.setText(resourceId > 0 ? getResources().getText(resourceId) : typedArray.getString(R.styleable.MyView2_Text));  
  21.                     break;  
  22.                 case R.styleable.MyView2_Img:  
  23.                     resourceId = typedArray.getResourceId(R.styleable.MyView2_Img, 0);  
  24.                     iv.setImageResource(resourceId > 0 ? resourceId : R.mipmap.ic_launcher);  
  25.                     break;  
  26.                 case R.styleable.MyView2_Oriental:  
  27.                     resourceId = typedArray.getInt(R.styleable.MyView2_Oriental, 0);  
  28.                     this.setOrientation(resourceId == 1 ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL );  
  29.                     break;  
  30.             }  
  31.         }  
  32.   
  33.         //记住要将TextView和ImageView都addView,否则什么都显示不了  
  34.         addView(iv);  
  35.         addView(tv);  
  36.         typedArray.recycle();   //释放资源  
  37.     }  
  38.   
  39.     @Override  
  40.     protected void onDraw(Canvas canvas) {  
  41.         super.onDraw(canvas);  
  42.     }  
  43. }  

      MainActivity中没有做任何逻辑。直接运行,看效果:


         这里看到这个红色框中就是我们的MyView2控件(不包括红色框)。肯定有人问,这里看起来这个控件就和一个ImageView和一个TextView水平线性排列就一个样子啊? 其实是的,但是区别在于——这里是一个控件,而不是两个控件——这里是为了简单演示,就简单的让MyView2继承了LineraLayout。根据实际的需求,我们可已让它继承RelativeLayout......各种布局,再创建自己的属性根据属性来做出漂亮的控件;可以继承已有的系统控件,重写其构造方法或是覆盖其onDraw方法来扩展出自己的个性化控件

        这些都是自定义控件开发最基础的知识,接下来我们将逐步深入,开发更高级和更美观的自定义控件,我也是在边学边写,如有不足希望大家指出,一起讨论,一起进步,也希望大家继续关注我的文章。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
封面 1 序 2 捐助说明 5 目 录 7 第一章 View的绘图流程 12 1.1、概述 12 1.2、Activity的组成结构 13 1.3、View树的绘图流程 15 1.3.1 测量组件大小 16 1.3.2 确定子组件的位置 17 1.3.3 绘制组件 18 1.4、说点别的 22 1.5 练习作业 22 第二章 Graphics2D API 23 2.1、概述 23 2.2、Point类和PointF类 23 2.3、Rect类和RectF类 25 2.4、Bitmap类和BitmapDrawable类 32 2.5、Canvas类与Paint类 34 2.5.1 绘图概述 34 2.5.2 Paint类 34 2.5.3 Canvas类 39 2.6 练习作业 63 第三章 使用Graphics2D实现动态效果 64 3.1 概述 64 3.2 invalidate()方法 65 3.3 坐标转换 69 3.4 剪切区(Clip) 73 3.5 案例:指针走动的手表 82 3.6 练习作业 88 第四章 双缓存技术 89 4.1 双缓存 89 4.2 在屏幕上绘制曲线 90 4.3 在屏幕上绘制矩形 99 4.4 案例:绘图App 104 4.4.1 绘图属性 106 4.4.2 软件参数 108 4.4.3 绘图缓冲区 109 4.4.4 撤消操作 111 4.4.5 图形绘制 113 4.4.6 绘图区 118 4.4.7 主界面 119 4.5 练习作业 122 第五章 阴影、渐变和位图运算 123 5.1 概述 123 5.2 阴影 123 5.3 渐变 125 5.3.1 线性渐变(LinearGradient) 126 5.3.2 径向渐变(RadialGradient) 130 5.3.3 扫描渐变(SweepGradient) 135 5.3.4 位图渐变(BitmapShader) 138 5.3.5 混合渐变(ComposeShader) 140 5.3.6 渐变与Matrix 142 5.4 位图运算 143 5.4.1 PorterDuffXfermode 143 5.4.2 图层(Layer) 146 5.4.3 位图运算技巧 148 5.5 案例1:圆形头像 152 5.6 案例2:刮刮乐 156 5.7 练习作业 161 第六章 自定义组件 163 6.1 概述 163 6.2 自定义组件的基本结构 164 6.3 重写onMeasure方法 166 6.4 组件属性 175 6.4.1 属性的基本定义 175 6.4.2 读取来自style和theme的属性 181 6.5 案例1:圆形ImageView组件 186 6.6 案例2:验证码组件CodeView 190 6.7 练习作业 202 第七章 自定义容器 204 7.1 概述 204 7.2 ViewGroup类 205 7.2.1 ViewGroup常用方法 205 7.2.2 ViewGroup的工作原理 208 7.2.3 重写onLayout()方法 213 7.3 CornerLayout布局 217 7.3.1 基本实现 217 7.3.2 内边距padding 224 7.3.3 外边距margin 228 7.3.4 自定义LayoutParams 238 7.4 案例:流式布局(FlowLayout) 246 7.5 练习作业 256 第八章 Scroller与平滑滚动 257 8.1 概述 257 8.2 认识scrollTo()和scrollBy()方法 258 8.3 Scroller类 264 8.4 平滑滚动的工作原理 271 8.5 案例:触摸滑屏 272 8.5.1 触摸滑屏的技术分析 272 8.5.2 速度跟踪器VelocityTracker 273 8.5.3 触摸滑屏的分步实现 274 8.6 练习作业 285 第九章 侧边栏 287 9.1 概述 287 9.2 使用二进制保存标识数据 289 9.2.1 位运算符 289 9.2.2 位运算的常用功能 292 9.3 继承自ViewGroup的侧边栏 293 9.4 继承自HorizontalScrollView的侧边栏 304 9.5 练习作业 312 第十章 加强版ListView 313 10.1 概述 313 10.2 ListView的基本使用 314 10.3 ListItem随手指左右滑动 318 10.4 向右滑动删除ListItem 326 10.5 滑动ListItem出现删除按钮 336 10.5.1 列表项专用容器ExtendLayout 337 10.5.2 列表项能滑出删除按钮的ListView 342 10.5.3 定义布局文件 350 10.5.4 显示ListView 351 10.6练习作业 353 案例代码说明 354

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值