做出一个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都不一样,对比发现只有几个字节不一样( ...
随机推荐
- SQL-常用数据类型
整数除了 INT 外,还有 TINYINT.SMALLINT.MEDIUMINT.BIGINT. CHAR 和 VARCHAR 的区别: CHAR 的长度是固定的,而 VARCHAR 的长度是可以变化 ...
- C4C Product Price List的模型中和有效期相关的两个字段
SAP C4C的price list实例可以在工作中心Products,视图Price Lists里看到. 我们点开第二个名为TEST的实例: 我写这篇文章的日期是2018年10月27日, 我现在把这 ...
- Selenium入门13 cookie的增删改查
cookie的增删改查: 查询:get_cookies()查询所有cookie,get_cookie(cookie的name)获取单个cookie 删除:delete_cookie(cookie的na ...
- IOS xcode 离线帮助文档安装和安装路径
将想要安装的xcode 帮助文档 版本,将 ‘帮助文档包’ 放入‘xcode 帮助文档安装路径’.再将Xcode软件重启. xcode 帮助文档安装路径: 在‘应用程序’->Xcode软件 右击 ...
- POJ-1159 Palindrome---变成回文串的最小代价
题目链接: https://cn.vjudge.net/problem/POJ-1159 题目大意: 题意很明确,给你一个字符串,可在任意位置添加字符,最少再添加几个字符,可以使这个字符串成为回文字符 ...
- next_permutation暴力搜索,POJ(3187)
题目链接:http://poj.org/problem?id=3187 解题报告: #include <stdio.h> #include <iostream> #includ ...
- Python类型转换+序列操作+基本概念辨析速查手册
第一部分是Python语言中基础中的基础,根据网上资料,合并如下: 1.类型转换 int(x [,base]) 将x转换为一个整数 long(x [,base]) 将x ...
- 【洛谷P1063】NOIP2006能量项链
能量项链 https://www.luogu.org/problemnew/show/P1063 好像比合并石子更水.. 区间动规,f[l][r]表示合并区间l~r的最大能量 按区间长度dp 枚举中间 ...
- 最大公约数(gcd模板)
int gcd(int a,int b) { ) { int t=a%b; a=b; b=t; } return a; }
- 【软件笔记】 ◆笔记·I◆ 各类冷门函数细解
[软件笔记·I] 各类冷门函数细解 ■题外话■ 总觉得作为一个志向远大的 coder (٩(◕‿◕。)۶),我觉得单单只会做题是不够的所以我开始尝试自己编写软件!初入道的我并不知道C++其实并不太适合 ...