26
2017
09

自定义控件和自定义属性的总结

自定义控件和自定属性

这是自己的学习笔记,不大完整和正确,希望大家指正,刚开始学习的时候的总结,还有很多没有完成,后面一一完成!告诉自己坚持下去,你会获取到你需要的!一个字就是干!

常见的控件的继承类型

最上层是view类,直接子类是Textview.Imageview,viewgroup;
Textview的直接子类是:Button和EditTEXT ;
Imageview,
ViewGroup的直接子类就是常见的布局,可以理解为容器.

我们常见的控件是view的直接子类的是:

1.imageView 2. progressbar 3.TextView 4.viewGroup

我们常见viewGroup的直接子类的是

常见的五大布局

RelativeLayout 相对布局 LinearLayout线性布局 TableLayout 表格布局 FrameLayout针布局 AbsoluteLayout 绝对布局

自定义控件的常见的分类:

常见的自定义控件有四种:

#

  1. 使用已经存在的控件组合起来,在加上动画构成一个新的自定义控件.我们写过的优酷菜单,其实就是各个控件的组合和旋转动画.

#

  1. 继承现有的控件做增强功能.也就是继承viewgroup的子类的一个增强.我们写过的继承RelateLayout,button,textview等.

#

  1. 完全继承view的自定义控件(完全继承view的自定义控件,的显示过程只需要测量和绘制就行,不需要进行布局.)
    注意点就是:继承view的测量方法是onMeasure()中调用setMeasuredDimension();来实现测量,此时view中没有孩子,只需要测量自己就行.
    我们做过的自定义控件:view开关按钮,ToggleButton

#

  1. 继承ViewGroup的自定义控件(继承ViewGroup)
    我们做过的侧滑控件就是继承viewGroup的

view的原理

首先是Android系统中的视图结构是也采用了组合模式,也就是说View是作为所有控件的基类,viewGroup相当于是view的派生类.
所谓的派生类在这里就是指继承view形成的一个类,viewgroup是一个相当于是一个容器,来存放其他的控件的.ViewGroup继承view作为视图容器类.
在View中定义了三个方法,来完成一个控件的展示过程,那就是,先测量控件的宽和高measure()方法,对后面控件的展示过程做到一个初始化参数的地步.后面就是布局,因为View是所有的控件,并且派生出一个ViewGroup的容器类,里面会有子类,我们需要指定子类控件的位置,所以View中还有一个layout()的方法,宽度和高度都有了,还具有了在父控件中位置,我们就可以将控件绘制出来.所以具有一个draw()方法.三个方法中个包含一个子方法,我们一一详解

方法一 measure()

在View中measure()方法的作用,主要是用来计算视图的大小,也就是计算视图的宽和高,定义的类型是final类型,所以在自定义控件的时候我们最好不要重写measure()方法;关键点就是在measure()中会调用自己的子方法 onMeasure();onMeasure()方法中会调用;setMeasuredDimension(with,heignt)方法

onMeasure(),真正去测量并设置View的大小。

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

方法二 layout()方法

在view中提供了layout()方法,主要是设置视图在屏幕中的显示位置,和measure()方法相似,定义的类型也是final,要求子类不去修改.
在layout中有两个关于位置的方法,第一个方法就是setFrame()
layout()中的方法一:
protected boolean setFrame(int left, int top, int right, int bottom)
layout()中的方法二
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {};
第一个方法是用来保存子视图在父视图中的位置,保存的其实是两个点,分别是左上点和右下点.onLayout()方法主要是给派生类viewGroup来实用的,用于指定子空间在viewgroup中的位置.

方法三 draw()方法

前面的measure()是计算视图宽和高,最后保存在onMeasure()中的setMeasureDimension(with,height)和位置,是对于绘制的参数的初始化,只有通过绘制,才能展示出来.

