Android自定义View(一)

此文章是转载笔者鸿祥大神的文章,加上自己的见解而成,本文的大多内容可在鸿祥大神的这篇博客找到Android自定义View(一)

其实这篇文章的也很棒【Android中将布局文件/View添加至窗口过程分析 ---- 从setContentView()谈起】

接下来我们进入正题哈[在这个项目中,笔者SDK的version是22]


  很多的Android入门程序猿来说对于android自定义View,可能都是比较恐惧的,但是这又是高手进阶的必经之路,所有准备在自定义View上面花一些功夫,多写一些文章。先总结下自定义View的步骤:
1、自定义View的属性
2、在View的构造方法中获得我们自定义的属性
[3、重写onMesure]
[4、重写onLayout ]
5、重写onDraw

我把3和4用[]标出了,所以说3和4不一定是必须的,当然了大部分情况下还是需要重写的。

1、自定义View的属性,首先在res/values/  下建立一个attribute.xml ,在里面定义我们的属性和声明我们的整个样式。

<?xml version="1.0" encoding="utf-8"?>
<!-- 文本的属性文件-->
<resources>
    <attr name="titleTextContent" format="string"/>
    <attr name="titleTextColor"   format="color"/>
    <attr name="titleTextSize"    format="dimension"/>

    <!-- 以上面的属性映射在一起,外界通过name="CustomTitleView"访问attribute.xml这个文件,相当于我们常说的接口吧"-->
    <declare-styleable name="CustomTitleView">
        <attr name="titleTextContent"/>
        <attr name="titleTextColor"/>
        <attr name="titleTextSize"/>
    </declare-styleable>
</resources>

我们定义了字体,字体颜色,字体大小3个属性,format是值该属性的取值类型:
一共有:string[字符串],color[颜色],demension[尺寸],integer[整形],enum[枚举],reference[引用],float[浮点],boolean[布尔],fraction[分数],flag[信号[型]]
然后在布局
文件[res/layout/activity_main.xml]中声明我们的自定义View

<?xml version="1.0" encoding="utf-8"?>
<!-- xmlns:custom="http://schemas.android.com/apk/res/com.wnyx.customview"-->
<RelativeLayout

    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.wnyx.customview.MainActivity"
    tools:ignore="ResAuto">
    
    <com.wnyx.customview.CustomTitleView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        custom:titleTextContent="3712"
        android:padding="10dp"
        custom:titleTextColor="#ff0000"
        android:layout_centerInParent="true"
        custom:titleTextSize="40sp"/>

</RelativeLayout>

其中笔者注释了【xmlns:custom="http://schemas.android.com/apk/res/com.wnyx.customview】[后面的包路径com.wnyx.customview指的是项目的package],

了解xml文件的读者就会知道xmlns其实就是xml文件的命名空间,不了解的读者可以看下面这两篇文章

Android 自定义view中的属性,命名空间,以及tools标签】

【android 布局文件中xmlns:android="http://schemas.android.com/apk/res/android"】
 
 
 
2、在View的构造方法中,获得我们的自定义的样式 

public class CustomTitleView extends View {

    //文本字体的内容
    private String titleTextContent ;
    //文本字体的颜色
    private int titleTextColor ;
    //文本字体的大小
    private int titleTextSize ;

    //文本的绘制范围[即规定矩形的宽高]
    //在计算机中,我们通过上、下、左、右四个点坐标来确定举行的大小[范围]。当然,两个点坐标也是可以确定矩形的大小[范围、尺寸]
    private Rect rect ;

    //画笔[Paint][Paint类保存有关如何绘制的样式[形状]和颜色信息几何,文本和位图]
    //像我们日常生活中的笔芯一样,有的写的字笔画粗点,有的写字笔画细点[笔芯的属性:字笔画的粗细,字笔画的颜色]
    //字的粗细和字的大小是两个概念,不要搞混,例如一根细笔芯能写出一个很小的字,也能写出一个很大的字
    private Paint paint ;

    public CustomTitleView(Context context) {
        //super(context);

        //调用本类含两个构造参数的构造函数
        this(context,null);
    }

    public CustomTitleView(Context context, AttributeSet attrs) {
        //super(context, attrs);
        //默认的布局文件调用的是两个参数的构造方法,所以记得让所有的构造方法调用我们的三个参数的构造方法,我们在三个参数的构造方法中获得自定义属性。
        //调用本类含三个构造参数的构造函数
        this(context,attrs,0);
    }

    public CustomTitleView(Context context, AttributeSet attrs, int defStyleAttr) {
        //调用父类的含三个参数的构造函数
        super(context, attrs, defStyleAttr);

        //Read customView's attribute from res/values/attribute.xml file .
        TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs,R.styleable.CustomTitleView,0,0);
        int count = typedArray.getIndexCount();
        for (int i=0;i<count;i++)
        {
            int attributeId = typedArray.getIndex(i);
            switch (attributeId)
            {
                case R.styleable.CustomTitleView_titleTextContent:
                    titleTextContent = typedArray.getString(attributeId);
                    break;
                case R.styleable.CustomTitleView_titleTextColor:
                    //默认设置颜色位灰色
                    titleTextColor = typedArray.getColor(attributeId, Color.BLUE);
                    break;
                case R.styleable.CustomTitleView_titleTextSize:
                    // 默认设置为16sp,TypeValue也可以把sp转化为px
                    titleTextSize = typedArray.getDimensionPixelSize(attributeId,
                            (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,14,getResources().getDisplayMetrics()));
                    break;
            }
        }

        //用完记得回收
        typedArray.recycle();
        typedArray = null;

        //获得绘制文本的宽和高[范围、边界]
        paint = new Paint();
        paint.setTextSize(titleTextSize);
        rect = new Rect();
        paint.getTextBounds(titleTextContent,0,titleTextContent.length(),rect);

    }
}
我们重写了3个构造方法,默认的布局文件调用的是两个参数的构造方法,所以记得让所有的构造调用我们的三个参数的构造,我们在三个参数的构造中获得自定义属性。

3、我们重写onMeasure()方法

    //1:在画图之前,我们先要测量[度量Measure]图的大小;
    //2:知道图的大小尺寸后,我们要在画板上寻找能放置该图的空间,即放置[布局Layout]图在画板上的位置
    //3:当然,上述的第一第二步只是我们在正式画图之前的准备工作,当准备工作做好之后,我们进入正式的画图[绘制Draw]阶段

    //测量[度量Measure]
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //规范[规格]:常指生产的成品或所使用的原材料等规定的质量标准,常用在制造学和物理学中。同一品种或同一型号产品的不同尺寸[具体参考互动百科]
        //我们这样来理解规格,其实规范相当模式的集合,但是每个模式都有一个范围,范围里面的某个具体的值我们称为size[大小]
        //就像数学中的数轴划分区间的方法一样
        //规范[规格]:0-1为一个规范;
        //模式:把 0-1分为2份[0-0.5(模式一)和0.5到1(模式2)]
        //大小:0.4为[0,0.5]这个区间的一个具体值,我们称之为具体的大小
        //笔者花这么多文字来描述,只想阐述一个道理:"计算机中的概念大部分来自现实生活,在现实生活中都可以找到对应的模板"。

        //通过规范[spec]得到得到模式[mode]
        int widthMode  = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        //通过规范[spec]得到得到大小[size]
        int widthSize  = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int width ;
        int height ;

        if (widthMode == MeasureSpec.EXACTLY)
        {
            width = widthSize;
        } else
        {
            paint.setTextSize(titleTextSize);
            paint.getTextBounds(titleTextContent, 0, titleTextContent.length(), rect);
            float textWidth = rect.width();
            //desired[期望]
            int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());
            width = desired;
        }

        if (heightMode == MeasureSpec.EXACTLY)
        {
            height = heightSize;
        } else
        {
            paint.setTextSize(titleTextSize);
            paint.getTextBounds(titleTextContent, 0, titleTextContent.length(), rect);
            float textHeight = rect.height();
            int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom());
            height = desired;
        }

        setMeasuredDimension(width, height);
    }

