作为一个有创意的开发者,或者软件对UI设计的要求比较高,你经常会遇到安卓自带的控件无法满足你的需求的情况,这种时候,我们只能去自己去实现适合项目的控件。同时,安卓也允许你去继承已经存在的控件或者实现你自己的控件以便优化界面和创造更加丰富的用户体验。
那么怎样来创建一个新的控件呢?
这得看需求是怎样的了。
1.需要在原生控件的基本功能上进行扩展,这个时候你只需要继承并对控件进行扩展。通过重写它的事件,onDraw ,但是始终都保持都父类方法的调用。如从已有的高级控件上继承,例如继承一个TextView。
2.需要几个控件的功能的加和,这个时候要把控件组合起来,就是通过合并几个控件来生成一个新控件。比如在ListView中用适配器来将多种控件有机的结合在一起,又如写一个控件是多个控件的组合,一般是自定义布局,可以用一个类继承一个布局。这个布局中包含多个控件。
3.白手起家自己创建一个新的控件。即直接从View,ViewGroup开始绘制控件
4.另外大家不要忘了,还有一个好用的东西<include>标签。 在一个项目中我们可能会需要用到相同的布局设计,如果都写在一个xml文件中,代码显得很冗余,并且可读性也很差,所以我们可以把相同布局的代码单独写成一个模块,然后用到的时候可以通过<include
/> 标签来重用layout代码。
作过Android 应用开发的朋友都知道,Android的UI界面都是由View和ViewGroup及其派生类组合而成的。基于安卓UI设计原理,我们作为开发者,完全能够按照自己的意愿开发出项目定制的组件。其中,View是所有UI组件的基类,而ViewGroup是容纳这些组件的容器,其本身也是从View派生出来的。AndroidUI界面的一般结构可参见下面的示意图:
可见,作为容器的ViewGroup可以包含作为叶子节点的View,也可以包含作为更低层次的子ViewGroup,而子ViewGroup又可以包含下一层的叶子节点的View和ViewGroup。事实上,这种灵活的View层次结构可以形成非常复杂的UI布局,开发者可据此设计、开发非常精致的UI界面。
ViewGroup可以通过重写onMeasure,onLayout为加入其中的View进行布局和处理,功能十分强大,我们这次先学习View类派生自定义组件:
View组件的作用类似于JAVA中Swing里的Panel,是一个矩形的空白区域,不带有任何内容,对于Android应用的其他UI控件来说,都是继承了View组件,然后绘制出来的。所以我们通过View子类并重写View类的方法来派生我们自己的控件。
Android自定义View实现很简单:
继承View,重写构造函数、onDraw,(onMeasure)等函数,下面会逐一列举。
如果自定义的View需要有自定义的属性,需要在values下建立attrs.xml。在其中定义你的属性。在使用到自定义View的xml布局文件中需要加入xmlns:前缀="http://schemas.android.com/apk/res/你的自定义View所在的包路径".在使用自定义属性的时候,使用前缀:属性名,如my:textColor="……"。
让我们先看一下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(android.graphics.Canvas) |
Called when the view should render its content. |
Event processing |
onKeyDown(int, KeyEvent) |
Called when a new hardware key event occurs. |
onKeyUp(int, KeyEvent) |
Called when a hardware 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, android.graphics.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. |
通常可能需要重写以下方法:
1.构造器,至少用来获取Context
2.onFinishlnflate()这是一个回调方法, 当应用从 XML 布局文件加载该组件并利用
它来构建界面之后, 该方法就会被回调。
3.onMeasure(int,int):调用该方法来检测View组件及它所包含的所有子组件的大小.
4.onlayout(boolean,int,int,int,int):当该组件需要分配其子组件的位置、大小时,
该方法就会被回调. View类中布局发生改变时会调用的方法,这个方法是所有View、ViewGroup及其派生类都具有的方法,重载该类可以在布局发生改变时作定制处理,这在实现一些特效时非常有用。
5.onSizeChanged(int,int, int, int):当该组件的大小被改变时回调该方法.
6.onDraw(canves): 当该组件将要绘制它的内容时回调该方法迸行绘制. View类中用于重绘的方法,这个方法是所有View、ViewGroup及其派生类都具有的方法,也是Android UI绘制最重要的方法。开发者可重载该方法,并在重载的方法内部基于参数canvas绘制自己的各种图形、图像效果。
7.onKeyDown(int,KeyEvent): 当某个键被按下时触发该方法.
8.onKayUp(int,KeyEvent), 当松开某个键时触发该方法.
9.onTrackballEvent (MotionEvent): 当发生轨迹球事件时触发该方法.
10.onTouchEvent (MotionEvent): 当发生触摸屏事件时触发该方法.
11.onWindowFocuschanged(boolean): 当该组件得到、失去焦点时触发该方法.
12.onAttachedToWindow():当把该组件放入某个窗口时触发该方法.
13.onDetachedFromWindow(): 当把该组件从某个窗口上分离时触发该方法.
14.onWindowVisibilityChanged(int):当包含该组件的窗口的可见性发生改变时触发该
方法.
另外再补充两个ViewGroup类经常重载的方法:
1.protected void dispatchDraw(Canvas canvas):ViewGroup类及其派生类具有的方法,这个方法主要用于控制子View的绘制分发,重载该方法可改变子View的绘制,进而实现一些复杂的视效。
2.protected boolean drawChild(Canvas canvas, View child, long drawingTime)):ViewGroup类及其派生类具有的方法,这个方法直接控制绘制某局具体的子view,重载该方法可控制具体某个具体子View。
在需要开发自定义View的时候,我们不需要列举出上面所有的方法,,而是可以根据业务需要来有选择的使用·上面的方法,下面我们看一个简单的示例程序,在这个示例程序里面我们只需要重写onDraw方法就可以了!
示例程序一:
我们要写一个跟随手指移动的小球,思路很简单,只要获取到用户点击屏幕的位置,并且在该位置处重绘小球即可:
下面我们看一下程序:
我注释写的比较清楚,我就说的简略一点:
首先我们写一个类DrawView,也就是我们自定义的控件,继承自View
然后我们先写出构造器,获取到Context,这里如果用只含有Context的构造器会在xml里调用控件的时候出错,详情请看我的另外一篇博文:
http://blog.csdn.net/sunmc1204953974/article/details/38101057
下面我们开始写:
// 构造方法 public DrawView(Context context,AttributeSet attrs){ super(context,attrs); } // 重写ondraw方法 @Override public void onDraw(Canvas canvas){ super.onDraw(canvas); // 创建画笔 Paint paint = new Paint(); // 设置画笔颜色 paint.setColor(Color.RED); // 画出小球 canvas.drawCircle(circleX, circleY, circleR, paint); }
然后不要忘了设置这些数据的setter和getter,因为我们需要再使用这个View的时候加上监听才可以:
// get set 方法 public float getCircleX() { return circleX; } public void setCircleX(float circleX) { this.circleX = circleX; } public float getCircleY() { return circleY; } public void setCircleY(float circleY) { this.circleY = circleY; } public float getCircleR() { return circleR; } public void setCircleR(float circleR) { this.circleR = circleR; }
这样我们的一个简单地自定控件就大功告成了,下面是该类的完整代码:
package com.example.moveball; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.view.View; public class DrawView extends View{ private float circleX = 40; private float circleY = 50; private float circleR = 15; // 构造方法 public DrawView(Context context,AttributeSet attrs){ super(context,attrs); } // 重写ondraw方法 @Override public void onDraw(Canvas canvas){ super.onDraw(canvas); // 创建画笔 Paint paint = new Paint(); // 设置画笔颜色 paint.setColor(Color.RED); // 画出小球 canvas.drawCircle(circleX, circleY, circleR, paint); } // get set 方法 public float getCircleX() { return circleX; } public void setCircleX(float circleX) { this.circleX = circleX; } public float getCircleY() { return circleY; } public void setCircleY(float circleY) { this.circleY = circleY; } public float getCircleR() { return circleR; } public void setCircleR(float circleR) { this.circleR = circleR; } }
之后我们就是像平时使用安卓原生控件那样使用就可以了,我们看一下Activity的代码:
package com.example.moveball; import android.os.Bundle; import android.app.Activity; import android.view.Menu; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; public class MainActivity extends Activity { // 定义DrawView组件 DrawView drawView = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 创建DrawView组件 drawView = (DrawView)this.findViewById(R.id.drawView); // 为DrawView组件绑定Touch事件 drawView.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View arg0, MotionEvent event) { // 获取坐标并改变小球的坐标 drawView.setCircleX(event.getX()); drawView.setCircleY(event.getY()); // 通知draw组件重绘 drawView.invalidate(); // 返回true表明被执行 return true; } }); } }
以及xml格式的布局文件:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <com.example.moveball.DrawView android:id="@+id/drawView" android:layout_width="match_parent" android:layout_height="match_parent" > </com.example.moveball.DrawView> </RelativeLayout>
这样一个简单的例子就呈现在大家面前了,无论是多么复杂的自定义控件,思路总是这样子的,大家是不是觉得怪怪的,对了,作为一个控件,我们居然还要为了他的实现为其增加麻烦的监听,这就是因为我们重写的方法太少的原因,下一讲再给大家介绍一个经常重写的方法:publicboolean onTouchEvent (MotionEvent event)。
源代码上面已经很详细了,我在最后一篇的最后还会发一个工程上来,欢迎大家一起学习!
我也还是学生,写的不好或者有问题的地方还请多多指教~