Android自定义View入门

View架构简介:

在Android中,控件主要以ViewGroup和View的形式存在。ViewGroup控件可以包含多个View控件,该复合控件负责其内部所有子控件的测量和绘制,并传递交互事件。如图,

在Android的移动开发中,每个Activity都包含了一个PhoneWindow对象,该对象将DecorView设置为应用窗口的根View。该视图上的所有监听事件都通过WindowManagerService来进行接收,并通过Activity来回调相应的onClickListener。DecorView将屏幕分为了两部分,TitleView和ContentView,而我们的setContentView方法则用于处理这个位置的布局显示。我们的activity_main即处于这样一个ID为content的FrameLayout中。如图:


自定义View的实现

自定义View流程,

  1. 自定义View初始化(创建类,引入自定义属性等)
  2. View的测量(重写onMeasure获取自定义的大小并设定)
  3. View的绘制(重写onDraw进行View的绘制)
  4. 设置自定义View的监听接口

View的测量

MeasureSpec

一个32位int值,高2位代表测量模式,低30位代表测量的大小,用于辅助View的测量,模式如下:

EXACTLY(精准模式):表示父控件已经确切的指定了子View的大小。 
AT_MOST(最大模式):依据子控件的变化而变化,但存在上限 
UNSPECIFIED(自定义模式):大小设置无限制,比较扯淡吧我觉得

首先必须重写onMeasure方法,至于为什么,源码走一波

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

在AS中通过Ctrl+B来观看onMeasure的源码,

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

显然,这里将自定义的MeasureSpec对象作为参数传递给setMeasuredDimension.

再来看看getDefaultSize的作用

public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

getDefaultSize则是根据对应的测量模式选取对应的Size。

在该方法的第二个参数,是一个getSuggestedMinimumWidth()或getSuggestedMinimumHeight()方法,以getSuggestedMinimumWidth()为例,

protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }

官方的解释是:Returns the suggested minimum width that the view should use. This

returns the maximum of the view’s minimum width。最合适的宽度就这样诞生了,,

由于View类默认支持EXACTLY模式,所以为了实现自定义View的wrapContent,就必须使用其他模式,即重写onMeasure方法。

其实onMeasure的重写很简单,就是通过Google提供的MeasureSpec获取Mode和Size,之后依据获得的Mode选取xml设定的数据,之后将数据传递给setMeasuredDimension即可。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        //从MeasureSpec中获取Mode和Size
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int width;
        int height ;
        //依据模式获取预设的width和height
        if (widthMode == MeasureSpec.EXACTLY)
        {
            width = widthSize;
        } else
        {
            mPaint.setTextSize(mTitleTextSize);
            mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
            float textWidth = mBound.width();
            int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());
            width = desired;
        }

        if (heightMode == MeasureSpec.EXACTLY)
        {
            height = heightSize;
        } else
        {
            mPaint.setTextSize(mTitleTextSize);
            mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
            float textHeight = mBound.height();
            int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom());
            height = desired;
        }
        //殊途同归,传递我们自定义的数据
        setMeasuredDimension(width, height);
    }

View的绘制

对于Android的2D绘图,我们主要借助于graphics包,其中主要包含了Canvas类、Paint类、Color类和Bitmap类等。

绘制图形必然要用到颜色,在Android开发中对颜色的使用和获取有疑问的看这篇文章:颜色

Paint类

要绘制图形,首先得调整画笔,按照自己的开发需要设置画笔的相关属性。

setAntiAlias: 设置画笔的锯齿效果。

setColor: 设置画笔颜色 。

setARGB: 设置画笔的a,r,p,g值。

setAlpha: 设置Alpha值 。

setTextSize: 设置字体尺寸。

setStyle: 设置画笔风格,空心或者实心。

setStrokeWidth: 设置空心的边框宽度。

getColor: 得到画笔的颜色 。

getAlpha: 得到画笔的Alpha值

详细参见:Paint详解

CanVas类

Canvas我们可以称之为画布,能够在上面绘制各种东西,是安卓平台2D图形绘制的基础,在使用该类前需要设置好paint。

