Android自定义View(1)——初步实现简单的自定义View

已经有一段时间没有给大家更新博客了,貌似自从学校的实训一开始就一直没有心思去学新的东西和写博客,因为这段时间一直都有很多事情要忙,而且笔者马上就要开始实习工作了,可能心理上也是有一定的压力哈哈。现在事情都处理的差不多了,可以重操旧业专心学习了!!!!!

今天给大家带来的是如何关于自定义View的文章,之后笔者会将动画和自定义View的文章陆陆续续整合到一起,方便大家阅读。


目录

自定义View简介

View、ViewGroup区别和联系

实现最简单的自定义View

自定义View

自定义ViewGroup

还有一个小问题



自定义View简介

在做Android项目中,视图这块,我们肯定都使用过View,像Button、ImageView、CheckBox、RadioButton、ProgressBar和SeekBar等等这些控件,都是从View继承的。

其实说白了,上面这些我们使用的,以及有些我们没有使用过的控件,其实都是人写出来的(这话说的可能有点2b),我的意思是,无论是以上的这些控件,还是我们将要提到的自定义控件,其实都是我们通过一系列方法将它定义出来,最终显示到我们的视图中。

public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public class ImageView extends View
public class Button extends TextView

根据我们最上面说到的,所有的这些的顶级父类都是View,或者说有些控件是在某些通过已经继承了View的控件之上再继承的方式实现自定义View。

我将自定义View分为三种类型:

  • 直接继承View
  • 直接继承ViewGroup
  • 继承已经继承View/ViewGroup的控件

View、ViewGroup区别和联系

上面除了View之外还涉及到一个ViewGroup,虽然ViewGroup是自View继承的,但是我们还是将它分开来说。因为几乎所有的视图类(包括控件类和布局类)都是继承View的,所以说View是所以视图控件的根不为过;但是还有一种情况,我们将那些控件类(就像上述的Button、ImageView等)统称为View。这只是我们行业内的一种习惯吧,并不是很规范。因为严格的来说ViewGroup也是继承自View类。

当然ViewGroup类大家没怎么少见,最为普遍的就是布局:六大布局。还有ToolBar、ListView等等。他们区别于View的最显著特征就是,可以在里面添加字View(这里的View也包括ViewGroup哦)。

实现最简单的自定义View

我们通过代码分别实现自定义View和自定义ViewGroup:

自定义View

在使用系统的一些控件中,一般情况下我们都是在layout.xml文件中添加标签:

以TextView为例:在使用时候我们可以通过在这里面设置TextView内的一系列属性来实现文字大小、颜色、大小写之类的改变。

是不是很神奇,没关系,今天通过自定义View我们也会学到这种方式。

1.首先需要创建一个自定义的类继承自View。

public class MyView extends View {
    public MyView(Context context) {
        super(context);
    }

    public MyView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
}

名字起得很随性:MyView。

注意到我实现了三个构造方法,对于View来说总共是有4个,但是第四个不是很常用,我们只需要看前三个就好。

这三个构造方法每个都比上一个多一个参数,依次解释一下这三个参数的含义:

  • context:上下文,这个没什么好说的吧。
  • attrs:属性集合。我们在布局文件中设置的那一系列属性都可以通过这个东西获取到。
  • defStyleAttr:默认风格属性,这个一般是对于那些企业级大型项目app,他们的控件都有属于自己的一套style。一般这个属性我们也用不到。

一般来说只是用到前两个构造方法,而前两个构造方法也是有所不同:

第一个构造方法是适用于通过new创建的自定义View对象,而第二种构造方法适用于在layout.xml中创建。

这里有一个规范的写法:

public class MyView extends View {
    public MyView(Context context) {
        this(context,null);
    }

    public MyView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        
    }
}

注意到前两个super被改为了this,而且都添加了新的参数。我们让第一个构造方法调用第二个,第二个调用第三个,这样一来无论我们使用哪种方法创建的MyView对象,最后都会进入第三个构造方法中。也就是说所有的逻辑代码全部都写到第三个方法中就可以了。

2.然后需要在values文件目录下创建名为attrs的.xml文件。

3.然后在attrs.xml文件中添加declare-styleable标签,name指定为我们java中自定义的类名。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyView">

    </declare-styleable>
</resources>

4.在declare-styleable下添加属性

在里面我们可以自己定义属性的名字,和属性的类型:

可以看到属性有很多的类型:

  • reference:参考某一资源id,想mipmap、drawable、string、color等等,如果定义为这种类型的话,在添加时我们可以使用@drawable/xxx的方式来添加属性值。
  • color:颜色值。使用的话通过#xxxxxx方式添加属性值。
  • integer:整形。
  • float:浮点型。没啥好说的跟上面差不多。
  • boolean:布尔型。属性添加为true/false。
  • string:字符串型。
  • fraction:百分比型。属性中的值为xxx%格式。
  • dimension:尺寸值。属性中的值为xxdp/sp/dip等等。
  • enum:枚举类型。
  • flag:位或运算,这个在Activity的很多flag中就是这种类型的属性。

