Android自定义控件进阶篇(一)

        Android自定义控件,相信每个开发安卓的 IT Boy 都想学会的技能,最近自己在摸索里面的门道,博客是大神进阶之路,虽然现在还是一个小弟(*^__^*) 嘻嘻……,但是懂得分享成果才是最重要的,希望我们的 IT Boy 都有一颗上进的心❤,今天为大家分享的是:Android自定义控件基础篇,本博文有参考博主: http://blog.163.com/ppy2790@126/blog/static/103242241201382210910473/ 博文,转载注明出处,尊重原创。

        一、开发自定义控件,首先我们要先知道Android 中的自定义控件可以分为哪几类:

       (1)、继承已有的控件来实现自定义控件,主要是当要实现的控件和已有的控件在很多方面比较类似, 通过对已有控件的扩展来满足要求。比如  MyEditText   extends EditText  重写EditTest控件来实现自己的需求等。

       (2)、通过继承一个布局文件实现自定义控件,一般来说组合控件可以通过这种方式来实现,例如 MyLayout  extends LinearLayout 等。

        (3)、通过继承 View 类来实现自定义控件,使用 GDI绘制出组件界面,一般如果无法通过重写组件达到要求的,就可以采用自己写控件。看起来是不是高大上 (*^__^*) 嘻嘻……。

       二、开发自定义控件的步骤:

        (1):了解View的工作原理

        (2):编写继承自 View 的子类

        (3):为自定义 View 类增加属性

        (4):绘制控件

        (5):响应用户消息

        (6):自定义回调函数


        2.1  了解View的结果原理

           Android 系统的视图结构设计也采用的是组合模式,即View作为所有图形的基类,Viewgroup对View继承扩展为视图容器类。

           View中定义了绘图的基本操作

           基本操作由三个函数来完成:measure()、layout()、draw(),这三和函数都被封装成了 final 类型,其内部又分别包含了onMeasure()、onLayout()、onDraw()三个子方法。具体操作如下:

          (1)onMeasure() , 视图大小将在这里最终确定,也就是说 measure() 函数只是对 onMeasure() 的一个封装,子类可以覆盖onMeasure() 方法实现自己的计算视图大小的方式并通过 setMeasuredDimension(width, height) 保存结算结果。

          (2)Layout() 操作

                    Layout()函数用于设置视图在屏幕中显示的位置。在View 中定义为 final类型,要求子类不能修改。Layout()函数中有两个基本操作:            

                   ①、setFrame(int left, int top, int right, int bottom)  left,top,right,bottom  即子视图在父视图中的具体位置,该函数用于将这些参数保存起来;                  

                   ②、onLayout(boolean changed, int left, int top, int right, int bottom) 在View中这个函数什么都不会做,提供该函数主要是为viewGroup类型布局子视图用的;


          (3)draw() 操作

                   draw() 函数利用前面两部分得到的参数,将视图显示在屏幕上,到这里也就完成了整个的视图绘制工作,子类应该修改该方法,因为内部定义了绘图的基本操作:

                    ①、绘制背景;

                    ②、如果视图要绘制渐变框,这里会做些准备工作;

                    ③、绘制视图本身,即调用 onDraw()函数。在View 中onDraw()是个空函数,也就是说具体的视图都要覆写该函数来实现自己的显示(比如TextView在这里实现了绘制文字的过程)。

                   注意:对于ViewGroup则不需要实现该函数,因为作为容器是“没有内容的”,其中包含了多个子View,而子View已经实现了自己的绘制方法,因此只需要告诉View绘制自己的就可以,也就是下面的 dispatchDraw() 函数。

                   ④、绘制子视图、即 dispatchDraw() 函数。在View中这事个空函数,具体的视图不需要实现该方法,它是专门为容器类准备的,也就是容器类不需实现该方法;

                   ⑤、如果需要(应用程序调用了setVerticalFadingEdge或者setHorizontalFadingEdge),开始绘制渐变框;

                   ⑥、绘制滚动条;

                   从上面可以看出自定义View 需要最少覆 onMeasure() 和 onDraw()两个方法。


        3  、View类的构造

               创建一个自定义控件,一般要实现如下三个构造函数,有没有想过为什么要实现三种构造呢,接下来分析原因。

             (1)、构造函数【1】 该构造函数,如果在代码中实例化一个View会调用该函数,也就是我们常用的代码中动态添加控件。

             (2)、构造函数【2】该构造如果在 xml中引入了该自定义控件会调用此函数。

             (3)、构造函数【3】该构造是定义的有默认样式的时候使用到此函数。

    public MyCustomView(Context context) {
        super(context);
    }

    public MyCustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

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


        4 、
自定义View增加属性的两种方法:

             (1)、在View中定义。通过构造函数中引入 AttributeSet 去查找XML布局的属性名称,然后找到它对应引用的资源ID去找值。

               案例:实现一个带文字的图片(图片、文字是onDraw方法重绘实现)