想要具体了解onMeasure()方法的读者请看这篇文章【Android开发之自定义控件(一)---onMeasure详解】,里面涉及到源代码


4、我们重写onLayout()方法

    //放置[布局Layout],确定View的位置
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        //super.onLayout(changed, left, top, right, bottom);
    }
想要具体了解onLayout()方法的读者请看这篇文章 【Android开发之自定义控件(二)---onLayout详解】里面涉及到源代码

5、我们重写onDraw()方法

    //画图[绘制Draw]  canvas[画布,即我们现实生活中画板、绘画纸]
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //绘画前先把需要画文本的位置的规划出来,即Rect充当容器,容器里面放置文本这个图
        paint.setColor(Color.YELLOW);
        canvas.drawRect(0,0,getMeasuredWidth(),getMeasuredHeight(),paint);

        //真正绘制文本这个图了
        paint.setColor(titleTextColor);
        canvas.drawText(titleTextContent, getWidth() / 2 - rect.width() / 2, getHeight() / 2 + rect.height(),paint);
    }
想要具体了解onDraw()方法的读者请看这篇文章Android 自定义 view(三)—— onDraw 方法理解


下面是完整的CustomTitleView.java的代码

package com.wnyx.customview;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;

/**
 * Created by wn on 2017/7/5.
 * CustomView:自定义View
 */

public class CustomTitleView extends View {

    //文本字体的内容
    private String titleTextContent ;
    //文本字体的颜色
    private int titleTextColor ;
    //文本字体的大小
    private int titleTextSize ;

    //文本的绘制范围[即规定矩形的宽高]
    //在计算机中,我们通过上、下、左、右四个点坐标来确定举行的大小[范围]。当然,两个点坐标也是可以确定矩形的大小[范围、尺寸]
    private Rect rect ;

    //画笔[Paint][Paint类保存有关如何绘制的样式[形状]和颜色信息几何,文本和位图]
    //像我们日常生活中的笔芯一样,有的写的字笔画粗点,有的写字笔画细点[笔芯的属性:字笔画的粗细,字笔画的颜色]
    //字的粗细和字的大小是两个概念,不要搞混,例如一根细笔芯能写出一个很小的字,也能写出一个很大的字
    private Paint paint ;

    public CustomTitleView(Context context) {
        //super(context);

        //调用本类含两个构造参数的构造函数
        this(context,null);
    }

    public CustomTitleView(Context context, AttributeSet attrs) {
        //super(context, attrs);
        //默认的布局文件调用的是两个参数的构造方法,所以记得让所有的构造方法调用我们的三个参数的构造方法,我们在三个参数的构造方法中获得自定义属性。
        //调用本类含三个构造参数的构造函数
        this(context,attrs,0);
    }

    public CustomTitleView(Context context, AttributeSet attrs, int defStyleAttr) {
        //调用父类的含三个参数的构造函数
        super(context, attrs, defStyleAttr);

        //Read customView's attribute from res/values/attribute.xml file .
        TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs,R.styleable.CustomTitleView,0,0);
        int count = typedArray.getIndexCount();
        for (int i=0;i<count;i++)
        {
            int attributeId = typedArray.getIndex(i);
            switch (attributeId)
            {
                case R.styleable.CustomTitleView_titleTextContent:
                    titleTextContent = typedArray.getString(attributeId);
                    break;
                case R.styleable.CustomTitleView_titleTextColor:
                    //默认设置颜色位灰色
                    titleTextColor = typedArray.getColor(attributeId, Color.BLUE);
                    break;
                case R.styleable.CustomTitleView_titleTextSize:
                    // 默认设置为16sp,TypeValue也可以把sp转化为px
                    titleTextSize = typedArray.getDimensionPixelSize(attributeId,
                            (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,14,getResources().getDisplayMetrics()));
                    break;
            }
        }

        //用完记得回收
        typedArray.recycle();
        typedArray = null;