Cnavas图形绘制参考:

除了进行基本的绘制外,Canvas也可以进行一些基本操作。位移、缩放、旋转、倾斜以及快照和回滚。

有需要自行参见:Canvas的基本操作

一个简单的圆形绘制示例

        Paint paint = new Paint();
        paint.setColor(Color.CYAN);
        paint.setStrokeWidth(3);
        paint.setAntiAlias(true);
        //空心
        paint.setStyle(Paint.Style.STROKE);
        canvas.drawCircle(160,60,50,paint);

        paint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(160,180,50,paint);

自定义属性

自定义属性流程

  1. 自定义一个CustomView类
  2. 创建attrs.xml文件,并添加所需的Item和declare-styleable
  3. 在布局文件中引用自定义属性并初始化
  4. 在CustomView类中的构造方法中通过TypedArray获取自定义属性和其值

该类的构造方法最多可有四个参数,

public void CustomView(Context context) {}

public void CustomView(Context context, AttributeSet attrs) {}

public void CustomView(Context context, AttributeSet attrs, int defStyleAttr) {}

public void CustomView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {}

在构造方法中AttributeSet中保存的是该自定义View所有属性的值名称和值,获取其中属性代码:

int count = attrs.getAttributeCount();
        for (int i = 0; i < count; i++) {
            String attrName = attrs.getAttributeName(i);
            String attrVal = attrs.getAttributeValue(i);
            Log.e(TAG, "attrName = " + attrName + " , attrVal = " + attrVal);
        }

通过这种方式我们只可以获取那些在xml中直接定义的属性,比如android:text = "horzion",但对于诸如android:text = “@String/name”则会直接反馈给你一段R文件中的标识码,为了简化工作这里引入TypeArray。

TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomTitleView, defStyle, 0);

此时,typeArray中的集合便是经过简化处理,可以直接获取使用。

一个完整的示例:

public CustomTitleView(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
        //获得我们所定义的自定义样式属性
        TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomTitleView, defStyle, 0);
        int n = typedArray.getIndexCount();
        for (int i = 0; i < n; i++)
        {
            int attr = typedArray.getIndex(i);
            switch (attr)
            {
                case R.styleable.CustomTitleView_titleText:
                    mTitleText = typedArray.getString(attr);
                    break;
                case R.styleable.CustomTitleView_titleTextColor1:
                    // 默认颜色设置为黑色
                    mTitleTextColor = typedArray.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.CustomTitleView_titleTextSize:
                    // 默认设置为16sp,TypeValue也可以把sp转化为px
                    mTitleTextSize = typedArray.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
                            TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
                    break;
            }
        }
        typedArray.recycle();//记得回收
    }

此外,declare-styleable标签既能告诉系统这是自定义的属性,又会帮助我们在R.java中生成一些常量方便我们使用。

最后,在获取完所需的属性后,我们将其设置到对应的paint上即可,创建paint并调用onDraw方法进行重绘。

在UI线程外,使用postInvalidate方法启动绘制,当需要重绘的时候则可以直接在UI线程中中使用inValidate方法实现重绘。

详细参见:postinvalidate 和invalidate的区别


设置监听接口

自定义完View之后,一般会对外暴露一些接口,用于控制View的状态等,或者监听View的变化.

直接在CustomView的构造方法在即可为View设置监听事件

        //设置监听事件
        this.setOnClickListener(new OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                mTitleText = randomText();
                postInvalidate();
            }

        });

一个验证码小示例:

源码地址:Custom



原来学RecyclerView,走到半截发觉对自定义分割线有很多不理解的地方,于是有转过头来重学自定义View,看了群英传和开发艺术探索,以及网络博客诸多,终于写够了这篇基础的自定义。

关于更深入的Canvas学习,可以参加这位大牛的最后一篇文章,Canvas之图片以及张洪洋一整个系列自定义View实战

路漫漫其修远兮,吾将上下而求索。纪念第一篇MarkDown博客

时间: 2024-10-05 05:21:34

