Android自定义View

为什么要自定义View

大多数情况下,开发者常常会因为下面四个原因去自定义View:

  • 让界面有特定的显示风格、效果
  • 让控件具有特殊的交互方式
  • 优化布局
  • 封装

让界面有特定的显示风格、效果

默认情况下,Android系统为开发者提供了很多控件,但有时,这并不能满足开发者的需求。例如,开发者想要用一个饼状图来展示一组数据,这时如果用系统提供的View就不能实现了,只能通过自定义View来实现。

让控件具有特殊的交互方式

默认情况下,Android系统为开发者提供的控件都有属于它们自己的特定的交互方式,但有时,控件的默认交互方式并不能满足开发者的需求。例如,开发者想要缩放ImageView中的图片内容,这时如果用系统提供的ImageView就不能实现了,只能通过自定义ImageView来实现。

优化布局

有时,有些布局如果用系统提供的控件实现起来相当复杂,需要各种嵌套,虽然最终也能实现了想要的效果,但性能极差,此时就可以通过自定义View来减少嵌套层级、优化布局。

封装

有些控件可能在多个地方使用,如大多数App里面的底部Tab,像这样的经常被用到的控件就可以通过自定义View将它们封装起来,以便在多个地方使用。

自定义View的分类

自定义View一共分为两大类,具体如下图:

åç±»
对于自定义View的类型介绍及使用场景如下图:

å·ä½ä»ç» & 使ç¨åºæ¯
3. 自定义View注意点
3.1 支持wrap_content
如果不在onMeasure()中对wrap_content作特殊处理,那么wrap_content属性不起自身应有的作用,而且是起到与match_parent相同作用。

wrap_contentmatch_parent区别:

  1. wrap_content:视图的宽/高被设定成刚好适应视图内容的最小尺寸
  2. match_parent:视图的宽/高被设置为充满整个父布局

问题出现在View的宽 / 高设置,那我们直接来看View测量时的默认实现

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
//View宽/高的测量值是通过getDefaultSize()获得的
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
           getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
}

继续往下看getDefaultSize

public static int getDefaultSize(int size, int measureSpec) {  

//参数说明:
// 第一个参数size:提供的默认大小
// 第二个参数:宽/高的测量规格(含模式 & 测量大小)

    //设置默认大小
    int result = size; 

    //获取宽/高测量规格的模式 & 测量大小
    int specMode = MeasureSpec.getMode(measureSpec);  
    int specSize = MeasureSpec.getSize(measureSpec);  

    switch (specMode) {  
        // 模式为UNSPECIFIED时,使用提供的默认大小
        // 即第一个参数:size 
        case MeasureSpec.UNSPECIFIED:  
            result = size;  
            break;  
        // 模式为AT_MOST,EXACTLY时,使用View测量后的宽/高值
        // 即measureSpec中的specSize
        case MeasureSpec.AT_MOST:  
        case MeasureSpec.EXACTLY:  
            result = specSize;  
            break;  
    }  

 //返回View的宽/高值
    return result;  
}

从上面发现:

  • getDefaultSize()的默认实现中,当View的测量模式是AT_MOST或EXACTLY时,View的大小都会被设置成子View MeasureSpec的specSize。
  • 因为AT_MOST对应wrap_content;EXACTLY对应match_parent,所以,默认情况下,wrap_contentmatch_parent是具有相同的效果的。

那么有人会问:wrap_content和match_parent具有相同的效果,为什么是填充父容器的效果呢?

  • 由于在getDefaultSize()的默认实现中,当View被设置成wrap_contentmatch_parent时,View的大小都会被设置成子View MeasureSpec的specSize。
  • 所以,这个问题的关键在于子View MeasureSpec的specSize的值是多少

我们知道,子View的MeasureSpec值是根据子View的布局参数(LayoutParams)和父容器的MeasureSpec值计算得来,具体计算逻辑封装在getChildMeasureSpec()里。

接下来,我们看生成子View MeasureSpec的方法:getChildMeasureSpec()的源码分析:

//作用:
/ 根据父视图的MeasureSpec & 布局参数LayoutParams,计算单个子View的MeasureSpec
//即子view的确切大小由两方面共同决定:父view的MeasureSpec 和 子view的LayoutParams属性 