        //获得绘制文本的宽和高[范围、边界]
        paint = new Paint();
        paint.setTextSize(titleTextSize);
        rect = new Rect();
        paint.getTextBounds(titleTextContent,0,titleTextContent.length(),rect);

    }

    //1:在画图之前,我们先要测量[度量Measure]图的大小;
    //2:知道图的大小尺寸后,我们要在画板上寻找能放置该图的空间,即放置[布局Layout]图在画板上的位置
    //3:当然,上述的第一第二步只是我们在正式画图之前的准备工作,当准备工作做好之后,我们进入正式的画图[绘制Draw]阶段


    //测量[度量Measure]
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //规范[规格]:常指生产的成品或所使用的原材料等规定的质量标准,常用在制造学和物理学中。同一品种或同一型号产品的不同尺寸[具体参考互动百科]
        //我们这样来理解规格,其实规范相当模式的集合,但是每个模式都有一个范围,范围里面的某个具体的值我们称为size[大小]
        //就像数学中的数轴划分区间的方法一样
        //规范[规格]:0-1为一个规范;
        //模式:把 0-1分为2份[0-0.5(模式一)和0.5到1(模式2)]
        //大小:0.4为[0,0.5]这个区间的一个具体值,我们称之为具体的大小
        //笔者花这么多文字来描述,只想阐述一个道理:"计算机中的概念大部分来自现实生活,在现实生活中都可以找到对应的模板"。

        //通过规范[spec]得到得到模式[mode]
        int widthMode  = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        //通过规范[spec]得到得到大小[size]
        int widthSize  = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int width ;
        int height ;

        if (widthMode == MeasureSpec.EXACTLY)
        {
            width = widthSize;
        } else
        {
            paint.setTextSize(titleTextSize);
            paint.getTextBounds(titleTextContent, 0, titleTextContent.length(), rect);
            float textWidth = rect.width();
            //desired[期望]
            int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());
            width = desired;
        }

        if (heightMode == MeasureSpec.EXACTLY)
        {
            height = heightSize;
        } else
        {
            paint.setTextSize(titleTextSize);
            paint.getTextBounds(titleTextContent, 0, titleTextContent.length(), rect);
            float textHeight = rect.height();
            int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom());
            height = desired;
        }

        setMeasuredDimension(width, height);
    }


    //放置[布局Layout],确定View的位置
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        //super.onLayout(changed, left, top, right, bottom);
    }


    //画图[绘制Draw]  canvas[画布,即我们现实生活中画板、绘画纸]
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //绘画前先把需要画文本的位置的规划出来,即Rect充当容器,容器里面放置文本这个图
        paint.setColor(Color.YELLOW);
        canvas.drawRect(0,0,getMeasuredWidth(),getMeasuredHeight(),paint);

        //真正绘制文本这个图了
        paint.setColor(titleTextColor);
        canvas.drawText(titleTextContent, getWidth() / 2 - rect.width() / 2, getHeight() / 2 + rect.height(),paint);
    }
}


下面是MainActivity.java的代码

package com.wnyx.customview;

import android.app.Activity;
import android.os.Bundle;

//注释:AppCompatActivity和Activity的setContentView(int layoutId)的实现方法不一样
public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

最后,上一张程序运行截图吧



最后,可能设计到高版本的SDK转到低版本的SDK,读者课参考这篇文章【修改Android Studio默认的API Level(SDK版本)】

当然,要修改的文件看你项目情况了,一般都是修改values文件下的xml文件


关于AndroidStudio中AndroidStudio中make Project、clean Project、Rebuild Project的区别?

1.Make Project:编译Project下所有Module,一般是自上次编译后Project下有更新的文件,不生成apk。
2.Make Selected Modules:编译指定的Module,一般是自上次编译后Module下有更新的文件,不生成apk。
3.Clean Project:删除之前编译后的编译文件,并重新编译整个Project,比较花费时间,不生成apk。
4.Rebuild Project:先执行Clean操作,删除之前编译的编译文件和可执行文件,然后重新编译新的编译文件,不生成apk,
  这里效果其实跟Clean Project是一致的,这个不知道Google搞什么鬼~~
5.Build APK:前面4个选项都是编译,没有生成apk文件,如果想生成apk,需要点击Build APK。
6.Generate Signed APK:生成有签名的apk。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值