draw操作利用前两部得到的参数,将视图显示在屏幕上,到这里也就完成了整个的视图绘制工作。子类也不应该修改该方法,因为其内部定义了绘图的基本操作:
(1)绘制背景;
(2)如果要视图显示渐变框,这里会做一些准备工作;
(3)绘制视图本身,即调用onDraw()函数。在view中onDraw()是个空函数,也就是说具体的视图都要覆写该函数来实现自己的显示(比如TextView在这里实现了绘制文字的过程)。而对于ViewGroup则不需要实现该函数,因为作为容器是“没有内容“的,其包含了多个子view,而子View已经实现了自己的绘制方法,因此只需要告诉子view绘制自己就可以了,也就是下面的dispatchDraw()方法;
(4)绘制子视图,即dispatchDraw()函数。在view中这是个空函数,具体的视图不需要实现该方法,它是专门为容器类准备的,也就是容器类必须实现该方法;
(5)如果需要(应用程序调用了setVerticalFadingEdge或者setHorizontalFadingEdge),开始绘制渐变框;
(6)绘制滚动条;

自定义控件继承View

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

自定义控件继承ViewGroup

http://blog.csdn.net/guiman/article/details/51225809

viewGroup内部可以用来存放多个View控件,并且根据自身的测量模式,来测量View子控件,并且决定View子控件的位置。子控件的测量根据父控件的测量数据来进行测量的,具体如下:

我们重写了onMeasure(),在方法里面,我们首先先获取ViewGroup中的子View的个数,然后遍历它所有的子View,得到每一个子View,调用measureChild()放来,来对子View进行测量。子View的测量是根据ViewGroup所提供的测量模式来进行来,所以在measureChild()方法中,把ViewGroup的widthMeasureSpec 和 heightMeasureSpec和子View一起传进去了,@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int childCount = getChildCount();
for(int i = 0 ; i < childCount ; i ++){
View children = getChildAt(i);
measureChild(children,widthMeasureSpec,heightMeasureSpec);
}
}


测量孩子的方法的具体实现:

measureChild()源码方法里面很好理解,它首先得到子View的LayoutParams,然后根据ViewGroup传递进来的宽高属性值和自身的LayoutParams 的宽高属性值及自身padding属性值分别调用getChildMeasureSpec()方法获取到子View的测量。由该方法我们也知道ViewGroup中在测量子View的大小时,测量结果分别是由父节点的测量模式和子View本身的LayoutParams及padding所决定的。

 protected void measureChild(View child, int parentWidthMeasureSpec,
        int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();

    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

etChildMeasureSpec()方法源码:
首先是获取父节点(这里是ViewGroup)的测量模式和测量的大小,并根据测量的大小值与子View自身的padding属性值相比较取最大值得到一个size的值。然后根据父节点的测量模式分别再来判定子View的LayoutParams属性值,根据LayoutParams的属性值从而获取到子View测量的大小和模式,知道了ziView的测量模式和大小就能决定子View的大小了。
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    int specMode = MeasureSpec.getMode(spec);
    int specSize = MeasureSpec.getSize(spec);

    int size = Math.max(0, specSize - padding);

    int resultSize = 0;
    int resultMode = 0;

    switch (specMode) {
    // Parent has imposed an exact size on us
    case MeasureSpec.EXACTLY:
        if (childDimension >= 0) {
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size. So be it.
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size. It can't be
            // bigger than us.
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;

    // Parent has imposed a maximum size on us
    case MeasureSpec.AT_MOST:
        if (childDimension >= 0) {
            // Child wants a specific size... so be it
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size, but our size is not fixed.
            // Constrain child to not be bigger than us.
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size. It can't be
            // bigger than us.
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;

    // Parent asked to see how big we want to be
    case MeasureSpec.UNSPECIFIED:
        if (childDimension >= 0) {
            // Child wants a specific size... let him have it
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size... find out how big it should
            // be
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size.... find out how
            // big it should be
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        }
        break;
    }
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

自定义的viewgroup需要覆写的方法是
必须的方法是onMeasure()方法来测量自己和测量子控件的宽和高,第二方法是onLayout()确定子空间在ViewGroup()中的位置,至于ondraw(),viewgroup不需要覆写,自己是一个容器,是不需要绘制,只需要告诉子空间绘制自己就行,dispatchDraw()方法就是告诉自己的子控件去绘制自己的方法.

自定义控件的实现

http://www.cnblogs.com/0616–ataozhijia/p/4003380.html

首先自定义控件的实现是继承一个控件,重写里面的三个方法,算是自定义控件的创建就完成了!
实际中有四个方法,

第一个方法是在new 出对象的时候来写调用,第二个方法是在具有一个属性的参数,表示的是我们在布局文件中使用的时候会调用到第二个方法,

第二个两个参数多额方法中就有一个自定义属性,在布局中使用,在使用之前我们需要先设置自定义属性.

第三个方法是我们 在布局中使用并且右样式的时候调用第三个方法.

自定义属性

步骤 在此使用的是Android Studio

在资源文件目录下d的values中新建一个attrs.xml;
也就是;res -> values -> attrs.xml

declare声明的意思
1) 添加自定义属性到xml文件中
<?xml version="1.0" encoding="utf-8"?>
<resources>
//第一name是声明对哪一个自定义控件的声明的自定义属性
<declare-styleable name="RatioLayout" >
// name自定义属性的名称 format:是在定义属性的类型,是double还是float还是true等类型
<attr name="ratio" format="float"/>
</declare-styleable>
</resources>

2) 在xml的中,指定属性的值
3) 在view中获取xml中的值

