您的位置:首页 > 移动开发 > Android开发

Android 自定义View(一)

2016-06-15 23:04 423 查看
Android系统给我们提供了丰富的组件库来创建丰富的UI效果,同时也提供了非常方便的扩展方法。但是在实际开发中,有时为了项目的需求,不得不自己去“绘制”一个View,这个时候通常已经不能用系统自带的控件来设计了,这个时候就要自己定义“View”了,来满足项目的需求。

通常有3种方法来自定义的控件

1,对现有控件进行扩展

2,通过组合来实现新的控件

3,重写View来实现全新的控件

1.在自定义控件之前,我们先来了解下系统是怎么测量和绘制View的。

     1.1 View的测量

            在显示生活中,假如我们要画一个图形,就必须知道它的大小和位置。同样,Android在绘制View之前,先必须得对View进行测量,即告诉系统要绘制View的尺寸,而这个过程是在onMesure()方法中进行。

          Android系统中有一个功能强大的类-----MeasureSpec类,通过它可以帮助我们测量View。MeasureSpec是一个32位的int值,其中高2位为测量模式,低30位为测量的大小,在计算中使用位运算的原因是为了提高并优化效率。通过MeasureSpec.getMode(widthMeasureSpec)来获取测量模式;通过MeasureSpec.getSize(heightMeasureSpec)来获取测量的大小,其中widthMeasureSpec和heightMeasureSpec是重写的onMeasure(int
widthMeasureSpec,int heightMeasureSpec)方法中的2个参数值。

         测量模式有3种

         (1)EXACTLY

                   官方的描述

The parent has determined an exact size for the child. The child is going to be given those bounds regardless of how big it wants to be.


                   精确值模式,当我们将控件的layout_width属性或者layout_height属性指定为具体数值时,或者为match_parent属性时,系统使用的是EXACTLY模式。

         (2)AT_MOST

The child can be as large as it wants up to the specified size.


                   最大值模式,当控件的layout_width属性或者layout_height属性为wrap_content时,控件大小一般随着控件的子控件或内容的变化而变化,此时控件的尺寸大小只要不超过父控件允许的最大尺寸即可。

         (3)UNSPECTIFIED

The parent has not imposed any constraint on the child. It can be whatever size it wants.


                    它不指定测量模式,View想多大就多大,一般用作Android系统内部,或者ListView和ScrollView等滑动控件。

             注意:

       View类默认的onMeasure()方法只支持EXACTLY模式,所以如果在自定义控件的时候不重写onMeasure()方法的话,就只能使用EXACTLY模式。而如果要让自定义View支持wrap_content属性,那么必须重写onMeasure()方法来指定wrap_content时的大小,否则它会默认填充整个父布局,重写onMeasure()方法的目的,就是为了能给View一个wrap_content属性下的默认大小。

    

    1.2 View的绘制

           当测量好以后,我们就可以简单地重写onDraw()方法,并在Canvas对象上来绘制所需要的图形。要想在Android的界面中绘制相应的图像,就必须在Canvas上进行绘制。Canvas就好比是一块画布,使用Paint(相当于画笔)就可以在上面作画了,通常需要继承View并重写它的onDraw()方法来完成绘图。

          onDraw()方法中有一个参数,就是Canvas canvas对象。使用这个对象,就可以进行绘制了。而在其他地方,通常需要使用代码来创建一个Canvas对象。代码如下:

          Canvas canvas=new Canvas(bitmap);

          传进去的bitmap对象的过程称之为装载画布。传进去的bitmap与创建的canvas对象紧紧的联系在一起,bitmap存储所有绘制在canvas上的像素信息,通过canvas.drwaXXX方法,可以进行一系列的绘图操作。

         下面通过代码来实践一下吧。

         布局文件activity_main.xml

<LinearLayout 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"
android:orientation="vertical"
tools:context="com.example.myview.MainActivity" >

<com.example.myview.MyCustomView
android:id="@+id/my_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

</LinearLayout>
        MyCustomView.java

package com.example.myview;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Message;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;

public class MyCustomView extends View implements Runnable{
private Paint mPaint;
private Context mContext;// 上下文环境引用
private int radiu;

public MyCustomView(Context context) {
super(context);
// TODO 自动生成的构造函数存根
}

public MyCustomView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
mContext = context;
// 初始化画笔
initPaint();
}

/**
* 初始化画笔
*/
private void initPaint() {
// TODO 自动生成的方法存根
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setAntiAlias(true);// 抗锯齿效果
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.CYAN);
mPaint.setStrokeWidth(5);
}

@Override
protected void onDraw(Canvas canvas) {
// TODO 自动生成的方法存根
super.onDraw(canvas);
canvas.drawCircle(MeasureUtil.getScreenWidth(mContext)/2,
MeasureUtil.getScreenHeight(mContext)/2, radiu, mPaint);

}

@Override
public void run() {
// TODO 自动生成的方法存根
while (true) {
if (radiu <=200) {
radiu += 10;
//刷新View
postInvalidate();

} else {
radiu = 0;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
}


其中上面的是第28行,有一个initPaint()方法,其在初始化构造的方法里,我们去初始化画笔,尽量不要在onDraw()方法里去初始化画笔,因为我们的图,不只一次会调用到OnDraw()方法,这样就会重复的去初始化对象,耗内存和时间。

onDraw()方法中,只画了一个圆,圆的半径使用的是屏幕的宽度的一半。当然2D画图有丰富的API,可以绘制出更丰富的图,通过这个圆,我们可以类似的做一个下载进度条的功能。

MeasureUtil.java

package com.example.myview;

import android.content.Context;
import android.view.Window;
import android.view.WindowManager;

public class MeasureUtil {
public static int getScreenWidth(Context mContext) {
int width = mContext.getResources().getDisplayMetrics().widthPixels;
return width;
}
public static int getScreenHeight(Context mContext) {
int height = mContext.getResources().getDisplayMetrics().heightPixels;
return height;
}

}
MainActivity.java

public class MainActivity extends Activity implements OnClickListener {
private MyCustomView myCustomView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myCustomView = (MyCustomView) findViewById(R.id.my_view);
new Thread(myCustomView).start();

}
}


后面还有3篇文章,会对3种自定义控件方法进行实践。

对上述文章若有疑问,欢迎留言指出。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: