做出一个SwitchButton的效果,并详细学习一下onDraw(Canvas canvas)方法的使用
代码的灵感和原理主要来自于android自定义开关控件-SlideSwitch http://blog.csdn.net/singwhatiwanna/article/details/9254309这篇文章!
1.效果
iphone上有开关控件,很漂亮,其实android4.0以后也有switch控件,但是只能用在4.0以后的系统中,这就失去了其使用价值,而且我觉得它的界面也不是很好看。最近看到了百度魔拍上面的一个控件,觉得很漂亮啊,然后反编译了下,尽管没有混淆过,但是还是不好读,然后就按照自己的想法写了个,功能和百度魔拍类似。
效果图入下:
2.原理
继承自view类,override其onDraw函数,把两个背景图(一个灰的一个红的)和一个开关图(圆开关)通过canvas画出来;同时override其onTouchEvent函数,实现滑动效果;最后开启一个线程做动画,实现缓慢滑动的效果。
3. 代码
代码SwitchButton如下:
package net.loonggg.switchbutton; import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup.LayoutParams; public class SwitchButton extends View {
public static final int SWITCH_OFF = 0;// 关闭状态
public static final int SWITCH_ON = 1;// 打开状态
public static final int SWITCH_SCROLING = 2;// 滚动状态
// 用于显示的文本,默认为打开和关闭
private String mOnText = "打开";
private String mOffText = "关闭"; /**
* SwitchButton切换开关的状态,默认为关闭状态
*/
private int mSwitchStatus = SWITCH_OFF; private int mBmpWidth = 0;
private int mBmpHeight = 0;
private int mThumbWidth = 0;
private OnSwitchChangedListener listener; private int mSrcX = 0, mDstX = 0; private boolean mHasScrolled = false;// 表示是否发生过滚动 // 开关状态图
Bitmap mSwitch_off, mSwitch_on, mSwitch_thumb; private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); public SwitchButton(Context context) {
this(context, null);
} public SwitchButton(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
} public SwitchButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
} /**
* 初始化图片
*/
private void initView() {
Resources res = getResources();
mSwitch_off = BitmapFactory.decodeResource(res,
R.drawable.switch_off_bg);
mSwitch_on = BitmapFactory.decodeResource(res, R.drawable.switch_on_bg);
mSwitch_thumb = BitmapFactory.decodeResource(res,
R.drawable.switch_thumb);
mBmpWidth = mSwitch_on.getWidth();
mBmpHeight = mSwitch_on.getHeight();
mThumbWidth = mSwitch_thumb.getWidth();
} @Override
public void setLayoutParams(LayoutParams params) {
params.width = mBmpWidth;
params.height = mBmpHeight;
super.setLayoutParams(params);
} /**
* 设置开关上面的文本
*
* @param onText
* 控件打开时要显示的文本
* @param offText
* 控件关闭时要显示的文本
*/
public void setText(String onText, String offText) {
mOnText = onText;
mOffText = offText;
invalidate();// 使整个视图无效,重新绘制,用来刷新View
} /**
* 设置开关的状态
*
* @param flag
* true为开,false为关闭
*/
public void setStatus(boolean flag) {
mSwitchStatus = flag ? SWITCH_ON : SWITCH_OFF;
} @Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
mSrcX = (int) event.getX();
break;
case MotionEvent.ACTION_MOVE:
mDstX = Math.max((int) event.getX(), 10);
mDstX = Math.min(mDstX, 62);
if (mSrcX == mDstX)
return true;
mHasScrolled = true;
TranslateAnimationRunnable move_runnable = new TranslateAnimationRunnable(
mSrcX, mDstX, 0);
new Thread(move_runnable).start();
mSrcX = mDstX;
break;
case MotionEvent.ACTION_UP:
if (mHasScrolled == false)// 如果没有发生过滑动,就意味着这是一次单击过程
{
mSwitchStatus = Math.abs(mSwitchStatus - 1);
int xFrom = 10, xTo = 62;
if (mSwitchStatus == SWITCH_OFF) {
xFrom = 62;
xTo = 10;
}
TranslateAnimationRunnable runnable = new TranslateAnimationRunnable(
xFrom, xTo, 1);
new Thread(runnable).start();
} else {
invalidate();
mHasScrolled = false;
}
// 状态改变的时候 回调事件函数
if (listener != null) {
listener.onSwitchChanged(this, mSwitchStatus);
}
break; default:
break;
}
return true;
} @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘图的时候 内部用到了一些数值的硬编码,其实不太好,
// 主要是考虑到图片的原因,图片周围有透明边界,所以要有一定的偏移
// 硬编码的数值只要看懂了代码,其实可以理解其含义,可以做相应改进。
mPaint.setTextSize(14);// 设置画笔文字大小
mPaint.setTypeface(Typeface.DEFAULT_BOLD);// 设置文字字体
if (mSwitchStatus == SWITCH_OFF) {
drawBitmap(canvas, null, null, mSwitch_off);
drawBitmap(canvas, null, null, mSwitch_thumb);
mPaint.setColor(Color.rgb(105, 105, 105));
canvas.translate(mSwitch_thumb.getWidth(), 0);
canvas.drawText(mOffText, 0, 20, mPaint);
} else if (mSwitchStatus == SWITCH_ON) {
drawBitmap(canvas, null, null, mSwitch_on);
int count = canvas.save();
canvas.translate(mSwitch_on.getWidth() - mSwitch_thumb.getWidth(),
0);
drawBitmap(canvas, null, null, mSwitch_thumb);
mPaint.setColor(Color.WHITE);
canvas.restoreToCount(count);
canvas.drawText(mOnText, 17, 20, mPaint);
} else {
mSwitchStatus = mDstX > 35 ? SWITCH_ON : SWITCH_OFF;
// 首先画出打开按钮样式
drawBitmap(canvas, new Rect(0, 0, mDstX, mBmpHeight), new Rect(0,
0, (int) mDstX, mBmpHeight), mSwitch_on);
mPaint.setColor(Color.WHITE);
canvas.drawText(mOnText, 17, 20, mPaint); // 在画出关闭按钮的样式
int count = canvas.save();
canvas.translate(mDstX, 0);
drawBitmap(canvas, new Rect(mDstX, 0, mBmpWidth, mBmpHeight),
new Rect(0, 0, mBmpWidth - mDstX, mBmpHeight), mSwitch_off);
canvas.restoreToCount(count); // 画出关闭的文字
count = canvas.save();
// clipRect()截取画布中的一个区域,在这个区域的基础上画上关闭的文字
canvas.clipRect(mDstX, 0, mBmpWidth, mBmpHeight);
canvas.translate(mThumbWidth, 0);
mPaint.setColor(Color.rgb(105, 105, 105));
canvas.drawText(mOffText, 0, 20, mPaint);
canvas.restoreToCount(count); // 画出那个圆点按钮
count = canvas.save();
canvas.translate(mDstX - mThumbWidth / 2, 0);
drawBitmap(canvas, null, null, mSwitch_thumb);
canvas.restoreToCount(count);
}
} /**
* 画出Bitmap
*
* @param canvas
* @param src
* @param dst
* @param bitmap
*/
private void drawBitmap(Canvas canvas, Rect src, Rect dst, Bitmap bitmap) {
dst = (dst == null ? new Rect(0, 0, bitmap.getWidth(),
bitmap.getHeight()) : dst);
Paint paint = new Paint();
canvas.drawBitmap(bitmap, src, dst, paint);
} public interface OnSwitchChangedListener {
/**
* 状态改变 回调函数
*
* @param status
* SWITCH_ON表示打开 SWITCH_OFF表示关闭
*/
public abstract void onSwitchChanged(SwitchButton switchButton,
int status);
} /**
* 为开关控件设置状态改变监听函数
*
* @param listener
*/
public void setOnSwitchChangedListener(OnSwitchChangedListener listener) {
if (listener != null) {
this.listener = listener;
}
} /**
* 改变控件状态的动画线程
*
* @author loonggg
*
*/
private class TranslateAnimationRunnable implements Runnable {
private int srcX, dstX;
private int duration; /**
* 滑动动画
*
* @param srcX
* 滑动起始点
* @param dstX
* 滑动终止点
* @param duration
* 是否采用动画,1采用,0不采用
*/
public TranslateAnimationRunnable(float srcX, float dstX, int duration) {
this.srcX = (int) srcX;
this.dstX = (int) dstX;
this.duration = duration;
} @Override
public void run() {
final int patch = (dstX > srcX ? 5 : -5);
if (duration == 0) {
SwitchButton.this.mSwitchStatus = SWITCH_SCROLING;
SwitchButton.this.postInvalidate();
} else {
int x = srcX + patch;
while (Math.abs(x - dstX) > 5) {
mDstX = x;
SwitchButton.this.mSwitchStatus = SWITCH_SCROLING;
SwitchButton.this.postInvalidate();
x += patch;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
mDstX = dstX;
SwitchButton.this.mSwitchStatus = mDstX > 35 ? SWITCH_ON
: SWITCH_OFF;
SwitchButton.this.postInvalidate();
}
} } }
布局文件的代码如下:
<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="80dp"
android:gravity="center_vertical"
android:orientation="horizontal" > <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="是否打开蓝牙" /> <net.loonggg.switchbutton.SwitchButton
android:id="@+id/sb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true" /> </RelativeLayout>
到这里控件的自定义生成就已经完了,当然咱的这篇文章还要学习一下onDraw()方法,对于onDraw()方法的使用,里面的代码的解释如下:
你看明白了吗?其实对于这些东西解释再明白也不如自己亲手写写代码,一步步测试来的更明白!
我感觉还得进一步解释一下int count = canvas.save()和canvas.restoreToCount(count),这两个的作用就是和save和restore方法一样。那么它们到底有什么用呢?请看下面的解释:
在onDraw方法里,我们经常会看到调用save和restore方法,它们到底是干什么用的呢?
save:用来保存Canvas的状态。save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作。
restore:用来恢复Canvas之前保存的状态。防止save后对Canvas执行的操作对后续的绘制有影响。
save和restore要配对使用(restore可以比save少,但不能多),如果restore调用次数比save多,会引发Error。
做出一个SwitchButton的效果,并详细学习一下onDraw(Canvas canvas)方法的使用的更多相关文章
- Flask从入门到做出一个博客的大型教程(一)
本项目全部在虚拟环境中运行,因此请参照前面的文章,链接为https://blog.csdn.net/u014793102/article/details/80302975 建立虚拟环境后,再接着完成本 ...
- CSharpGL(34)以从零编写一个KleinBottle渲染器为例学习如何使用CSharpGL
CSharpGL(34)以从零编写一个KleinBottle渲染器为例学习如何使用CSharpGL +BIT祝威+悄悄在此留下版了个权的信息说: 开始 本文用step by step的方式,讲述如何使 ...
- Hbase技术详细学习笔记
注:转自 Hbase技术详细学习笔记 最近在逐步跟进Hbase的相关工作,由于之前对Hbase并不怎么了解,因此系统地学习了下Hbase,为了加深对Hbase的理解,对相关知识点做了笔记,并在组内进行 ...
- Kelp.Net是一个用c#编写的深度学习库
Kelp.Net是一个用c#编写的深度学习库 基于C#的机器学习--c# .NET中直观的深度学习 在本章中,将会学到: l 如何使用Kelp.Net来执行自己的测试 l 如何编写测试 l ...
- [AI开发]一个例子说明机器学习和深度学习的关系
深度学习现在这么火热,大部分人都会有‘那么它与机器学习有什么关系?’这样的疑问,网上比较它们的文章也比较多,如果有机器学习相关经验,或者做过类似数据分析.挖掘之类的人看完那些文章可能很容易理解,无非就 ...
- 如何做出一个好的c++游戏
目录 一.游戏分类 1.文字型 2.画图型 3.键盘型 二.游戏创意 你的程序可以比较激情.热血 1.打怪,爆装备型 2.答题闯关型 可以添加一些不可思议的物品和玩法 三.学号c++/c的语法,是成功 ...
- 一个很好的MySQL在线学习平台
一个很好的MySQL在线学习平台 https://www.techonthenet.com/sql/
- 收藏一个带动画效果的ScrollViewer以及ScrollBar的模板
这里介绍一个带动画效果的ScrollViewer和ScrollBar,总共分为两个资源字典,直接拿来引用即可: 1 ScrollBarStyle.xaml <ResourceDictionary ...
- 一个DOS攻击木马的详细分析过程
一个DOS攻击木马的详细分析过程 0×01 起因 网路流量里发现了大量的的1.exe的文件,而且一直在持续,第一感觉就像是一个木马程序,而且每个1.exe的MD5都不一样,对比发现只有几个字节不一样( ...
随机推荐
- 【转】run方法与start方法的区别
在java线程中 start与run的不同start与run方法的主要区别在于当程序调用start方法一个新线程将会被创建,并且在run方法中的代码将会在新线程上运行,然而在你直接调用run方法的时候 ...
- CEFSharp在anycpu下的编译
记录一篇博文,将CEFSharp在AnyCpu下使用: 地址:http://pengxiaobo123.blog.163.com/blog/static/20471515420172164593726 ...
- mysql:数据库保存时间的类型——int和datetime的区别
我们都知道,时间保存在数据库中,可以选择使用两种类型,一种是int,一种是datetime 那么,它们两个有什么区别呢?要怎么用呢? 现在和小仓鼠一起来探讨一下 1.int和datetime的使用区别 ...
- Linux Mint,Ubuntu 18 ,Deepin15.7 安装mysql 没有提示输入密码,修改root用户密码过程
刚刚装Deepin15.7 和 MySQL5.7 发现没有提示用户输入密码的过程(近日发现Linux Mint 和 Ubuntu18 也适用) 百度了一大堆如何修改root密码 也没什么卵用,终于这篇 ...
- 二叉搜索树实现MAP
二叉搜索树的基本实现. /* Date: 2014-04-29 purpose: An implementation of MAP using binary search tree. */ #ifnd ...
- python web应用--web框架(三)
了解了WSGI框架,我们发现:其实一个Web App,就是写一个WSGI的处理函数,针对每个HTTP请求进行响应. 但是如何处理HTTP请求不是问题,问题是如何处理100个不同的URL. 每一个URL ...
- list 用法的随手记
在list 用法中.1. add是直接添加 一个变量.不能添加一个 集合元素,比如数组 这种写法是错误的 ,因为不能添加集合 这种写法是对的,因为直接添加元素 2. 但是addrannge 是添加一个 ...
- json 序列化和反序列化的3个方法
https://www.cnblogs.com/caofangsheng/p/5687994.html
- 通过redis实现的一个抢红包流程,仅做模拟【上】
建议结合下一篇一起看 下一篇 数据结构+基础设施 数据结构 这里通过spring-data-jpa+mysql实现DB部分的处理,其中有lombok的参与 @MappedSuperclass @Dat ...
- 【转】基于JavaMail的Java邮件发送
http://blog.csdn.net/xietansheng/article/details/51673073 http://blog.csdn.net/xietansheng/article/d ...