先看效果图咯~
我们可以看到一个很常见的开关按钮,那就来分析一下。
这是由两张图片构成的:
①一张为有开和关的背景图片
②一张为控制开和关的滑动按钮图片
代码编写的步骤:1.写个类继承View,并重写几个方法:
第一个为构造函数,重写一个参数的函数和两个参数的函数就够了,因为两个参数的函数能够使用自定义属性
第二个为控制控件的大小–>protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {}
第三个为绘制控件的方法–>protected void onDraw(Canvas canvas) {}
2.将用户指定的两张图片加载进来,写俩个方法--setToggleBackground和setToggleSolid方法:
//设置背景图片 public void setToggleBackground(int resId){ toggleBackground=BitmapFactory.decodeResource(getResources(), resId); } //设置滑块图片 public void setToggleSolid(int resId){ toggleSolid=BitmapFactory.decodeResource(getResources(), resId); }3. onMeasure方法来控制控件的大小,我们可以看到这个开关按钮的大小就跟背景的大小一样大,只需要设置为背景的大小:
//控制控件的大小 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // TODO Auto-generated method stub if(toggleBackground!=null){ //指定控件宽高 setMeasuredDimension(toggleBackground.getWidth(), toggleBackground.getHeight()); }else{ super.onMeasure(widthMeasureSpec, heightMeasureSpec); } }4.既然这个开关按钮需要通过点击或移动来控制控件的开和关,所以就需要实现 onTouchEvent方法,当然应该有三个事件会触发:按下、移动和抬起的时候,每次点击、移动或抬起都需要重绘,我们先来分析下滑块的状态有哪些(应该有四种状态)一开始默认状态为空:
1.点击的时候
2.移动的时候
3.抬起的时候
4.空的时候(即什么都没干的时候)
先分析下点击的时候的情况:
①当按下或移动的坐标大于滑块宽度一半时将滑块右移
②当按下或移动的坐标小于滑块宽度一半时滑块不动
注:防止滑块移至背景外面,最大是滑块右边和背景右边对齐(即最大剩余宽度等于滑块离左边-为背景宽度-滑块宽度)再来分析移动的时候的情况,可以发现应该是和点击的时候是一样的。
再来分析抬起的时候的情况:
①如果开关状态是打开的就将滑块移动至右边
②如果开关状态是关闭的就将滑块移动至左边
那怎么判断什么时候是打开状态和关闭状态呢??
①抬起的坐标大于背景宽度一半的时候设为打开状态
②抬起的坐标小于背景宽度坐标一 半的时候设为关闭状态
再来分析下空的时候,可以发现它和抬起的时候的情况是一样的。5.在onDraw方法中将背景和滑块绘制出来。刚才分析了onTouchEvent方法,这次是一样的,滑块的四个状态分别处理,前面onTouchEvent方法中滑块的状态改变,然后通过invalidate()方法来通知系统重绘。
//控制控件的样式 @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub if(toggleBackground!=null){ canvas.drawBitmap(toggleBackground, 0, 0, paint); } switch (state) { case DOWN: case MOVE: if(currentX>toggleSolid.getWidth()/2f){ //让滑块向右移动(重新绘制滑块的位置) leftX=currentX-toggleSolid.getWidth()/2f; maxLeft=toggleBackground.getWidth()-toggleSolid.getWidth(); if(leftX>maxLeft){ leftX=maxLeft; } canvas.drawBitmap(toggleSolid, leftX, 0, paint); }else{ //滑块不动 canvas.drawBitmap(toggleSolid, 0, 0, paint); } break; case NONE: case UP: if(isOpen){ canvas.drawBitmap(toggleSolid, maxLeft, 0, paint); }else { canvas.drawBitmap(toggleSolid, 0, 0, paint); } break; } }
6.我们做这个自定义控件是为了让用户使用的,现在这个是没有什么用的,用户用不了的,所以可以通过设置监听器来对外提供接口。
onToggleListener listener; public void setOnToggleListener(onToggleListener listener){ this.listener=listener; } //接口 public interface onToggleListener{ void onToggleChanged(boolean isOpen); }这个监听器中的boolean值需要赋值,那在什么时候赋值呢,应该是在抬起或空的状态的时候给它赋值,因为那个时候才真正确定开关按钮是打开的还是关闭的。
7.在布局文件中把自定义的这个控件定义出来~
<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"> <com.example.zidingyi.ToggleVie android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/toggle"> </com.example.zidingyi.ToggleVie> </LinearLayout>8.自定义的View-ToggleVie:
public class ToggleVie extends View{ public ToggleVie(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub } public static final int DOWN=1; public static final int MOVE=2; public static final int UP=3; public static final int NONE=0; //标记状态 private int state=NONE; boolean isOpen; private Bitmap toggleBackground,toggleSolid; float currentX,leftX,maxLeft; private Paint paint=new Paint(); //控制控件的大小 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // TODO Auto-generated method stub if(toggleBackground!=null){ //指定控件宽高 setMeasuredDimension(toggleBackground.getWidth(), toggleBackground.getHeight()); }else{ super.onMeasure(widthMeasureSpec, heightMeasureSpec); } } //控制控件的样式 @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub if(toggleBackground!=null){ canvas.drawBitmap(toggleBackground, 0, 0, paint); } switch (state) { case DOWN: case MOVE: if(currentX>toggleSolid.getWidth()/2f){ //让滑块向右移动(重新绘制滑块的位置) leftX=currentX-toggleSolid.getWidth()/2f; maxLeft=toggleBackground.getWidth()-toggleSolid.getWidth(); if(leftX>maxLeft){ leftX=maxLeft; } canvas.drawBitmap(toggleSolid, leftX, 0, paint); }else{ //滑块不动 canvas.drawBitmap(toggleSolid, 0, 0, paint); } break; case NONE: case UP: if(isOpen){ canvas.drawBitmap(toggleSolid, maxLeft, 0, paint); }else { canvas.drawBitmap(toggleSolid, 0, 0, paint); } break; } } public ToggleVie(Context context) { super(context); // TODO Auto-generated constructor stub } //设置背景图片 public void setToggleBackground(int resId){ toggleBackground=BitmapFactory.decodeResource(getResources(), resId); } //设置滑块图片 public void setToggleSolid(int resId){ toggleSolid=BitmapFactory.decodeResource(getResources(), resId); } @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub switch (event.getAction()) { case MotionEvent.ACTION_DOWN: state=DOWN;//修改标记位 //通知界面重新绘制 invalidate(); // postInvalidate(); //在子线程的 break; case MotionEvent.ACTION_UP: currentX=event.getX(); state=UP; if(currentX>toggleBackground.getWidth()/2f){ //滑块在右边 isOpen=true; if(listener!=null){ listener.onToggleChanged(true); } }else{ //滑块在左边 isOpen=false; if(listener!=null){ listener.onToggleChanged(false); } } invalidate(); break; case MotionEvent.ACTION_MOVE: currentX=event.getX(); state=MOVE; invalidate(); break; default: break; } return true; } onToggleListener listener; public void setOnToggleListener(onToggleListener listener){ this.listener=listener; } //接口 public interface onToggleListener{ void onToggleChanged(boolean isOpen); } }9.MainActivity:
public class MainActivity extends Activity { private ToggleVie toggle; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); toggle=(ToggleVie) findViewById(R.id.toggle); toggle.setToggleBackground(R.drawable.switch_background); toggle.setToggleSolid(R.drawable.slide_button_background); toggle.setOnToggleListener(new onToggleListener() { @Override public void onToggleChanged(boolean isOpen) { // TODO Auto-generated method stub if(isOpen){ Toast.makeText(MainActivity.this, "开启", 0).show(); }else { Toast.makeText(MainActivity.this, "关闭", 0).show(); } } }); } }