Android自定义View入门的相关文章

Android 自定义View (一)

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24252901 很多的Android入门程序猿来说对于Android自定义View,可能都是比较恐惧的,但是这又是高手进阶的必经之路,所有准备在自定义View上面花一些功夫,多写一些文章.先总结下自定义View的步骤: 1.自定义View的属性 2.在View的构造方法中获得我们自定义的属性 [ 3.重写onMesure ] 4.重写onDraw 我把3用[]标出了,所以说3不一

Android自定义View探索(一)—生命周期

Activity代码: public class FiveActivity extends AppCompatActivity { private MyView myView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.e("log", "Activity生命周期:onCreate"); setConte

Android 自定义View视图

创建全新的视图将满足我们独特的UI需求. 本文介绍在指南针开发中会用到的罗盘的界面UI,通过继承View类实现的自定义视图,以此来深刻了解自定义视图. 实现效果图: 源代码: 布局文件activity_main(其中CompassView继承View类): <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.

android自定义View (一)MeasureSpec

A MeasureSpec encapsulates the layout requirements passed from parent to child. Each MeasureSpec represents a requirement for either the width or the height. A MeasureSpec is comprised of a size and a mode. There are three possible modes: UNSPECIFIED

(转)[原] Android 自定义View 密码框 例子

遵从准则 暴露您view中所有影响可见外观的属性或者行为. 通过XML添加和设置样式 通过元素的属性来控制其外观和行为,支持和重要事件交流的事件监听器 详细步骤见:Android 自定义View步骤 样子 支持的样式 可以通过XML定义影响外边和行为的属性如下 边框圆角值,边框颜色,分割线颜色,边框宽度,密码长度,密码大小,密码颜色 <declare-styleable name="PasswordInputView"> <attr name="border

Android自定义View——圆形进度条式按钮

介绍 今天上班的时候有个哥们问我怎么去实现一个按钮式的进度条,先来看看他需要实现的效果图. 和普通的圆形进度条类似,只是中间的地方有两个状态表示,未开始,暂停状态.而且他说圆形进度的功能已经实现了.那么我们只需要对中间的两个状态做处理就行了. 先来看看实现的效果图: 上面说了我们只需要处理中间状态的变化就可以了,对于进度的处理直接使用了弘洋文章中实现: http://blog.csdn.net/lmj623565791/article/details/43371299 下面开始具体实现. 具体实

android 自定义View【2】对话框取色&amp;色盘取色的实现

android 自定义View[2]对话框取色&色盘取色的实现    上一篇文章基本介绍了android自定义view的流程:继承view,复写view的一些方法.实现简单的自定义view.这篇文章主要介绍的是系统对话框取色功能,然后顺便介绍升级版,色盘取色[类似于ps中的吸管,对图片点击相应位置,获取那个位置的颜色]. 一.概述:通过该例子了解以下内容: 1.进一步了解android 自定义view. 2.知道如何获取图片上的颜色值. 3.监听屏幕touch,实现移动的时候自动取色.[onDr

Android自定义View(二、深入解析自定义属性)

转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51468648 本文出自:[openXu的博客] 目录: 为什么要自定义属性 怎样自定义属性 属性值的类型format 类中获取属性值 Attributeset和TypedArray以及declare-styleable ??在上一篇博客<Android自定义View(一.初体验)>中我们体验了自定义控件的基本流程: 继承View,覆盖构造方法 自定义属性 重写onMeasure方法测量宽

Android 自定义 view(三)&mdash;&mdash; onDraw

前言: 上一篇已经介绍了用自己定义的属性怎么简单定义一个view<Android 自定义view(二) -- attr 使用>,那么接下来我们继续深究自定义view,下一步将要去简单理解自定义view的两个比较重要的方法 onDraw(Canvas canvas) ,在探究 onDraw方法之前,我们必须先深入了解两个类Paint和Canvas .   第一:认识Paint 在探究onDraw之前首先必须要认识两个类,这里给出非常不错的两个资料参考网站,我也是从这里得到想要知道的东西,简单的说