1.  开关按钮点击效果,如下:

2. 继承已有View实现自定义View

3. 下面通过一个案例实现滑动开关的案例:

(1)新建一个新的Android工程,命名为" 开关按钮",接下来我们按照上面的步骤来:自定义类MyToggleButton继承自View。

(2)编写设计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"
tools:context="com.himi.togglebtn.MainActivity" > <com.himi.togglebtn.MyToggleButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/my_toggle_btn"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"/> </RelativeLayout>

注意这里使用的自定的MyToggleButton(View),要使用全路径。

(3)编写自定义的MyToggleButton。

• 重写onMeasure()方法,指定控件大小;

• 重写onDraw()方法,绘制控件内容。

 package com.himi.togglebtn;

 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;
import android.view.View.OnClickListener; public class MyToggleButton extends View implements OnClickListener { //作为背景的图片
private Bitmap backgroundBitmap;
//滑动的开关图片
private Bitmap slidebtn;
private Paint paint; //滑动按钮的左边界
private float slidebtn_left; /**
* 当前开关的状态
* true :为开
* false:为关
*/
private boolean currState = false; /**
* 我们在代码里面创建对象的时候,使用此构造方法
* @param context
*/
public MyToggleButton(Context context) {
super(context);
// TODO 自动生成的构造函数存根
} /**
* 在布局文件xml中声明的View,创建时候由系统自动调用此构造方法。
* 倘若我们(使用全路径)在xml布局文件中,声明使用这个自定义的View,但是我们没有这个构造方法,就会报错(系统不能找到这个构造)
* @param context
* @param attrs
*/
public MyToggleButton(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
} /**
* 这个构造方法比上面的构造方法都了一个参数 defStyle,这个参数View默认的样式,这里可以重新这个构造,设置defStyle
* 改变生成自定义的View的样式style
* @param context
* @param attrs
* @param defStyle
*/
public MyToggleButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO 自动生成的构造函数存根
} //初始化
private void initView() {
//初始化图片
backgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background);
slidebtn = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button);
//初始化画笔
paint = new Paint();
paint.setAntiAlias(true);//打开抗锯齿
//添加Onclick事件监听
setOnClickListener(this);
} /*
* View对象显示在屏幕上,有几个重要步骤:
* 1. 构造方法 创建 对象.
* 2. 测量View的大小. onMeasure(int, int):系统调用的方法,获知View的大小
* 3. 确定View的位置,View自身有一些建议权,View位置决定权在父View手中. onLayout(): ViewGroup调用
* 4. 绘制View的内容 onDraw(canvas)
*
*/ /**
*
* 测量尺寸时候的回调方法
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
/**
* 设置当前View的大小
* width :当前View的宽度
* height:当前view的高度(单位:像素)
*/
setMeasuredDimension(backgroundBitmap.getWidth(), backgroundBitmap.getHeight());
} /**
* 自定义的View,作用不大
* 确定位置的时候,系统调用的方法(我们不用关心),这里我们就不改写这个方法
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
// TODO 自动生成的方法存根
super.onLayout(changed, left, top, right, bottom);
} /**
* 绘制当前View的内容
*/
@Override
protected void onDraw(Canvas canvas) {
// TODO 自动生成的方法存根
//super.onDraw(canvas);
//绘制背景图
/*
* backgroundBitmap:要绘制的图片
* left 图片的左边界
* top 图片的上边界
* paint 绘制图片要使用的画笔
*/
canvas.drawBitmap(backgroundBitmap, 0, 0, paint);
//绘制可滑动的按钮
canvas.drawBitmap(slidebtn, slidebtn_left, 0, paint);
} public void onClick(View v) {
currState = ! currState;
flushState();//刷新当前开关状态 } /**
* 刷新当前开关视图
*/
private void flushState() {
if(currState) {
slidebtn_left = backgroundBitmap.getWidth()-slidebtn.getWidth();
}else {
slidebtn_left =0;
} /*
* invalidate():刷新当前View,会导致onDraw方法的执行
* 上面只是设置参数设置,下面必须将上面的参数传递到onDraw方法中,利用onDraw方法重新绘制,才能实现刷新的效果
*
*/
invalidate(); } }

代码逻辑如下:

备注:

  初始状态slideBtn 左边为0

          

  开的时候slideBtn left值为background.width-slidebtn.width

          

与此同时,MainActivity.java,如下:

 package com.himi.togglebtn;

 import android.app.Activity;
import android.os.Bundle; public class MainActivity extends Activity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
} }

(4)布署程序到模拟器上如下:

(5)实现开关的弹性滑动,上面的只能让开关左右切换,不能手机拖动滑动开关,用户体验不好,我们要优化。

