守望先锋加载loading在Android上的实现

前言

最近玩守望屁股,看到它的加载loading效果不错,于是便想把它实现到Android上。经过实践,这个小项目已初步完成,全部内容只有一个类文件,可以拷贝到任何Android项目中使用,非常方便。

正文

效果:

守望先锋的效果:


守望先锋loading效果

项目demo的效果:


这里写图片描述

效果还是不错哒!

相关资源

项目托管在github上,获取地址为:https://github.com/zhangyuChen1991/OverWatchLoading 欢迎前去下载。

实现过程及原理

先梳理一下整体流程吧,其实整个view就是在不断的重绘,一共有7个六边形,我们不断的重置每个六边形的大小和透明度然后绘制整个图案,因为刷新频率达到了一定的高度,所以就等于一帧一帧的放出画面,就有了最后的动画效果了。从头到尾我们所做的工作就是不断的画7个大小和透明度不同的正六边形,要做这些工作我们必须要先计算出一些需要的数据——绘制所用的基准数据。第一步,为了获取这些数据,我们得先画出完整大小的7个六边形的图案。
1.绘制正六边形
首先我们来看看loading的全部图案都显示的情况是什么样的吧。

完整图案
添加辅助线后的图案

一共是七个正六边形,而且排列非常规律,各个六边形之间有相同的间隔距离。如果没有这个间隔,它们边与边之间就是完全重合的,只要确定了整个图案中心点坐标,就可以确定所有需要的点的坐标位置,进而画出整个图案。现在多了一个间隔,稍微增加了一点计算的复杂度,暂时先不管它。
我们一步一步来看,首先,画一个六边形的关键就是确定它的中心点和半径,有了这两个值,就可以确定其余六个顶点的位置。如下图所示:

示意图

图中示例了一个点的计算方式,其余各点类同。
有了中心点坐标位置和它的半径就可以计算出它每个顶点的坐标位置进而绘制出完整的正六边形图案。
所以我们第一步要做的就是确定所有七个六边形的中心点坐标。首先,最中心那个最容易确定,就把它设定在整个view的中心,并确立为基准,以此来计算其他周围六个六边形的中心点坐标。
如果没有那个间隙,其余六个中心点也很好找,但是麻烦就在这个间隙。我们把六边形之间的间隙分开来看,即横向间隙和纵向间隙。如下图:
间隙示意图

图中由于做了圆角处理,所以纵向间隙标的不准确,其实就是两个顶点之间的垂直距离了。我们在计算的时候,设定水平间距与竖直间距相等,由自己设置一个合适的距离。然后根据中心点坐标、六边形半径和间距计算边围六边形的中心点。如下图:
这里写图片描述

我们获得R和r的关系过后,就可以根据根据r、中心点坐标和间距来计算各个边围六边形的中心点了,也就是图中蓝色六边形的顶点。原理同上。
计算出各六边形的中心点之后,我们计算出它们的顶点也就手到擒来了。这里要注意的是,计算顶点的时候,我们要添加一个缩放值作为参数,以方便以后实现动画效果做计算的时候用。至此,所有我们需要的数据都已经获取完毕。
在代码实现时,首先获取整个view的宽高,确定中心点。

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     viewHeight = getMeasuredHeight();
     viewWidth = getMeasuredWidth();
     if (viewWidth != 0 && viewHeight != 0) {
         center.x = viewWidth / 2f;
         center.y = viewHeight / 2f;
         ...
         ...
     }
 }

计算7个六边形的中心点:

private void initHexagonCenters() {
    float bigR = (float) ((1.5 * hexagonRadius + space) / cos30);
    hexagonCenters[0] = new Point(center.x - bigR * sin30, center.y - bigR * cos30);
    hexagonCenters[1] = new Point(center.x + bigR * sin30, center.y - bigR * cos30);
    hexagonCenters[2] = new Point(center.x + bigR, center.y);
    hexagonCenters[3] = new Point(center.x + bigR * sin30, center.y + bigR * cos30);
    hexagonCenters[4] = new Point(center.x - bigR * sin30, center.y + bigR * cos30);
    hexagonCenters[5] = new Point(center.x - bigR, center.y);
    //左上角为第0个,中心六边形为第6个
    for (int i = 0; i < 6; i++) {
        hexagons[i] = new Hexagon(hexagonCenters[i], hexagonRadius);
    }
    hexagons[6] = new Hexagon(center, hexagonRadius);
}

创建Point类和一个六边形的类,来储存相关数据。

private class Point {
    public float x, y;

    public Point(float x, float y) {
        this.x = x;
        this.y = y;
    }