public static int getChildMeasureSpec(int spec, int padding, int childDimension) {  

 //参数说明
 * @param spec 父view的详细测量值(MeasureSpec) 
 * @param padding view当前尺寸的的内边距和外边距(padding,margin) 
 * @param childDimension 子视图的布局参数(宽/高)

    //父view的测量模式
    int specMode = MeasureSpec.getMode(spec);     

    //父view的大小
    int specSize = MeasureSpec.getSize(spec);     

    //通过父view计算出的子view = 父大小-边距(父要求的大小,但子view不一定用这个值)   
    int size = Math.max(0, specSize - padding);  

    //子view想要的实际大小和模式(需要计算)  
    int resultSize = 0;  
    int resultMode = 0;  

    //通过父view的MeasureSpec和子view的LayoutParams确定子view的大小  


    // 当父view的模式为EXACITY时,父view强加给子view确切的值
   //一般是父view设置为match_parent或者固定值的ViewGroup 
    switch (specMode) {  
    case MeasureSpec.EXACTLY:  
        // 当子view的LayoutParams>0,即有确切的值  
        if (childDimension >= 0) {  
            //子view大小为子自身所赋的值,模式大小为EXACTLY  
            resultSize = childDimension;  
            resultMode = MeasureSpec.EXACTLY;  

        // 当子view的LayoutParams为MATCH_PARENT时(-1)  
        } else if (childDimension == LayoutParams.MATCH_PARENT) {  
            //子view大小为父view大小,模式为EXACTLY  
            resultSize = size;  
            resultMode = MeasureSpec.EXACTLY;  

        // 当子view的LayoutParams为WRAP_CONTENT时(-2)      
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
            //子view决定自己的大小,但最大不能超过父view,模式为AT_MOST  
            resultSize = size;  
            resultMode = MeasureSpec.AT_MOST;  
        }  
        break;  

    // 当父view的模式为AT_MOST时,父view强加给子view一个最大的值。(一般是父view设置为wrap_content)  
    case MeasureSpec.AT_MOST:  
        // 道理同上  
        if (childDimension >= 0) {  
            resultSize = childDimension;  
            resultMode = MeasureSpec.EXACTLY;  
        } else if (childDimension == LayoutParams.MATCH_PARENT) {  
            resultSize = size;  
            resultMode = MeasureSpec.AT_MOST;  
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
            resultSize = size;  
            resultMode = MeasureSpec.AT_MOST;  
        }  
        break;  

    // 当父view的模式为UNSPECIFIED时,父容器不对view有任何限制,要多大给多大
    // 多见于ListView、GridView  
    case MeasureSpec.UNSPECIFIED:  
        if (childDimension >= 0) {  
            // 子view大小为子自身所赋的值  
            resultSize = childDimension;  
            resultMode = MeasureSpec.EXACTLY;  
        } else if (childDimension == LayoutParams.MATCH_PARENT) {  
            // 因为父view为UNSPECIFIED,所以MATCH_PARENT的话子类大小为0  
            resultSize = 0;  
            resultMode = MeasureSpec.UNSPECIFIED;  
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
            // 因为父view为UNSPECIFIED,所以WRAP_CONTENT的话子类大小为0  
            resultSize = 0;  
            resultMode = MeasureSpec.UNSPECIFIED;  
        }  
        break;  
    }  
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  

总结表格如下: 

从上面可以看出,当子View参数设置为WRAP_CONTENT时,测量大小为父容器剩余空间大小,也就是和设置为MATCH_PARENT是一样的效果,都是充满父容器剩余空间 。

解决方案

当自定义View的布局参数设置成wrap_content时,指定一个默认大小(宽 / 高)。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    super.onMeasure(widthMeasureSpec, heightMeasureSpec);


    // 获取宽-测量规则的模式和大小
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);

    // 获取高-测量规则的模式和大小
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    // 设置wrap_content的默认宽 / 高值
    // 默认宽/高的设定并无固定依据,根据需要灵活设置
    // 类似TextView,ImageView等针对wrap_content均在onMeasure()对设置默认宽 / 高值有特殊处理,具体读者可以自行查看
    int mWidth = 400;
    int mHeight = 400;

  // 当模式是AT_MOST(即wrap_content)时设置默认值
    if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(mWidth, mHeight);
    // 宽 / 高任意一个模式为AT_MOST(即wrap_content)时,都设置默认值
    } else if (widthMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(mWidth, heightSize);
    } else if (heightMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(widthSize, mHeight);
}

这样,当你的自定义View的宽 / 高设置成wrap_content属性时就会生效了。

3.2 支持padding & margin
如果不支持,那么padding和margin(ViewGroup情况)的属性将失效
对于继承View的控件,padding是在draw()中处理
对于继承ViewGroup的控件,padding和margin会直接影响measure和layout过程
3.3 多线程应直接使用post方式
View的内部本身提供了post系列的方法,完全可以替代Handler的作用,使用起来更加方便、直接。

