最近在用QQ的时候发现了一个有意思的小细节,如图所示:

可以看到Tab按钮都有一个随着用户拖动而转动的特效,一开始被这个效果惊艳到了,QQ还是很细致的,注重细节和用户体验。

于是利用空闲时间实现了这个效果,所有代码均用kotlin实现,项目效果如图所示:

哈哈是不是一模一样呢,完整的实现代码并不长,只有200多行,但是找思路花了一些时间,也遇到过许多弯路,不过最后都还是坚持下来了,实现的思路概括一下:

首先需要两个背景,内背景(笑脸表情图片)和外背景(笑脸轮廓背景图片),通过反编译QQ的包得到了这两个图片资源文件。然后根据view的onTouchListner,分别在DOWN点击的时候触发放大的动画效果(即上图中的选中状态动画),以及在MOVE的时候判断内背景和外背景的运动,都可以算是向着触摸的点偏移,但是内背景的偏移量比外背景图要多(肉眼可以看出来吧..),所以实现的时候只要注意这个点,以及对偏移边缘(轨迹圆)的判断就可以了。

下面介绍一下实现的步骤以及难点:

1.首先是自定义view的布局文件:

 <com.ng.ui.view.CentralTractionButtonandroid:id="@+id/ctt_main"android:button="@null"android:layout_width="100dp"android:layout_height="100dp"android:layout_centerHorizontal="true"android:layout_centerVertical="true"android:background="@android:color/transparent"app:normalexternalbackground="@drawable/iv_rb1_bg_normal"app:normalinsidebackground="@drawable/iv_rb1_in_normal"app:selectedexternalbackground="@drawable/iv_rb1_bg_selected"app:selectedinsidebackground="@drawable/iv_rb1_in_selected"app:text="消息"app:textdimension="12sp" />

其中自定义了几个属性,其中分别对应为:

normalexternalbackground - 未选中状态下的外部背景 
normalinsidebackground - 未选中状态下的内部背景

selectedexternalbackground - 选中状态下的外部背景

selectedinsidebackground-选中状态下的内部背景

