自定义点击开关按钮
一、继承已有View实现自定义View
通过对android原生控件的研究,可以发现android中的控件都是继承view类,如textView、ImageView等,通过重写相关的方法来实现新的果,通过这个我们得到两点:
1.我们可以在已有控件的基础上,通过重写相关方法来实现我们的需求。
2.继承view类或viewgroup类,来创建我们所需要的控件。一般来讲,通过继承已有的控件,来自定义控件要简单一点。
/**
* 自定义按钮
* @author afu
*/
public class MyToggleButton extends View {
// 增加一个默认显示样式时候使用
public MyToggleButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
// 在布局文件中声明view的时候,该方法有系统调用
public MyToggleButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
// 在代码中new实例化时调用
public MyToggleButton(Context context) {
super(context);
}
}
布局文件activity_main.xml:
<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" >
<com.xbmu.toggle.MyToggleButton
android:layout_marginTop="50dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true" />
</RelativeLayout>
二、一个View从创建到显示屏幕的步骤
1.执行view构造方法,创建对象
2.测量view大小
onMeasure(int,int);来完成测量动作
3.指定view的位置,子View只有建议权,父View才有决定权;
onLayout(boolean,int,int,int ,int);
这个方法一般用不着,如果自定义ViewGoup才用到
4.绘制view的内容
onDraw(canvas);
三、画个矩形背景和圆形
package com.xbmu.togglebutton;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
/**
* 自定按钮
* @author afu
*/
public class MyToggleButton extends View {
/**
* 一个View从创建到显示屏幕上的主要步骤:
* 1.执行view构造方法,创建对象
* 2.测量view大小
* onMeasure(int,int);来完成测量动作
* 3.指定view的位置,子View只有建议权,父View才有决定权;
* onLayout(boolean,int,int,int ,int);
* 这个方法一般用不着,如果自定义ViewGoup才用到
* 4.绘制view的内容
* onDraw(canvas);
*
*/
private Paint paint;
/**
* 测量
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//设置当前view的测量大小
setMeasuredDimension(100, 100);
}
/**
* 绘制
*/
@Override
protected void onDraw(Canvas canvas) {
// super.onDraw(canvas);
//绘制颜色,可以理解成背景颜色
canvas.drawColor(Color.RED);
//绘制圆形
canvas.drawCircle(50, 50, 20, paint);
}
private void init(Context context) {
paint = new Paint();
paint.setColor(Color.GREEN);
//设置抗锯齿,让边缘圆滑,一般都会设置
paint.setAntiAlias(true);
}
// 增加一个默认显示样式时候使用
public MyToggleButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
// 在布局文件中声明view的时候,该方法有系统调用
public MyToggleButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
// 在代码中new实例化时调用
public MyToggleButton(Context context) {
super(context);
init(context);
}
}
四、画按钮背景
package com.xbmu.togglebutton;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
/**
* 自定按钮
* @author afu
*/
public class MyToggleButton extends View {
/**
* 一个View从创建到显示屏幕上的主要步骤:
* 1.执行view构造方法,创建对象
* 2.测量view大小
* onMeasure(int,int);来完成测量动作
* 3.指定view的位置,子View只有建议权,父View才有决定权;
* onLayout(boolean,int,int,int ,int);
* 这个方法一般用不着,如果自定义ViewGoup才用到
* 4.绘制view的内容
* onDraw(canvas);
*
*/
private Paint paint;
private Bitmap backGroundBitmap;
private Bitmap slideBitmap;
private Context context;
/**
* 测量
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//设置当前view的测量大小
setMeasuredDimension(backGroundBitmap.getWidth(), backGroundBitmap.getHeight());
}
/**
* 绘制
*/
@Override
protected void onDraw(Canvas canvas) {
// super.onDraw(canvas);
//绘制颜色,可以理解成背景颜色
// canvas.drawColor(Color.RED);
//绘制圆形
// canvas.drawCircle(50, 50, 20, paint);
canvas.drawBitmap(backGroundBitmap, 0, 0, paint);
}
private void init(Context context) {
this.context = context;
paint = new Paint();
paint.setColor(Color.GREEN);
//设置抗锯齿,让边缘圆滑,一般都会设置
paint.setAntiAlias(true);
//初始化图片-从资源文件中解析成Bitmap对象
slideBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button);
backGroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background);
}
// 增加一个默认显示样式时候使用
public MyToggleButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
// 在布局文件中声明view的时候,该方法有系统调用
public MyToggleButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
// 在代码中new实例化时调用
public MyToggleButton(Context context) {
super(context);
init(context);
}
}
五、画滑动按钮
canvas.drawBitmap(slideBitmap, 45, 0, paint);
分别设置0和30运行看看效果
六、点击时改变按钮状态
package com.itheima.togglebutton;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
/**
* 自定按钮
* @author afu
*/
public class MyToggleButton extends View implements View.OnClickListener {
/**
* 一个View从创建到显示屏幕上的主要步骤:
* 1.执行view构造方法,创建对象
* 2.测量view大小
* onMeasure(int,int);来完成测量动作
* 3.指定view的位置,子View只有建议权,父View才有决定权;
* onLayout(boolean,int,int,int ,int);
* 这个方法一般用不着,如果自定义ViewGoup才用到
* 4.绘制view的内容
* onDraw(canvas);
*
*/
private Paint paint;
private Bitmap backGroundBitmap;
private Bitmap slideBitmap;
private Context context;
/**
* 距离左边的距离
*/
private float slideLeft;
/**
* 判断当前开关状态
* true为开
* false为关
*/
private boolean curStata = false;
/**
* 测量
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//设置当前view的测量大小
setMeasuredDimension(backGroundBitmap.getWidth(), backGroundBitmap.getHeight());
}
/**
* 绘制
*/
@Override
protected void onDraw(Canvas canvas) {
// super.onDraw(canvas);
//绘制颜色,可以理解成背景颜色
// canvas.drawColor(Color.RED);
//绘制圆形
// canvas.drawCircle(50, 50, 20, paint);
canvas.drawBitmap(backGroundBitmap, 0, 0, paint);
//绘制滑动按钮
canvas.drawBitmap(slideBitmap, slideLeft, 0, paint);
}
private void init(Context context) {
this.context = context;
paint = new Paint();
paint.setColor(Color.GREEN);
//设置抗锯齿,让边缘圆滑,一般都会设置
paint.setAntiAlias(true);
//初始化图片-从资源文件中解析成Bitmap对象
slideBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button);-
backGroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background);
setOnClickListener( MyToggleButton.this);
}
// 增加一个默认显示样式时候使用
public MyToggleButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
// 在布局文件中声明view的时候,该方法有系统调用
public MyToggleButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
// 在代码中new实例化时调用
public MyToggleButton(Context context) {
super(context);
init(context);
}
@Override
public void onClick(View v) {
curStata = !curStata;
flushState();
}
/**
* 刷新状态
*/
private void flushState() {
//设置距离左边的距离
if(curStata){
slideLeft = backGroundBitmap.getWidth()-slideBitmap.getWidth();
}else{
slideLeft = 0;
}
/**
* 刷新View,会导致当前View的onDraw方法执行
*/
invalidate();
}
}
运行效果图:
自定义滑动开关按钮
一、实现滑动效果:
/**
* 第一次按下的x坐标
*/
int startX = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN://按下
//1.记录第一次按下坐标
startX = (int) event.getRawX();
break;
case MotionEvent.ACTION_MOVE://滑动
//2.来到新的坐标
int newX = (int) event.getRawX();
//3.计算偏移量
int dX = newX - startX;
slideLeft += dX;
//4.更新UI-onDraw方法即可--invalidate();
invalidate();
//5.重新记录坐标
startX = (int) event.getRawX();
break;
case MotionEvent.ACTION_UP://离开
break;
}
return true;
}
看运行效果图,但是存在bug:
二、取消点击事件,屏蔽非法滑动:
public class MyToggleButton extends View implements OnClickListener {
private Paint paint;
/**
* 一个View从创建到显示到屏幕过程中的步骤
*1.执行View的构造方法,实例化;通常在构造方法里面加载资源
*2.测量view对象
* onMeasure(int,int)
*3.指定View的位置 - 一般的View用不到,自定义包括其他View进来这样的控才用到
* onLayout(boolean,int,int,int,int)
*4.绘制View对象 onDraw(canvas)
*
*/
private Bitmap backgroundBitmap;
private Bitmap slideBitmap;
/**
* 滑动图片,距离左边的距离
*/
private float slideLeft;
/**
* 按钮的状态 false为关闭 true为开
*/
private boolean curState = false;
// 测量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 设置测量值
setMeasuredDimension(backgroundBitmap.getWidth(),
backgroundBitmap.getHeight());
}
// 绘制
@Override
protected void onDraw(Canvas canvas) {
// super.onDraw(canvas);
// canvas.drawColor(Color.GREEN);
// canvas.drawCircle(50, 50, 20, paint);
canvas.drawBitmap(backgroundBitmap, 0, 0, paint);
canvas.drawBitmap(slideBitmap, slideLeft, 0, paint);
}
/**
* 第一次按下的x坐标
*/
int startX = 0;
int maxLeft;
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN://按下
//1.记录第一次按下坐标
startX = (int) event.getRawX();
break;
case MotionEvent.ACTION_MOVE://滑动
//2.来到新的坐标
int newX = (int) event.getRawX();
//3.计算偏移量
int dX = newX - startX;
slideLeft += dX;
//4.更新UI-onDraw方法即可--invalidate();
flushView();
//5.重新记录坐标
startX = (int) event.getRawX();
break;
case MotionEvent.ACTION_UP://离开
break;
default:
break;
}
return true;
}
// 刷新View的状态,并且纠正非法滑动
private void flushView() {
if(slideLeft < 0){
slideLeft = 0;
}
if(slideLeft > maxLeft){
slideLeft = maxLeft;
}
//屏蔽非法滑动
invalidate();
}
private void init(Context context) {
paint = new Paint();
paint.setColor(Color.RED);
// 设置抗锯齿-使其变得光滑
paint.setAntiAlias(true);
// 加载资源图片
backgroundBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.switch_background);
slideBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.slide_button);
//滑动图片距离左边的距离
maxLeft = backgroundBitmap.getWidth()-slideBitmap.getWidth();
// 设置点击事件
// setOnClickListener(this);
}
// 一般会在代码中实例化
public MyToggleButton(Context context) {
super(context);
init(context);
}
// 带有两个参数的构造方法,在布局文件中使用的时候,就会回调
public MyToggleButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
// 我们需要设置默认的样式风格的时候
public MyToggleButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
@Override
public void onClick(View v) {
curState = !curState;
flushState();
}
// 刷新View的状态
private void flushState() {
if (curState) {
slideLeft = maxLeft;
} else {
slideLeft = 0;
}
flushView();
}
}
三、处理滑动到一小半时时不好看的问题
先画图分析:
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN://按下
//1.记录第一次按下坐标
startX = (int) event.getRawX();
break;
case MotionEvent.ACTION_MOVE://滑动
//2.来到新的坐标
int newX = (int) event.getRawX();
//3.计算偏移量
int dX = newX - startX;
slideLeft += dX;
//4.更新UI-onDraw方法即可--invalidate();
flushView();
//5.重新记录坐标
startX = (int) event.getRawX();
break;
case MotionEvent.ACTION_UP://离开
/**
* 当UP事件发生的时候,由按钮的左边距离(btn_left)确定View的状态;
当btn_left >= maxLeft/2 设置为开状态
当btn_left < maxLeft/2 设置为 关闭状态
*/
if(slideLeft >= maxLeft/2){
curState = true;
}else{
curState = false;
}
flushState();
break;
}
return true;
}
恢复点击事件
演示会有bug
四、解决点击事件和滑动事件导致的bug
public class MyToggleButton extends View implements OnClickListener {
private Paint paint;
/**
* 一个View从创建到显示到屏幕过程中的步骤:
*1.执行View的构造方法,实例化;通常在构造方法里面加载资源
*2.测量view对象
* onMeasure(int,int)
*3.指定View的位置 - 一般的View用不到,自定义包括其他View进来这样的控才用到
* onLayout(boolean,int,int,int,int)
*4.绘制View对象 onDraw(canvas)
*
*/
private Bitmap backgroundBitmap;
private Bitmap slideBitmap;
/**
* 滑动图片,距离左边的距离
*/
private float slideLeft;
/**
* 按钮的状态 false为关闭 true为开
*/
private boolean curState = false;
// 测量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 设置测量值
setMeasuredDimension(backgroundBitmap.getWidth(),
backgroundBitmap.getHeight());
}
// 绘制
@Override
protected void onDraw(Canvas canvas) {
// super.onDraw(canvas);
// canvas.drawColor(Color.GREEN);
// canvas.drawCircle(50, 50, 20, paint);
canvas.drawBitmap(backgroundBitmap, 0, 0, paint);
canvas.drawBitmap(slideBitmap, slideLeft, 0, paint);
}
/**
* 第一次按下的x坐标
*/
int startX = 0;
/**
* 最初的历史位置
*/
int lastX = 0;
/**
* 滑动按钮距离左边的最大距离
*/
int maxLeft;
/**
* 点击事件是否可用
* true 可用
* false 不可用
*/
boolean isClickEnable = true;
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN://按下
//1.记录第一次按下坐标
lastX = startX = (int) event.getRawX();
isClickEnable = true;
break;
case MotionEvent.ACTION_MOVE://滑动
//2.来到新的坐标
int newX = (int) event.getRawX();
//3.计算偏移量
int dX = newX - startX;
slideLeft += dX;
//4.更新UI-onDraw方法即可--invalidate();
if(Math.abs(event.getRawX()-lastX)>5){
isClickEnable = false;
}
flushView();
//5.重新记录坐标
startX = (int) event.getRawX();
break;
case MotionEvent.ACTION_UP://离开
if(!isClickEnable){
/**
* 当UP事件发生的时候,由按钮的左边距离(btn_left)确定View的状态;
当btn_left >= maxLeft/2 设置为开状态
当btn_left < maxLeft/2 设置为 关闭状态
*/
if(slideLeft >= maxLeft/2){
curState = true;
}else{
curState = false;
}
flushState();
}
break;
default:
break;
}
return true;
}
// 刷新View的状态,并且纠正非法滑动
private void flushView() {
if(slideLeft < 0){
slideLeft = 0;
}
if(slideLeft > maxLeft){
slideLeft = maxLeft;
}
//屏蔽非法滑动
invalidate();
}
private void init(Context context) {
paint = new Paint();
paint.setColor(Color.RED);
// 设置抗锯齿-使其变得光滑
paint.setAntiAlias(true);
// 加载资源图片
backgroundBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.switch_background);
slideBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.slide_button);
//滑动图片距离左边的距离
maxLeft = backgroundBitmap.getWidth()-slideBitmap.getWidth();
// 设置点击事件
setOnClickListener(this);
}
// 一般会在代码中实例化
public MyToggleButton(Context context) {
super(context);
init(context);
}
// 带有两个参数的构造方法,在布局文件中使用的时候,就会回调
public MyToggleButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
// 我们需要设置默认的样式风格的时候
public MyToggleButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
@Override
public void onClick(View v) {
if(isClickEnable){
curState = !curState;
flushState();
}
}
// 刷新View的状态
private void flushState() {
if (curState) {
slideLeft = maxLeft;
} else {
slideLeft = 0;
}
flushView();
}
}
全部代码:
package com.xbmu.toggle;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* 1.执行view构造方法,创建对象 2.测量view大小 onMeasure(int,int)来完成测量动作
* 3.指定view的位置,子View只有建议权,父View才有决定权; onLayout(boolean,int,int,int,int);
* 这个方法一般用不着,如果自定义ViewGroup才用到 4.绘制View的内容 onDraw(canvas)
*
*/
public class MyToggleButton extends View implements View.OnClickListener {
// 定义绘制(画笔)对象
private Paint paint;
private Bitmap slideBitmap;
private Bitmap backGroundBitmap;
private Context context;
/**
* 距离左边的距离
*/
private float slideLeft;
/**
* 判断当前开关状态 true为开 false为关
*/
private boolean curState = false;
/**
* 第一次按下的x坐标
*/
int startX = 0;
int maxLeft;
/**
* 最初的历史位置
*/
int lastX = 0;
/**
* 点击事件是否可用
* true可用
* false不可用
*/
boolean isClickEnable = true;
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN://按下
//1.记录第一次按下坐标
startX = (int) event.getRawX();
isClickEnable = true;
break;
case MotionEvent.ACTION_MOVE://滑动
//2.来到新的坐标
int newX = (int) event.getRawX();
//3.计算偏移量
int dx = newX - startX;
slideLeft += dx;
//4.更新UI-onDraw方法即可--invalidate();
if(Math.abs(event.getRawX()-lastX) > 5){
isClickEnable = false;
}
flushView();
//5.重新记录坐标
startX = (int) event.getRawX();
break;
case MotionEvent.ACTION_UP://离开
if(!isClickEnable){
/**
* 当UP事件发生的时候,由按钮的左边距离(btn_left)确定View的状态;
* 当btn_left >= maxLeft/2设置为开状态
* 当btn_left < maxLeft/2设置为关状态
*/
if(slideLeft >= maxLeft/2){
curState = true;
}else{
curState = false;
}
flushState();
}
break;
}
return true;
}
//刷新View的状态,并且纠正非法滑动
private void flushView() {
if(slideLeft < 0){
slideLeft = 0;
}
if(slideLeft > maxLeft){
slideLeft = maxLeft;
}
//屏蔽非法滑动
invalidate();
}
/**
* 测量
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 设置当前view的测量带大小
setMeasuredDimension(backGroundBitmap.getWidth(),
backGroundBitmap.getHeight());
}
/**
* 绘制
*/
@Override
protected void onDraw(Canvas canvas) {
// super.onDraw(canvas);
// 绘制颜色,可以理解成背景颜色
// canvas.drawColor(Color.RED);
// 绘制圆形
// canvas.drawCircle(50, 50, 20, paint);
canvas.drawBitmap(backGroundBitmap, 0, 0, paint);
// 绘制滑动按钮
canvas.drawBitmap(slideBitmap, slideLeft, 0, paint);
}
private void initView(Context context2) {
paint = new Paint();
paint.setColor(Color.GREEN);
// 设置抗锯齿,让边缘圆滑,一般都会设置
paint.setAntiAlias(true);
slideBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.slide_button);
backGroundBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.switch_background);
//滑动图片距离左边的距离
maxLeft = backGroundBitmap.getWidth()-slideBitmap.getWidth();
//设置点击事件
setOnClickListener(MyToggleButton.this);
}
@Override
public void onClick(View v) {
if(isClickEnable){
curState = !curState;
flushState();
}
}
/**
* 刷新状态
*/
private void flushState() {
// 设置距离左边的距离
if (curState) {
slideLeft = backGroundBitmap.getWidth() - slideBitmap.getWidth();
} else {
slideLeft = 0;
}
/**
* 刷新View,会导致当前View的onDraw方法执行
*/
flushView();
}
public MyToggleButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView(context);
}
//带有两个参数的构造方法,在布局文件中使用的时候,就会回调
public MyToggleButton(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
//一般会在代码中实例化
public MyToggleButton(Context context) {
super(context);
initView(context);
}
}