Android简易实战教程--第二十七话《自定义View入门案例之开关按钮详细分析》
转载此博客请注明出处点击打开链接 http://blog.csdn.net/qq_32059827/article/details/52444145
对于自定义view,可能是一个比较大的瓶颈期。笔者也是如此,就像毛主席说的,抓住主要矛盾,一切都不难。一些大神也声称过自定义view并不难。PS笔者比较实在,是真还没到感觉自定义view不难的水平!
这一篇文章,将对一个案例做详细的代码分析;即使说是入门的案例,但是掌握起来也不是那么容易;笔者尽量把所有代码标注注释帮助理解,同时,我觉得对初学者入门可能还要一段时间。
首先简单介绍view的绘制过程以及一些简单的理论知识:
View的绘制流程(相对性)
1. mearsue: 测量,final,控制控件的大小
2. layout: 布局,用来控制自己的布局位置
3. draw: 绘制,用来控制控件的显示样式
mearsure --> layout ---> draw。这是系统的绘制调用过程。但是对于开发者而言,暴露的方法是下边这三个方法:1. onMearsure: 2. onLayout:3. onDraw:
View的行为:
1. click,longClick,安卓系统没有这些方法,只有ontauch方法,都是封装好的
1. dispatchTouchEvent():touch分发,android希望用来处理是否分发touch事件
2. onInterceptTouchEvent():touch拦截,android希望处理是否拦截touch事件
1. 是否拦截孩子touch
3. onTouchEvent(): touch处理, anroid希望开发人员封装触摸行为给用户提供交换
4. setOnTouchListener():暴露给开发者,实现监听的回调
View的刷新调用顺序: invalidate() ---> draw() ---> onDraw()
好了,废话了一大堆。也该来点代码 ”解解渴“。先看一下自定义View类是咋个回事:
package com.itydl.Switch; import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View; public class SwitchView extends View { private Bitmap mSwitchBackground;
Bitmap mSwitchSlide;
private boolean isOpened = true;// 滑块的状态,默认开启 /** 为行为打标记 **/
private final static int STATE_NONE = 0;// 默认状态值,空状态
private final static int STATE_DOWN = 1;// 按下状态值
private final static int STATE_MOVE = 2;// 移动状态值
private final static int STATE_UP = 3;// 点击松开状态值
private float curentX; private int currentState = STATE_NONE;// 当前状态标记,默认空状态
private OnSwitchClickListener mListener; public SwitchView(Context context) {
this(context, null);
// 构造方法,new的时候调用
} public SwitchView(Context context, AttributeSet attrs) {
super(context, attrs);
//构造方法,写布局的时候调用
} /**
* 设置背景的图片
*
* @param resId
* 设置背景图片的资源id
*/
public void setSwitchBackground(int resId) {
// 背景图片
mSwitchBackground = BitmapFactory.decodeResource(getResources(), resId);
} /**
* 设置滑块的图片
*
* @param resId
* 设置滑块图片的资源id
*/
public void setSwitchSlide(int resId) {
// 滑块
mSwitchSlide = BitmapFactory.decodeResource(getResources(), resId);
} /**
* 当前控件测量自己的宽高时,调用此方法
*/
// 要不要绘制控件的大小?
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 用于设置自己的大小。在onMearsure里面调用setMeasuredDimension(width,height):
// 用来设置(测量)自己的大小
if (mSwitchBackground != null) {
int width = mSwitchBackground.getWidth();// 获取背景图片的大小,宽度
int height = mSwitchBackground.getHeight();// 获取背景图片的大小,高度
setMeasuredDimension(width, height);// 设置背景大小。设置【控件自己的宽高】正好等于背景图片的宽高
} else {
// 系统默认设置的大小(其实是填充父组件大小)
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
} /**
* 当前控件开始绘制时,回调此方法
* 绘制开关的状态
*/
// 要不要设置控件的样子?绘制
@Override
protected void onDraw(Canvas canvas) {
// 父类空实现,注销下边代码
// super.onDraw(canvas);
// 绘制背景的显示
if (mSwitchBackground != null) {
int left = 0;//bitmap相对于控件是贴在一起的,所以值为0
int top = 0;
/**
* bitmap The bitmap to be drawn 要绘制谁,绘制背景图片 表示相对控件左上角坐标位置:
* position of the left side of the bitmap being drawn ,绘制的背景图片,相对于控件的相对宽度
* position of the top side of the bitmap being drawn ,绘制的背景图片,相对于控件的相对高度
* paint used to draw the bitmap (may be null) 画笔为null
*/
//绘制背景的显示,背景图片绘制出来。
canvas.drawBitmap(mSwitchBackground, left, top, null);//在控件上画图片
} if (mSwitchSlide == null) {
return;
} // 获取控件滑块的中点坐标
int slideWidth = mSwitchSlide.getWidth();// 滑块的宽度
// 获取背景的宽度坐标
int switchBackWidth = mSwitchBackground.getWidth();// 背景宽度 switch (currentState) {//onDraw()不知道具体什么状态,因此用使用状态标记
case STATE_DOWN:
case STATE_MOVE://移动状态和按下状态是一样的 // 是否是按下
if (!isOpened) {
// 滑块处于关闭状态
// 判断左右,设置状态
if (curentX < slideWidth / 2f) {//点击左侧
// 点击的滑块左侧,不变化。但是要有一次绘制当前状态位置,绘制在左侧
canvas.drawBitmap(mSwitchSlide, 0, 0, null);
} else {
// 点击滑块的右侧,滑块的中线和按下鼠标的x坐标对齐。计算滑块左侧的坐标值
float left = curentX - slideWidth / 2f;
// 获取最大的left值(滑块往右滑动,有临界值,否则会滑出背景)
float maxLeft = switchBackWidth - slideWidth;
// 判断是否越界
if (left > maxLeft) {//如果越界
left = maxLeft;// 设置滑块左侧坐标为最大位置
}
// 绘制这个位置
canvas.drawBitmap(mSwitchSlide, left, 0, null);
} } else {
// 滑块处于打开状态
// 获取滑块的中线坐标
float middleX = switchBackWidth - slideWidth / 2f;
if (curentX > middleX) {
// 点击的位置在滑块的中线右侧,无反应,设置默认
canvas.drawBitmap(mSwitchSlide, switchBackWidth
- slideWidth, 0, null);
} else {
// 点击的位置在滑块的中线左侧,滑块中线等于当前点击的x值位置
// 当前滑块左边的坐标
float left = curentX - slideWidth / 2f;
if (left < 0) {// 点击左边不要越界
left = 0;
}
// 点击位置没有越界,滑块中线等于当前点击的x值位置
canvas.drawBitmap(mSwitchSlide, left, 0, null); } }
break; case STATE_UP:
if (!isOpened) {
// 关闭状态
canvas.drawBitmap(mSwitchSlide, 0, 0, null); // System.out.println("关闭");
} else { canvas.drawBitmap(mSwitchSlide, switchBackWidth - slideWidth,
0, null); // System.out.println("打开");
} break;
case STATE_NONE:// 设置默认状态
// 绘制滑块,判断状态标记,来设置滑块的默认状态位置
if (!isOpened) {// 关闭
// 先写死为(0,0)
canvas.drawBitmap(mSwitchSlide, 0, 0, null);
} else {
// 打开状态
canvas.drawBitmap(mSwitchSlide, switchBackWidth - slideWidth,
0, null);
} break; default:
break;
} } /**
* 用户触摸调用
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
// 重写onTouchEvent方法就养成写出三大状态的习惯
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 状态改变,行为标记改变
currentState = STATE_DOWN;
// 按下鼠标的相对横坐标:
curentX = event.getX(); //获取到最新坐标,需要绘制一下最新坐标。但是在onTouchEvent方法中是获取不到canvas对象的。因此使用invalidate(); invalidate();// View的刷新调用顺序: invalidate() ---> draw() ---> onDraw() break;
case MotionEvent.ACTION_MOVE:// 绘制过程和点击绘制过程一样,滑块中点跟着当前鼠标x坐标变化
currentState = STATE_MOVE;
curentX = event.getX(); invalidate();// View的刷新调用顺序: invalidate() ---> draw() ---> onDraw().---->主线程调用
//postInvalidate();//触发刷新----->子线程调用的
break;
case MotionEvent.ACTION_UP:
currentState = STATE_UP;
curentX = event.getX();
// 根据滑动松开位置情况,设置跳转到打开还是关闭状态标记
// 获取背景中点
int switchBackWidth = mSwitchBackground.getWidth();
if (curentX < switchBackWidth / 2f && isOpened) {// 节省资源,本来关闭的,不再触发关闭打印。&& isOpened表示是否当前是打开状态。
// 状态值设置为关闭状态
isOpened = false;
if (mListener != null) { //调用接口方法,实际调用实现类方法
mListener.onSwitchChanged(isOpened);
}
} else if (curentX >= switchBackWidth / 2f && !isOpened) {// && !isOpened表示当前是否是关闭状态
isOpened = true;
if (mListener != null) { //调用接口方法,实际调用实现类方法
mListener.onSwitchChanged(isOpened); }
} invalidate();// View的刷新调用顺序: invalidate() ---> draw() ---> onDraw()
break; default:
break;
}
// 消费触摸事件,不使用父类的
return true;
} /**
* 调用此方法,设置开关状态的点击事件
*
* @param listener
*/
public void setOnSwitchClickListener(OnSwitchClickListener listener) {
this.mListener = listener;
} /**
* 开关状态监听
*
* @author lenovo
*
*/
public interface OnSwitchClickListener {
/**
* 接口回调,实现此方法,根据开关打开关闭做相应的事件。参数:打开true;关闭false
*/
void onSwitchChanged(boolean isOpend);
} }
自定义了view,就引入这个view——
<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" > <com.itydl.Switch.SwitchView
android:id="@+id/swit_switchview"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
</com.itydl.Switch.SwitchView> </RelativeLayout>
既然设置了回调,就看看manactivity代码:
package com.itydl.Switch; import android.app.Activity;
import android.os.Bundle;
import android.widget.Toast; import com.itydl.Switch.SwitchView.OnSwitchClickListener; public class MainActivity extends Activity { private SwitchView mSwitchView; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initView();
} private void initView() {
// TODO Auto-generated method stub
setContentView(R.layout.activity_main);
//获取自定义view的实例
mSwitchView = (SwitchView) findViewById(R.id.swit_switchview);
//设置背景图片,传递资源到自定义view
mSwitchView.setSwitchBackground(R.drawable.switch_background);
//设置滑块资源图片,把滑块的资源图片id传递到自定义view
mSwitchView.setSwitchSlide(R.drawable.slide_button_background); //根据开关打开关闭,设置点击事件
mSwitchView.setOnSwitchClickListener(new OnSwitchClickListener() { @Override
public void onSwitchChanged(boolean isOpend) {
// TODO Auto-generated method stub
Toast.makeText(getApplicationContext(), isOpend? "打开":"关闭", 0).show();
}
});
} }
运行结果如下:
欢迎关注本博客点击打开链接 http://blog.csdn.net/qq_32059827,每天花上5分钟,阅读一篇有趣的安卓小文哦
Android简易实战教程--第二十七话《自定义View入门案例之开关按钮详细分析》的更多相关文章
- Android简易实战教程--第十七话《自定义彩色环形进度条》
转载请注明出处:http://blog.csdn.net/qq_32059827/article/details/52203533 点击打开链接 在Android初级教程里面,介绍了shape用法 ...
- Android简易实战教程--第二十三话《绚丽的菜单项》
转载本博客请注明出处:点击打开链接 http://blog.csdn.net/qq_32059827/article/details/52327456 今天这篇稍微增强点代码量,可能要多花上5分钟喽 ...
- Android简易实战教程--第二十话《通过广播接收者,对拨打电话外加ip号》
没睡着觉,起来更篇文章吧哈哈!首先祝贺李宗伟击败我丹,虽然我是支持我丹的,但是他也不容易哈哈,值得尊敬的人!切入正题:这一篇来介绍个自定义广播接收者. 通常我们在外拨电话的时候,一般为使用网络电话.如 ...
- Android简易实战教程--第二十九话《创建图片副本》
承接第二十八话加载大图片,本篇介绍如何创建一个图片的副本. 安卓中加载的原图是无法对其修改的,因为默认权限是只读的.但是通过创建副本,就可以对其做一些修改,绘制等了. 首先创建一个简单的布局.一个放原 ...
- Android简易实战教程--第二十六话《网络图片查看器在本地缓存》
本篇接第二十五话 点击打开链接 http://blog.csdn.net/qq_32059827/article/details/52389856 上一篇已经把王略中的图片获取到了.生活中有这么 ...
- Android简易实战教程--第二十二话《自定义组合控件模拟qq登录下拉框和其中的一些”小技巧”》
转载此文章请注明出处:点击打开链接 http://blog.csdn.net/qq_32059827/article/details/52313516 首先,很荣幸此专栏能被CSDN推荐到主页.荣 ...
- Android简易实战教程--第二话《两种进度条》
点击按钮模拟进度条下载进度,"下载"完成进度条消失. 代码如下: xml: <?xml version="1.0" encoding="utf- ...
- Android简易实战教程--第二十八话《加载大图片》
Android系统以ARGB表示每个像素,所以每个像素占用4个字节,很容易内存溢出.假设手机内存比较小,而要去加载一张像素很高的图片的时候,就会因为内存不足导致崩溃.这种异常是无法捕获的 内存不足并不 ...
- Android简易实战教程--第二十五话《网络图片查看器》
访问网络已经有了很成熟的框架.这一篇只是介绍一下HttpURLConnection的简单用法,以及里面的"注意点".这一篇可以复习或者学习HttpURLConnection.han ...
随机推荐
- .Net Core小技巧 - 使用Swagger上传文件
前言 随着前后端分离开发模式的普及,后端人员更多是编写服务端API接口.调用接口实现文件上传是一个常见的功能,同时也需要一个选择文件上传的界面,可以编写前端界面上传,可以使用Postman.curl来 ...
- [COGS 2258][HZOI 2015]复仇的序幕曲
Description 你还梦不梦痛不痛,回忆这么重你怎么背得动 ----序言 当年的战火硝烟已经渐渐远去,可仇恨却在阿凯蒂王子的心中越来越深 他的叔父三年前谋权篡位,逼宫杀死了他的父王,用铁血手腕平 ...
- [ZJOI 2006]书架
Description 小T有一个很大的书柜.这个书柜的构造有些独特,即书柜里的书是从上至下堆放成一列.她用1到n的正整数给每本书都编了号. 小T在看书的时候,每次取出一本书,看完后放回书柜然后再拿下 ...
- DP测试总结
T1:三取方格数 题目描述 设有N*N的方格图,我们将其中的某些方格填入正整数,而其他的方格中放入0.某人从图得左上角出发,可以向下走,也可以向右走,直到到达右下角.在走过的路上,他取走了方格中的数. ...
- 【LSGDOJ 1850】滑雪课程
题目描述 贝西去科罗拉多州去滑雪,不过还她不太会玩,只是个能力为 1 的渣渣.贝西从 0 时刻进入滑雪场,一到 T 时刻就必须离开.滑雪场里有 N 条斜坡,第 i 条斜坡滑行一次需要 D i 分钟,要 ...
- HDU 4787 GRE Words Revenge
Description Now Coach Pang is preparing for the Graduate Record Examinations as George did in 2011. ...
- [BZOJ]1018 堵塞的交通(SHOI2008)
一道有点神的线段树. Description 有一天,由于某种穿越现象作用,你来到了传说中的小人国.小人国的布局非常奇特,整个国家的交通系统可以被看成是一个2行C列的矩形网格,网格上的每个点代表一个城 ...
- BZOJ4727 [POI2017]Turysta
这题太神了还是去看刺儿神题解吧. http://www.cnblogs.com/neighthorn/p/6538364.html #include <cstdio> #include & ...
- 树莓派超声波测距+蜂鸣器(c语言)
前边我们已经详细的讲解了树莓派控制超声波模块测距(http://www.cnblogs.com/yuemo/p/8888342.html)和超声波控制蜂鸣器模块发声(http://www.cnblog ...
- 记录一次widora sdk编译ipk 实战编译redis
因为业务需求,需要用到redis存储一点简单的数据,因为redis有良好的哈希机制,可以完美实现我的某些需求,但openwrt官方提供memcached的ipk并没有提供redis,没办法,只能自 ...