做出一个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都不一样,对比发现只有几个字节不一样( ...
随机推荐
- 使用md5的密码加密,处理用户的密码
需求 1.新增用户保存:使用md5的密码加密,如果用户没有填写密码,设置初始密码“123”: 2.修改用户保存:使用md5的加密加密 *如果修改了密码,需要进行md5的密码加密: *如果没有修改密码, ...
- ajax异步请求的使用情景
先上代码 var sysAllowedExts; $.ajax({ url: url+"/getExtSetting", //请求的url地址 dataType: "js ...
- 开源框架 epics,开源画面编辑软件 edm
epics Experimental Physics and Industrial Control System 一套开源软件框架,实验物理和工业控制系统 http://www.aps.anl.gov ...
- leetcode: 字符串
1. palindrome-partitioning Given a string s, partition s such that every substring of the partition ...
- php通过gbk编码判断 含有连续数字 可用于判断QQ号,手机号等。
有可能有些输入,不希望让用户的评论或者私信中含有类似于QQ号,手机号的文本,比如交友网站.还有些恶意SEO通过,构造恶意检索词,检索词中包含QQ,手机号等,让百度爬取到,增加展现.也需要将这些检索词屏 ...
- ios 创建自己的.a文件
1:首先创建个 静态工程(Cocoa Touch Static Library); 方法名字,一定要暴露在.h文件中, 2:分别在模拟器环境和真机环境下 Analyze (shift+command+ ...
- 使用cmd命令创建maven(web)项目+项目转换成IDEA项目+项目打包+Jetty运行Web项目
3条件:配置好环境 配置环境教程:https://www.cnblogs.com/weibanggang/p/9623705.html 第一步:查看版本信息,在cmd输入mvn –version,如果 ...
- NVIDIA CUDA Library Documentation
http://developer.download.nvidia.com/compute/cuda/4_1/rel/toolkit/docs/online/index.html 英伟达CUDA库说明文 ...
- scp 远程拷贝
scp拷贝时,可以从本地拷贝到远程,也可以远程拷贝到本地.唉,我记得之前是都用过的,但是现在完全忘了,今天同事写出来我才意识到自己之前用过的. 唉,这几年在新单位如果不好好积累一下理论上的东西,真的是 ...
- Mybatis学习的一些细节
一.mybatis 基本配置 最近几天一直在学习mybatis,看了一些源码,本文讲述mybatis的一些基本配置和基本的用法和注意到一些细节.个人时间和精力有限,本文属于流水账类型,不成体系,算是自 ...