//我们已经在自定义了属性,那么我们就可以在自定义在布局中声明命名控件,那我们就可以在布局中使用了


命名空间:xmlns:**itheima**="http://schemas.android.com/apk/res-auto"
itheima;可以自己随意定义,不要和标准相重合就行.
<cast.itheima.com.mygoogleapp94.view.RatioLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
itheima:ratio="2.45">
<ImageView
android:id="@+id/subjectpagerdetail_pullto_icon"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@mipmap/ic_launcher" />
</cast.itheima.com.mygoogleapp94.view.RatioLayout>

//使用后,会对自定义属性进行复制,我们可以在自定义控件中获取赋予的值.


在自定义控件中获取我们设置的自定义属性的值的两中方法

第一种: 使用自定义控件中的属性参数 (AttributeSet attrs)
参数说明:第一个参数是:命名控件,在使用布局中使用自定义属性的时定义过
attrs.getAttributeFloatValue(参数三个);

float scale = attrs.getAttributeFloatValue(NAMESPACE,"ratio",-1);
参数说明:第一个参数是:命名空间 private static final String NAMESPACE= “http://schemas.android.com/apk/res-auto/“;
参数二:是自定义属性的名称:”ratio”
参数三:自定义属性类型的默认值:


获取自定义属性在布局中的复制方法二:
使用context方法来获取:

//第二种方法就是使用context的方法
//当View被创建的时候,可以通过AttributeSet读取所有的定义在xml中的属性,在构造函数中通过obtainStyledAttributes读取attrs,
// 该方法会返回一个TypeArray数组。通过TypeArray可以读取到已经定义在XML中的方法。
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RatioLayout);
参数说明:参数一:类型 AttributeSet 表示的是自定义属性的集合,也就是在自定义属性中定义的<attr name ="">中的集合,
参数二:是对于哪一个自定义控件,我们自定义属性首先会声明是对哪一个自定义的控件进行申明的自定义属性.
结果:是包含TypedArray类中Container for an array of values that were retrieved with
//获取ration
ratio = typedArray.getFloat(R.styleable.RatioLayout_ratio, -1);
//回收
typedArray.recycle();

4) 将获取的值应用到view中

在上面的中需要注意点:

第一是:命名控件的生成
xmlns:itheima=”http://schemas.android.com/apk/res-auto”
首先是: xmls 后面的只是一个标识,随意些,不要和系统命名重名就行,相当于xmlns:android=”http://schemas.android.com/apk/res/android”中的android

在eclipse中
res-auto是自定义控件的全类名,在androidStudio中:xmlns:itheima=”http://schemas.android.com/apk/res-auto”

快捷键:

ctrl+ h 表示的是层次结构

获取自定义属性的两种方法

第一中方法就是:

第二种方式就是:


https://github.com/viclee2014/VerticalSwitchTextView/blob/master/app/src/main/res/raw/vertical_switch_textview.gif

实现tetxview的竖直方向的跑马灯的效果的自定义控件:


上一篇:[RK3288][Android6.0] 调试笔记 --- AndroidTool低格无效问题 下一篇:Android延迟界面跳转的方法