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

来源:互联网 发布:网络上好听的流行歌曲 编辑:程序博客网 时间:2024/06/15 02:36
        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 0