自定义View

今天跟着简书学习自定义view的详解:传送门

1、自定义的分类

自定义View一共分两大类:
自定义View

2、具体使用场景

自定义View

3、使用注意点

下面是自定义View会出现的一些坑:
自定义View
3.1、支持特殊属性

  • 支持wrap_content
    就是在onMeasure中去根据LayoutParams去设置指定的宽高。下面是为什么要支持wrap_content的原因。
    为什么自定义view的wrap_content无效
  • 支持padding & margin
    如果不支持,那么padding和margin(ViewGroup情况)的属性将失效
    (1)对于继承View的控件,padding是在onDraw()中处理
    (2)对于继承ViewGroup的控件,padding会直接影响layout和draw的过程。

3.2、多线程应该使用post方式
View内部本身提供了post系列的方法,安全可以替代Handler的作用。使用起来更加方便。

3.3、避免内存泄漏
当View退出或者不可见时,应该及时关闭view中的动画以及耗时操作。否则会造成内存泄漏。

3.4、处理好滑动冲突
如果View中含有滑动嵌套情况时,应该处理好滑动冲突。

4、实例

4.1继承View
我们将实现一个实心圆,然后为其提供颜色等属性,并且使其支持wrap_content和padding

4.2、实现过程
步骤1:我们创建一个CircleView.java:

// 用于绘制自定义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);

    }

}

步骤2:在xml中加入这个view

<?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="Rikka.MainActivity">

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

</RelativeLayout>

效果如下:
自定义View
接着手动支持wrap_content和padding,以及为自定义view设置属性。
(1)支持wrap_content
(2)padding,在CircleView中的onDraw添加如下代码:
绘制时考虑传入padding属性值(4个方向)

// 仅看复写的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);

    }

这时候在 xml的view中添加 android:padding="20dp",view就会支持padding了
自定义View
(3)提供自定义属性:

// 基本是以android开头
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#000000"
        android:padding="30dp"

上面是系统自带的自定义属性。
但有时候我们需要定义系统所没有的属性,称为自定义属性,实现步骤有下:
①、在values目录下自定义属性的xml文件
②、在自定义view的构造方法中解析自己定义属性的值
③、在布局文件中自己定义属性

具体实现:
①:在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>
// 使用格式
  // 1. Java代码
  private int ResID;
  private Drawable ResDraw;
  ResID = typedArray.getResourceId(R.styleable.SuperEditText_background, R.drawable.background); // 获得资源ID
  ResDraw = getResources().getDrawable(ResID); // 获得Drawble对象

  // 2. xml代码
<ImageView
    android:layout_width="42dip"
    android:layout_height="42dip"
    app: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" />

②、在自定义view的构造方法中解析自定义的属性值:
这里要解析"circle_color"这个值

// 该构造函数需要重写
  public CircleView(Context context, AttributeSet attrs) {

        this(context, attrs,0);
        // 原来是:super(context,attrs);
        init();


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

这样,默认情况下为:
自定义View
这样一个自定义View就实现了。