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

 

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

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

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • MIUI解BL锁失败[通俗易懂]

    MIUI解BL锁失败[通俗易懂]最后解决办法是:换USB2.0接口分析问题:或许可能是软件兼容性不好,USB3.0影响读取设备信息,导致无法解锁。

    2022年5月27日
    84
  • 什么是PCM?它和.wav文件是什么关系?[通俗易懂]

    什么是PCM?它和.wav文件是什么关系?[通俗易懂]什么是PCM?它和.wav文件是什么关系?

    2022年4月20日
    53
  • 用户 不在 sudoers 文件中。此事将被报告。

    用户 不在 sudoers 文件中。此事将被报告。文章目录背景解决方案背景普通linux用户使用sudo命令执行只有root用户才可以执行的命令时出现了该错误,如下图示:简单说明一下操作。命令$ll/etc/sudoers表示查看文件的属性,属性包括有:文件拥有者、文件所属组以及其他用户组对该文件拥有的读写权限和文件的类型等,上图的/etc/sudoers文件表示拥有者和所属组都是root且只能读取,其他用户组的没有任何读写权限。命…

    2022年6月20日
    44
  • 阅读书源最新2020在线导入_书源篇三及6.5.0版本介绍

    阅读书源最新2020在线导入_书源篇三及6.5.0版本介绍书源篇三及6.5.0版本介绍魔幻2020魔幻的2020,开启不一样的生活状态,作为一名技术宅,不出门虽我愿,但看到空荡荡的街头,心中却有种难言的难过与害怕。我不向往繁华。但喜欢车马如龙,街灯繁华。愿祖国强盛人长久,我辈身强振家兴!书源及工作原理书源:一个网站的规则描述文件,可能包括有多个来源;来源:聚合网站包括多个网站的内容,一个来源表示其中一个网站。仓库:存储书源的地方…

    2022年6月16日
    1.0K
  • stm32f103c6t6引脚图_74ls163引脚图及功能表

    stm32f103c6t6引脚图_74ls163引脚图及功能表今天准备画一个STM32F103C8T6的最小系统板,就去STM32F103C8的数据手册查看了一下相应的引脚,因为数据手册里面的引脚表有中容量的多种封装描述,看上去比较麻烦,我就单独做了一个LQFP48脚的引脚表。方便后期自己画封装,就图看的省力一点哈。其部分图片如下所示:有需要的朋友可以从我的资源里去下,资源链接:STM32F103C8T6详细引脚表本人水平有限,上述信息仅供学习参考,如有错误和不妥之处,请多多指教。另外创作不易,请勿抄袭,如果有帮助到大家的话希望大家可以点个赞,谢谢~…

    2022年9月25日
    5

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

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