3.4 避免内存泄露
主要针对View中含有线程或动画的情况:当View退出或不可见时,记得及时停止该View包含的线程和动画,否则会造成内存泄露问题。

启动或停止线程/ 动画的方式:

启动线程/ 动画:使用view.onAttachedToWindow(),因为该方法调用的时机是当包含View的Activity启动的时刻
停止线程/ 动画:使用view.onDetachedFromWindow(),因为该方法调用的时机是当包含View的Activity退出或当前View被remove的时刻
3.5 处理好滑动冲突
当View带有滑动嵌套情况时,必须要处理好滑动冲突,否则会严重影响View的显示效果。

3.6 onDraw()方法

onDraw()方法中不创建对象,避免内存抖动

在onDraw()方法中调用invalidate()会导致主线程handler一直不空闲,idelHandler得不到执行

3.7 View的坐标系

屏幕的左上角为坐标的原点,屏幕上边缘往右为X轴正方向,屏幕左边缘往下为Y轴正方向.
View坐标系
(1)View自身坐标:getLeft(),getTop(),getRight(),getBottom()

(2)View自身宽高: getWidth(),getMeasuredWidth(),getHeight(),getMeasuredHeight()

(3)MotionEvent获取坐标:getX(),getY(),getRawX(),getRawY()

(4)View的Padding:getPaddingLeft(),getPaddingTop(),getPaddingRight(),getPaddingBottom()

Position:
getLeft() : 子view的左边缘相对于父view左边缘的距离,单位是像素。
getRight(): 子view的右边缘相对于父view左边缘的距离,单位是像素。
getTop (): 子view的上边缘相对于父view上边缘的距离,单位是像素。
getBottom():子view的下边缘相对于父view上边缘的距离,单位是像素。

Size
getMeasuredWidth():是在XML或者代码中设置的宽度,即原始宽度。
getWidth() :是最终测量的宽度,是在onLayout()之后获得的值。
两者之间可能相同,可能不同,getMeasuredHeight(),getHeight()同理。

MotionEvent
getX():点击事件的点在控件中的X坐标,即点相对于控件左边缘的距离。
getY():点击事件的点在控件中的Y坐标,即点相对于控件上边缘的距离。
getRawX():点击事件的点在整个屏幕中的X坐标,即点相对于屏幕左边缘的距离。
getRawY():点击事件的点在整个屏幕中的Y坐标,即点相对于屏幕上边缘的距离。

padding
getPaddingLeft():View里的content距离View左边缘的距离。
getPaddingTop():View里的content距离View上边缘的距离。
getPaddingRight():View里的content距离View右边缘的距离。
getPaddingBottom():View里的content距离View下边缘的距离。

3.8 Android的角度(angle)与弧度(radian)

自定义View实际上是将一些简单的形状通过计算,从而组合到一起形成的效果。
这会涉及到画布的相关操作(旋转)、正余弦函数计算等,即会涉及到角度(angle)与弧度(radian)的相关知识。

角度和弧度都是描述角的一种度量单位,区别如下图::

è§åº¦å弧度åºå«
在默认的屏幕坐标系中角度增大方向为顺时针。

å±å¹åæ ç³»è§åº¦å¢å¤§æ¹å

注:在常见的数学坐标系中角度增大方向为逆时针

 

如何自定义View 

自定义View包括三部分内容:

  1. 布局
  2. 绘制
  3. 事件处理

布局阶段:确定View的位置和尺寸。

绘制阶段:绘制View的内容。

事件处理:输入事件处理,点击事件,滑动冲突等。

自定义View实例:

使用自定义View绘制一个圆形:

  1. 创建自定义View类(继承View类)
  2. 布局文件添加自定义View组件
  3. 注意点设置(支持wrap_content & padding属性自定义属性等等)

步骤1:创建自定义View类(继承View类)

// 用于绘制自定义View的具体内容
// 具体绘制是在复写的onDraw()内实现

public class CircleView extends View {

    // 设置画笔变量
    Paint mPaint1;

    // 自定义View有四个构造函数
    // 如果View是在Java代码里面new的,则调用第一个构造函数
    public CircleView(Context context){
        super(context);

        // 在构造函数里初始化画笔的操作
        init();
    }


// 如果View是在.xml里声明的,则调用第二个构造函数
// 自定义属性是从AttributeSet参数传进来的
    public CircleView(Context context,AttributeSet attrs){
        super(context, attrs);
        init();

    }

// 不会自动调用
// 一般是在第二个构造函数里主动调用
// 如View有style属性时
    public CircleView(Context context,AttributeSet attrs,int defStyleAttr ){
        super(context, attrs,defStyleAttr);
        init();
    }


    //API21之后才使用
    // 不会自动调用
    // 一般是在第二个构造函数里主动调用
    // 如View有style属性时
    public  CircleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    // 画笔初始化
    private void init() {

        // 创建画笔
        mPaint1 = new Paint ();
        // 设置画笔颜色为蓝色
        mPaint1.setColor(Color.BLUE);
        // 设置画笔宽度为10px
        mPaint1.setStrokeWidth(5f);
        //设置画笔模式为填充
        mPaint1.setStyle(Paint.Style.FILL);

    }


    // 复写onDraw()进行绘制  
    @Override
    protected void onDraw(Canvas canvas) {

        super.onDraw(canvas);

       // 获取控件的高度和宽度
        int width = getWidth();
        int height = getHeight();

        // 设置圆的半径 = 宽,高最小值的2分之1
        int r = Math.min(width, height)/2;

        // 画出圆(蓝色)
        // 圆心 = 控件的中央,半径 = 宽,高最小值的2分之1
        canvas.drawCircle(width/2,height/2,r,mPaint1);

    }

}

View的构造函数的详细解析

步骤2:在布局文件中添加自定义View类的组件

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="scut.carson_ho.diy_view.MainActivity">

<!-- 注意添加自定义View组件的标签名:包名 + 自定义View类名-->
    <!--  控件背景设置为黑色-->
    <scut.carson_ho.diy_view.CircleView
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:background="#000000"

</RelativeLayout>

好了,至此,一个基本的自定义View已经实现了。接下来继续看自定义View所有应该注意的点:

1. 支持wrap_content属性

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    super.onMeasure(widthMeasureSpec, heightMeasureSpec);


    // 获取宽-测量规则的模式和大小
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);

    // 获取高-测量规则的模式和大小
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    // 设置wrap_content的默认宽 / 高值
    // 默认宽/高的设定并无固定依据,根据需要灵活设置
    // 类似TextView,ImageView等针对wrap_content均在onMeasure()对设置默认宽 / 高值有特殊处理,具体读者可以自行查看
    int mWidth = 200;
    int mHeight = 200;

  // 当模式是AT_MOST(即wrap_content)时设置默认值
    if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(mWidth, mHeight);
    // 宽 / 高任意一个模式为AT_MOST(即wrap_content)时,都设置默认值
    } else if (widthMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(mWidth, heightSize);
    } else if (heightMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(widthSize, mHeight);
}

2. 支持padding属性

padding属性:用于设置控件内容相对控件边缘的边距;

绘制时考虑传入的padding属性值(四个方向)。

在自定义View类的复写onDraw()进行设置

// 仅看复写的onDraw()
@Override
    protected void onDraw(Canvas canvas) {

        super.onDraw(canvas);

        // 获取传入的padding值
        final int paddingLeft = getPaddingLeft();
        final int paddingRight = getPaddingRight();
        final int paddingTop = getPaddingTop();
        final int paddingBottom = getPaddingBottom();


        // 获取绘制内容的高度和宽度(考虑了四个方向的padding值)
        int width = getWidth() - paddingLeft - paddingRight ;
        int height = getHeight() - paddingTop - paddingBottom ;

        // 设置圆的半径 = 宽,高最小值的2分之1
        int r = Math.min(width, height)/2;

        // 画出圆(蓝色)
        // 圆心 = 控件的中央,半径 = 宽,高最小值的2分之1
        canvas.drawCircle(paddingLeft+width/2,paddingTop+height/2,r,mPaint1);

    }

 3. 为自定义View提供自定义属性(如颜色等等)

步骤如下:

  1. 在values目录下创建自定义属性的xml文件
  2. 在自定义View的构造方法中解析自定义属性的值
  3. 在布局文件中使用自定义属性

步骤1:在values目录下创建自定义属性的xml文件
attrs_circle_view.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--自定义属性集合:CircleView-->
    <!--在该集合下,设置不同的自定义属性-->
    <declare-styleable name="CircleView">
        <!--在attr标签下设置需要的自定义属性-->
        <!--此处定义了一个设置图形的颜色:circle_color属性,格式是color,代表颜色-->
        <!--格式有很多种,如资源id(reference)等等-->
        <attr name="circle_color" format="color"/>

    </declare-styleable>
</resources>


对于自定义属性类型 & 格式如下:

<-- 1. reference:使用某一资源ID -->
<declare-styleable name="名称">
    <attr name="background" format="reference" />
</declare-styleable>
// 使用格式
<ImageView
    android:layout_width="42dip"
    android:layout_height="42dip"
    android:background="@drawable/图片ID" />

<--  2. color:颜色值 -->
<declare-styleable name="名称">
    <attr name="textColor" format="color" />
