android 自定义view之选座功能
效果图:
界面比较粗糙,主要看原理。
这个界面主要包括以下几部分
1、座位
2、左边的排数
3、左上方的缩略图
4、缩略图中的红色区域
5、手指移动时跟随移动
6、两个手指缩放时跟随缩放
主要技术点
1、矩阵Matrix
2、GestureDetector与ScaleGestureDetector
3、Bitmap的一下基本用法
4、这里只需要重写view的onDraw就可实现全部功能
可以发现这个其实没什么难度,主要就是一些位置的计算。
为了能便于理解首先把要用到的知识点进行一下梳理
1、矩阵Matrix
Matrix由3*3矩阵中9个值来决定,我们对Matrix的所有设置, 就是对这9个值的操作。
{MSCALE_X,MSKEW_X,MTRANS_X,
MSKEW_Y,MSCALE_Y,MTRANS_Y,
MPERSP_0,MPERSP_1,MPERSP_2}
这是矩阵的9个值,看名字也知道他们是什么意思了。
这里主要用到缩放和平移,下面以缩放为例来了解一下缩放的控制
通过android提供的api我们可以调用setScale、preScale、postScale来改变MSCALE_X和MSCALE_Y的值达到缩放的效果
所以只要理解setScale、preScale、postScale这三个方法的区别我们就可以简单的进行缩放控制了
1、setScale(sx,sy),首先会将该Matrix设置为对角矩阵,即相当于调用reset()方法,然后在设置该Matrix的MSCALE_X和MSCALE_Y直接设置为sx,sy的值
2、preScale(sx,sy),不会重置Matrix,而是直接与Matrix之前的MSCALE_X和MSCALE_Y值结合起来(相乘),M’ = M * S(sx, sy)。
3、postScale(sx,sy),不会重置Matrix,而是直接与Matrix之前的MSCALE_X和MSCALE_Y值结合起来(相乘),M’ = S(sx, sy) * M。
这么说其实也有些不好理解,举个栗子一看就明白
1、pre….的执行顺序
Matrix matrix=new Matrix();
float[] points=new float[]{10.0f,10.0f};
matrix.preScale(2.0f, 3.0f);
matrix.preTranslate(8.0f,7.0f);
matrix.mapPoints(points);
Log.i("test", points[0]+"");
Log.i("test", points[1]+"");
结果为点坐标为(36.0,51.0)
可以得出结论,进行变换的顺序是先执行preTranslate(8.0f,7.0f),在执行的preScale(2.0f,3.0f)。即对于一个Matrix的设置中,所有pre….是倒着向后执行的。
2、post…的执行顺序
Matrix matrix=new Matrix();
float[] points=new float[]{10.0f,10.0f};
matrix.postScale(2.0f, 3.0f);
matrix.postTranslate(8.0f,7.0f);
matrix.mapPoints(points);
Log.i("test", points[0]+"");
Log.i("test", points[1]+"");
结果为点坐标为(28.0,37.0)
可以得出结论,进行变换的顺序是先执行postScale(2.0f,3.0f),在执行的postTranslate(8.0f,7.0f)。即对于一个Matrix的设置中,所有post….是顺着向前执行的。
这里主要知道set…和post…方法就行,因为只用到了这两个。
自我理解其实和scrollTo、scrollBy类似。
2、GestureDetector与ScaleGestureDetector
GestureDetector主要用于识别一些特定手势,只要调用GestureDetector.onTouchEvent()把MotionEvent传递进去就可以了
ScaleGestureDetector用于处理缩放的攻击类用法和GestureDetector类似
3、Bitmap的一下基本用法
参考:关于bitmap你不知道的一些事
了解一下bitmap的注意事项即可
下面开始正式画这个选座的功能了
1、画座位:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
/**
* 如果第一次进入 使座位图居中
*/
if (mViewH != 0 && mViewW != 0&&isFrist) {
isFrist = false;
matrix.setTranslate(-(mViewW-getMeasuredWidth())/2, 0);
}
/**
* 画座位
*/
drawSeat(canvas);
/**
* 画排数
*/
drawText(canvas);
/**
* 画缩略图
*/
drawOverView(canvas);
/**
* 画缩略图选择区域
*/
drawOvewRect(canvas);
}
private void drawSeat(Canvas canvas) {
float zoom = getMatrixScaleX();
scale1 = zoom;
tranlateX = getTranslateX();
tranlateY = getTranslateY();
/**
* 使用两层for循环来画出所有座位
*/
for (int i = 0; i < row; i++) {
float top = i * SeatHight * scale * scale1 + i * mSpaceY * scale1
+ tranlateY;
for (int j = 0; j < column; j++) {
float left = j * SeatWidth * scale * scale1 + j * mSpaceX
* scale1 + tranlateX;
tempMatrix.setTranslate(left, top);
tempMatrix.postScale(scale, scale, left, top);
tempMatrix.postScale(scale1, scale1, left, top);
/**
* 获取每个位置的信息
*/
int state = getSeatType(i, j);
/**
* 根据位置信息画不同的位置图片
*/
switch (state) {
case SEAT_TYPE_SOLD:
canvas.drawBitmap(SeatLock, tempMatrix, null);
break;
case SEAT_TYPE_SELECTED:
canvas.drawBitmap(SeatChecked, tempMatrix, null);
break;
case SEAT_TYPE_AVAILABLE:
canvas.drawBitmap(SeatNormal, tempMatrix, null);
break;
case SEAT_TYPE_NOT_AVAILABLE:
break;
}
}
}
}
这里其实没什么难度,主要就是使用两层for循环,一层画行,一层画列
另外要注意的就是当前位置的计算 top = (当前位置i)(座位图标大小SeatHight 它本身的缩放比scale*缩放时的缩放比scale1)+(当前位置i* 垂直方向的间距mSpaceY*缩放时的缩放比scale1)+垂直方向移动是的移动距离
2、画排数
private void drawText(Canvas canvas) {
mTextPaint.setColor(bacColor);
RectF rectF = new RectF();
rectF.top = getTranslateY() - mNumberHeight/2;
rectF.bottom = getTranslateY()+ mViewH* getMatrixScaleX() + mNumberHeight/2;
rectF.left = 0;
rectF.right = mTextWidth;
canvas.drawRoundRect(rectF, mTextWidth/2, mTextWidth/2, mTextPaint);
mTextPaint.setColor(Color.WHITE);
for (int i = 0; i < row; i++) {
float top = (i *SeatHight*scale + i * mSpaceY) * getMatrixScaleX() + getTranslateY();
float bottom = (i * SeatHight*scale + i * mSpaceY + SeatHight) * getMatrixScaleX() + getTranslateY();
float baseline = (bottom + top - lineNumberPaintFontMetrics.bottom - lineNumberPaintFontMetrics.top ) / 2-6;
canvas.drawText(lineNumbers.get(i), mTextWidth / 2, baseline, mTextPaint);
}
}
3、画缩略图
private void drawOverView(Canvas canvas) {
/**
* 1、先画张背景图片
*/
mBitMapOverView = Bitmap.createBitmap((int)mOverViewWidth,(int)mOverViewHight,Bitmap.Config.ARGB_8888);
Canvas OverViewCanvas = new Canvas(mBitMapOverView);
Paint paint = new Paint();
paint.setColor(bacColor);
scaleoverX = mOverViewWidth / mViewW;
scaleoverY = mOverViewHight / mViewH;
float tempX = mViewW * scaleoverX;
float tempY = mViewH * scaleoverY;
OverViewCanvas.drawRect(0, 0, (float)tempX, (float)tempY, paint);
Matrix tempoverMatrix = new Matrix();
/**
* 2、和画座位图一样在缩略图中画座位
*/
for (int i = 0; i < row; i++) {
float top = i * SeatHight * scale * scaleoverY+ i * mSpaceY * scaleoverY;
for (int j = 0; j < column; j++) {
float left = j * SeatWidth * scale * scaleoverX + j * mSpaceX * scaleoverX+mTextWidth*scaleoverX;
tempoverMatrix.setTranslate(left, top);
tempoverMatrix.postScale(scale*scaleoverX, scale*scaleoverY, left, top);
int state = getSeatType(i, j);
switch (state) {
case SEAT_TYPE_SOLD:
OverViewCanvas.drawBitmap(SeatLock, tempoverMatrix, null);
break;
case SEAT_TYPE_SELECTED:
OverViewCanvas.drawBitmap(SeatChecked, tempoverMatrix, null);
break;
case SEAT_TYPE_AVAILABLE:
OverViewCanvas.drawBitmap(SeatNormal, tempoverMatrix, null);
break;
case SEAT_TYPE_NOT_AVAILABLE:
break;
}
}
}
canvas.drawBitmap(mBitMapOverView,0,0,null);
}
4、缩略图中的红色区域
private void drawOvewRect(Canvas canvas) {
OverRectPaint = new Paint();
OverRectPaint.setColor(Color.RED);
OverRectPaint.setStyle(Paint.Style.STROKE);
OverRectPaint.setStrokeWidth(overRectLineWidth);
int tempViewW ;
int tempViewH;
if(getMeasuredWidth()<mViewW){
tempViewW = getMeasuredWidth();
}else{
tempViewW = mViewW;
}
if(getMeasuredHeight()<mViewH){
tempViewH = getMeasuredHeight();
}else{
tempViewH = mViewH;
}
try{
Rect rect ;
if(getMatrixScaleX()>= 1.0f){
rect = new Rect((int)(scaleoverX*Math.abs(getTranslateX())/getMatrixScaleX()),
(int)(scaleoverY*Math.abs(getTranslateY())/getMatrixScaleX()),
(int)(scaleoverX*Math.abs(getTranslateX())/getMatrixScaleX()+tempViewW*scaleoverX/getMatrixScaleX()),
(int)(scaleoverY*Math.abs(getTranslateY())/getMatrixScaleX()+tempViewH*scaleoverY/getMatrixScaleX()));
}else{
rect = new Rect((int)(scaleoverX*Math.abs(getTranslateX())),
(int)(scaleoverY*Math.abs(getTranslateY())),
(int)(scaleoverX*Math.abs(getTranslateX())+tempViewW*scaleoverX),
(int)(scaleoverY*Math.abs(getTranslateY())+tempViewH*scaleoverY));
}
canvas.drawRect(rect, OverRectPaint);
}catch(Exception e){
e.printStackTrace();
}
}
5、手指移动时跟随移动
@Override
public boolean onTouchEvent(MotionEvent event) {
/**
* 缩放事件交由ScaleGestureDetector处理
*/
scaleGestureDetector.onTouchEvent(event);
/**
* 移动和点击事件交由GestureDetector处理
*/
gestureDetector.onTouchEvent(event);
return true;
}
/**
* 移动事件 这里只是简单判断了一下,需要更细致的进行条件判断
*/
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
float tempMViewW = column * SeatWidth*scale*scale1+(column -1)*mSpaceX*scale1+mTextWidth-getWidth();
float tempmViewH = row * SeatHight * scale * scale1 + (row -1) * mSpaceY * scale1 - getHeight();
if((getTranslateX()>mTextWidth+mSpaceX)&& distanceX<0){
distanceX = 0.0f;
}
if((Math.abs(getTranslateX())>tempMViewW)&&(distanceX>0)){
distanceX = 0.0f;
}
if((getTranslateY()>0)&&distanceY<0){
distanceY=0.0f;
}
if((Math.abs(getTranslateY())>tempmViewH)&&(distanceY>0)){
distanceY = 0.0f;
}
matrix.postTranslate(-distanceX, -distanceY);
invalidate();
return false;
}
/**
* 单击事件
*/
public boolean onSingleTapConfirmed(MotionEvent e) {
int x = (int) e.getX();
int y = (int) e.getY();
for (int i = 0; i < row; i++) {
for (int j = 0; j < column; j++) {
int tempX = (int) ((j * SeatWidth * scale + j * mSpaceX) * getMatrixScaleX() + getTranslateX());
int maxTemX = (int) (tempX + SeatWidth * scale * getMatrixScaleX());
int tempY = (int) ((i * SeatHight * scale + i * mSpaceX) * getMatrixScaleY() + getTranslateY());
int maxTempY = (int) (tempY + SeatHight * scale * getMatrixScaleY());
if (x >= tempX && x <= maxTemX && y >= tempY
&& y <= maxTempY) {
int id = getID(i, j);
int index = isHave(id);
if (index >= 0) {
remove(index);
} else {
addChooseSeat(i, j);
}
float currentScaleY = getMatrixScaleY();
if (currentScaleY < 1.7f) {
scaleX = x;
scaleY = y;
/**
* 选中时进行缩放操作
*/
zoomAnimate(currentScaleY, 1.9f);
}
invalidate();
break;
}
}
}
return super.onSingleTapConfirmed(e);
}
});
6、两个手指缩放时跟随缩放
public boolean onScale(ScaleGestureDetector detector) {
float scaleFactor = detector.getScaleFactor();
//scaleX = detector.getCurrentSpanX();
//scaleY = detector.getCurrentSpanY();
//直接判断大于2会导致获取的matrix缩放比例继续执行一次从而导致变成2.000001之类的数从而使
//判断条件一直为真从而不会执行缩小动作
//判断相乘大于2 可以是当前获得的缩放比例即使是1.9999之类的数如果继续放大即使乘以1.0001也会比2大从而
//避免上述问题。
if (getMatrixScaleY() * scaleFactor > 2) {
scaleFactor = 2 / getMatrixScaleY();
}
if (getMatrixScaleY() * scaleFactor < 0.8) {
scaleFactor = 0.8f / getMatrixScaleY();
}
matrix.postScale(scaleFactor, scaleFactor);
invalidate();
return true;
}
至此这个比较粗糙的选座功能就实现了,有时间会继续优化下细节问题。
下面两个demo都比较给力我就不上传demo了
主要参考:
Android例子源码高仿QQ电影票选座功能例子
android 自定义view之选座功能的更多相关文章
- Android 自定义View合集
自定义控件学习 https://github.com/GcsSloop/AndroidNote/tree/master/CustomView 小良自定义控件合集 https://github.com/ ...
- (转)[原] Android 自定义View 密码框 例子
遵从准则 暴露您view中所有影响可见外观的属性或者行为. 通过XML添加和设置样式 通过元素的属性来控制其外观和行为,支持和重要事件交流的事件监听器 详细步骤见:Android 自定义View步骤 ...
- [原] Android 自定义View步骤
例子如下:Android 自定义View 密码框 例子 1 良好的自定义View 易用,标准,开放. 一个设计良好的自定义view和其他设计良好的类很像.封装了某个具有易用性接口的功能组合,这些功能能 ...
- [原] Android 自定义View 密码框 例子
遵从准则 暴露您view中所有影响可见外观的属性或者行为. 通过XML添加和设置样式 通过元素的属性来控制其外观和行为,支持和重要事件交流的事件监听器 详细步骤见:Android 自定义View步骤 ...
- android自定义View之NotePad出鞘记
现在我们的手机上基本都会有一个记事本,用起来倒也还算方便,记事本这种东东,如果我想要自己实现,该怎么做呢?今天我们就通过自定义View的方式来自定义一个记事本.OK,废话不多说,先来看看效果图. 整个 ...
- Android 自定义 View 圆形进度条总结
Android 自定义圆形进度条总结 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 微信公众号:牙锅子 源码:CircleProgress 文中如有纰漏,欢迎大家留言指出. 最近 ...
- android 自定义view 前的基础知识
本篇文章是自己自学自定义view前的准备,具体参考资料来自 Android LayoutInflater原理分析,带你一步步深入了解View(一) Android视图绘制流程完全解析,带你一步步深入了 ...
- android自定义View绘制天气温度曲线
原文:android自定义View绘制天气温度曲线 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/u012942410/article/detail ...
- 【朝花夕拾】Android自定义View篇之(八)多点触控(上)MotionEvent简介
前言 在前面的文章中,介绍了不少触摸相关的知识,但都是基于单点触控的,即一次只用一根手指.但是在实际使用App中,常常是多根手指同时操作,这就需要用到多点触控相关的知识了.多点触控是在Android2 ...
随机推荐
- JavaScript 随机数相关算法
// Math.ceil() 返回大于等于数字参数的最小整数(取整函数),对数字进行上舍入 // Math.floor() 返回小于等于数字参数的最大整数,对数字进行下舍入 // Math.round ...
- java中抽象类和接口之间的异同点
抽象类 接口 声明方式 abstratc class ClassName interface ClassName 包含内容 构造方法,普通方法,抽象方法.static方法 .变量常量 全局常量.抽 ...
- springmvc文件下载之文件名下划线问题终极解决方案
直接上代码:Action中代码片段. @RequestMapping("download")public String download(ModelMap model, @Mode ...
- 前端性能监控系统 & 前端数据分析系统
前端监控系统 目前已经上线,欢迎使用! 背景:应工作要求,需要整理出前端项目的报错信息,尝试过很多统计工具,如: 腾讯bugly.听云.OneApm.还有一个忘记名字的工具. 因为各种原因,如: 统计 ...
- [测试题]神在夏至祭降下了神谕(oracle)
Description 我们村子在过去的 400 年中, 断绝与下界的接触, 过着自给自足的生活.夏至祭是一场迎接祖灵于夏季归来, 同时祈求丰收的庆典.村里的男人会在广场上演出夏之军和冬之军的战争. ...
- [UOJ]#36. 【清华集训2014】玛里苟斯
题目大意:给n个数字,求子集的异或和的k次方的期望(n<=10^5,k<=5,保证答案小于2^63) 做法:首先如果从集合中拿出a和b,把a和a xor b放回集合,子集的异或和与原来是一 ...
- ●BZOJ 2839 集合计数
题链: http://www.lydsy.com/JudgeOnline/problem.php?id=2839 题解: 容斥原理 真的是神题!!! 定义 f[k] 表示交集大小至少为 k时的方案数怎 ...
- centos7 支持中文显示
http://www.linuxidc.com/Linux/2017-07/145572.htm这篇文章比较全.我印证了一下,没有问题 centos7的与centos6有少许不同: 1.安装中文包: ...
- 3.5 find() 判断是否存在某元素
vector 判断是否存在某元素: if(find(A.begin(), A.end(), A[i]) != A.end()){ // 若存在 A[i] // find() 返回一个指针 }
- JAVA 第二天 内部类
package com.company; /** * Created by Administrator on 2016/8/23. */ public class Outter {//生成的字节码文件 ...