<span style="font-size:18px;">    public class MyView extends View {

    private String mtext;
    private int msrc;

    public MyView(Context context) {
        super(context);
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);

        int resourceId = 0;

        /**
         *  attrs.getAttributeResourceValue(null, "Text", 0); 注释
         *
         * 返回“属性”作为资源标识符的值。
         * 请注意,这是不同于 {@link #getAttributeNameResource}
         * 在该属性中返回该属性所包含的值
         * 资源标识符(例如,一个值的形式 “@package:type/resource"”)
         * 另一方法返回资源标识属性名称的*标识符。
         *
         * @param namespace    属性的名称空间检索。
         * @param attribute    检索的属性。
         * @param defaultValue 如果属性没有找到返回的值,返回默认值。
         */
        int textId = attrs.getAttributeResourceValue(null, "Text", 0);
        int srcId = attrs.getAttributeResourceValue(null, "Src", 0);

        mtext = context.getResources().getText(textId).toString();
        msrc = srcId;

    }


    @Override
    protected void onDraw(Canvas canvas) {

        Paint paint = new Paint();
        paint.setColor(Color.RED);
        InputStream is = getResources().openRawResource(msrc);

        Bitmap mBitmap = BitmapFactory.decodeStream(is);

        //图片和文字的位置需要再定?还有问题

        canvas.drawBitmap(mBitmap, 0, 0, paint);
        int w = mBitmap.getWidth();
        int h = mBitmap.getHeight();

        //canvas.drawCircle(40, 90, 15, paint);

        canvas.drawText(mtext, w / 2, 20, paint);
    }


}</span>


               布局文件:

                                  属性 Text, Src在自定义View类的构造方法中读取。

</pre><pre name="code" class="html"><span style="font-size:18px;"><?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <com.example.myimageview2.MyView
        android:id="@+id/myView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" 
        Text="@string/hello_world"
        Src="@drawable/xh"/>

</LinearLayout></span>



             (2)、通过XML为View注册属性。与Android提供的标准属性写法一样。

              案例:实现一个带文字说明的ImageView (ImageView+TextView 的组合)          


 
 <span style="font-size:18px;">  public class MyImageView extends LinearLayout {

    public MyImageView(Context context) {
        super(context);

    }

    public MyImageView(Context context, AttributeSet attrs) {
        super(context, attrs);

        int resourceId = -1;

        /**
         * 4个参数的意思分别是:

         *  set:属性值的集合

         * attrs:我们要获取的属性的资源ID的一个数组,如同ContextProvider中请求数据库时的Projection数组,就是从一堆属性中我们希望查询什么属性的值

         * defStyleAttr:这个是当前Theme中的一个attribute,是指向style的一个引用,当在layout xml中和style中都没有为View指定属性时,会从Theme中这个attribute指向的Style中查找相应的属性值,这就是defStyle的意思,如果没有指定属性值,就用这个值,所以是默认值,但这个attribute要在Theme中指定,且是指向一个Style的引用,如果这个参数传入0表示不向Theme中搜索默认值

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

         */
        TypedArray typedArray = context.obtainStyledAttributes(attrs,
                R.styleable.MyImageView);


        ImageView iv = new ImageView(context);
        TextView tv = new TextView(context);

        int N = typedArray.getIndexCount();
        for (int i = 0; i < N; i++) {
            int attr = typedArray.getIndex(i);

            switch (attr) {
                case R.styleable.MyImageView_Oriental:
                    resourceId = typedArray.getInt(
                            R.styleable.MyImageView_Oriental, 0);
                    this.setOrientation(resourceId == 1 ? LinearLayout.HORIZONTAL
                            : LinearLayout.VERTICAL);
                    break;

                case R.styleable.MyImageView_Text:
                    resourceId = typedArray.getResourceId(
                            R.styleable.MyImageView_Text, 0);
                    tv.setText(resourceId > 0 ? typedArray.getResources().getText(
                            resourceId) : typedArray
                            .getString(R.styleable.MyImageView_Text));
                    break;


                case R.styleable.MyImageView_Src:
                    resourceId = typedArray.getResourceId(
                            R.styleable.MyImageView_Src, 0);
                    iv.setImageResource(resourceId > 0 ? resourceId : R.mipmap.ic_launcher);

                    break;

            }

        }

        addView(iv);
        addView(tv);
        typedArray.recycle();

    }

}</span>
 



               attrs.xml 进行属性声明, 文件放在values目录下


<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="MyImageView">
        <attr name="Text" format="reference|string"></attr>
        <attr name="Oriental" >
            <enum name="Horizontal" value="1"></enum>
            <enum name="Vertical" value="0"></enum>
        </attr>
        <attr name="Src" format="reference|integer"></attr>
    </declare-styleable>

</resources>

               注意:使用Android Studio开发时要引用如自定义属性声明,然后才可以像系统定义的属性一样引用


<span style="font-size:18px;"><span style="color:#FF0000;"><span style="color:#000000;">xmlns:custom="http://schemas.android.com/apk/res-auto"</span></span></span>


<span style="font-size:18px;"><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:uview="http://schemas.android.com/apk/res/com.example.myimageview2"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />

    <com.example.myimageview2.MyImageView
        android:id="@+id/myImageView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        uview:Text="这是一个图片说明" 
        uview:Src="@drawable/tw"
        uview:Oriental="Vertical">
    </com.example.myimageview2.MyImageView>

</LinearLayout></span>


        5 、Android自定义控件还有很多需要学习的地方,下一篇我们继续深入了解,一下是在自定义中长用到的函数。

onFinishInflate() 回调方法,当应用从XML加载该组件并用它构建界面之后调用的方法

onMeasure() 检测View组件及其子组件的大小

onLayout() 当该组件需要分配其子组件的位置、大小时

onSizeChange() 当该组件的大小被改变时

onDraw() 当组件将要绘制它的内容时

onKeyDown 当按下某个键盘时

onKeyUp  当松开某个键盘时

onTrackballEvent 当发生轨迹球事件时

onTouchEvent 当发生触屏事件时

onWindowFocusChanged(boolean)  当该组件得到、失去焦点时

onAtrrachedToWindow() 当把该组件放入到某个窗口时

onDetachedFromWindow() 当把该组件从某个窗口上分离时触发的方法

onWindowVisibilityChanged(int): 当包含该组件的窗口的可见性发生改变时触发的方法
 


             

    

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值