分享

android中

 點點滴滴 2012-09-14

android中-自定义View对象(二)

热度 2已有 815 次阅读2011-8-24 23:08 |个人分类:android 学习篇

上一篇文 章我们了解android.view.View的原理而且制作了一个功能简单的效果:点击屏幕随机在点击的位置生成一个半径是1-10的圆圈。我们是使用 一个自定义View对象完成的,但是它对View所做的修改有限,只是修改了onDraw和onTouchEvent两个方法,这样不能涵盖整个自定义 View所能完成的功能。所以这里我们再补充下自定义View相关的一些内容。

在SDK文档中描述了View创建的几个主要过程:

Category Methods Description
Creation Constructors There is a form of the constructor that are called when the view is created from code and a form that is called when the view is inflated from a layout file. The second form should parse and apply any attributes defined in the layout file.
onFinishInflate() Called after a view and all of its children has been inflated from XML.
Layout onMeasure(int, int) Called to determine the size requirements for this view and all of its children.
onLayout(boolean, int, int, int, int) Called when this view should assign a size and position to all of its children.
onSizeChanged(int, int, int, int) Called when the size of this view has changed.
Drawing onDraw(Canvas) Called when the view should render its content.
Event processing onKeyDown(int, KeyEvent) Called when a new key event occurs.
onKeyUp(int, KeyEvent) Called when a key up event occurs.
onTrackballEvent(MotionEvent) Called when a trackball motion event occurs.
onTouchEvent(MotionEvent) Called when a touch screen motion event occurs.
Focus onFocusChanged(boolean, int, Rect) Called when the view gains or loses focus.
onWindowFocusChanged(boolean) Called when the window containing the view gains or loses focus.
Attaching onAttachedToWindow() Called when the view is attached to a window.
onDetachedFromWindow() Called when the view is detached from its window.
onWindowVisibilityChanged(int) Called when the visibility of the window containing the view has changed.

上面的列表很详细的将View视图从被创建(Creation)构建布局(layout)执行绘图(drawing)事件处理(Event Processing)焦点处理(Focus)被加载到Activity容器(Attaching)的 几个过程所执行的生命周期方法都列出来了。我们可以很清晰的看到它的整个处理过程,当你需要自定义一个View的时候,你可以根据你自定义的View所要 完成的功能选择性的重写上面的几个方法。以上一篇的画实心圆为例,我们重写了它的被创建、执行绘图和事件处理三个方法。

下面我们编写一个自定义View对象,让其可以通过xml文件配置一些自定义属性,可手动设置大小,并在绘图时添加一些自定义的图形。

首先我们编写一个类,继承android.view.View对象,重写里面的构造方法(在构造方法里获取从xml布局文件中设置的自定义属性并执行初始化)、onMeasure方法(实现可以在xml配置文件中定义大小)和onDraw方法(绘制视图内容,同时在视图对象的中间添加一条横线),CustomLabelView.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package com.android777.demo.uicontroller.view;
  
import com.android777.demo.uicontroller.R;
  
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
  
public class CustomLabelView extends View {
  
    Paint paint;
    String mText;
  
    public CustomLabelView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }
  
    //在xml布局文件中定义这个View时会调用这个构造方法
    public CustomLabelView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
  
        //获取xml文件里定义的View的所有属性
        TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.CustomLabelView);
  
        //获取text属性
        CharSequence s = a.getString(R.styleable.CustomLabelView_text);
        //获取backgroundColor属性
        int color = a.getColor(R.styleable.CustomLabelView_backgroundColor, Color.BLACK);
        //填充text属性值
        if(s != null){
            setText(s.toString());
        }
        //设置背景颜色为 xml文件中定义的backgroundColor
        setBackgroundColor(color);
  
        //回收资源
        a.recycle();
  
    }
  
    public CustomLabelView(Context context) {
        super(context);
        init();
    }
  
    /**
     *
     * @param s 设置显示的文本内容
     *
     * 当设置显示文本内容后,我们将要显示的内容保存到View对象中,然后执行
     * invalidate让View视图更新显示画面。
     *
     */
    public void setText(String s){
        mText = s;
        requestLayout(); //计算尺寸
        invalidate(); //重新绘制视图对象
    }
  
    private void init(){
  
        paint = new Paint();
        paint.setColor(Color.WHITE);
        paint.setTextSize(16);
        paint.setAntiAlias(true);
  
    }
  
    //重写这个方法实现能在xml文件中定义视图大小
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
  
        //定义最终确定的宽度和高度
        int width,height;
  
        //如果在xml文件中是明确定义视图大小,那么就使用明确定义的值
        if(widthSpecMode == MeasureSpec.EXACTLY){
            width = widthSpecSize;
        }else{
            //否则测量需要显示的内容所占的宽度(就是分配视图占用的宽度)
            width = (int) paint.measureText(mText);
  
            //如果有定义一个最大宽度, 那么视图分配的宽度不得超过最大宽度
            if(widthSpecMode == MeasureSpec.AT_MOST){
                width = Math.min(width, widthSpecSize);
            }
        }
  
        //下面测量高度的方式跟宽度差不多
        if(heightSpecMode == MeasureSpec.EXACTLY){
            height = heightSpecSize;
        }else{
            height = (int) (-paint.ascent() + paint.descent());
  
            if(heightSpecMode == MeasureSpec.AT_MOST){
                height = Math.min(height, heightSpecSize);
            }
        }
  
        //这个一定要调用,  告诉系统测量的最终结果 需要的宽度是width 高度是height
        setMeasuredDimension(width, height);
  
    }
  
    @Override
    protected void onDraw(Canvas canvas) {
  
        //这个是自定义添加的操作,在视图绘制的区域中间画一条横线
        canvas.drawLine(0, getMeasuredHeight()/2, getMeasuredWidth(), getMeasuredHeight() /2, paint);
        //绘制视图的内容
        canvas.drawText(mText, 0, -paint.ascent(), paint);
  
    }
  
}