</declare-styleable>
// 格式使用
<TextView
    android:layout_width="42dip"
    android:layout_height="42dip"
    android:textColor="#00FF00" />

<-- 3. boolean:布尔值 -->
<declare-styleable name="名称">
    <attr name="focusable" format="boolean" />
</declare-styleable>
// 格式使用
<Button
    android:layout_width="42dip"
    android:layout_height="42dip"
    android:focusable="true" />

<-- 4. dimension:尺寸值 -->
<declare-styleable name="名称">
    <attr name="layout_width" format="dimension" />
</declare-styleable>
// 格式使用:
<Button
    android:layout_width="42dip"
    android:layout_height="42dip" />

<-- 5. float:浮点值 -->
<declare-styleable name="AlphaAnimation">
    <attr name="fromAlpha" format="float" />
    <attr name="toAlpha" format="float" />
</declare-styleable>
// 格式使用
<alpha
    android:fromAlpha="1.0"
    android:toAlpha="0.7" />

<-- 6. integer:整型值 -->
<declare-styleable name="AnimatedRotateDrawable">
    <attr name="frameDuration" format="integer" />
    <attr name="framesCount" format="integer" />
</declare-styleable>
// 格式使用
<animated-rotate
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:frameDuration="100"
    android:framesCount="12"
 />

<-- 7. string:字符串 -->
<declare-styleable name="MapView">
    <attr name="apiKey" format="string" />
</declare-styleable>
// 格式使用
<com.google.android.maps.MapView
 android:apiKey="0jOkQ80oD1JL9C6HAja99uGXCRiS2CGjKO_bc_g" />

<-- 8. fraction:百分数 -->
<declare-styleable name="RotateDrawable">
    <attr name="pivotX" format="fraction" />
    <attr name="pivotY" format="fraction" />
</declare-styleable>
// 格式使用
<rotate
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:pivotX="200%"
    android:pivotY="300%"
 />


<-- 9. enum:枚举值 -->
<declare-styleable name="名称">
    <attr name="orientation">
        <enum name="horizontal" value="0" />
        <enum name="vertical" value="1" />
    </attr>
</declare-styleable>
// 格式使用
<LinearLayout
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
/>

<-- 10. flag:位或运算 -->
<declare-styleable name="名称">
    <attr name="windowSoftInputMode">
        <flag name="stateUnspecified" value="0" />
        <flag name="stateUnchanged" value="1" />
        <flag name="stateHidden" value="2" />
        <flag name="stateAlwaysHidden" value="3" />
        <flag name="stateVisible" value="4" />
        <flag name="stateAlwaysVisible" value="5" />
        <flag name="adjustUnspecified" value="0x00" />
        <flag name="adjustResize" value="0x10" />
        <flag name="adjustPan" value="0x20" />
        <flag name="adjustNothing" value="0x30" />
    </attr>
</declare-styleable>、
// 使用
<activity
    android:name=".StyleAndThemeActivity"
    android:label="@string/app_name"
    android:windowSoftInputMode="stateUnspecified | stateUnchanged | stateHidden" >

    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>



<-- 特别注意:属性定义时可以指定多种类型值 -->
<declare-styleable name="名称">
    <attr name="background" format="reference|color" />
</declare-styleable>
// 使用
<ImageView
    android:layout_width="42dip"
    android:layout_height="42dip"
    android:background="@drawable/图片ID|#00FF00" />


步骤2:在自定义View的构造方法中解析自定义属性的值
此处是需要解析circle_color属性的值

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

        // 加载自定义属性集合CircleView
        TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.CircleView);

        // 解析集合中的属性circle_color属性
        // 该属性的id为:R.styleable.CircleView_circle_color
        // 将解析的属性传入到画圆的画笔颜色变量当中(本质上是自定义画圆画笔的颜色)
        // 第二个参数是默认设置颜色(即无指定circle_color情况下使用)
        mColor = a.getColor(R.styleable.CircleView_circle_color,Color.RED);

        // 解析后释放资源
        a.recycle();

        init();

步骤3:在布局文件中使用自定义属性

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  <!--必须添加schemas声明才能使用自定义属性-->
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="scut.carson_ho.diy_view.MainActivity"
    >
  
<!-- 注意添加自定义View组件的标签名:包名 + 自定义View类名-->
    <!--  控件背景设置为黑色-->
    <scut.carson_ho.diy_view.CircleView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"

        android:background="#000000"
        android:padding="30dp"

    <!--设置自定义颜色-->
        app:circle_color="#FF4081"
         />
</RelativeLayout>

添加属性处理后的效果:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值