    public Point() {
    }
}

 /**
  * 六边形
  */
 private class Hexagon {

     //缩放值
     private float scale = 1;
     //透明度
     private int alpha = 255;
     public Point centerPoint;
     public float radius;
     //六个顶点
     private Point[] vertexs = new Point[6];

     public Hexagon(Point centerPoint, float radius) {
         this.centerPoint = centerPoint;
         this.radius = radius;
         calculatePointsPosition();
     }

     public void drawHexagon(Canvas canvas, Paint paint) {
         paint.setAlpha(alpha);
         canvas.drawPath(getPath(), paint);
     }

     private int calculatePointsPosition() {
         if (centerPoint == null) {
             return -1;
         }
         //从最上方顺时针数1-6给各顶点标序号 共6个点,scale作为动态参数,用以控制六边形的大小,围绕中心点做缩放变化
         vertexs[0] = new Point(centerPoint.x, centerPoint.y - radius * scale);
         vertexs[1] = new Point(centerPoint.x + radius * cos30 * scale, centerPoint.y - radius * sin30 * scale);
         vertexs[2] = new Point(centerPoint.x + radius * cos30 * scale, centerPoint.y + radius * sin30 * scale);
         vertexs[3] = new Point(centerPoint.x, centerPoint.y + radius * scale);
         vertexs[4] = new Point(centerPoint.x - radius * cos30 * scale, centerPoint.y + radius * sin30 * scale);
         vertexs[5] = new Point(centerPoint.x - radius * cos30 * scale, centerPoint.y - radius * sin30 * scale);
         return 1;
     }

    //根据六个顶点绘制path
     private Path getPath() {
         Path path = new Path();
         for (int i = 0; i < 6; i++) {
             if (i == 0)
                 path.moveTo(vertexs[i].x, vertexs[i].y);
             else
                 path.lineTo(vertexs[i].x, vertexs[i].y);
         }
         path.close();
         return path;
     }
     ...
     ...
}

2.动画实现
到上一步,六边形的绘制就算基本完成了,现在我们要对它们做动画。由效果图中我们可以看见loading的动画流程是依次渐变出现,再依次渐变消失,循环往复。这里有一个细节要主意,各个六边形并不是等上一个完全出现之后才开始动画的,而是上一个还没有完全完成动画时这一个就开始自己的动画过程了,这样看起来才有连贯流畅的动画效果,实现的时候是需要注意的。
理清了流程,我们就开始想办法实现吧。首先,从第一个开始,缩放值(scale)由原始值的0倍变大到1倍,透明度(alpha)由0变大到255(由paint.setAlpha(alpha)参数值决定)。然后,当第一个放大到一定大小时,第二个开始动画,依次类推,直到第七个完全出现时,第一个开始缩小并逐步变透明,缩小到一定大小时,第二个开始缩小...依次类推,然后循环往复。
在代码实现上,我们利用一个线程来更新数据,由它来触发每一次的变化,并在变化完成后刷新整个view完成重绘。

//更新数据的线程
private Runnable animRunnable = new Runnable() {
   @Override
   public void run() {
       try {
           while (runAnim) {
               Thread.sleep(2);
               flush();
               draw();
           }
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
   }
};

/**
 * 更新数据
 */
private void flush() {
    if (nowAnimatorFlag == ShowAnimatorFlag) {//逐个显示出来
        hexagons[0].addScale();
        hexagons[0].addAlpha();
        for (int i = 0; i < hexagons.length - 1; i++) {
            if (hexagons[i].getScale() >= scaleCritical) {
                hexagons[i + 1].addScale();
                hexagons[i + 1].addAlpha();
            }
        }

         if (hexagons[6].getScale() == 1) {//当最后一个六边形都完全显示时,切换模式,下一轮逐个消失
             nowAnimatorFlag = HideAnimatorFlag;
         }

     } else {//逐个消失
         hexagons[0].subScale();
         hexagons[0].subAlpha();
         for (int i = 0; i < hexagons.length - 1; i++) {
             if (hexagons[i].getScale() <= 1 - scaleCritical) {
                 hexagons[i + 1].subScale();
                 hexagons[i + 1].subAlpha();
             }
         }
         if (hexagons[6].getScale() == 0) {//当最后一个六边形都完全消失时,切换模式,下一轮逐个开始显示
             nowAnimatorFlag = ShowAnimatorFlag;
         }
     }
}

最后,在draw()方法里面,绘制六边形就可以了

/**
     * 绘制图案
     */
private void draw() {
    Canvas canvas = surfaceHolder.lockCanvas();
    paint.setXfermode(new PorterDuffXfermode(android.graphics.PorterDuff.Mode.CLEAR));
    canvas.drawPaint(paint);
    paint.setXfermode(new PorterDuffXfermode(android.graphics.PorterDuff.Mode.DST_OVER));

    for (int i = 0; i < 7; i++) {
        hexagons[i].drawHexagon(canvas, paint);
    }
    surfaceHolder.unlockCanvasAndPost(canvas);
}

所以到这里,整个实现的流程就都捋了一遍了,你清楚了吗?

尾声

最后,再贴一次项目地址吧 https://github.com/zhangyuChen1991/OverWatchLoading
有兴趣的童鞋可以前去下载,欢迎提出意见!且如发现问题,请斧正!非常感谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,391评论 25 707
  • 第三章 管线一览 本章我们会学到什么 OpenGL管线的每个阶段做什么的 如果连接着色器和固定功能管线阶段 如果创...
    葭五阅读 6,208评论 2 18
  • 一:canvas简介 1.1什么是canvas? ①:canvas是HTML5提供的一种新标签 ②:HTML5 ...
    GreenHand1阅读 4,660评论 2 32
  • 可能生活很辛苦,很无趣,但只要你愿意,还是能活出春意盎然。 今天妖风很大,路口的妹子长裙被风吹得很张扬,抱着肩膀抱...
    是你吗x阅读 271评论 3 0