MainActivity.java,修改为:

 package com.himi.togglebtn;

 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.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener; public class MyToggleButton extends View implements OnClickListener { //作为背景的图片
private Bitmap backgroundBitmap;
//滑动的开关图片
private Bitmap slidebtn;
private Paint paint; //滑动按钮的左边界
private float slidebtn_left; /**
* 当前开关的状态
* true :为开
* false:为关
*/
private boolean currState = false; /**
* 我们在代码里面创建对象的时候,使用此构造方法
* @param context
*/
public MyToggleButton(Context context) {
super(context);
// TODO 自动生成的构造函数存根
} /**
* 在布局文件xml中声明的View,创建时候由系统自动调用此构造方法。
* 倘若我们(使用全路径)在xml布局文件中,声明使用这个自定义的View,但是我们没有这个构造方法,就会报错(系统不能找到这个构造)
* @param context
* @param attrs
*/
public MyToggleButton(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
} /**
* 这个构造方法比上面的构造方法都了一个参数 defStyle,这个参数View默认的样式,这里可以重新这个构造,设置defStyle
* 改变生成自定义的View的样式style
* @param context
* @param attrs
* @param defStyle
*/
public MyToggleButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO 自动生成的构造函数存根
} //初始化
private void initView() {
//初始化图片
backgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background);
slidebtn = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button);
//初始化画笔
paint = new Paint();
paint.setAntiAlias(true);//打开抗锯齿
//添加Onclick事件监听
setOnClickListener(this);
} /*
* View对象显示在屏幕上,有几个重要步骤:
* 1. 构造方法 创建 对象.
* 2. 测量View的大小. onMeasure(int, int):系统调用的方法,获知View的大小
* 3. 确定View的位置,View自身有一些建议权,View位置决定权在父View手中. onLayout(): ViewGroup调用
* 4. 绘制View的内容 onDraw(canvas)
*
*/ /**
*
* 测量尺寸时候的回调方法
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
/**
* 设置当前View的大小
* width :当前View的宽度
* height:当前view的高度(单位:像素)
*/
setMeasuredDimension(backgroundBitmap.getWidth(), backgroundBitmap.getHeight());
} /**
* 自定义的View,作用不大
* 确定位置的时候,系统调用的方法(我们不用关心),这里我们就不改写这个方法
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
// TODO 自动生成的方法存根
super.onLayout(changed, left, top, right, bottom);
} /**
* 绘制当前View的内容
*/
@Override
protected void onDraw(Canvas canvas) {
// TODO 自动生成的方法存根
//super.onDraw(canvas);
//绘制背景图
/*
* backgroundBitmap:要绘制的图片
* left 图片的左边界
* top 图片的上边界
* paint 绘制图片要使用的画笔
*/
canvas.drawBitmap(backgroundBitmap, 0, 0, paint);
//绘制可滑动的按钮
canvas.drawBitmap(slidebtn, slidebtn_left, 0, paint);
} /**
* 判断是否发生拖到
* 如果拖动了,就不再响应Onclick事件
* true:发生拖动
* false:没有发生拖动
*/
private boolean isDrag = false; /**
* onClick事件在view.onTouchEvent中被解析
* 系统对Onclick事件的解析,过于简陋,只要有down事件和up事件,系统即认为发生了click事件
*/
public void onClick(View v) {
/*
* 如果没有拖动,才执行改变状态的动作
*/
if(!isDrag) {
currState = ! currState;
flushState();//刷新当前开关状态
}
} /**
* 刷新当前开关视图
*/
private void flushState() {
if(currState) {
slidebtn_left = backgroundBitmap.getWidth()-slidebtn.getWidth();
}else {
slidebtn_left =0;
} flushView();
} public void flushView() {
/**
* 对slidebtn_left的值进行判断
* 0 <= slidebtn_left <= backgroundwidth-slidebtnwidth(这样才能保证滑动的开关不会滑动越界)
*
*/
int maxLeft = backgroundBitmap.getWidth()-slidebtn.getWidth();//slidebtn左边界最大值
//确保slidebtn_left >= 0
slidebtn_left =(slidebtn_left>0)?slidebtn_left:0;
//确保slidebtn_left <=maxLeft
slidebtn_left = (slidebtn_left<maxLeft)?slidebtn_left:maxLeft; //告诉系统我需要刷新当前视图,只要当前视图可见状态,就会调用onDraw方法重新绘制,达到刷新视图的效果
invalidate();
} /**
* down事件时的x值
*/
private int firstX;
/**
* touch事件的上一个x值
*/
private int lastX; @Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
firstX = lastX = (int) event.getX();
isDrag = false;
break;
case MotionEvent.ACTION_MOVE:
//判断是否发生拖动
if(Math.abs(event.getX()-lastX)>5) {
isDrag = true;
} //计算手指在屏幕上移动的距离
int dis = (int) (event.getX()-lastX);
//将本次的位置设置给lastX
lastX = (int) event.getX();
//根据手指移动的距离,改变slidebtn_left的值
slidebtn_left = slidebtn_left+dis;
break;
case MotionEvent.ACTION_UP: //在发生拖动的情况下,根据最后的位置,判断当前的开关的状态
if(isDrag){
int maxLeft = backgroundBitmap.getWidth()-slidebtn.getWidth();//slidebtn左边界最大值
/**
* 根据slidebtn_left判断,当前应该是什么状态
*
*/
if(slidebtn_left>maxLeft/2) {//应为打开状态
currState = true;
}else {
currState = false;
}
flushState();
} break; }
flushView();
return true;
} }

