Android应用界面开发——自定义控件(实现俯卧撑计数器)

对于Android应用的其他UI控件来说,它们都继承了View,然后在View提供的空白区域上进行绘制。当Android系统中提供的UI控件不足以满足需要时,我们可以来自定义控件。本篇文章主要介绍自定义控件的创建形式以及通过一个俯卧撑计数器Demo来介绍如何进行自定义控件。

在介绍自定义控件之前,先学习一下关于尺寸(dp,sp,px)和Inflater的知识。

px,dp,sp


px:像素,密度不同,效果不同,一般不用,在屏幕上画一条很细的线时偶尔会用。例如:

<View
        android:layout_width="match_parent"
        android:layout_height="1px"
        android:background="#000000"/>

dp:density-independent pixel,密度无关像素。非文字尺寸一律用dp。 dip = dp。 sp:scale-independent pixel,缩放无关像素。文字尺寸一律用sp。 dpi:dots per inch,一英寸多少个像素点。标准值为160,一般称作像素密度。 density:密度,和标准dpi的比例(160px/inc)。

换算公式如下:

  • dp = (dpi/(160像素/英寸))px = density px 注意:这里都是带单位的。px是单位,dp是单位,density没单位。
  • 那么转换为数值计算的话,应该是下面这个式子: PX = density * DP 也就是像素值 = density * 设备无关像素值 ,�请注意这里有个字。

Inflater

LayoutInflater:布局填充器,将xml布局转化为View对象。

获得LayoutInflater实例的三种方式:

  • getLayoutInflater();
  • getSystemService(LAYOUT_INFLATER_SERVICE);
  • LayoutInflater.from(context);

提取布局的属性:theme & style


当布局中的控件有一些共同的属性时,可以把公共的部分提取成theme或style,也可以通过为其设置parent为它们的父类,以继承父类的属性。

  • Theme是针对窗体级别的,改变窗体样式。
  • Style是针对窗体元素级别的,改变指定控件或者Layout的样式。

系统中默认的styles.xml如下:

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>

如果自定义的话,可以模仿该文件,style定义好以后,那么它是被如何引用的,还是看看系统中引用吧,如下:

android:theme="@style/AppTheme"

由于style中定义的是主题,所以引用的时候为android:theme,然后@定义的主题名称路径即可引用定义好的主题。

自定义控件的三种创建形式


我们知道所有的控件都继承于View,所以自定义控件也需要继承View,那么View是如何工作的呢,和其他类一样,需要通过构造器对其进行初始化,接着调用它的各种方法来完成它的功能。

View的常用方法:

  • onMesure():定大小。
  • onLayout():定位置。
  • onDraw():绘制。
  • invalidate():刷新。

自定义控件的三种创建形式:

  • 继承已有的控件来实现自定义控件。
  • 通过继承一个布局文件实现自定义控件。
  • 通过继承View类来实现自定义控件。

自定义控件实现俯卧撑计数器


需求:

  1. 在主界面输入一个数字。
  2. 在计数器界面做一个圆形的黑色按钮。
  3. 将主界面上输入的数字显示到黑色按钮的中间。
  4. 数字起始为用户输入。
  5. 每点击一次(鼻子每接触一次按钮)减少1。

主界面主要用到了之前学过的EditText和Button,并将EditText中输入的数字通过Intent传到计数器界面,这里不多阐述,主要讲自定义控件部分。

主界面如图:

自定义控件的代码:CounterView.java

public class CounterView extends View implements View.OnClickListener {

    private Paint mPaint;
    private Rect mRect;
    private int number = Integer.parseInt(PushUpsActivity.number);
    private int mBackgroundColor;
    private int mTextColor;
    private int mTextSize;


    public CounterView(Context context) {
        //在构造器中用this调用后边的构造器,这样只在最后一个构造器中初始化view即可。
        this(context, null);
    }

    public CounterView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CounterView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        mPaint = new Paint();
        mRect = new Rect();