2.在自定义的view中对这些属性做初始化,对应代码如下:

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {val ta = context.obtainStyledAttributes(attrs, R.styleable.ctattrs)text = ta.getString(R.styleable.ctattrs_text)textdimension = ta.getDimension(R.styleable.ctattrs_textdimension, 1f)normalexternalbackground = ta.getResourceId(R.styleable.ctattrs_normalexternalbackground, 0)normalinsidebackground = ta.getResourceId(R.styleable.ctattrs_normalinsidebackground, 0)selectedinsidebackground = ta.getResourceId(R.styleable.ctattrs_selectedinsidebackground, 0)selectedexternalbackground = ta.getResourceId(R.styleable.ctattrs_selectedexternalbackground, 0)//打印所有的属性val count = attrs.attributeCountfor (i in 0..count - 1) {val attrName = attrs.getAttributeName(i)val attrVal = attrs.getAttributeValue(i)LogUtils.d("attrName = $attrName , attrVal = $attrVal")}ta.recycle()init()}

在init方法中,进行图形的Rect初始化:

   private fun init() {initPaint()LogUtils.d("-----init-----")//得到组件宽高中的较小值,再/2得到ob的距离if (mHeight > mWidth) mR = mHeight / 2 else mR = mWidth / 2LogUtils.d("ob的距离:" + mR)mr = mR / 2// 背景图绘制区域mExternalDestRect = Rect((centerx - mr).toInt(), (centery - mr).toInt(),(centerx + mr).toInt(),(centery + mr).toInt())//初始化: 75 75 225 225// 中心图绘制区域mInsideDestRect = Rect((centerx - mr).toInt(), (centery - mr).toInt(),(centerx + mr).toInt(),(centery + mr).toInt())// 内外的图形externalBD = resources.getDrawable(normalexternalbackground) as BitmapDrawablemExternalSrcRect = Rect(0, 0, externalBD!!.intrinsicWidth, externalBD!!.intrinsicHeight)insidelBD = resources.getDrawable(normalinsidebackground) as BitmapDrawablemInsideSrcRect = Rect(0, 0, insidelBD!!.intrinsicWidth, insidelBD!!.intrinsicHeight)}

3.在onDraw方法中进行对内外背景的绘制:

    override fun onDraw(canvas: Canvas) {super.onDraw(canvas)//暂时画个边框表示范围val bianKuanPaint = Paint()bianKuanPaint.isAntiAlias = truebianKuanPaint.strokeWidth = 2fbianKuanPaint.style = Paint.Style.STROKEbianKuanPaint.color = resources.getColor(R.color.black)canvas.drawRect(0f, 0f, this.width.toFloat(), this.height.toFloat(), bianKuanPaint)//绘制默认状态下背景图val externalBM = externalBD!!.bitmapcanvas.drawBitmap(externalBM, mExternalSrcRect, mExternalDestRect, bmPaint)//绘制默认状态下中心图val insidelBM = insidelBD!!.bitmapcanvas.drawBitmap(insidelBM, mInsideSrcRect, mInsideDestRect, bmPaint)}

可以看到在onDraw中并没有做什么事情,只是绘制图形而已。

4.在onTouchEvent中进行判断:

 override fun onTouchEvent(event: MotionEvent): Boolean {//相较于视图的XYvar mx1 = event.xvar my1 = event.yvar mx2 = event.xvar my2 = event.y  //需要减掉标题栏高度LogUtils.d("---onTouchEvent---")LogUtils.d(" 点击坐标:$mx1 $my1")when (event.action) {MotionEvent.ACTION_DOWN -> {LogUtils.d("ACTION_DOWN")//TODO 弹动一下的动画效果postInvalidate()}MotionEvent.ACTION_MOVE -> {LogUtils.d("ACTION_MOVE:" + scrollX + " " + scrollY)//判断点击位置距离中心的距离var distanceToCenter = getDistanceTwoPoint(mx1, my1, centerx, centery)var mExternalOffesetLimit = mr / 4var mInsideOffesetLimit = mr / 2//如果区域在轨迹圆内则移动if (distanceToCenter > mExternalOffesetLimit) {//如果点击位置在组件外,则获取点击位置和中心点连线上的一点(该点满足矩形在组件内)为中心作图// oc/oa = od/obvar od = mx1 - centerxvar ob = getDistanceTwoPoint(centerx, centery, mx1, my1)var oc = od / ob * mExternalOffesetLimit// ca/oa = db/obvar db = centery - my1var ac = db / ob * mExternalOffesetLimit//得到ac和oc判断得出a点的位置mx1 = centerx + ocmy1 = centery - acod = mx2 - centerxob = getDistanceTwoPoint(centerx, centery, mx2, my2)oc = od / ob * mInsideOffesetLimit// ca/oa = db/obdb = centery - my2ac = db / ob * mInsideOffesetLimit//得到ac和oc判断得出a点的位置mx2 = centerx + ocmy2 = centery - ac} else {//获得与中点的距离,*2,如图3var ab = my2 - centeryvar bo = mx2 - centerxLogUtils.d("ab:" + ab + "  bo:" + bo)mx2 = centerx + 2f * bomy2 = centery + 2f * abdistanceToCenter = getDistanceTwoPoint(mx1, my1, centerx, centery)if (distanceToCenter > mExternalOffesetLimit) {return super.onTouchEvent(event)}}var left: Int = (mx1 - mr).toInt()var right: Int = (mx1 + mr).toInt()var top: Int = (my1 - mr).toInt()var bottom: Int = (my1 + mr).toInt()//更新背景图绘制区域mExternalDestRect = Rect(left, top, right, bottom)left = (mx2 - mr).toInt()right = (mx2 + mr).toInt()top = (my2 - mr).toInt()bottom = (my2 + mr).toInt()//更新中心图绘制区域mInsideDestRect = Rect(left, top, right, bottom)postInvalidate()}MotionEvent.ACTION_UP -> {LogUtils.d("ACTION_UP")//复原背景图绘制区域mExternalDestRect = Rect((centerx - mr).toInt(), (centery - mr).toInt(),(centerx + mr).toInt(),(centery + mr).toInt())//复原中心图绘制区域mInsideDestRect = Rect((centerx - mr).toInt(), (centery - mr).toInt(),(centerx + mr).toInt(),(centery + mr).toInt())postInvalidate()}}LogUtils.d("---end---")return super.onTouchEvent(event)}

其中最复杂的就是在onMonve里的判断了,首先会判断点击的位置距离组件中心点的距离distanceToCenter,如果这个距离大于我指定的轨迹半径(这里取的是外背景图的轨迹圆半径的四分之一,这样的话偏移量就很小了,更接近于QQ的效果)。 如果点击位置大于这个距离,则执行下面的代码:

 //如果点击位置在组件外,则获取点击位置和中心点连线上的一点(该点满足矩形在组件内)为中心作图// oc/oa = od/obvar od = mx1 - centerxvar ob = getDistanceTwoPoint(centerx, centery, mx1, my1)var oc = od / ob * mExternalOffesetLimit// ca/oa = db/obvar db = centery - my1var ac = db / ob * mExternalOffesetLimit//得到ac和oc判断得出a点的位置mx1 = centerx + ocmy1 = centery - acod = mx2 - centerxob = getDistanceTwoPoint(centerx, centery, mx2, my2)oc = od / ob * mInsideOffesetLimit// ca/oa = db/obdb = centery - my2ac = db / ob * mInsideOffesetLimit//得到ac和oc判断得出a点的位置mx2 = centerx + ocmy2 = centery - ac

这段代码要结合下图来看:

可以看到是根据b点(点击的位置),等比计算出a点的位置(即内外轨迹圆的圆心点),并进行内外背景图的绘制。

如果如果点击位置小于distanceToCenter,则执行下面的代码:

  //获得与中点的距离,*2,如图3var ab = my2 - centeryvar bo = mx2 - centerxLogUtils.d("ab:" + ab + "  bo:" + bo)mx2 = centerx + 2f * bomy2 = centery + 2f * abdistanceToCenter = getDistanceTwoPoint(mx1, my1, centerx, centery)if (distanceToCenter > mExternalOffesetLimit) {return super.onTouchEvent(event)}

结合下图:

可以计算出内圆的圆心点的坐标。这里将内圆的横纵坐标偏移量都延长了两倍,以实现内背景图偏移得更快的效果。

最后全部的代码如下:

/*** Created by GG on 2017/11/2.*/
class CentralTractionButton : RadioButton {//四个图片的idprivate var normalexternalbackground: Int = 0private var normalinsidebackground: Int = 0private var selectedinsidebackground: Int = 0private var selectedexternalbackground: Int = 0//文字private var textdimension: Float = 0fprivate var text: String = ""//绘制图形的画笔private var bmPaint: Paint? = null//图形偏移距离private var offsetDistanceLimit: Float = 0.toFloat()//组件宽高private var mWidth: Float = 0.toFloat()private var mHeight: Float = 0.toFloat()//中心点坐标,相较于屏幕private var centerX: Float = 0.toFloat()private var centerY: Float = 0.toFloat()//中心点坐标,相较于组件内private var centerx: Float = 0.toFloat()private var centery: Float = 0.toFloat()constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {val ta = context.obtainStyledAttributes(attrs, R.styleable.ctattrs)text = ta.getString(R.styleable.ctattrs_text)textdimension = ta.getDimension(R.styleable.ctattrs_textdimension, 1f)normalexternalbackground = ta.getResourceId(R.styleable.ctattrs_normalexternalbackground, 0)normalinsidebackground = ta.getResourceId(R.styleable.ctattrs_normalinsidebackground, 0)selectedinsidebackground = ta.getResourceId(R.styleable.ctattrs_selectedinsidebackground, 0)selectedexternalbackground = ta.getResourceId(R.styleable.ctattrs_selectedexternalbackground, 0)//打印所有的属性val count = attrs.attributeCountfor (i in 0..count - 1) {val attrName = attrs.getAttributeName(i)val attrVal = attrs.getAttributeValue(i)LogUtils.d("attrName = $attrName , attrVal = $attrVal")}ta.recycle()init()}override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {super.onLayout(changed, left, top, right, bottom)mWidth = measuredWidth.toFloat()mHeight = measuredHeight.toFloat()LogUtils.d("onLayout: $mWidth $mHeight")//可供位移的距离offsetDistanceLimit = mWidth / 6centerY = ((getBottom() + getTop()) / 2).toFloat()centerX = ((getRight() + getLeft()) / 2).toFloat()centerx = mWidth / 2centery = mHeight / 2LogUtils.d("中心点坐标: $centerX $centerY")init()}override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {super.onMeasure(widthMeasureSpec, heightMeasureSpec)}//轨迹圆外径的半径mR = obvar mR: Float = 0.toFloat()//背景图图形的半径 = 长宽(这里类似于直径)/2 = ob/2var mr: Float = 0.toFloat()private fun init() {initPaint()LogUtils.d("-----init-----")//得到组件宽高中的较小值,再/2得到ob的距离if (mHeight > mWidth) mR = mHeight / 2 else mR = mWidth / 2LogUtils.d("ob的距离:" + mR)mr = mR / 2// 背景图绘制区域mExternalDestRect = Rect((centerx - mr).toInt(), (centery - mr).toInt(),(centerx + mr).toInt(),(centery + mr).toInt())//初始化: 75 75 225 225// 中心图绘制区域mInsideDestRect = Rect((centerx - mr).toInt(), (centery - mr).toInt(),(centerx + mr).toInt(),(centery + mr).toInt())// 内外的图形externalBD = resources.getDrawable(normalexternalbackground) as BitmapDrawablemExternalSrcRect = Rect(0, 0, externalBD!!.intrinsicWidth, externalBD!!.intrinsicHeight)insidelBD = resources.getDrawable(normalinsidebackground) as BitmapDrawablemInsideSrcRect = Rect(0, 0, insidelBD!!.intrinsicWidth, insidelBD!!.intrinsicHeight)setOnCheckedChangeListener { compoundButton, b ->if (b) {externalBD = resources.getDrawable(selectedexternalbackground) as BitmapDrawableinsidelBD = resources.getDrawable(selectedinsidebackground) as BitmapDrawableval pvhX = PropertyValuesHolder.ofFloat("scaleX", 0.1f,1f)val pvhY = PropertyValuesHolder.ofFloat("scaleY", 0.1f,1f)val objectAnimator = ObjectAnimator.ofPropertyValuesHolder(this, pvhX, pvhY)objectAnimator.duration = 500val overshootInterpolator = OvershootInterpolator(1.2f)objectAnimator.interpolator = overshootInterpolatorobjectAnimator.start()postInvalidate()} else {externalBD = resources.getDrawable(normalexternalbackground) as BitmapDrawableinsidelBD = resources.getDrawable(normalinsidebackground) as BitmapDrawablepostInvalidate()}}}//初始化画笔private fun initPaint() {//绘制图形的画笔bmPaint = Paint()bmPaint!!.isAntiAlias = true//抗锯齿功能bmPaint!!.style = Paint.Style.FILL//设置填充样式   Style.FILL/Style.FILL_AND_STROKE/Style.STROKE}internal var mExternalSrcRect: Rect? = nullinternal var mExternalDestRect: Rect? = nullinternal var mInsideSrcRect: Rect? = nullinternal var mInsideDestRect: Rect? = nullvar externalBD: BitmapDrawable? = nullvar insidelBD: BitmapDrawable? = nulloverride fun onDraw(canvas: Canvas) {super.onDraw(canvas)//暂时画个边框表示范围val bianKuanPaint = Paint()bianKuanPaint.isAntiAlias = truebianKuanPaint.strokeWidth = 2fbianKuanPaint.style = Paint.Style.STROKEbianKuanPaint.color = resources.getColor(R.color.black)canvas.drawRect(0f, 0f, this.width.toFloat(), this.height.toFloat(), bianKuanPaint)//绘制默认状态下背景图val externalBM = externalBD!!.bitmapcanvas.drawBitmap(externalBM, mExternalSrcRect, mExternalDestRect, bmPaint)//绘制默认状态下中心图val insidelBM = insidelBD!!.bitmapcanvas.drawBitmap(insidelBM, mInsideSrcRect, mInsideDestRect, bmPaint)}override fun setOnCheckedChangeListener(listener: CompoundButton.OnCheckedChangeListener) {super.setOnCheckedChangeListener(listener)}override fun onTouchEvent(event: MotionEvent): Boolean {//相较于视图的XYvar mx1 = event.xvar my1 = event.yvar mx2 = event.xvar my2 = event.y  //需要减掉标题栏高度LogUtils.d("---onTouchEvent---")LogUtils.d(" 点击坐标:$mx1 $my1")when (event.action) {MotionEvent.ACTION_DOWN -> {LogUtils.d("ACTION_DOWN")//TODO 弹动一下的动画效果postInvalidate()}MotionEvent.ACTION_MOVE -> {LogUtils.d("ACTION_MOVE:" + scrollX + " " + scrollY)//判断点击位置距离中心的距离var distanceToCenter = getDistanceTwoPoint(mx1, my1, centerx, centery)var mExternalOffesetLimit = mr / 4var mInsideOffesetLimit = mr / 2//如果区域在轨迹圆内则移动if (distanceToCenter > mExternalOffesetLimit) {//如果点击位置在组件外,则获取点击位置和中心点连线上的一点(该点满足矩形在组件内)为中心作图// oc/oa = od/obvar od = mx1 - centerxvar ob = getDistanceTwoPoint(centerx, centery, mx1, my1)var oc = od / ob * mExternalOffesetLimit// ca/oa = db/obvar db = centery - my1var ac = db / ob * mExternalOffesetLimit//得到ac和oc判断得出a点的位置mx1 = centerx + ocmy1 = centery - acod = mx2 - centerxob = getDistanceTwoPoint(centerx, centery, mx2, my2)oc = od / ob * mInsideOffesetLimit// ca/oa = db/obdb = centery - my2ac = db / ob * mInsideOffesetLimit//得到ac和oc判断得出a点的位置mx2 = centerx + ocmy2 = centery - ac} else {//获得与中点的距离,*2,如图3var ab = my2 - centeryvar bo = mx2 - centerxLogUtils.d("ab:" + ab + "  bo:" + bo)mx2 = centerx + 2f * bomy2 = centery + 2f * abdistanceToCenter = getDistanceTwoPoint(mx1, my1, centerx, centery)if (distanceToCenter > mExternalOffesetLimit) {return super.onTouchEvent(event)}}var left: Int = (mx1 - mr).toInt()var right: Int = (mx1 + mr).toInt()var top: Int = (my1 - mr).toInt()var bottom: Int = (my1 + mr).toInt()//更新背景图绘制区域mExternalDestRect = Rect(left, top, right, bottom)left = (mx2 - mr).toInt()right = (mx2 + mr).toInt()top = (my2 - mr).toInt()bottom = (my2 + mr).toInt()//更新中心图绘制区域mInsideDestRect = Rect(left, top, right, bottom)postInvalidate()}MotionEvent.ACTION_UP -> {LogUtils.d("ACTION_UP")//复原背景图绘制区域mExternalDestRect = Rect((centerx - mr).toInt(), (centery - mr).toInt(),(centerx + mr).toInt(),(centery + mr).toInt())//复原中心图绘制区域mInsideDestRect = Rect((centerx - mr).toInt(), (centery - mr).toInt(),(centerx + mr).toInt(),(centery + mr).toInt())postInvalidate()}}LogUtils.d("---end---")return super.onTouchEvent(event)}//得到两点之间的距离fun getDistanceTwoPoint(x1: Float, y1: Float, x2: Float, y2: Float): Float {return Math.sqrt((Math.pow((x1 - x2).toDouble(), 2.toDouble()) +Math.pow((y1 - y2).toDouble(), 2.toDouble()))).toFloat()}}

Github地址: https://github.com/jiangzhengnan/UI
有什么不懂的可以加我微信问我~~

Android——腾讯QQ的Tab按钮动画效果完美实现相关推荐

  1. android的动态tab,Android自定义view仿QQ的Tab按钮动画效果(示例代码)

    话不多说 先上效果图 实现其实很简单,先用两张图 一张是背景的图,一张是笑脸的图片,笑脸的图片是白色,可能看不出来.实现思路:主要是再触摸view的时候同时移动这两个图片,但是移动的距离不一样,造成的 ...

  2. Android实现仿QQ登录界面背景动画效果

    登录QQ的时候,我们会看到在登录界面的背景不是静态的,而是一段动画效果,刚开始觉得蛮好奇的,现在我们也来实现一下这种效果,实现起来还是挺简单的. 实现步骤: 1.自定义CustomVideoView类 ...

  3. html5 特效 背景 腾讯,html5腾讯QQ登录界面背景动画特效

    特效描述:html5 腾讯QQ 登录界面 背景动画特效.腾讯QQ登陆界面动态背景,直接从腾讯网站获取,js代码有加密,做了个简单地示例 代码结构 1. 引入JS 2. HTML代码 *{margin: ...

  4. android切换页面上滑动动画,Android ViewPager多页面滑动切换以及动画效果

    评论 #28楼[楼主] 2012-06-01 14:27D.Winter @孤寒江雪 我猜 要么在头尾各再加入一个页卡 在页卡切换监听中判断,如果选中了头尾的页卡,就返回到相邻的那个页卡.头尾页卡的界 ...

  5. 超炫button按钮动画效果

    今天从网上看到一个这样的效果,感觉很有创意,自己也搜集了一些资料,仿照着实现了一下. 下面就直接上源码: 首先看一下布局文件: <?xml version="1.0" enc ...

  6. 简单的UIButton按钮动画效果iOS源码

    这个是简单的UIButton按钮动画效果案例,源码,简单的UIButton按钮动画,可以自定义button属性. 效果图: <ignore_js_op> 使用方法: 使用时把ButtonA ...

  7. android炫酷动画代码,Android高级UI特效仿直播点赞动画效果

    Android高级UI特效仿直播点赞动画效果 发布时间:2020-10-02 16:06:18 来源:脚本之家 阅读:117 作者:mrr 本文给大家分享高级UI特效仿直播点赞效果-一个优美炫酷的点赞 ...

  8. 【每日一练】68—CSS实现一组渐变按钮动画效果

    在之前,我们也练习过一些按钮动画的效果,今天我们再来练习一组CSS实现的按钮动画效果,下面是今天练习的最终效果: 接下来,我们再来看一下这个案例的源码. HTML代码: <!doctype ht ...

  9. CSS特效(二):利用html和css制作毛玻璃特效和按钮动画效果

    最终的效果图片: 毛玻璃效果:在style标签中,在form表单的before中利用filter的blur属性以及box-shadow的值设置,就可以做出form表单后面的毛玻璃效果背景,还要记得设置 ...

最新文章

  1. windows7 ORA-12514 TNS 监听程序当前无法识别连接描述符中请求服务 的解决方法
  2. Gartner:到2020年人工智能将创造出230万个工作岗位
  3. getRealPath(““)与getRealPath(“/“)区别及用法——计算机网络相关学习笔记
  4. python十分钟教程_简洁的十分钟Python入门教程
  5. zabbix监控Linux系统服务
  6. 你整明白了吗?Linux Shell 中各种括号的作用 ()、(())、[]、[[]]、{}
  7. 基于springboot+vue的旅游信息(旅游线路)网站(前后端分离)
  8. 机器学习算法之KNN算法
  9. JavaScript数组实现图片轮播
  10. Hive安装详细步骤
  11. libiconv android编译,编译cBPM-android-19—CodeBlocks—CentOS7— ndk10—编译libiconv和xerces-c...
  12. python中day_python day02
  13. 云服务器初始化失败怎么办,提示交互式登录进程初始化失败是什么原因?解决方法步骤教程...
  14. 数据报表体系搭建流程
  15. There is no getter for property named ‘keyword‘ in ‘class cn.wolfcode.qo.Subentry‘] with root caus
  16. gmail 无法登录 原因解决
  17. 11 个最佳免费安全网站
  18. 2021-11-09 Cynthia XSS
  19. 做一个蓝色的我,有海的辽阔,有天的色泽,有浪漫的裙褶,有纯洁的底色
  20. 2017奇虎360春招笔试编程

热门文章

  1. 最简单的基于Flash的流媒体示例:网页播放器(HTTP,RTMP,HLS)
  2. 计算机权限删除文件win10,win10系统使用管理员权限无法删除部分文件的详细步骤...
  3. Leap Motion开发第一步环境配置
  4. matlab 棋盘格畸变矫正
  5. Spring Boot,Whitelabel Error Page解决方法
  6. win10安装TeamView 提示rollback framework could not be initialized
  7. oracle 按天数 均值,oracle 按天数统计数据
  8. unity cardboard 导出
  9. 百度地图开发(3)实现本地两点间步行导航
  10. 数学中奇妙的“金蝉脱壳”(转)