Android自定义控件浅谈

对于Android自定义控件和动画的知识,我一直是一窍不通。今天趁着“闲暇”时间,在网上搜罗了一些关于自定义控件的资料,现将其中个人觉得比较好的内容整理如下,供读者朋友们参考。

个人觉得如果想真正去实现一个自定义控件,了解其原理和实现步骤还是很有必要的。这哥们在博客里给出的实现开发步骤还是很详细的:

a)了解View的工作原理;

b)编写继承自View的子类;

c)为自定义的View类增加属性;

d)绘制控件;

e)响应用户消息;

f)自定义回调函数。

现在我们就沿着上述步骤来做个简单的说明。

1)Android中View的工作原理

Android系统的View在设计上采用了组合模式,类View是所有View控件的父类,ViewGroup同样继承自View,用于放置其他View控件(含ViewGroup控件,也称为视图容器)。

与View的绘制密切相关的方法主要有三个,即measure()、layout()和draw()方法,其内部又分别调用了onMeasure()、onLayout和onDraw()三个子方法。其具体操作如下:

1.1)measure操作

measure主要用于计算View的大小,即View的width和height值。在view中,该方法是final类型,不允许子类继承该方法。在measure操作内部调用了onMeasure()方法,View的大小最终是通过调用onMeasure()方法确定的,并且通过调用setMeasuredDimention(width, height)方法保存计算结果。

1.2)layout操作

layout操作主要用于控制View在屏幕中显示的位置,该方法同样为final类型,主要用在ViewGroup中。与layout()方法相关的操作主要是setFrame(l, t, r, b)和onLayout()方法。setFrame中的参数指定了子View在视图容器中的位置,通过该方法将位置信息保存。onLayout()方法主要用于ViewGroup对子视图显示位置的控制。

1.3)draw操作

draw操作会根据measure和layout计算到的结果,来完成View在屏幕中的显示。子类也不应该修改该方法,因为其内部定义了绘图的基本操作:

(1)绘制背景;

(2)如果要view显示渐变框,在该方法中会做一些准备工作;
(3)绘制view本身,即调用onDraw()函数。在view中onDraw()是个空函数,也就是说具体的视图都要覆写该函数来实现自己的显示(比如TextView在这里实现了绘制文字的过程)。而对于ViewGroup则不需要实现该函数,因为作为容器是“没有内容“的,其包含了多个子view,而子View已经实现了自己的绘制方法,因此只需要告诉子view绘制自己就可以了,也就是下面的dispatchDraw()方法;
(4)绘制子视图,即dispatchDraw()函数。在view中这是个空函数,具体的视图不需要实现该方法,它是专门为容器类准备的,也就是容器类必须实现该方法;
(5)如果需要(应用程序调用了setVerticalFadingEdge或者setHorizontalFadingEdge),开始绘制渐变框;
(6)绘制滚动条;

下面这段内容来自大牛博客,我只是做了排版上的修改:
整个 draw是从根View开始的,ViewGroup向子View发出 draw的请求,然后子View负责自己重画它们的invalid区域。Drawing一个Layout必须通过两个步骤:
i)测量   measure() 从根节点到叶子节点依次测量,这样每个View都会存有各自的dimension.在我们的程序种我们可以重写onMeasure来设置更为精确的content大小,重写完后必须调用setMeasureDimension来存储View的宽和高。
ii)布局   layout也是从父节点到子节点,父节点根据刚才传递进来的measure信息来拜访它们的子节点。
当一个视图的measure()方法返回时,它的getMeasuredWidth()和getMeasuredHeight() 值必须被设置,以及所有
这个视图子节点的值。一个view的measure的宽度和高度值必须符合父视图的限制。这确保在度量过程之后,所有父节点接受所有它们的子节点的度量值。一个父视图可能会在其子视图上多次调用measure()方法。比如,父视图可能会通过未指定的尺寸调用measure来找到它们的大小,然后使用实际数值再次调用measure(),如果所有子视图未做限制的尺寸总合过大或过小(也即是,如果子视图之间不能对各自占据的空间达成共识的话,父视图将会干预并且使用第二个过程的规则)。
View.MeasureSpec 用于子View告诉父View它们想如何被测量和怎么放置。而 LayoutParams则是描述了View的宽和高是多少。对于每一个区域可以指定如下的值:
  • 一个准确的数值。
  • ·FILL_PARENT,这意味着视图想和父视图一样大(减掉填充padding)。
  • WRAP_CONTENT,这意味着视图只想有刚好包装其内容那么大(加上padding)。
对于不同的ViewGroup子类,有相应的LayoutParams子类。比如,相对布局RelativeLayout有它自己的LayoutParams子类,这包含了能够让子视图横向和竖向居中显示的能力。
 
度量规格(MeasureSpecs)被用来沿着树从父到子的下传度量需求。一个MeasureSpecs可以是下面三种模式之一:
  • UNSPECIFIED:父视图来决定其子视图的理想尺寸。比如,一个线性布局可能在它的子视图上调用measure(),通过设置其高度为UNSPECIFIED 以及一个宽度为EXACTLY 240,来找出这个子视图在给定240像素宽度的情况下需要显示多高。
  • EXACTLY:父视图用来给子视图强加一个准确的尺寸。子视图必须使用这个大小,并确保其所有的后代将适合这个尺寸。
  • AT_MOST:这被父视图用来给子视图强加一个最大尺寸。子视图必须确保它自己以及所有的后代都适合这个尺寸。