        this.setOnClickListener(this);

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CounterView);
        mBackgroundColor = typedArray.getColor(R.styleable.CounterView_backgroundColor, Color.BLACK);
        mTextColor= typedArray.getColor(R.styleable.CounterView_textColor, Color.YELLOW);
        mTextSize = (int) typedArray.getDimension(R.styleable.CounterView_textSize, 18);
    }

    /**
     * 在onDraw方法中画圆形按钮和数字
     *
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //画图需要画笔,但不能在onDraw中创建Paint,因为onDraw使用频繁,会导致过多的内存消耗,所以创建工作放在了init中。
        mPaint.setColor(mBackgroundColor);
        mPaint.setAntiAlias(false);
        //画圆形按钮
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2, mPaint);
        //画白色数字需要将画笔颜色设置为白色
        mPaint.setColor(mTextColor);
        //设置字体大小
        mPaint.setTextSize(mTextSize);
        String text = String.valueOf(number);
        mPaint.getTextBounds(text, 0, text.length(), mRect);

        int textWidth = mRect.width();
        int textHeight = mRect.height();

        canvas.drawText(text, getWidth() / 2 - textWidth / 2, getHeight() / 2 + textHeight / 2, mPaint);//居中显示
    }

    @Override
    public void onClick(View v) {
        if (number > 0) {
            number--;
            invalidate();
        } else if (number == 0) {
            Toast.makeText(getContext(), "训练结束", Toast.LENGTH_LONG).show();
        }
    }
}

新建CounterView.java继承View,并实现其构造器,前两个构造器中用this调用后面的构造器,这样只在最后一个构造器中用init初始化View即可。

接下来重写onDraw方法,在该方法中画圆形按钮和数字,onDraw方法中传入了画布canvas,还缺一个画笔,创建一个画笔Paint,因为onDraw使用的比较频繁,所以Paint的创建以及后面Rect的创建都放入init中。创建好画笔,为画笔设置颜色,通过paint.setAntiAlias去掉画笔的锯齿,利用canvas.drawCircle就可以画圆了,drawCircle的前两个参数为圆心坐标,第三个参数为半径,第四个参数为画笔。

圆形按钮画好了,接着画中间的数字,同样设置画笔颜色,字体大小,这里需要注意,画数字之前需要通过getTextBounds测量一下数字的大小。getTextBounds字面意思是获取text边界,即把text用一个Rect框起来,它的第一个参数是要画的text,第二个参数是起始位置,第三个参数是结束位置,其实就是text的长度,第四个参数是Rect。

设置完毕,通过canvas.drawText来画数字,第一个参数是要画的text,第二和第三个参数分别为x,y坐标,第四个参数为paint。

处理按钮点击,这里的视图本身就是一个按钮,所以this.setOnClickListener(this);如果数字大于零,每点击一次让数字减一,并用invalidate刷新视图,直到数字为零。

写好自定义控件,在布局文件中引用即可,引用之前需要了解一下自定义控件的属性。自定义控件属性的步骤为:

1.定义attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CounterView">
        <attr name="backgroundColor" format="color"/>
        <attr name="textColor" format="color"/>
        <attr name="textSize" format="dimension"/>
    </declare-styleable>
</resources>

2.添加命名空间

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

定义好自定义控件的属性,引用自定义控件的布局如下:

activity_pushups.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingLeft="20dp"
        android:paddingTop="20dp"
        android:text="开始训练吧,练出完美胸肌"
        android:textColor="#000000"
        android:textSize="25sp" />

    <com.trampcr.pushupcounter.CounterView
        android:id="@+id/counter_view"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_below="@+id/text"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="80dp"
        app:backgroundColor="#000000"
        app:textColor="#ffff00"
        app:textSize="80sp" />

    <Button
        android:id="@+id/btn_stop"
        android:layout_width="200dp"
        android:layout_height="60dp"
        android:layout_below="@+id/counter_view"
        android:layout_centerHorizontal="true"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="60dp"
        android:text="结束训练"
        android:textColor="#000000"
        android:textSize="20sp" />

</RelativeLayout>

引用了自定义控件,并使用了自定义控件属性,那么如何获取自定义控件中属性的内容呢?先通过Context.obtainStyleAttributes方法检索到TypeArray类型的style属性,再通过TypeArray.getXxx获取每个属性的实例,具体代码如下:

TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CounterView);
        mBackgroundColor = typedArray.getColor(R.styleable.CounterView_backgroundColor, Color.BLACK);
        mTextColor= typedArray.getColor(R.styleable.CounterView_textColor, Color.YELLOW);
        mTextSize = (int) typedArray.getDimension(R.styleable.CounterView_textSize, 18);

到这里,用自定义控件实现的俯卧撑计数器已经完成,如图:


全部代码放在了github上了,点击跳转到源代码地址


之前写过一篇关于自定义控件的文章,和这篇有一点不一样,不同之处是:

这篇文章中自定义控件中的布局是自己画的,而之前那篇文章中自定义控件中布局是通过Inflater将xml文件转化而来的。

感兴趣的可以看一下:自定义View实现设置中心的功能视图

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏cnblogs

深入理解和应用display属性(一)

    Display在官方定义:规定元素应该生成的框的类型。本文只重点分析常用的6个值:none、block、inline、inline-block、inhe...

22050
来自专栏Java技术分享圈

杨老师课堂之JavaScript右下角广告弹框

10830
来自专栏Android开发实战

Android组件View绘制流程原理分析

如上图,Activity的window组成,Activity内部有个Window成员,它的实例为PhoneWindow,PhoneWindow有个内部类是Dec...

29840
来自专栏程序员叨叨叨

听说你想玩RecyclerView嵌套GridView

有很多小伙伴们可能会遇到这样的问题: 为什么不论我传入多大size的List,我的GridView只能显示一行?

1.1K20
来自专栏移动开发的那些事儿

TextView实现自定义换行以及缩进文字的格式化对齐

我们通常在自定义控件的时候会遇到不想用系统默认的文字换行,而需要根据自己的需求来实现文字的换行,原理如下:

29120
来自专栏一“技”之长

Java开发GUI之可编辑区域 原

    Java的awt包中提供了单行的文本编辑组件TextField与多行的文本编辑区TextArea,这两个组件都是继承自TextComponent类。

9020
来自专栏向治洪

android 自定义控件那些事

概述 在android应用开发过程中,固定的一些控件和属性可能满足不了开发的需求,所以在一些特殊情况下,我们需要自定义控件与属性。而自定义控件通常有两种:自定义...

22880
来自专栏我就是马云飞

View的绘制流程源码分析

概述 View的绘制流程主要是指测量、布局以及绘制显示,在View中,measure是测量View的宽高,layout是控制View四个顶点的位置,而draw就...

20950
来自专栏技术小黑屋

自定义View:Padding与绘制内容

有些时候,扩展Android框架提供的view并不能很好地解决问题。很多情况下,我们需要进行view绘制来实现想要的效果。本文我们将介绍如何使用Canvas绘制...

11920
来自专栏上善若水

017android初级篇之android canvas的使用

Canvas的意思是画布,表现在屏幕上就是一块区域,我们可以再上面使用各种API绘制我们想要的东西。 一个Canvas对象有四大基本要素:

41230

扫码关注云+社区

领取腾讯云代金券