做出一个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都不一样,对比发现只有几个字节不一样( ...
随机推荐
- 笨办法学Python(二十六)
习题 26: 恭喜你,现在可以考试了! 你已经差不多完成这本书的前半部分了,不过后半部分才是更有趣的.你将学到逻辑,并通过条件判断实现有用的功能. 在你继续学习之前,你有一道试题要做.这道试题很难,因 ...
- 怎样下载YouTube 4K视频
随着科技的进步,人们生活水平的提高,视频的清晰度也越来越高,以前那个观看模糊视频的时代已经一去不复返了.从最开始的720P和1080P高清视频,再到2K,进而到如今的4K(即3840×2160)极清视 ...
- python 3+djanjo 2.0.7简单学习(二)--创建数据库和模型
我们紧接上次,这里将建立数据库,创建第一个模型提示:这里我们不需要去一直启动,django会在我们ctrl+s的时候自动刷新并启动服务,很方便吧 1.数据库配置 现在,打开 vote_mysite/ ...
- 2017.10.9 JVM入门学习
1.什么是JVM JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现 ...
- android ndk中使用gprof
1.下载gprof支持库 下载地址: http://code.google.com/p/android-ndk-profiler/ 2.代码上的修改添加 在初始化时: monstartup(" ...
- Vuex基础-Mutation
借助官网的一张图,更改 Vuex 的 store 中的状态的唯一方法是提交 mutation.不可以直接对其进行赋值改变.需要注意的是,mutations只能做一些同步的操作. 代码结构: ...
- Mybatis-generator自动生成
第一步:导入架包 <build> <plugins> <plugin> <groupId>org.mybatis.generator</group ...
- 【Java-Method】读《重构》有感_Java方法到底是传值调用还是传引用调用(传钥匙调用)
今天读<重构>P279, Separate Query from Modifier,将查询函数和修改函数分离. 问题的产生 突然想到 Java 的传对象作为参数的方法到底是 传引用调用,还 ...
- 精心收集的48个JavaScript代码片段,仅需30秒就可理解
源文链接 :https://github.com/Chalarangelo/30-seconds-of-code#anagrams-of-string-with-duplicates 该项目来自于 G ...
- Status bar - iOS之状态栏
(一)设置状态栏显示和隐藏 1.通过 Info.plist 文件增加字段,控制状态栏全局显示和隐藏 在 Info.plist 文件中增加字段 Status bar is initially hidde ...