我们设置一个名为viewBackground的属性,顾名思义是指这个控件的背景,所以我们指定为reference类型。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyView">
        <attr name="viewBackground" format="reference" />
        
    </declare-styleable>
</resources>

5.关于设置的部分我们就完成了,下面我们看看如何获取到这些属性的值。

还记得构造方法中的第二个参数AttrsbuteSets吗,我们说是通过他获取到属性值的,

TypedArray attr = context.obtainStyledAttributes(attrs,R.styleable.MyView);

通过上下文context的obtainStyledAttributes方法获取到属性集合。这个方法有四个重载方法,这个重载方法的参数第一个是构造方法中第二个参数,第二个参数就是我们在attrs.xml中添加的declare-styleable的属性名。

那么获取到了这个属性集合,该如何使用呢?

TypedArray中为我们提供了一系列方法去获取属性值。

都是getXXX的格式,有两个参数,第一个参数使我们declare-styleable的attr标签下的name,第二个为默认不设置该属性的情况下,他的属性值是多少。

可以看到这些get方法都是对应着我们attr的format属性值的。

int background = attr.getResourceId(R.styleable.MyView_viewBackground,R.color.colorAccent);

现在我们获取到了背景颜色,如果不设定的话就是这个粉色。接下来把它添加到背景中。

获取属性值的方法是统一的,但是如何使用就看我们自己了,你可以打印一个Log什么都不做-。=

setBackgroundResource(background);

我们通过setBackgroundResource方法添加背景颜色。

好了,这是最最最最简答的自定义控件了,因为我们什么都没干,这是添加了一个背景色的属性。现在我们看看他的样子是什么。

<pers.jibai.lp.MyView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

可以看到他是有这个属性值的,我们先不设定,看看是不是粉色:

没毛病,就是粉色,现在我们给他设置成深蓝色:

<pers.jibai.lp.MyView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:viewBackground="@color/colorPrimaryDark"/>

效果很明显,这样一个改变颜色的自定义view就让我们做出来了(虽然没什么用,但是这是我们伟大的第一次实践,通过这种方法我么我们可以给自定义的view添加各式各样的属性。)


自定义ViewGroup

上面说完了自定义View,这个自定义ViewGroup就简单很多了。

悄悄问大家一下,大家有没有在布局文件中实现一个自定义的标题栏:左面一个按钮,右面一个按钮,然后中见是标题,这样写出来的代码不但死板费事,而且拓展性几乎为0。下一次在使用就通过复制粘贴这一大坨代码,然后修改里面的值,相信我,大家以前都这么干过(反正我之前就是一直这样的。),下面我们通过自定义ViewGroup的方式,来实现这个效果:

public class MyToolBar extends LinearLayout {
    public MyToolBar(Context context) {
        this(context,null);
    }

    public MyToolBar(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public MyToolBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
}

前几步骤跟上面的一样,注意在这里我们继承的不是ViewGroup,而是他的实现类之一LinearLayout(这里面有坑,后面会讲到)。

与上面不同的是,我们还需要给MyToolBar创建一个自己的布局文件。

<?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="horizontal">

    <Button
        android:id="@+id/but_left"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="左按钮"/>

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center"
        android:textSize="18sp"
        android:text="标题"/>
    <Button
        android:id="@+id/but_right"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="右按钮"/>
</LinearLayout>

贼丑的一个布局,大家凑乎这看吧。

然后需要在MyToolBar构造方法中调用如下方法:

public MyToolBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    LayoutInflater.from(context).inflate(R.layout.mytoolbar_layout,this,true);
        
}

我们给布局绑定父类为MyToolBar,说明这就是我们MyToolBar的布局了。

我们还有三个控件,现在也写到里面:

    public MyToolBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        LayoutInflater.from(context).inflate(R.layout.mytoolbar_layout,this,true);

        leftButton = findViewById(R.id.but_left);
        rightButton = findViewById(R.id.but_right);
        title = findViewById(R.id.tv_title);
    }

现在我们将他添加到布局中,看一看效果:

    <pers.jibai.lp.MyToolBar
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    </pers.jibai.lp.MyToolBar>

可以看到这个跟我们mytoolbar_layout布局中一模一样,当然界面很丑,而且颜色也不好看,关于如何添加自定义属性的方法在自定义View中,大家可以自行查看,然后设置属性,最后设置出自己喜欢的标题栏。

有的读者可能会问了,那两个按钮想设置监听事件该怎么办呢。很简单,在MyToolBar中添加两个public方法,用来给两个按钮设置监听器不就好了。(这个不用给代码了吧,大家都会懂)。

还有一个小问题

有没有读者注意到,我们在上面自定义的View中,设定为了wrap_content,但是还是沾满了全屏呢。这个问题在下一篇文章中会被迎刃而解。

好了,今天的文章就到此结束了,喜欢的朋友希望多多支持。!!

猜你喜欢

转载自blog.csdn.net/zy_jibai/article/details/81264825