代码逻辑如下:

布署程序到模拟器上,如下:

自定义控件(视图)2期笔记05:自定义控件之继承自View(滑动开关)的更多相关文章

  1. 自定义控件(视图)2期笔记01:自定义控件之自定义View的步骤

    1. 根据Android Developers官网的介绍,自定义控件你需要以下的步骤: (1)创建View (2)处理View的布局 (3)绘制View (4)与用户进行交互 (5)优化已定义的Vie ...

  2. Android笔记——Android自定义控件

    目录: 1.自定义控件概述 01_什么是自定义控件 Android系统中,继承Android系统自带的View或者ViewGroup控件或者系统自带的控件,并在这基础上增加或者重新组合成我们想要的效果 ...

  3. 《uml大战需求分析》阅读笔记05

    <uml大战需求分析>阅读笔记05 这次我主要阅读了这本书的第九十章,通过看这章的知识了解了不少的知识开发某系统的重要前提是:这个系统有谁在用?这些人通过这个系统能做什么事? 一般搞清楚这 ...

  4. MYSQL视图的学习笔记

    MYSQL视图的学习笔记,学至Tarena金牌讲师,金色晨曦科技公司技术总监沙利穆 课程笔记的综合. 视图及图形化工具   1.       视图的定义 视图就是从一个或多个表中,导出来的表,是一个虚 ...

  5. 强化学习读书笔记 - 05 - 蒙特卡洛方法(Monte Carlo Methods)

    强化学习读书笔记 - 05 - 蒙特卡洛方法(Monte Carlo Methods) 学习笔记: Reinforcement Learning: An Introduction, Richard S ...

  6. JS自学笔记05

    JS自学笔记05 1.例题 产生随机的16进制颜色 function getColor(){ var str="#"; var arr=["0","1 ...

  7. JAVA自学笔记05

    JAVA自学笔记05 1.方法 1)方法就是完成特定功能的代码块,类似C语言中的函数. 2)格式: 修饰符 返回值类型 方法名(参数类型 参数名1,参数类型 参数名2,-){ 函数体; return ...

  8. 机器学习实战(Machine Learning in Action)学习笔记————05.Logistic回归

    机器学习实战(Machine Learning in Action)学习笔记————05.Logistic回归 关键字:Logistic回归.python.源码解析.测试作者:米仓山下时间:2018- ...

  9. CS229 笔记05

    CS229 笔记05 生成学习方法 判别学习方法的主要思想是假设属于不同target的样本,服从不同的分布. 例如 \(P(x|y=0) \sim {\scr N}(\mu_1,\sigma_1^2) ...

随机推荐

  1. addEventListener之handleEvent

    addEventListener() 方法是将指定的事件监听器注册到目标对象上,当该对象触发指定的事件时,指定的回调函数就会被执行.语法: element.addEventListener(type, ...

  2. 测试stopwatch频率

    测试stopwatch频率 using UnityEngine; using System.Collections; using System.Diagnostics; public class te ...

  3. python challenge 16

    前情回顾:上一篇 第16关地址 打开16关,又是一张奇奇怪怪很多点点的图片,应该又是与PIL库有关的. 页面的标题是:let me get this straight.这是英语中的一句俚语,意思是让我 ...

  4. 4位bcd数转换为2进制数

    DATA   SEGMENTBUF    DW  1234HBUF1   DW  ?    ;2进制数放到buf1内存单元DATA   ENDSCODE   SEGMENTASSUME CS:CODE ...

  5. Java获取昨天的时间

    Calendar   cal   =   Calendar.getInstance();  cal.add(Calendar.DATE,   -1);  String yesterday = new ...

  6. 第三方分页控件aspnetPager出现问题解决方法

    问题描述: 今天在打开以前的项目使用vs2013打开后并且生成解决方案的时候发现报错了.经过检查发现是由于第三方分页控件aspnetPager在页面上不能引用到了. 解决方法: 1. 首先将AspNe ...

  7. struts1、ajax、jquery、json简单实例

    1.页面ajax代码,使用$.ajax,获得json对象后each $.ajax({ type:"GET", url:ctx + "/uploadImg.do" ...

  8. DeDe调用body文章内容

    {dede:sql sql='select * from dede_addonarticle where aid=3'} <div class="pageArea hide" ...

  9. centos 安装 mongo3.0

    官方网站传贴,每次去翻doc,麻烦 vi  /etc/yum.repos.d/mongodb-org-3.0.repo [mongodb-org-3.0]name=MongoDB Repository ...

  10. Codeforces 138D World of Darkraft

    有一个n*m 的棋盘,每个点上标记了L,R,X 中的一个每次能选择一个没有被攻击过的点(i,j),从这个点开始发射线,射线形状为:1. 若字符是 L,向左下角和右上角发,遇到被攻击过的点就停下来2. ...