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方法重绘实现)


    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);
    }


}


               布局文件:

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




    





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

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


 
   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();

    }

}
 



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





    
        
        
            
            
        
        
    



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


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




    

    
    



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

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

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

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

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

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

onKeyDown 当按下某个键盘时

onKeyUp  当松开某个键盘时

onTrackballEvent 当发生轨迹球事件时

onTouchEvent 当发生触屏事件时

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

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

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

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


             

    

你可能感兴趣的:(Android,自定义控件,Android,自定义控件,View绘制,控件组合)