上面代码的一个难点是在于如何测量文字所占的尺寸,文字占用的宽度比较容易计算,但是高度就比较复杂,因为各个字体使用的间距不一样。我们在所有三 个构造方法中执行初始化代码,初始化代码其实没做什么,只是定义了一个画笔对象,它的颜色是白色的,文字大小是16。这边有三个构造方法,它们分别对应的 功能是不一样的:

CustomLabelView(Context) :这个构造方法是当程序中动态创建CustomLabelView这个对象时调用的。

CustomLabelView(Context,AttributeSet):这个构造方法是当你使用xml布局文件加载视图对象时系统会调用的构造方法,所以你可以在这个构造方法里通过AttributeSet这个参数获得在xml中对这个视图对象定义的全部xml属性。

CustomLabelView(Context,AttributeSet,int):这个构造方法用得比较少,详细用法我也不是很清楚。

我们在第二个构造方法中获取自定义的属性,然后对View对象配置这些属性,在这个步骤前记得要先定义这些可配置的属性,否则就会报错,具体是定义在res\values\attrs.xml文件中:

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<resources>
  
    <declare-styleable name="CustomLabelView">
        <attr name="text" format="string" />
        <attr name="backgroundColor" format="color" />
    </declare-styleable>
</resources>

这样你就可以在代码中引 用:R.styleable.CustomLabelView,R.styleable.CustomLabelView_text和 R.styleable.CustomLabelView_backgroundColor属性了。然后我们重写了onMeasure方法,在里面做了一 些根据xml的配置测量View内部内容所占的尺寸的测量工作,最后调用setMeasureDimension通知系统测量的最终结果。

然后重写onDraw方法,在这里我们添加了一个自己定义的步骤:在视图所占区域的中间画一条线,最后打出视图中的内容。

布局xml文件,res\layout\custom_view.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas./apk/res/android"
  xmlns:app="http://schemas./apk/res/com.android777.demo.uicontroller"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  
  <com.android777.demo.uicontroller.view.CustomLabelView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        app:text="第一个LabelView"
        app:backgroundColor="#7f00"
  />
  
  <com.android777.demo.uicontroller.view.CustomLabelView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:text="第二个LabelView"
        app:backgroundColor="#77ffff00"
  />
  
  <com.android777.demo.uicontroller.view.CustomLabelView
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        app:text="第三个LabelView"
        app:backgroundColor="#2ff0"
  />
  
</LinearLayout>

在布局文件中我们创建一个LinearLayout容器,按照纵向排列放了3个自定义的视图对象,这边使用自定义视图对象的方式是将xml的节点名 字换成你自定义View的类全路径(包名+类名),然后就可以在节点的属性中配置View的属性。因为上面我们有用到自定义的属性,所以需要自己创建一个 xml名称空间,这个名称空间的规范是:

1
xmlns:xxx="http://schemas./apk/res/yyy"

其中xxx就是下面要引用到的名称空间,yyy一定要是项目的包名(刚创建Android项目时填入的包名,也可以查看 AndroidMenifest.xml文件中menifest根节点中的package属性,就是这个属性值)。因为我项目的包名 是:com.android777.demo.uicontroller,所以我的xml名称空间设置为:

1
xmlns:app="http://schemas./apk/res/com.android777.demo.uicontroller"

然后我在自定义的View节点中,使用这个名称空间配置在res\values\attrs.xml中定义的text和 backgroundColor属性。这样整个自定义View步骤就完成了,最后只需要一个Activity来查看效 果,CustomLabelViewActivity.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.android777.demo.uicontroller.view;
  
import com.android777.demo.uicontroller.R;
  
import android.app.Activity;
import android.os.Bundle;
  
public class CustomLabelViewActivity extends Activity {
  
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
  
        setContentView(R.layout.custom_view);
  
    }
  
}

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多