自定义控件的步骤、用到的主要方法:
1、首先需要定义一个类,继承自View;对于继承View的类,会需要实现至少一个构造方法;实际上这里一共有三个构造方法:
public View (Context context)是在java代码创建视图的时候被调用(使用new的方式),如果是从xml填充的视图,就不会调用这个
public View (Context context, AttributeSet attrs)这个是在xml创建但是没有指定style的时候被调用
public View (Context context, AttributeSet attrs, int defStyle)这个是在第二个基础上添加style的时候被调用的
所以对于这里来说,如果不使用style, 我们重点关注第二个构造方法即可
2、对于任何一个控件来说,它需要显示在我们的界面上,那么肯定需要定义它的大小;
在这里Google提供了一个方法:protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec);我们去看这个方法的内部,实际上是调用了
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight);这个方法,其中第一个参数是view的宽,第二个参数是view的高,这样我们就可以设置view的宽高了,但是要注意,这样设置的单位都是像素
3、对于一个需要显示的控件来说,我们往往还需要确定它的位置:
这就要求重写onLayout方法;但是实际上这个方法在自定义view的时候使用的不多,原因是因为对于位置来说,控件只有建议权而没有决定权,决定权一般在父控件那里。
4、对于一个控件,需要显示,我们当然需要将它绘制出来,这里就需要重写onDraw方法,来将这个控件绘制出来
5、当控件状态改变的时候,我们很可能需要刷新view的显示状态,这时候就需要调用invalidate()方法,这个方法实际上会重新调用onDraw方法来重绘控件
6、在定义控件的过程中,如果需要对view设置点击事件,可以直接使用setOnClickListener方法,而不需要写view.setOnClickListener;
7、在布局文件中将这个自定义控件定义出来,注意名字要使用全类名;而且,由于是继承自view控件,所以在xml文件中如果是view本身的属性都可以直接使用,比如android:layout_width等等
这里比较关键的地方就在于这个onDraw方法,我们一起来看一下:
/** * 画view的方法,绘制当前view的内容 */ @Override protected void onDraw(Canvas canvas) { // super.onDraw(canvas); Paint paint = new Paint(); // 打开抗锯齿 paint.setAntiAlias(true); // 画背景 canvas.drawBitmap(backgroundBitmap, 0, 0, paint); // 画滑块 canvas.drawBitmap(slideButton, slideBtn_left, 0, paint); }
onDraw方法传入的参数是一个Canvas画布对象,这个实际上跟Java中的差不太多,我们要在画布上画画也需要一个画笔,我们这里也将其初始化出来Paint paint = new Paint(),同时设置了一个抗锯齿效果paint.setAntiAlias(true),然后调用drawBitmap的方法,先后绘制了开关的背景和开关的滑块,分别入下图:
这里要注意的一点就是,drawBitmap(Bitmap bitmap, float left, float top, Paint paint)方法中间的两个float类型的参数,分别代表绘制图形的左上角的x和y的坐标(原点设置在左上角),所以这里如果我们个绘制坐标都传入0,0,那么开关会处在一个关的状态,这里,我们对于滑块使用了一个变量slideBtn_left来设置其位置,那么对于关闭状态,slideBtn_left的值就应该为0,对于开启状态,slideBtn_left的值就应该是backgroundBitmap(背景)的宽度减去slideButton(滑块)的宽度;
下面来看具体的代码,注解比较详细:
自定义控件的类DJSwitch.java,继承自View:
package com.example.test.view; import com.example.test.R; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.util.AttributeSet; import android.view.View; public class DJSwitch extends View { /** * 在代码中创建View的时候,调用的是此构造方法 * 类似于iOS中的initWithFrame方法 * @param context */ public DJSwitch(Context context) { super(context); // TODO Auto-generated constructor stub } /** * 在XML创建View的时候,调用的是此构造方法 * 类似于iOS中的initWithCoder及awakeFromNib方法 * @param context * @param attrs */ public DJSwitch(Context context, AttributeSet attrs) { super(context, attrs); initView(); } /** 当前开关状态 */ private boolean currentState = false; /** * 初始化View */ private void initView() { /* * 从资源库中加载一个image对象 * ios UIImage *backgroundImage = [UIImage imageNamed:@"app_switch_bg"]; * 也就是说,android里面的图像实体bitmap,可以当成是ios中的UIImage * 区别在于,ios中UIImage可以通过类方法来获取,android里面则是通过工厂方法来实现 */ switch_bg_img = BitmapFactory.decodeResource(getResources(), R.drawable.app_switch_bg); switch_btn_img = BitmapFactory.decodeResource(getResources(), R.drawable.app_switch_btn); // 添加监听 setOnClickListener(new MyOnSwitchClickListener()); // 可以理解为ios中的addTarget方法,或addGestureRecognizer } private int switchBtnX; private int switchBtnY; private Bitmap switch_bg_img; private Bitmap switch_btn_img; private class MyOnSwitchClickListener implements OnClickListener { @Override public void onClick(View v) { switchBtnClick(); } } /** 当点击时调用此方法 */ private void switchBtnClick() { currentState = !currentState; invalidate(); // 类似于ios中[self setNeedDisplay]方法 } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 相当于设置这个View的CGSize属性 setMeasuredDimension(switch_bg_img.getWidth(), switch_bg_img.getHeight()); } /** * 画View的方法,绘制当前View的内容,类似于ios中的drawRect方法 * ios在绘制时传入的是CGRect这个结构体,android里面传入的是canvas对象 */ @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Paint paint = new Paint(); // 中文意思为油漆,颜料 paint.setAntiAlias(true); // 设置抗锯齿 /** * 画背景 * Quartz2D中也有类似的API * UIImage *image = [UIImage imageNamed:@"hhaa"] * [image drawAtPoint]; * [image drawInRect]; // 将左边的图片放置到右边的矩形框里面,并且拉伸 */ canvas.drawBitmap(switch_bg_img, 0, 0, paint); // 画按钮 if (currentState) { switchBtnX = switch_bg_img.getWidth() - switch_btn_img.getWidth(); } else { switchBtnX = 0; } canvas.drawBitmap(switch_btn_img, switchBtnX, switchBtnY, paint); } }
在布局文件中将其定义出来:
<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" > <!-- 为了清楚的看见该View的大小及位置,给其定义背景 --> <com.example.test.view.DJSwitch android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#ff0000" android:layout_centerInParent="true" /> </RelativeLayout>
这里由于没有写任何点击触发业务的逻辑,只是一个单纯的控件,所以在MainActivity里面没有加入多的代码:
package com.example.test; import android.app.Activity; import android.os.Bundle; import android.view.Menu; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } }
自此,一个自定义按钮就完成了,最终效果如下: