android 之旋转罗盘 风车 开发[通俗易懂]

android 之旋转罗盘 风车 开发[通俗易懂]我要介绍的是一个能旋转的view,说这个view能旋转有点不切实际,那是视觉效果,其实是对图片的旋转。目前它只支持图片。你可以把它认为是一个能响应手势旋转的View。它的功能有:1.会响应手势旋转2

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

我要介绍的是一个 能旋转的view,说这个view能旋转有点不切实际,那是视觉效果,其实是对图片的旋转。目前它只支持图片。你可以把它认为是一个能响应手势旋转的View。
它的功能有:
1.会响应手势旋转
2.该view模拟真实罗盘旋转:a.旋转的时候会有惯性,继续旋转,而且是减速旋转b.旋转期间手指扳动罗盘,能加速罗盘旋转c.当罗盘在旋转的时候,手指按住罗盘,它会有刹车的效果。
效果截图:
为了形象点我用了一张风车的图作为例子

 

android 之旋转罗盘 风车 开发[通俗易懂]

技术要点
1.需要扩展一个view,重写ondraw(),onTouchEvent(),onMeasure(),onDetachedFromWindow()方法
a.onDraw():主要是控制图片旋转绘图
b.onTouchEvent():主要是监听手势
c.onMeasere():用来测量view的长宽,在xml里最好配置成wrap_content,因为如果为固定值可能会因为长宽不够,导致显示不全
d.onDetachedFromWindow():用来回收bitmap
2.需要通过handler来处理惯性
3.需要一个速度分析器,来分析手势离开时的瞬时速度
4.需要用到圆和三角函数的知识:如反正切函数,弧度等

 

技术难点分析

1.如何扩展这个View
a.View的旋转图片的设置
我们可以提供一个方法来设置旋转的图片,并定义旋转图片的成员变量,这里我将它命名为rotaBitmap

public void setRotatBitmap(Bitmap bitmap) { 
rotatBitmap = bitmap; 
initSize(); 
postInvalidate(); 
} 
  
public void setRotatDrawableResource(int id) { 
  
BitmapDrawable drawable = (BitmapDrawable)getContext().getResources().getDrawable(id); 
  
setRotatDrawable(drawable); 
} 
  
public void setRotatDrawable(BitmapDrawable drawable) { 
rotatBitmap = drawable.getBitmap(); 
initSize(); 
postInvalidate(); 
}

 

b.View长宽确认
有了图片就可以确认这个view的大小了,这里view的大小不是image的大小,因为还要考虑旋转,一个矩形要确保旋转360度都能被看见,那这个区域应该是个正方形,而且这个正方形的内切圆半径是这个矩形的对角线一半。不知道能否说明白,还是我画图吧。

android 之旋转罗盘 风车 开发[通俗易懂]

通过上图,应该很容易发现,这个view的长度应该是被旋转图的对角线的长度
这样我们可以加上这样一段代码:

private void initSize() { 
if (rotatBitmap == null) { 
  
// throw new NoBitMapError("Error,No bitmap in RotatView!"); 
return; 
} 
width = rotatBitmap.getWidth(); 
height = rotatBitmap.getHeight(); 
  
maxwidth = Math.sqrt(width * width + height * height); 
o_x = o_y = (float)(maxwidth / 2);//确定圆心坐标 
}

 

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
// TODO Auto-generated method stub 
super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
// 它的宽高不是图片的宽高,而是以宽高为直角的矩形的对角线的长度 
setMeasuredDimension((int)maxwidth, (int)maxwidth); 
  
}

c.旋转原理
图片的旋转是在ondraw()里实现的,通过一个变量:deta_degree 来控制旋转的度数

/** 
* 当前圆盘所转的弧度(以该 view 的中心为圆点) 
*/
float deta_degree;

 

然后用Matrix来控制旋转图片,主要是preRotate(deta_degree)这里的单位是度,360度为一圈,最后把旋转的图画到画布上

@Override
protected void onDraw(Canvas canvas) { 
  
Matrix matrix = new Matrix(); 
// 设置转轴位置 
matrix.setTranslate((float)width / 2, (float)height / 2); 
  
// 开始转 
matrix.preRotate(deta_degree); 
// 转轴还原 
matrix.preTranslate(-(float)width / 2, -(float)height / 2); 
  
// 将位置送到view的中心 
matrix.postTranslate((float)(maxwidth - width) / 2, (float)(maxwidth - height) / 2); 
  
canvas.drawBitmap(rotatBitmap, matrix,paint); 
  
super.onDraw(canvas); 
}

 

考虑到它的周期为360,如果detaDegree的度数太大可能会越界,我们可以做一个于求余处理,让它的值在-360到360之间

/** 
* 通过此方法来控制旋转度数,如果超过360,让它求余,防止,该值过大造成越界 
*  
* @param added 
*/
private void addDegree(float added) { 
deta_degree += added; 
if (deta_degree > 360 || deta_degree < -360) { 
deta_degree = deta_degree % 360; 
} 
  
}

这里的动画是通过不停的走ondraw()方法刷新,产生的效果,类似放电影一样
d.view的手势响应
当用户触摸它时会响应onTouch事件,在onTouch里分析手指的坐标,通过坐标算出与圆心的夹角
下图为手指与view中心的夹角(这里的原点就是view的旋转中心):

android 之旋转罗盘 风车 开发[通俗易懂]

 

每次手指滑动或是松开都会计算它与原点的夹角,这个方法可以通过反正切函数求出来,详情:

  /** 
     * 计算以(src_x,src_y)为坐标圆点,建立直角体系,求出(target_x,target_y)坐标与x轴的夹角 
     * 主要是利用反正切函数的知识求出夹角 
     *  
     * @param src_x 
     * @param src_y 
     * @param target_x 
     * @param target_y 
     * @return 
     */
    float detaDegree(float src_x, float src_y, float target_x, float target_y) { 
  
        float detaX = target_x - src_x; 
        float detaY = target_y - src_y; 
        double d; 
        //坐标在四个象限里 
        if (detaX != 0) { 
            float tan = Math.abs(detaY / detaX); 
  
            if (detaX > 0) { 
  
                //第一象限 
                if (detaY >= 0) { 
                    d = Math.atan(tan); 
  
                } else { 
                    //第四象限 
                    d = 2 * Math.PI - Math.atan(tan); 
                } 
  
            } else { 
                if (detaY >= 0) { 
                    //第二象限 
                    d = Math.PI - Math.atan(tan); 
                } else { 
                    //第三象限 
                    d = Math.PI + Math.atan(tan); 
                } 
            } 
  
        } else { 
            //坐标在y轴上 
            if (detaY > 0) { 
                //坐标在y>0上 
                d = Math.PI / 2; 
            } else { 
                //坐标在y<0上 
                d = -Math.PI / 2; 
            } 
        } 
  
        return (float)((d * 180) / Math.PI); 
    }

通过以上方法,可以把每次移动的时候的夹角求出来,把当前的夹角和上次的手指夹角坐差运算就能求出手指相对圆心旋转的角度增量,得到这个角度增量就可以通过调用
这前提过的addDegree()方法,改变图片的角度,然后调用invalidate()方法重绘,就实现了罗盘随手指旋转的效果。
在这里赋上ontouch处理down和move事件的代码(up事件是用来处理惯性用的):

@Override
public boolean onTouchEvent(MotionEvent event) { 
// TODO Auto-generated method stub 
if (rotatBitmap == null) { 
  
throw new NoBitMapError("Error,No bitmap in RotatView!"); 
} 
switch (event.getAction()) { 
case MotionEvent.ACTION_DOWN: { 
down_x = event.getX(); 
down_y = event.getY(); 
current_degree = detaDegree(o_x, o_y, down_x, down_y); 
  
break; 
  
} 
case MotionEvent.ACTION_MOVE: { 
down_x = target_x = event.getX(); 
down_y = target_y = event.getY(); 
float degree = detaDegree(o_x, o_y, target_x, target_y); 
  
// 滑过的弧度增量 
float dete = degree - current_degree; 
// 如果小于-90度说明 它跨周了,需要特殊处理350->17, 
if (dete < -270) { 
dete = dete + 360; 
  
// 如果大于90度说明 它跨周了,需要特殊处理-350->-17, 
} else if (dete > 270) { 
dete = dete - 360; 
} 
  
addDegree(dete); 
current_degree = degree; 
invalidate(); 
  
break; 
}

e.View的瞬时速度获取

为了得到瞬时速度,我的思路是通过一个固定长度的2维数组,把手指与原点的最近几次夹角增量和时间点记录下来,通过这几个夹角增量和时间点,可以算出平均速度,因为手指滑动的时候,响应ontouch事件次数非常多,我们可以把最后几次ontouch记录下的数据,取平均值,把它认为是瞬时速度。
这里用到了一点物理知识:

android 之旋转罗盘 风车 开发[通俗易懂]

假如上图为record记录的4组数据,t代表时间点,表示产生这个事件的时间,d代表手指与圆心夹角的增量,它是这次夹角与上次夹角的差值

这样我们可以把t=t3-t0算出经过的时间,把sum=d1+d2+d3算出这段时间一共经历过的弧度

再把sum/t就是平均速度了

但需要注意一个细节:d0是无效的
这里给出计算速度 的代码:
因为考虑不能让速度太快,所以给出了一个最大值

/** 
* 最大速度 
*/
public static final double max_speed = 8; 
  
/** 
* 通过数组里所装载的数据分析出即时速度<br> 
* 原理是:计算数组里的时间长度和增量的总数,然后求出每毫秒所走过的弧度<br> 
* 当然不能超过{@link VRecord#max_speed} 
*  
* @return 
*/
public double getSpeed() { 
  
if (addCount == 0) { 
return 0; 
} 
int maxIndex = Math.min(addCount, length) - 1; 
  
if ((record[0][1] - record[maxIndex][1]) == 0) { 
return 0; 
} 
  
double detaTime = record[0][1] - record[maxIndex][1]; 
double sumdegree = 0; 
for (int i = 0; i < length - 1; i++) { 
  
sumdegree += record<I>[0]; 
// System.out.println(record[0]); 
} 
  
// System.out.println("----------"); 
// System.out.println(sumdegree); 
// System.out.println(detaTime); 
double result = sumdegree / detaTime; 
if (result > 0) { 
return Math.min(result, max_speed); 
} else { 
return Math.max(result, -max_speed); 
} 
// System.out.println("v=" + result); 
  
}

讲到这我要提一下,这个二维数组是如何做到获取最近的数据,如果超过容量,它将把原来的数据丢弃,我想直接上代码,大家就能看懂吧

/** 
* 二维数组,1.保存弧度增量.2.保存产生这个增量的时间点 
*/
double[][] record = new double[length][2]; 
  
/** 
* 为二维数组装载数据<br> 
* 注:通过此方法,有个特点,能把最后的length组数据记录下来,length以外的会丢失 
*  
* @param detadegree 
* @param time 
*/
public void add(double detadegree, double time) { 
  
for (int i = length - 1; i > 0; i--) { 
record[0] = record[i - 1][0]; 
record[1] = record[i - 1][1]; 
} 
record[0][0] = detadegree; 
record[0][1] = time; 
addCount++; 
  
}

f.View惯性处理
这里的惯性处理就是用到加速度了,再用handler发消息,控制它不停减速,减到它为零为止就停止发消息
下面是handler代码

@Override
public void handleMessage(Message msg) { 
  
double detaTime = System.currentTimeMillis() - currentTime; 
switch (msg.what) { 
  
case play: { 
//如果是顺时针 
if (isClockWise) { 
speed = speed - a * detaTime;//减速 
if (speed <= 0) { 
return; 
} else { 
handler.sendEmptyMessageDelayed(play, delayedTime); 
} 
} else { 
speed = speed + a * detaTime; 
if (speed >= 0) { 
return; 
} else { 
handler.sendEmptyMessageDelayed(play, delayedTime); 
} 
} 
  
addDegree((float)(speed * detaTime + (a * detaTime * detaTime) / 2));//高中物理算路程S=vt+at2 
  
// if (a < a_max) { 
// a = (float)(a + a_add*detaTime); 
// System.out.println("a:"+a); 
// } 
currentTime = System.currentTimeMillis(); 
invalidate(); 
  
break; 
} 
case stop: { 
speed = 0; 
handler.removeMessages(play); 
} 
} 
  
super.handleMessage(msg); 
} 
};

转载请写以下地址:http://www.eoeandroid.com/thread-207498-1-1.html 

 

源码下载:MyRotation.rar

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/168160.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • oracle字符串自身去重,oracle拼接字符串函数(去重和不去重)「建议收藏」

    oracle字符串自身去重,oracle拼接字符串函数(去重和不去重)「建议收藏」oracle拼接字符串函数(去重和不去重)1.不去重FUNCTIONf_linkFunctionf_linkCREATEORREPLACEFUNCTIONf_link(p_strVARCHAR2)RETURNVARCHAR2PARALLEL_ENABLEAGGREGATEUSINGt_link;Typet_linkCREATEORREPLACETYPET_LINK…

  • CentOs安装Python3.9

    CentOs安装Python3.9下载python3源码包wgethttps://www.python.org/ftp/python/3.9.0/Python-3.9.0.tgz或者python官网下载https://www.python.org/ftp/python/3.9.0/Python-3.9.0.tgz然后放过去解压缩源码包tar-zxvfPython-3.9.0.tgz进入源码包文件夹cdPython-3.9.0编译且安装进入源码包目录ls查看源码包内容释放编译文件Makefile.

  • 【windows屏幕扩展】把你多余屏幕利用起来,spacedesk屏幕扩展超低延迟解决方案[通俗易懂]

    【windows屏幕扩展】把你多余屏幕利用起来,spacedesk屏幕扩展超低延迟解决方案[通俗易懂]目录扫盲扫盲spacedesk是一款基于TCP/IP协议的屏幕扩展工具,通过这款工具你可以把自己身边的闲置的平板手机或者笔记本利用起来,扩展你的屏幕。只要你的两台设备处于同一个网络环境下(只要互相能够ping通),你就可以实现屏幕扩展(卡不卡我就不知道了)。用过win10中的wifi扩展屏幕的同学都知道,扩展的屏幕显示质量和网络环境成正比。而win10的屏幕扩展很玄学,…

  • moxa串口服务器的配置(波特率如何设置)

    MOXA串口服务器产品配置说明附图.doc第一章:准备工作准备工作我们用一条交叉网线把NPort5110和PC机的网口连接起来,并把NPort上电。首先,打开控制面板,网络连接。在本地连接上点右键,选择属性。双击进入Internet协议(TCP/IP),点击“使用下面的IP地址”写入IP地址和子网掩码,记住要和NPORT的IP地址在同一子网段内。如NPORT默认IP为192.168…

  • mysql分区表_MySQL分区表的正确使用方法

    mysql分区表_MySQL分区表的正确使用方法MySQL分区表概述我们经常遇到一张表里面保存了上亿甚至过十亿的记录,这些表里面保存了大量的历史记录。对于这些历史数据的清理是一个非常头疼事情,由于所有的数据都一个普通的表里。所以只能是启用一个或多个带where条件的delete语句去删除(一般where条件是时间)。这对数据库的造成了很大压力。即使我们把这些删除了,但底层的数据文件并没有变小。面对这类问题,最有效的方法就是在使用分区表。最常…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号