2)编写继承自View的子类
创建自定义控件的3种主要实现方式:
i)继承已有的控件来实现自定义控件: 主要是当要实现的控件和已有的控件在很多方面比较类似, 通过对已有控件的扩展来满足要求。
ii)通过继承一个布局文件实现自定义控件,一般来说做组合控件时可以通过这个方式来实现。
    注意此时不用onDraw方法,在构造方法中通过inflater加载自定义控件的布局文件,再addView(view),自定义控件的图形界面就加载进来了。
iii)通过继承view类来实现自定义控件,使用GDI绘制出组件界面,一般无法通过上述两种方式来实现时用该方式。
3)为自定义的View类增加属性
i)在View类中定义。通过构造函数中引入的AttributeSet 去查找XML布局的属性名称,然后找到它对应引用的资源ID去找值。
案例:实现一个带文字的图片(图片、文字是onDraw方法重绘实现)

<span style="font-family:SimSun;font-size:18px;">public class MyView extends View {

private String mtext;

private int msrc;


public MyView(Context context) {

super(context);

// TODO Auto-generated constructor stub

}


public MyView(Context context, AttributeSet attrs) {

super(context, attrs);

// TODO Auto-generated constructor stub

int resourceId = 0;

int textId = attrs.getAttributeResourceValue(null, "Text",0);

int srcId = attrs.getAttributeResourceValue(null, "Src", 0);

mtext = context.getResources().getText(textId).toString();

msrc = srcId;

}


@Override

protected void onDraw(Canvas canvas) {

// TODO Auto-generated method stub

Paint paint = new Paint();

paint.setColor(Color.RED);

InputStream is = getResources().openRawResource(msrc); 

        Bitmap mBitmap = BitmapFactory.decodeStream(is);

        

        int bh = mBitmap.getHeight();

        int bw = mBitmap.getWidth();

        

canvas.drawBitmap(mBitmap, 0,0, paint);

//canvas.drawCircle(40, 90, 15, paint);

canvas.drawText(mtext, bw/2, 30, paint);

}


}</span>

布局文件:
<span style="font-family:SimSun;font-size:18px;"><?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:orientation="vertical" >


    <com.example.myimageview2.MyView

        android:id="@+id/myView1"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content" 

        Text="@string/hello_world"

        Src="@drawable/xh"/>


</LinearLayout></span>

属性Text, Src在自定义View类的构造方法中读取。

ii)通过XML为View注册属性。与Android提供的标准属性写法一样。
案例:  实现一个带文字说明的ImageView (ImageView+TextView组合,文字说明,可在布局文件中设置位置)

<span style="font-family:SimSun;font-size:18px;">public class MyImageView extends LinearLayout {


public MyImageView(Context context) {

super(context);

// TODO Auto-generated constructor stub

}


public MyImageView(Context context, AttributeSet attrs) {

super(context, attrs);

// TODO Auto-generated constructor stub

int resourceId = -1;

TypedArray typedArray = context.obtainStyledAttributes(attrs,

R.styleable.MyImageView);

ImageView iv = new ImageView(context);

TextView tv = new TextView(context);


int N = typedArray.getIndexCount();

for (int i = 0; i < N; i++) {

int attr = typedArray.getIndex(i);


switch (attr) {

case R.styleable.MyImageView_Oriental:

resourceId = typedArray.getInt(

R.styleable.MyImageView_Oriental, 0);

this.setOrientation(resourceId == 1 ? LinearLayout.HORIZONTAL

: LinearLayout.VERTICAL);

break;


case R.styleable.MyImageView_Text:

resourceId = typedArray.getResourceId(

R.styleable.MyImageView_Text, 0);

tv.setText(resourceId > 0 ? typedArray.getResources().getText(

resourceId) : typedArray

.getString(R.styleable.MyImageView_Text));

break;

case R.styleable.MyImageView_Src:

resourceId = typedArray.getResourceId(

R.styleable.MyImageView_Src, 0);

iv.setImageResource(resourceId > 0 ?resourceId:R.drawable.ic_launcher);

break;

}


}


addView(iv);

addView(tv);

typedArray.recycle();


}

}

</span>

attrs.xml进行属性声明, 文件放在values目录下

<span style="font-family:SimSun;font-size:18px;"><?xml version="1.0" encoding="utf-8"?>

<resources>


    <declare-styleable name="MyImageView">

        <attr name="Text" format="reference|string"></attr>

        <attr name="Oriental" >

            <enum name="Horizontal" value="1"></enum>

            <enum name="Vertical" value="0"></enum>

        </attr>

        <attr name="Src" format="reference|integer"></attr>

    </declare-styleable>


</resources>

</span>

MainActivity的布局文件:先定义命名空间 xmlns:uview="http://schemas.android.com/apk/res/com.example.myimageview2"
然后可以像使用系统的属性一样使用:uview:Oriental="Vertical"

<span style="font-family:SimSun;font-size:18px;"><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:uview="http://schemas.android.com/apk/res/com.example.myimageview2"

    xmlns:tools="http://schemas.android.com/tools"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:orientation="vertical"

    tools:context=".MainActivity" >


    <TextView

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="@string/hello_world" />


    <com.example.myimageview2.MyImageView

        android:id="@+id/myImageView1"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        uview:Text="这是一个图片说明" 

        uview:Src="@drawable/tw"

        uview:Oriental="Vertical">

    </com.example.myimageview2.MyImageView>


</LinearLayout></span>

4)绘制控件
5)响应用户消息
6)自定义回调函数

参考博文:
1、Android自定义控件:http://blog.163.com/ppy2790@126/blog/static/103242241201382210910473/
2、开发者指南-Android如何绘制View:http://zuiniuwang.blog.51cto.com/3709988/718274
3、
Android应用程序窗口(Activity)的测量(Measure)、布局(Layout)和绘制(Draw)过程分析: http://blog.csdn.net/luoshengyang/article/details/8372924

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值