Android自定义View(一)

来源:互联网 发布:八个字网络流行语 编辑:程序博客网 时间:2024/06/05 08:59

此文章是转载笔者鸿祥大神的文章,加上自己的见解而成,本文的大多内容可在鸿祥大神的这篇博客找到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。

原创粉丝点击