├── .gitignore ├── FancyDrawable.apk ├── README-BEZIER.md ├── README.md ├── android-fancy-drawable.iml ├── bezierinterpolator ├── .gitignore ├── bezierinterpolator.iml ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── ivonhoe │ │ └── android │ │ └── bezierinterpolator │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── ivonhoe │ │ │ └── android │ │ │ └── bezierinterpolator │ │ │ ├── BezierCurve.java │ │ │ ├── BezierInterpolator.java │ │ │ ├── Curve.java │ │ │ ├── CurveInterpolator.java │ │ │ ├── CurveSampler.java │ │ │ ├── DisplacementSampler.java │ │ │ └── Point.java │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── ivonhoe │ └── android │ └── bezierinterpolator │ └── ExampleUnitTest.java ├── build.gradle ├── build └── generated │ └── mockable-android-25.jar ├── fancydrawable ├── .gitignore ├── build.gradle ├── fancydrawable.iml ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── ivonhoe │ │ └── android │ │ └── fancydrawable │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── ivonhoe │ │ │ └── android │ │ │ └── fancydrawable │ │ │ ├── AtomDrawable.java │ │ │ ├── AtomStyle.java │ │ │ ├── BallsCircleDrawable.java │ │ │ ├── BallsLineDrawable.java │ │ │ ├── ColorfulDrawable.java │ │ │ ├── FancyDrawable.java │ │ │ ├── GridDrawable.java │ │ │ ├── NetEaseDrawable.java │ │ │ ├── StreakDrawable.java │ │ │ ├── TaoBaoDrawable.java │ │ │ ├── TextDrawable.java │ │ │ └── YouZanDrawable.java │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── ivonhoe │ └── android │ └── fancydrawable │ └── ExampleUnitTest.java ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── libs └── BezierInterpolator.jar ├── local.properties ├── sample ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── sample.iml └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── ivonhoe │ │ └── demo │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── ivonhoe │ │ │ └── android │ │ │ └── fancyDrawable │ │ │ └── MyActivity.java │ └── res │ │ ├── layout │ │ └── main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── ivonhoe │ └── demo │ └── ExampleUnitTest.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | /.idea/ 3 | /gen/ 4 | -------------------------------------------------------------------------------- /FancyDrawable.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ivonhoe/FancyDrawable/006586eecf26be8fdfcc922a7b4739b849e9e77a/FancyDrawable.apk -------------------------------------------------------------------------------- /README-BEZIER.md: -------------------------------------------------------------------------------- 1 | # BezierInterpolator 2 | [基于贝塞尔曲线的安卓动画插值器](https://ivonhoe.github.io/2015/04/17/%E5%9F%BA%E4%BA%8E%E8%B4%9D%E5%A1%9E%E5%B0%94%E6%9B%B2%E7%BA%BF%E7%9A%84Android%E5%8A%A8%E7%94%BB%E5%B7%AE%E5%80%BC%E5%99%A8/) 3 | --- 4 | 5 | 6 | ## 一、简介 7 | 贝塞尔曲线于1962,由法国工程师皮埃尔·贝塞尔(Pierre Bézier)所广泛发表,他运用贝塞尔曲线来为汽车的主体进行设计。贝塞尔曲线最初由Paul de Casteljau于1959年运用de Casteljau算法开发,以稳定数值的方法求出贝兹曲线。贝塞尔曲线不仅仅可以应用到工业设计中,在计算机动画开发中同样占有一席之地,通过构造贝塞尔曲线模拟物体运动的轨迹、速度甚至加速度,来达到想要的动画效果。在CSS开发中使用‘cubic-bezier’方法,传递三次贝赛尔曲线的两个控制点P1和P2来生成一条平滑的曲线。甚至也有很多javaScript动画库使用贝赛尔曲线来实现完美的动画效果。 8 | 9 | ![通过P1和P2控制曲线](https://ivonhoe.github.io/res/bezier/TimingFunction.png) 10 | 11 | 12 | 13 | 而我要做的通过贝塞尔曲线的原理生成Android动画插值器,在Android平台上实现基于贝赛尔曲线的动画效果。想要了解Android动画原理请先阅读[这篇文章](https://ivonhoe.github.io/2015/02/09/Android%E5%8A%A8%E7%94%BB%E6%80%BB%E7%BB%93/)。了解贝塞尔曲线绘制过程可以先阅读[贝塞尔曲线扫盲](http://www.html-js.com/article/1628),写的很好。 14 | 15 | 16 | ## 二、De Casteljau算法 17 | 18 | ![二次贝塞尔曲线](https://ivonhoe.github.io/res/bezier/decu.png) 19 | 20 | 贝塞尔曲线常见的算法是可以通过多项式、De Casteljau算法和递归算法来进行计算。针对De算法,设P0,P1,P2确定了一条二次贝塞尔曲线q,引入参数t,令![](https://ivonhoe.github.io/res/bezier/1.gif)即有: 21 | 22 | ![ ](https://ivonhoe.github.io/res/bezier/2.gif) 23 | 24 | 当t从0变到1,第一、二式是两条一次Bezier曲线。将一、二式代入第三式得到: 25 | 26 | ![ ](https://ivonhoe.github.io/res/bezier/5.gif) 27 | 28 | 当t从0变到1时,它表示了由P0、P1、P2三个控制点形成一条二次Bezier曲线。并且这个二次Bezier曲线可以分别由前两个顶点(P0,P1)和后两个顶点(P1,P2)决定的一次Bezier曲线的线性组合。以此类推,由四个控制点定义的三次Bezier曲线可被定义为分别由(P0,P1,P2)和(P1,P2,P3)确定的两条二次Bezier曲线的线性组合,由(n+1)个控制点定义的n次Bezier曲线可被定义为分别由前后n个控制点定义的两条(n-1)次Bezier曲线的线性组合: 29 | 30 | ![ ](https://ivonhoe.github.io/res/bezier/3.gif) 31 | 32 | 由此得到Bezier曲线的递推计算公式: 33 | 34 | ![ ](https://ivonhoe.github.io/res/bezier/4.gif) 35 | 36 | 这就是De Casteljau算法。使用这个递推公式,在给定参数下,求Bezier曲线上一点P(t)非常有效。 37 | 38 | ## 三、Bezier动画插值器实现 39 | 40 | 基于De Casteljau算法的递推公式求曲线上点的坐标: 41 | 42 | public static Point deCasteljau(Point[] points, float t) { 43 | final int n = points.length; 44 | for (int i = 1; i <= n; i++) 45 | for (int j = 0; j < n - i; j++) { 46 | points[j].x = (1 - t) * points[j].x + t * points[j + 1].x; 47 | points[j].y = (1 - t) * points[j].y + t * points[j + 1].y; 48 | } 49 | 50 | return points[0]; 51 | } 52 | De Casteljau算法目的是求得曲线上的每一个点,如何用这些采样点描述一条曲线插值器还需要进一步的处理。主要就是处理精度的问题,用距离很近的两个点连线的线段近似描述曲线,理论上采样点越密集描述的越准确,但是很明显在实际项目中不能选择太多的采样点,因为要考虑内存和效率的问题。所以用尽可能少的点尽可能精细的用直线段描述一条曲线,一个不错的做法就是在采样点的在横坐标方向上不要等距分布,而是在曲线变化较快的地方(即斜率较大)采样点尽可能的密集,而在曲线变化平缓的地方采样点选择稀疏。所以需要在通过De Casteljau算法获取初步的采样点后,再进一步获取非均匀分布的采样点,更加处理后的采样点再进行计算。 53 | 54 | 考虑到针对不同动画的编辑,可能不仅仅是动画进度的插值,还需要动画速度的插值和动画变化率的插值,针对不同曲线类型的变化,通过下面的方式进行扩展。详细代码可以查看[这里](https://github.com/Ivonhoe/BezierInterpolator) 55 | 56 | 57 | ## 四、使用范例 58 | 59 | 通过控制点构造贝塞尔曲线插值器,例如,可以通过构造一个特殊的插值器,控制drawable的重绘和每个小球的动画并重绘drawable就可以实现类似window phone上经典的Balls Line进度条效果,详细实现可以参考[这里](https://ivonhoe.github.io/2015/04/28/Drawable-%E4%BB%8E%E7%AE%80%E5%8C%96%E5%B8%83%E5%B1%80%E8%B0%88%E8%B5%B7/)。 60 | 61 | ![window 进度条](https://ivonhoe.github.io/res/bezier/windows_balls_line.GIF) 62 | 63 | 通过两个控制点构造三次贝赛尔插值器(默认会增加(0,0)和(1,1)作为控制点)。 64 | 65 | if (mBezierInterpolator == null) { 66 | // 贝塞尔插值器 67 | mBezierInterpolator = new BezierInterpolator(0.03f, 0.615f, 0.995f, 0.415f); 68 | } 69 | if (mLinearInterpolator == null){ 70 | // 普通线性插值器 71 | mLinearInterpolator = new LinearInterpolator(); 72 | } 73 | 74 | 同样可以传递包含所有控制点的List构造插值器。 75 | 76 | mBezierInterpolator = new BezierInterpolator(new ArrayList()); 77 | 78 | 针对BezierInterpolator的构造会有一定的耗时,所以并不建议在需要用到的时候才去构造,也不建议频繁的构造相同的插值器实例。 79 | 80 | ## 五、参考文档 81 | 82 | [3D计算机图形学 Samuel R.Buss](http://baike.baidu.com/view/10167166.htm) 83 | [Bezier曲线的算法描述及其程序实现](http://wenku.baidu.com/view/2beaa4bc960590c69ec376cd.html) 84 | [在线贝塞尔曲线编辑器](http://cubic-bezier.com/) 85 | [window phone loading animation](http://thecodeplayer.com/walkthrough/windows-phone-loading-animation) 86 | 87 | ## 六、Github 88 | 89 | [https://github.com/Ivonhoe/BezierInterpolator](https://github.com/Ivonhoe/BezierInterpolator) 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | 4 | ## Android动画总结之有赞、邮箱大师进度条实现原理 5 | 6 | ### [原文地址](https://ivonhoe.github.io/2015/09/01/Android%E5%8A%A8%E7%94%BB%E6%80%BB%E7%BB%93%E4%B9%8B%E6%9C%89%E8%B5%9E%E3%80%81%E9%82%AE%E7%AE%B1%E5%A4%A7%E5%B8%88%E8%BF%9B%E5%BA%A6%E6%9D%A1%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/) 7 | 8 | ### 相关文章 [基于贝塞尔曲线的安卓动画插值器](https://github.com/Ivonhoe/android-fancy-drawable/blob/master/README-BEZIER.md) 9 | 10 | ## 一、效果 11 | ![ ](https://ivonhoe.github.io/res/android_animation2/xiaoguo.gif) 12 | 13 | ## 二、有赞加载进度条 14 | 15 | 四个正方形的运动可以分解成两个分运动,一个是平移运动,一个是自身的旋转运动。在实现这个动画上有两个思路: 16 | - 一个是通过Android提供的Animation或者Animator操作视图,让一个正方形的视图在translate动画的同时进行rotate动画,只需要设置rotate动画的pivot坐标为视图的中心点就可以了。 17 | - 另一个是直接在canvas上绘制正方形,通过canvas的坐标变换实现动画。 18 | 19 | 20 | 下面主要说第二种方式的原理: 21 | - 平移:平移canvas,在平行与手机屏幕的平面坐标系中,水平向右方向为X轴,竖直向下方向为Y轴,把原点平移到视图的中心点,只需要在水平和竖直的正方向平移就可以了。 22 | ``` 23 | canvas.translate(mBounds.width() / 2, mBounds.height() / 2); 24 | ``` 25 | 26 | - 旋转:想要完成一个矩形围绕其中心点顺时针旋转一个角度,首先要意识到旋转的过程中,只改变了坐标系的方向并没有改变坐标系的原点位置。换句话说,如果你需要围绕坐标系原点做旋转,那么你只需要旋转操作,如果你需要围绕除了原点以外的另外一个点(比如当前现在正方形的中心点),那么你需要先做平移操作,先把坐标系平移到一个正确的点在做旋转操作。如图所示: 27 | ![ ](https://ivonhoe.github.io/res/android_animation2/xuanzhuanzuobiao.png) 28 | 29 | ``` 30 | canvas.translate((float) (x + (1 - 1.414f * Math.sin((45 - degree) / 180f * Math.PI)) * halfLength),(float) (y - (1.414f * Math.cos((45 - degree) / 180f * Math.PI) - 1) * halfLength)); 31 | canvas.rotate(degree); 32 | drawable.draw(canvas); 33 | ``` 34 | 35 | - 动画插值:可以看到,每个正方形的平移运动周期是从起始点回到起始点,在时间的中点上达到平移的最大值。反应在平面坐标中的情况就是,一条通过(0, 0), (0.5, 1),(1, 0)三点,在(0.5, 1)达到最大值的一元二次方程。可以间接得到这个插值器是: 36 | ![ ](https://ivonhoe.github.io/res/android_animation2/chazhiqi.jpg) 37 | 38 | ``` 39 | // 过(0,0),(0.5,1),(1,0)的一元二次方程 40 | Interpolator mInterpolator = new Interpolator() { 41 | @Override 42 | public float getInterpolation(float input) { 43 | return -4 * input * input + 4 * input; 44 | } 45 | }; 46 | ``` 47 | 48 | ## 三、网易邮箱大师加载进度条 49 | 50 | 圆弧的动画需要分解成四个分动画: 51 | - 画笔宽度变化:绘制圆弧的画笔宽度在动画 52 | - 圆弧长度变化:绘制圆弧的长度在动画 53 | - 旋转变化:绘制每段圆弧的起点在动画 54 | - 圆弧半径变化:绘制圆弧的半径在动画 55 | 56 | ``` 57 | public ObjectAnimator[] getAtomAnimator(Atom atom, Rect bound) { 58 | ObjectAnimator[] result = new ObjectAnimator[4]; 59 | result[0] = ObjectAnimator.ofFloat(atom, "delta", 4f, 9f); 60 | result[0].setInterpolator(mPaintInterpolator); 61 | switch (atom.getId()) { 62 | case 0: 63 | result[1] = ObjectAnimator.ofInt(atom, "rotate", 0, 360); 64 | break; 65 | case 1: 66 | result[1] = ObjectAnimator.ofInt(atom, "rotate", 120, 480); 67 | break; 68 | case 2: 69 | result[1] = ObjectAnimator.ofInt(atom, "rotate", 240, 600); 70 | break; 71 | default: 72 | throw new RuntimeException(); 73 | } 74 | result[1].setInterpolator(mRotateInterpolator); 75 | result[2] = ObjectAnimator.ofFloat(atom, "length", 80f, 59f); 76 | result[2].setInterpolator(mPaintInterpolator); 77 | result[3] = ObjectAnimator.ofFloat(atom, "r", 0, mScaleFactor * getIntrinsicWidth()); 78 | result[3].setInterpolator(mPaintInterpolator); 79 | return result; 80 | } 81 | ``` 82 | 83 | ## 四、Canvas图形变换原理 84 | 85 | ### 2.1、平移 86 | 设图形上点P(x, y),在x轴和y轴方向分别移动Tx和Ty,结果生成新的点P'(x', y'),则: 87 | ![ ](https://ivonhoe.github.io/res/android_animation2/pingy0.png) 88 | 89 | 用矩阵形式可表示为: 90 | ![ ](https://ivonhoe.github.io/res/android_animation2/pingyi.png) 91 | 平移变换矩阵为: 92 | ![ ](https://ivonhoe.github.io/res/android_animation2/pingyi2.png) 93 | ![ ](https://ivonhoe.github.io/res/android_animation2/pingyimatrix_副本.png) 94 | 95 | ### 2.2、缩放 96 | 设图形上的点P(x, y)在x轴和y轴方向分别作Sx倍和Sy倍的缩放,结果生成新的点坐标P'(x', y'),则: 97 | ![ ](https://ivonhoe.github.io/res/android_animation2/suofang.png) 98 | 用矩阵表示为: 99 | ![ ](https://ivonhoe.github.io/res/android_animation2/suofang2.png) 100 | 比例变换矩阵为: 101 | ![ ](https://ivonhoe.github.io/res/android_animation2/suofang3.png) 102 | ![ ](https://ivonhoe.github.io/res/android_animation2/suofangMatrix_副本.png) 103 | 104 | 105 | ### 2.3、旋转 106 | 设点P(x, y)绕原点旋转变换θ角度(假设按逆时针旋转为正角),生成的新的点坐标P'(x', y'),则: 107 | ![ ](https://ivonhoe.github.io/res/android_animation2/xuanzhuan1.png) 108 | 用矩阵表示为: 109 | ![ ](https://ivonhoe.github.io/res/android_animation2/xuanzhuan2.png) 110 | 旋转变换矩阵为: 111 | ![ ](https://ivonhoe.github.io/res/android_animation2/xuanzhuan3.png) 112 | ![ ](https://ivonhoe.github.io/res/android_animation2/xuanzhuanMatrix_副本.png) -------------------------------------------------------------------------------- /android-fancy-drawable.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /bezierinterpolator/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /bezierinterpolator/bezierinterpolator.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /bezierinterpolator/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.2" 6 | 7 | defaultConfig { 8 | minSdkVersion 15 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | 13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 14 | 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | } 23 | 24 | dependencies { 25 | compile fileTree(dir: 'libs', include: ['*.jar']) 26 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 27 | exclude group: 'com.android.support', module: 'support-annotations' 28 | }) 29 | compile 'com.android.support:appcompat-v7:25.3.1' 30 | testCompile 'junit:junit:4.12' 31 | } 32 | -------------------------------------------------------------------------------- /bezierinterpolator/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/Ivonhoe/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /bezierinterpolator/src/androidTest/java/ivonhoe/android/bezierinterpolator/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package ivonhoe.android.bezierinterpolator; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("ivonhoe.android.bezierinterpolator.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /bezierinterpolator/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /bezierinterpolator/src/main/java/ivonhoe/android/bezierinterpolator/BezierCurve.java: -------------------------------------------------------------------------------- 1 | package ivonhoe.android.bezierinterpolator; 2 | 3 | import android.util.Log; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by ivonhoe on 15-4-15. 9 | */ 10 | public class BezierCurve extends Curve { 11 | 12 | public BezierCurve(Point... points) { 13 | super(points); 14 | } 15 | 16 | public BezierCurve(List points) { 17 | super(points); 18 | } 19 | 20 | @Override 21 | public Point getPathPoint(float t) { 22 | final int size = mControlPoints.length; 23 | if (size < 2) { 24 | return new Point(0, 0); 25 | } 26 | 27 | for (int i = 0; i < size; i++) { 28 | if (mTempPoints[i] == null) { 29 | mTempPoints[i] = new Point(); 30 | } 31 | mTempPoints[i].set(mControlPoints[i]); 32 | } 33 | return deCasteljau(mTempPoints, t); 34 | } 35 | 36 | public static Point deCasteljau(Point[] points, float t) { 37 | final int n = points.length; 38 | for (int i = 1; i <= n; i++) 39 | for (int j = 0; j < n - i; j++) { 40 | points[j].x = (1 - t) * points[j].x + t * points[j + 1].x; 41 | points[j].y = (1 - t) * points[j].y + t * points[j + 1].y; 42 | } 43 | 44 | return points[0]; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /bezierinterpolator/src/main/java/ivonhoe/android/bezierinterpolator/BezierInterpolator.java: -------------------------------------------------------------------------------- 1 | package ivonhoe.android.bezierinterpolator; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * Created by ivonhoe on 15-4-15. 8 | */ 9 | public class BezierInterpolator extends CurveInterpolator { 10 | 11 | public BezierInterpolator(float x1, float y1, float x2, float y2) { 12 | List controlPoints = new ArrayList(); 13 | controlPoints.add(new Point(0f, 0f)); 14 | controlPoints.add(new Point(x1, y1)); 15 | controlPoints.add(new Point(x2, y2)); 16 | controlPoints.add(new Point(1f, 1f)); 17 | buildSampler(controlPoints); 18 | } 19 | 20 | public BezierInterpolator(List points) { 21 | buildSampler(points); 22 | } 23 | 24 | private void buildSampler(List controlPoints) { 25 | BezierCurve bezierCurve = new BezierCurve(controlPoints); 26 | setCurveType(bezierCurve); 27 | DisplacementSampler samplers = new DisplacementSampler(); 28 | setSamplerUsage(samplers); 29 | build(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /bezierinterpolator/src/main/java/ivonhoe/android/bezierinterpolator/Curve.java: -------------------------------------------------------------------------------- 1 | package ivonhoe.android.bezierinterpolator; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Created by ivonhoe on 15-4-15. 7 | */ 8 | public abstract class Curve { 9 | 10 | protected Point[] mControlPoints; 11 | 12 | protected Point[] mTempPoints; 13 | 14 | public Curve(Point... points) { 15 | setControlPoints(points); 16 | } 17 | 18 | public Curve(List points) { 19 | setControlPoints(points); 20 | } 21 | 22 | public void setControlPoints(Point... points) { 23 | mControlPoints = points; 24 | } 25 | 26 | public void setControlPoints(List points) { 27 | mControlPoints = new Point[points.size()]; 28 | mTempPoints = new Point[points.size()]; 29 | points.toArray(mControlPoints); 30 | } 31 | 32 | public abstract Point getPathPoint(float t); 33 | } 34 | -------------------------------------------------------------------------------- /bezierinterpolator/src/main/java/ivonhoe/android/bezierinterpolator/CurveInterpolator.java: -------------------------------------------------------------------------------- 1 | package ivonhoe.android.bezierinterpolator; 2 | 3 | import android.view.animation.Interpolator; 4 | 5 | /** 6 | * Created by ivonhoe on 15-4-15. 7 | */ 8 | public class CurveInterpolator implements Interpolator { 9 | 10 | private Curve mCurve; 11 | 12 | private CurveSampler mCurveSampler; 13 | 14 | private CurveListener mListener; 15 | 16 | private float mCurrentTime; 17 | 18 | // 设置曲线类型,是贝塞尔曲线还是其他曲线 19 | public void setCurveType(Curve curve) { 20 | mCurve = curve; 21 | } 22 | 23 | // 设置曲线的用途,表示是位移?速度?还是加速度? 24 | public void setSamplerUsage(CurveSampler sampler) { 25 | mCurveSampler = sampler; 26 | } 27 | 28 | public void build() throws RuntimeException { 29 | if (mCurve == null) { 30 | throw new RuntimeException( 31 | "Can not build curve sampler without curve,please create curve first"); 32 | } 33 | if (mCurveSampler == null) { 34 | mCurveSampler = new DisplacementSampler(); 35 | } 36 | 37 | mCurveSampler.attach(mCurve); 38 | } 39 | 40 | @Override 41 | public float getInterpolation(float input) { 42 | float result; 43 | mCurrentTime = input; 44 | if (mCurveSampler == null) { 45 | result = input; 46 | } else { 47 | result = mCurveSampler.getSamplerValue(input); 48 | } 49 | 50 | notify(input, result); 51 | return result; 52 | } 53 | 54 | public float getAnimatedTime() { 55 | return mCurrentTime; 56 | } 57 | 58 | public void setInterpolatorListener(CurveListener listener) { 59 | mListener = listener; 60 | } 61 | 62 | public void notify(float input, float result) { 63 | if (mListener != null) 64 | mListener.drawDistance(input, result); 65 | } 66 | 67 | public interface CurveListener { 68 | public void drawDistance(float input, float result); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /bezierinterpolator/src/main/java/ivonhoe/android/bezierinterpolator/CurveSampler.java: -------------------------------------------------------------------------------- 1 | package ivonhoe.android.bezierinterpolator; 2 | 3 | /** 4 | * Created by ivonhoe on 15-4-17. 5 | */ 6 | public abstract class CurveSampler { 7 | 8 | private final static int SAMPLER_SIZE = 100; 9 | 10 | private Curve mCurve; 11 | protected float[] mSamplers; 12 | 13 | public void attach(Curve curve) { 14 | mCurve = curve; 15 | mSamplers = buildSamplers(mCurve, SAMPLER_SIZE); 16 | } 17 | 18 | protected abstract float[] buildSamplers(Curve curve, int size); 19 | 20 | protected abstract float getSamplerValue(float t); 21 | 22 | protected static float interpolateValue(float[] values, float t) { 23 | int size = values.length; 24 | float interval = 1.f / (size - 1); 25 | 26 | int floorIndex = (int) ((size - 1) * t); 27 | int ceilIndex = Math.min(floorIndex + 1, size - 1); 28 | if (floorIndex == ceilIndex) { 29 | return values[size - 1]; 30 | } 31 | 32 | float floor = floorIndex * interval; 33 | float ceil = floor + interval; 34 | return linearInterpolate(t, floor, values[floorIndex], ceil, values[ceilIndex]); 35 | } 36 | 37 | protected static float linearInterpolate(float t, float t0, float v0, float t1, float v1) { 38 | float factor = (t - t0) / (t1 - t0); 39 | return v0 + factor * (v1 - v0); 40 | } 41 | 42 | protected static int searchNearestFloor(Point[] points, int start, float x) { 43 | int low = start; 44 | int mid = start; 45 | int high = points.length - 1; 46 | while (low <= high && points[low].x <= x) { 47 | mid = (low + high) >>> 1; 48 | float midVal = points[mid].x; 49 | if (midVal < x) 50 | low = mid + 1; 51 | else if (midVal > x) 52 | high = mid - 1; 53 | else 54 | return mid; 55 | } 56 | return mid; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /bezierinterpolator/src/main/java/ivonhoe/android/bezierinterpolator/DisplacementSampler.java: -------------------------------------------------------------------------------- 1 | package ivonhoe.android.bezierinterpolator; 2 | 3 | import android.util.Log; 4 | 5 | /** 6 | * Created by ivonhoe on 15-4-17. 7 | */ 8 | public class DisplacementSampler extends CurveSampler { 9 | 10 | @Override 11 | protected float[] buildSamplers(Curve curve, int size) { 12 | float[] displacementSamplers = new float[size]; 13 | 14 | int superSamplerSize = 4 * size; 15 | 16 | Point[] superSamplers = new Point[superSamplerSize]; 17 | for (int i = 0; i < superSamplerSize; i++) { 18 | Point point = curve.getPathPoint(i / (float) (superSamplerSize - 1)); 19 | superSamplers[i] = new Point(point.x, point.y); 20 | } 21 | 22 | int prevFloorIndex = 0; 23 | for (int i = 0; i < size; i++) { 24 | float t = i / (float) (size - 1); 25 | 26 | int floorIndex = searchNearestFloor(superSamplers, prevFloorIndex, t); 27 | int ceilIndex = Math.min(floorIndex + 1, superSamplerSize - 1); 28 | 29 | float t0 = superSamplers[floorIndex].x; 30 | float t1 = superSamplers[ceilIndex].x; 31 | 32 | float d0 = superSamplers[floorIndex].y; 33 | float d1 = superSamplers[ceilIndex].y; 34 | 35 | if (floorIndex != ceilIndex && t0 != t1) { 36 | displacementSamplers[i] = linearInterpolate(t, t0, d0, t1, d1); 37 | } else { 38 | displacementSamplers[i] = d0; 39 | } 40 | prevFloorIndex = floorIndex; 41 | } 42 | return displacementSamplers; 43 | } 44 | 45 | @Override 46 | protected float getSamplerValue(float t) { 47 | float t1 = t < 0 ? 0 : t; 48 | t1 = t1 > 1 ? 1 : t; 49 | 50 | return interpolateValue(mSamplers, t1); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /bezierinterpolator/src/main/java/ivonhoe/android/bezierinterpolator/Point.java: -------------------------------------------------------------------------------- 1 | package ivonhoe.android.bezierinterpolator; 2 | 3 | /** 4 | * Created by ivonhoe on 15-4-15. 5 | */ 6 | public class Point { 7 | public float x; 8 | public float y; 9 | 10 | public Point(){ 11 | } 12 | 13 | public Point(float x, float y){ 14 | this.x = x; 15 | this.y = y; 16 | } 17 | 18 | public void set(Point point){ 19 | this.x = point.x; 20 | this.y = point.y; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /bezierinterpolator/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | BezierInterpolator 3 | 4 | -------------------------------------------------------------------------------- /bezierinterpolator/src/test/java/ivonhoe/android/bezierinterpolator/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package ivonhoe.android.bezierinterpolator; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | mavenLocal() 6 | jcenter() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:2.2.0' 10 | 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | jcenter() 19 | } 20 | } 21 | 22 | task clean(type: Delete) { 23 | delete rootProject.buildDir 24 | } 25 | 26 | -------------------------------------------------------------------------------- /build/generated/mockable-android-25.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ivonhoe/FancyDrawable/006586eecf26be8fdfcc922a7b4739b849e9e77a/build/generated/mockable-android-25.jar -------------------------------------------------------------------------------- /fancydrawable/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /fancydrawable/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.2" 6 | 7 | defaultConfig { 8 | minSdkVersion 15 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | 13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 14 | 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | } 23 | 24 | dependencies { 25 | compile fileTree(dir: 'libs', include: ['*.jar']) 26 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 27 | exclude group: 'com.android.support', module: 'support-annotations' 28 | }) 29 | compile 'com.android.support:appcompat-v7:25.3.1' 30 | testCompile 'junit:junit:4.12' 31 | 32 | compile project(':bezierinterpolator') 33 | } 34 | -------------------------------------------------------------------------------- /fancydrawable/fancydrawable.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /fancydrawable/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/Ivonhoe/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /fancydrawable/src/androidTest/java/ivonhoe/android/fancydrawable/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package ivonhoe.android.fancydrawable; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("ivonhoe.android.fancydrawable.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /fancydrawable/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /fancydrawable/src/main/java/ivonhoe/android/fancydrawable/AtomDrawable.java: -------------------------------------------------------------------------------- 1 | package ivonhoe.android.fancydrawable; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.AnimatorSet; 6 | import android.animation.ObjectAnimator; 7 | import android.annotation.TargetApi; 8 | import android.graphics.Canvas; 9 | import android.graphics.Paint; 10 | import android.graphics.Rect; 11 | import android.graphics.drawable.Drawable; 12 | import android.os.Build; 13 | import android.os.Handler; 14 | import android.os.Message; 15 | import android.text.TextUtils; 16 | import android.view.animation.Interpolator; 17 | 18 | import java.util.ArrayList; 19 | 20 | import ivonhoe.android.bezierinterpolator.BezierInterpolator; 21 | 22 | /** 23 | * @author Ivonhoe on 2014/12/24. 24 | */ 25 | public abstract class AtomDrawable extends FancyDrawable { 26 | 27 | protected Interpolator DEFAULT_INTERPOLATOR = new BezierInterpolator(0.03f, 0.615f, 0.995f, 28 | 0.415f); 29 | protected AnimatorSet mAtomsAnimatorSet; 30 | protected Atom[] mAtoms; 31 | protected AtomStyle mAtomStyle; 32 | protected float mDensity; 33 | 34 | public AtomDrawable() { 35 | AtomStyle style = new AtomStyle(); 36 | initialize(style); 37 | } 38 | 39 | public AtomDrawable(AtomStyle style) { 40 | initialize(style); 41 | } 42 | 43 | protected void initialize(AtomStyle style) { 44 | mAtomStyle = style; 45 | int atomCount = mAtomStyle.getSectionCount(); 46 | mAtoms = new Atom[atomCount]; 47 | for (int i = 0; i < atomCount; i++) { 48 | mAtoms[i] = new Atom(i); 49 | } 50 | mAtomsAnimatorSet = null; 51 | } 52 | 53 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 54 | public void onStart() { 55 | if (mBounds != null) { 56 | setupAnimators(mBounds); 57 | mAtomsAnimatorSet.start(); 58 | } 59 | } 60 | 61 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 62 | private void setupAnimators(final Rect bound) { 63 | if (mAtomStyle != null && bound != null) { 64 | mAtomStyle.setParentBound(bound); 65 | getAtomsAnimation(mAtomStyle, mAtoms); 66 | } 67 | } 68 | 69 | @Override 70 | public void onDraw(Canvas canvas) { 71 | for (int i = 0; i < mAtomStyle.getSectionCount(); i++) { 72 | drawAtom(canvas, mAtomStyle, mAtoms[i]); 73 | } 74 | } 75 | 76 | @Override 77 | protected void onBoundsChange(Rect bounds) { 78 | super.onBoundsChange(bounds); 79 | recreateAnimator(); 80 | } 81 | 82 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 83 | private void recreateAnimator() { 84 | if (isRunning()) { 85 | if (mAtomsAnimatorSet != null) { 86 | mAtomsAnimatorSet.cancel(); 87 | } 88 | mAtomsAnimatorSet = null; 89 | onStart(); 90 | } else { 91 | mAtomsAnimatorSet = null; 92 | if (mBounds != null) 93 | setupAnimators(mBounds); 94 | } 95 | } 96 | 97 | public void setInterpolator(Interpolator interpolator) { 98 | mAtomStyle.setInterpolator(interpolator); 99 | } 100 | 101 | /** 102 | * 103 | */ 104 | public void setAtomText(String text) { 105 | mAtomStyle.setText(text); 106 | // recreate atom list 107 | if (text != null && text.length() != mAtoms.length) { 108 | mAtoms = new Atom[text.length()]; 109 | for (int i = 0; i < mAtoms.length; i++) { 110 | mAtoms[i] = new Atom(i); 111 | } 112 | } 113 | recreateAnimator(); 114 | } 115 | 116 | public void setAtomTextSize(float size) { 117 | mAtomStyle.getPaint().setTextSize(size); 118 | } 119 | 120 | public void setPaint(Paint paint) { 121 | mAtomStyle.setPaint(paint); 122 | } 123 | 124 | public AtomStyle getAtomStyle() { 125 | return mAtomStyle; 126 | } 127 | 128 | /** 129 | * 一个元素如何动画 130 | */ 131 | public abstract ObjectAnimator[] getAtomAnimator(final Atom atom, final Rect bound); 132 | 133 | /** 134 | * 一个元素是否在动画中 135 | */ 136 | public abstract boolean isAtomRunning(Atom atom); 137 | 138 | @Override 139 | public int getIntrinsicWidth() { 140 | return super.getIntrinsicWidth(); 141 | } 142 | 143 | @Override 144 | public int getIntrinsicHeight() { 145 | return super.getIntrinsicHeight(); 146 | } 147 | 148 | /** 149 | * 根据元素样式得到动画集合 150 | */ 151 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 152 | public AnimatorSet getAtomsAnimation(AtomStyle atomStyle, Atom[] atoms) { 153 | int count = atomStyle.getSectionCount(); 154 | ArrayList temp = new ArrayList(); 155 | String text = atomStyle.getText(); 156 | final Rect bound = atomStyle.getParentBound(); 157 | if (bound == null || mAtomsAnimatorSet != null) { 158 | return mAtomsAnimatorSet; 159 | } 160 | 161 | mAtomsAnimatorSet = new AnimatorSet(); 162 | for (int i = 0; i < count; i++) { 163 | final Atom atom = atoms[i]; 164 | if (!TextUtils.isEmpty(text)) { 165 | atom.setText(text.substring(text.length() - i - 1, text.length() - i)); 166 | } 167 | ObjectAnimator[] animators = getAtomAnimator(atoms[i], bound); 168 | for (Animator animator : animators) { 169 | if (animator != null) { 170 | animator.setStartDelay(atomStyle.getDelay() * i); 171 | temp.add(animator); 172 | } 173 | } 174 | } 175 | 176 | //Log.d("simply", "--temp:" + temp.size() + ",mAtomsAnimatorSet:" + mAtomsAnimatorSet); 177 | mAtomsAnimatorSet.setDuration(atomStyle.getDuration()); 178 | mAtomsAnimatorSet.playTogether(temp); 179 | if (atomStyle.getInterpolator() != null) { 180 | mAtomsAnimatorSet.setInterpolator(atomStyle.getInterpolator()); 181 | } 182 | mAtomsAnimatorSet.setStartDelay(atomStyle.getDelay()); 183 | mAtomsAnimatorSet.addListener(new AnimatorListenerAdapter() { 184 | @Override 185 | public void onAnimationEnd(Animator animation) { 186 | if (mAtomsAnimatorSet.getDuration() > 0) { 187 | mHandler.sendEmptyMessage(0); 188 | } 189 | //Log.d("simply", "+++animation :" + animation); 190 | } 191 | }); 192 | return mAtomsAnimatorSet; 193 | } 194 | 195 | protected Handler mHandler = new Handler() { 196 | @Override 197 | public void handleMessage(Message msg) { 198 | super.handleMessage(msg); 199 | removeMessages(0); 200 | if (mAtomsAnimatorSet != null) { 201 | mAtomsAnimatorSet.start(); 202 | } 203 | } 204 | }; 205 | 206 | /** 207 | * 根据每个元素的状态和样式绘制 208 | */ 209 | public void drawAtom(Canvas canvas, AtomStyle style, Atom atom) { 210 | if (!isAtomRunning(atom)) { 211 | return; 212 | } 213 | Paint paint = style.getPaint(); 214 | AtomStyle.Shape shape = style.getShape(); 215 | 216 | switch (shape) { 217 | case CHAR: 218 | canvas.drawText(atom.getText(), atom.getLocationX(), atom.getLocationY(), paint); 219 | break; 220 | 221 | case POINT: 222 | canvas.drawPoint(atom.getLocationX(), atom.getLocationY(), paint); 223 | break; 224 | 225 | case CIRCLE: 226 | canvas.drawCircle(atom.getLocationX(), atom.getLocationY(), 5, paint); 227 | break; 228 | 229 | case DRAWABLE: 230 | drawDrawable(canvas, style, atom); 231 | break; 232 | } 233 | } 234 | 235 | protected void drawDrawable(Canvas canvas, AtomStyle style, Atom atom) { 236 | Drawable drawable = style.getDrawable(); 237 | int degree = atom.getRotate(); 238 | float x = atom.getLocationX(); 239 | float y = atom.getLocationY(); 240 | 241 | canvas.save(); 242 | canvas.translate( 243 | (float) (x + (1 - 1.414f * Math.sin((45 - degree) / 180f * Math.PI)) * 20f), 244 | (float) (y - (1.414f * Math.cos((45 - degree) / 180f * Math.PI) - 1) * 20f)); 245 | canvas.rotate(degree); 246 | //canvas.translate((float) (20 / Math.cos((float) degree / 180 * Math.PI)), 0); 247 | drawable.draw(canvas); 248 | canvas.restore(); 249 | } 250 | 251 | public static class Atom { 252 | private int id = -1; 253 | private float delta = 0; 254 | private float length = 0; 255 | private float locationX = 0; 256 | private float locationY = 0; 257 | private int rotate = 0; 258 | private String text; 259 | 260 | public Atom(int id) { 261 | this.id = id; 262 | } 263 | 264 | public float getLocationX() { 265 | return locationX; 266 | } 267 | 268 | public void setLocationX(float locationX) { 269 | this.locationX = locationX; 270 | } 271 | 272 | public float getLocationY() { 273 | return locationY; 274 | } 275 | 276 | public void setLocationY(float locationY) { 277 | this.locationY = locationY; 278 | } 279 | 280 | public String getText() { 281 | return text; 282 | } 283 | 284 | public void setText(String text) { 285 | this.text = text; 286 | } 287 | 288 | public float getDelta() { 289 | return delta; 290 | } 291 | 292 | public void setDelta(float delta) { 293 | this.delta = delta; 294 | } 295 | 296 | public int getRotate() { 297 | return rotate; 298 | } 299 | 300 | public void setRotate(int rotate) { 301 | this.rotate = rotate; 302 | } 303 | 304 | public int getId() { 305 | return id; 306 | } 307 | 308 | public float getLength() { 309 | return length; 310 | } 311 | 312 | public void setLength(float length) { 313 | this.length = length; 314 | } 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /fancydrawable/src/main/java/ivonhoe/android/fancydrawable/AtomStyle.java: -------------------------------------------------------------------------------- 1 | package ivonhoe.android.fancydrawable; 2 | 3 | import android.graphics.Color; 4 | import android.graphics.Paint; 5 | import android.graphics.Rect; 6 | import android.graphics.drawable.Drawable; 7 | import android.view.animation.Interpolator; 8 | import android.view.animation.LinearInterpolator; 9 | 10 | /** 11 | * Created by ivonhoe on 2015/1/5. 12 | */ 13 | public class AtomStyle { 14 | 15 | private static int DURATION = 4000; 16 | private static int DELAY = 200; 17 | private static int SECTION_COUNT = 6; 18 | private static int PADDING = 10; 19 | 20 | public enum Shape { 21 | POINT, CHAR, CIRCLE, DRAWABLE 22 | }; 23 | 24 | Rect parentBound; 25 | 26 | // draw a point , a char, circle or drawable? 27 | Shape shape; 28 | 29 | Interpolator interpolator; 30 | 31 | int duration; 32 | 33 | int delay; 34 | 35 | int sectionCount; 36 | 37 | Paint paint; 38 | 39 | String text; 40 | 41 | int radius; 42 | 43 | Drawable drawable; 44 | 45 | public AtomStyle() { 46 | } 47 | 48 | public String getText() { 49 | return text; 50 | } 51 | 52 | public Drawable getDrawable() { 53 | return drawable; 54 | } 55 | 56 | public void setDrawable(Drawable drawable) { 57 | this.drawable = drawable; 58 | } 59 | 60 | public void setText(String text) { 61 | this.paint.setStyle(Paint.Style.FILL); 62 | this.text = text; 63 | this.shape = Shape.CHAR; 64 | if (text != null) 65 | this.sectionCount = text.length(); 66 | } 67 | 68 | public Rect getParentBound() { 69 | return parentBound; 70 | } 71 | 72 | public void setParentBound(Rect parentBound) { 73 | int top = parentBound.top + PADDING; 74 | int bottom = parentBound.bottom - PADDING; 75 | this.parentBound = new Rect(parentBound.left, top, parentBound.right, bottom); 76 | } 77 | 78 | public Shape getShape() { 79 | return shape; 80 | } 81 | 82 | public void setShape(Shape type) { 83 | this.shape = type; 84 | } 85 | 86 | public Interpolator getInterpolator() { 87 | return interpolator; 88 | } 89 | 90 | public void setInterpolator(Interpolator interpolator) { 91 | this.interpolator = interpolator; 92 | } 93 | 94 | public int getDuration() { 95 | return duration; 96 | } 97 | 98 | public void setDuration(int duration) { 99 | this.duration = duration; 100 | } 101 | 102 | public int getDelay() { 103 | return delay; 104 | } 105 | 106 | public void setDelay(int delay) { 107 | this.delay = delay; 108 | } 109 | 110 | public int getSectionCount() { 111 | return sectionCount; 112 | } 113 | 114 | public void setSectionCount(int count) { 115 | this.sectionCount = count; 116 | } 117 | 118 | public Paint getPaint() { 119 | return paint; 120 | } 121 | 122 | public void setPaint(Paint paint) { 123 | this.paint = paint; 124 | } 125 | 126 | public int getRadius() { 127 | return radius; 128 | } 129 | 130 | public void setRadius(int radius) { 131 | this.radius = radius; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /fancydrawable/src/main/java/ivonhoe/android/fancydrawable/BallsCircleDrawable.java: -------------------------------------------------------------------------------- 1 | package ivonhoe.android.fancydrawable; 2 | 3 | import android.animation.ObjectAnimator; 4 | import android.graphics.Rect; 5 | 6 | /** 7 | * Created by ivonhoe on 15-4-30. 8 | */ 9 | public class BallsCircleDrawable extends AtomDrawable{ 10 | @Override 11 | public ObjectAnimator[] getAtomAnimator(Atom atom, Rect bound) { 12 | return null; 13 | } 14 | 15 | @Override 16 | public boolean isAtomRunning(Atom atom) { 17 | return false; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /fancydrawable/src/main/java/ivonhoe/android/fancydrawable/BallsLineDrawable.java: -------------------------------------------------------------------------------- 1 | package ivonhoe.android.fancydrawable; 2 | 3 | import android.animation.ObjectAnimator; 4 | import android.annotation.TargetApi; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.Rect; 8 | import android.os.Build; 9 | import android.view.animation.LinearInterpolator; 10 | 11 | /** 12 | * Created by ivonhoe on 15-4-30. 13 | */ 14 | public class BallsLineDrawable extends AtomDrawable { 15 | 16 | public BallsLineDrawable() { 17 | AtomStyle atomStyle = new AtomStyle(); 18 | atomStyle.setShape(AtomStyle.Shape.POINT); 19 | atomStyle.setInterpolator(DEFAULT_INTERPOLATOR); 20 | atomStyle.setDuration(4000); 21 | atomStyle.setDelay(200); 22 | atomStyle.setSectionCount(6); 23 | 24 | Paint paint = new Paint(); 25 | paint.setStyle(Paint.Style.STROKE); 26 | paint.setStrokeWidth(7); 27 | paint.setDither(false); 28 | paint.setAntiAlias(false); 29 | paint.setColor(Color.BLUE); 30 | atomStyle.setPaint(paint); 31 | initialize(atomStyle); 32 | } 33 | 34 | @Override 35 | public ObjectAnimator[] getAtomAnimator(Atom atom, Rect bound) { 36 | if (atom == null || bound == null) 37 | return null; 38 | AtomStyle style = getAtomStyle(); 39 | Paint paint = style.getPaint(); 40 | AtomStyle.Shape shape = style.getShape(); 41 | if (shape == AtomStyle.Shape.CHAR) { 42 | Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt(); 43 | int baseline = mBounds.top + 44 | (mBounds.bottom - mBounds.top - fontMetrics.bottom + fontMetrics.top) / 2 - 45 | fontMetrics.top; 46 | paint.setTextAlign(Paint.Align.CENTER); 47 | atom.setLocationY(baseline); 48 | } else { 49 | atom.setLocationY(bound.centerY()); 50 | } 51 | 52 | ObjectAnimator[] result = new ObjectAnimator[1]; 53 | result[0] = ObjectAnimator.ofFloat(atom, "locationX", 0, bound.width()); 54 | return result; 55 | } 56 | 57 | @Override 58 | public boolean isAtomRunning(Atom atom) { 59 | return atom.getLocationX() > 0 && atom.getLocationX() < mBounds.width(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /fancydrawable/src/main/java/ivonhoe/android/fancydrawable/ColorfulDrawable.java: -------------------------------------------------------------------------------- 1 | package ivonhoe.android.fancydrawable; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Color; 5 | import android.view.animation.AnimationUtils; 6 | 7 | import java.util.Random; 8 | 9 | /** 10 | * Created by ivonhoe on 14-12-18. 11 | */ 12 | public class ColorfulDrawable extends FancyDrawable { 13 | 14 | private static final long ANIMATION_DURATION = 1500; 15 | 16 | private static final int ACCENT_COLOR = 0x33FFFFFF; 17 | private static final int DIM_COLOR = 0x33000000; 18 | 19 | private static final Random mRandom = new Random(); 20 | 21 | private int mStartColor; 22 | private int mEndColor; 23 | private int mCurrentColor; 24 | 25 | private long mStartTime; 26 | 27 | @Override 28 | public void onDraw(Canvas canvas) { 29 | mPaint.setColor(mCurrentColor); 30 | canvas.drawRect(mBounds, mPaint); 31 | 32 | mPaint.setColor(ACCENT_COLOR); 33 | canvas.drawRect(mBounds.left, mBounds.top, mBounds.right, mBounds.top + 1, mPaint); 34 | 35 | mPaint.setColor(DIM_COLOR); 36 | canvas.drawRect(mBounds.left, mBounds.bottom - 2, mBounds.right, mBounds.bottom, mPaint); 37 | } 38 | 39 | @Override 40 | public void onStart() { 41 | mStartTime = AnimationUtils.currentAnimationTimeMillis(); 42 | mStartColor = randomColor(); 43 | mEndColor = randomColor(); 44 | } 45 | 46 | @Override 47 | public void onFrame() { 48 | long now = AnimationUtils.currentAnimationTimeMillis(); 49 | long duration = now - mStartTime; 50 | if (duration >= ANIMATION_DURATION) { 51 | mStartColor = mEndColor; 52 | mEndColor = randomColor(); 53 | mStartTime = now; 54 | mCurrentColor = mStartColor; 55 | } else { 56 | float fraction = duration / (float) ANIMATION_DURATION; 57 | //@formatter:off 58 | mCurrentColor = Color.rgb( 59 | evaluate(fraction, Color.red(mStartColor), Color.red(mEndColor)), // red 60 | evaluate(fraction, Color.green(mStartColor), Color.green(mEndColor)), 61 | // green 62 | evaluate(fraction, Color.blue(mStartColor), 63 | Color.blue(mEndColor))); // blue 64 | //@formatter:on 65 | } 66 | } 67 | 68 | private static int randomColor() { 69 | return mRandom.nextInt() & 0x00FFFFFF; 70 | } 71 | 72 | private static int evaluate(float fraction, int startValue, int endValue) { 73 | return (int) (startValue + fraction * (endValue - startValue)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /fancydrawable/src/main/java/ivonhoe/android/fancydrawable/FancyDrawable.java: -------------------------------------------------------------------------------- 1 | package ivonhoe.android.fancydrawable; 2 | 3 | import android.annotation.TargetApi; 4 | import android.graphics.*; 5 | import android.graphics.drawable.Animatable; 6 | import android.graphics.drawable.ColorDrawable; 7 | import android.graphics.drawable.Drawable; 8 | import android.os.Build; 9 | import android.os.Handler; 10 | import android.os.Message; 11 | import android.os.SystemClock; 12 | import android.util.Log; 13 | 14 | /** 15 | * Created by ivonhoe on 14-12-18. 16 | * Based on http://cyrilmottier.com/2012/11/27/actionbar-on-the-move/ 17 | */ 18 | @TargetApi(Build.VERSION_CODES.DONUT) 19 | public abstract class FancyDrawable extends Drawable implements Animatable { 20 | 21 | protected static final long FRAME_DURATION = 1000 / 60; 22 | 23 | protected boolean mIsRunning; 24 | protected Paint mPaint = new Paint(); 25 | 26 | protected Rect mBounds; 27 | protected Drawable mBackground; 28 | 29 | protected Runnable mUpdater = new Runnable() { 30 | @Override 31 | public void run() { 32 | onFrame(); 33 | 34 | scheduleSelf(mUpdater, SystemClock.uptimeMillis() + FRAME_DURATION); 35 | invalidateSelf(); 36 | } 37 | }; 38 | 39 | @Override 40 | public void draw(Canvas canvas) { 41 | canvas.clipRect(mBounds); 42 | 43 | if (mBackground != null) { 44 | mBackground.setBounds(mBounds); 45 | mBackground.draw(canvas); 46 | } 47 | 48 | if (isRunning()) { 49 | onDraw(canvas); 50 | } 51 | } 52 | 53 | @Override 54 | public void start() { 55 | if (!isRunning()) { 56 | mIsRunning = true; 57 | 58 | // start 59 | onStart(); 60 | scheduleSelf(mUpdater, SystemClock.uptimeMillis() + FRAME_DURATION); 61 | invalidateSelf(); 62 | } 63 | } 64 | 65 | @Override 66 | public void stop() { 67 | if (isRunning()) { 68 | mIsRunning = false; 69 | unscheduleSelf(mUpdater); 70 | 71 | //stop 72 | onStop(); 73 | } 74 | } 75 | 76 | @Override 77 | public boolean isRunning() { 78 | return mIsRunning; 79 | } 80 | 81 | protected void onStart() { 82 | } 83 | 84 | protected void onStop() { 85 | } 86 | 87 | protected void onFrame() { 88 | } 89 | 90 | public abstract void onDraw(Canvas canvas); 91 | 92 | @Override 93 | public void setAlpha(int alpha) { 94 | mPaint.setAlpha(alpha); 95 | } 96 | 97 | @Override 98 | public void setColorFilter(ColorFilter cf) { 99 | mPaint.setColorFilter(cf); 100 | } 101 | 102 | @Override 103 | public int getOpacity() { 104 | return PixelFormat.TRANSPARENT; 105 | } 106 | 107 | @Override 108 | protected void onBoundsChange(Rect bounds) { 109 | super.onBoundsChange(bounds); 110 | if (mBounds == null) { 111 | mBounds = new Rect(); 112 | } 113 | mBounds.set(bounds); 114 | } 115 | 116 | public void setBackground(Drawable background) { 117 | this.mBackground = background; 118 | } 119 | 120 | public void setBackgroundColor(int color) { 121 | this.mBackground = new ColorDrawable(color); 122 | } 123 | 124 | public void setPaint(Paint paint) { 125 | mPaint = paint; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /fancydrawable/src/main/java/ivonhoe/android/fancydrawable/GridDrawable.java: -------------------------------------------------------------------------------- 1 | package ivonhoe.android.fancydrawable; 2 | 3 | import android.graphics.*; 4 | import android.graphics.drawable.Drawable; 5 | import android.util.Log; 6 | 7 | import java.util.ArrayList; 8 | 9 | /** 10 | * Created by ivonhoe on 15-4-22. 11 | */ 12 | public class GridDrawable extends Drawable { 13 | 14 | private static final int DEFAULT_PAINT_FLAGS = 15 | Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG; 16 | private static final int DEFAULT_PADDING = 5; 17 | private static final int DEFAULT_ITEM_SIZE = (int) (80); 18 | private static final int DEFAULT_COLUMN_COUNT = 3; 19 | private static final int DEFAULT_ROW_COUNT = 3; 20 | 21 | private Rect mBounds; 22 | private int mColumnCount = DEFAULT_COLUMN_COUNT; 23 | private int mRowCount = DEFAULT_ROW_COUNT; 24 | private ArrayList mChildDrawable = new ArrayList(); 25 | private int mAlpha; 26 | private int mPaddingL = DEFAULT_PADDING; 27 | private int mPaddingT = DEFAULT_PADDING; 28 | private int mPaddingR = DEFAULT_PADDING; 29 | private int mPaddingB = DEFAULT_PADDING; 30 | private int hGap = -1; 31 | private int vGap = -1; 32 | private int mChildH = DEFAULT_ITEM_SIZE; 33 | private int mChildW = DEFAULT_ITEM_SIZE; 34 | 35 | private Paint mPaint = new Paint(DEFAULT_PAINT_FLAGS); 36 | 37 | public GridDrawable() { 38 | super(); 39 | mAlpha = 255; 40 | mBounds = new Rect(); 41 | } 42 | 43 | @Override 44 | public void draw(Canvas canvas) { 45 | 46 | final int N = Math.min(mChildDrawable.size(), mRowCount * mColumnCount); 47 | for (int i = 0; i < N; i++) { 48 | ChildDrawable item = mChildDrawable.get(i); 49 | Drawable drawable = item.mDrawable; 50 | if (drawable != null) { 51 | item.mDrawable.setBounds(item.mInsetL, item.mInsetT, item.mInsetR, item.mInsetB); 52 | drawable.draw(canvas); 53 | } 54 | } 55 | } 56 | 57 | @Override 58 | protected void onBoundsChange(Rect bounds) { 59 | //Update the internal bounds in response to any external requests 60 | mBounds.set(bounds); 61 | measureSpace(); 62 | } 63 | 64 | @Override 65 | public void setAlpha(int alpha) { 66 | mAlpha = alpha; 67 | mPaint.setAlpha(alpha); 68 | } 69 | 70 | @Override 71 | public void setFilterBitmap(boolean filterBitmap) { 72 | mPaint.setFilterBitmap(filterBitmap); 73 | mPaint.setAntiAlias(filterBitmap); 74 | } 75 | 76 | @Override 77 | public void setColorFilter(ColorFilter cf) { 78 | mPaint.setColorFilter(cf); 79 | } 80 | 81 | @Override 82 | public int getOpacity() { 83 | return PixelFormat.TRANSLUCENT; 84 | } 85 | 86 | @Override 87 | public int getIntrinsicHeight() { 88 | final int N = mChildDrawable.size(); 89 | int height = mPaddingT + mPaddingB; 90 | for (int i = 0; i < N && i < mRowCount * mColumnCount; i++) { 91 | if ((i + 1) % mColumnCount == 0) { 92 | height += mChildH + vGap; 93 | } 94 | } 95 | 96 | return height - vGap; 97 | } 98 | 99 | @Override 100 | public int getIntrinsicWidth() { 101 | final int N = mChildDrawable.size(); 102 | int width = mPaddingL + mPaddingR; 103 | if (N < mColumnCount) { 104 | width += mChildW * N + (N - 1) * hGap; 105 | } else { 106 | width += mChildW * mColumnCount + (mColumnCount - 1) * hGap; 107 | } 108 | 109 | return width; 110 | } 111 | 112 | public int getAlpha() { 113 | return mAlpha; 114 | } 115 | 116 | public void addDrawable(Drawable gridItem) { 117 | if (gridItem == null) { 118 | return; 119 | } 120 | ChildDrawable childDrawable = new ChildDrawable(gridItem); 121 | childDrawable.mDrawable = gridItem; 122 | mChildDrawable.add(childDrawable); 123 | 124 | measureChildrenPosition(); 125 | invalidateSelf(); 126 | } 127 | 128 | public void addDrawable(int i, Drawable gridItem) { 129 | if (gridItem == null || i > mChildDrawable.size()) { 130 | return; 131 | } 132 | 133 | ChildDrawable childDrawable = new ChildDrawable(gridItem); 134 | childDrawable.mDrawable = gridItem; 135 | mChildDrawable.add(i, childDrawable); 136 | 137 | measureChildrenPosition(); 138 | invalidateSelf(); 139 | } 140 | 141 | public void remove(Drawable drawable) { 142 | if (drawable == null) { 143 | return; 144 | } 145 | for (ChildDrawable childDrawable : mChildDrawable) { 146 | if (childDrawable.mDrawable == drawable) { 147 | boolean result = mChildDrawable.remove(drawable); 148 | } 149 | } 150 | 151 | measureChildrenPosition(); 152 | invalidateSelf(); 153 | } 154 | 155 | public void setPaint(Paint paint) { 156 | mPaint = paint; 157 | } 158 | 159 | public void setPadding(int left, int top, int right, int bottom) { 160 | mPaddingL = left; 161 | mPaddingT = top; 162 | mPaddingR = right; 163 | mPaddingB = bottom; 164 | measureSpace(); 165 | } 166 | 167 | public void setDefaultChildSize(int width, int height) { 168 | mChildW = width; 169 | mChildH = height; 170 | measureSpace(); 171 | } 172 | 173 | public void setGap(int hGap, int vGap) { 174 | this.hGap = hGap; 175 | this.vGap = vGap; 176 | } 177 | 178 | private void measureSpace() { 179 | if (mColumnCount > 1) { 180 | int width = mBounds.width(); 181 | hGap = (width - (mColumnCount * mChildW) - mPaddingL - mPaddingR) / 182 | (mColumnCount - 1); 183 | hGap = Math.max(hGap, 0); 184 | } 185 | 186 | if (mRowCount > 1) { 187 | int height = mBounds.height(); 188 | vGap = (height - (mRowCount * mChildH) - mPaddingT - mPaddingB) / (mRowCount - 1); 189 | vGap = Math.max(vGap, 0); 190 | } 191 | 192 | measureChildrenPosition(); 193 | } 194 | 195 | private void measureChildrenPosition() { 196 | int childLeft = mPaddingL; 197 | int childTop = mPaddingT; 198 | final int N = Math.min(mChildDrawable.size(), mRowCount * mColumnCount); 199 | for (int i = 0; i < N; i++) { 200 | ChildDrawable item = mChildDrawable.get(i); 201 | item.mInsetL = childLeft; 202 | item.mInsetT = childTop; 203 | item.mInsetR = childLeft + mChildW; 204 | item.mInsetB = childTop + mChildH; 205 | 206 | if (i != 0 && (i + 1) % mColumnCount == 0) { 207 | childLeft = mPaddingL; 208 | childTop += mChildH + vGap; 209 | } else { 210 | childLeft += mChildW + hGap; 211 | } 212 | } 213 | } 214 | 215 | static class ChildDrawable { 216 | public ChildDrawable(Drawable drawable) { 217 | mDrawable = drawable; 218 | } 219 | 220 | public Drawable mDrawable; 221 | public int mInsetL, mInsetT, mInsetR, mInsetB; 222 | public int mId; 223 | } 224 | 225 | } 226 | -------------------------------------------------------------------------------- /fancydrawable/src/main/java/ivonhoe/android/fancydrawable/NetEaseDrawable.java: -------------------------------------------------------------------------------- 1 | package ivonhoe.android.fancydrawable; 2 | 3 | import android.animation.ObjectAnimator; 4 | import android.content.Context; 5 | import android.graphics.*; 6 | import android.view.animation.*; 7 | import android.view.animation.Interpolator; 8 | 9 | /** 10 | * @auther Ivonhoe 11 | */ 12 | public class NetEaseDrawable extends AtomDrawable { 13 | 14 | private Paint mPaint; 15 | private RectF mRectF; 16 | private int mPadding; 17 | private int mRectLeft; 18 | private int mRectTop; 19 | private int mRectRight; 20 | private int mRectBottom; 21 | 22 | /** 23 | * dp 24 | */ 25 | private int mSize; 26 | 27 | private float mScaleFactor; 28 | 29 | /** 30 | * 画笔宽度的插值 31 | */ 32 | private Interpolator mPaintInterpolator; 33 | 34 | /** 35 | * 圆弧旋转的产值 36 | */ 37 | private Interpolator mRotateInterpolator; 38 | 39 | public NetEaseDrawable(Context context) { 40 | mDensity = context.getResources().getDisplayMetrics().density; 41 | mPaint = new Paint(); 42 | mPaint.setColor(Color.RED); 43 | mPaint.setAntiAlias(true); 44 | mPaint.setStyle(Paint.Style.STROKE); 45 | 46 | mRectF = new RectF(); 47 | mPadding = (int) (4 * mDensity + 0.5f); 48 | mSize = 40; 49 | mScaleFactor = 0.11f; 50 | 51 | // 过(0,0),(0.5,1),(1,0)的一元二次方程 52 | mPaintInterpolator = new android.view.animation.Interpolator() { 53 | @Override 54 | public float getInterpolation(float input) { 55 | return -4 * input * input + 4 * input; 56 | } 57 | }; 58 | mRotateInterpolator = new LinearInterpolator(); 59 | 60 | AtomStyle style = new AtomStyle(); 61 | style.setDelay(0); 62 | style.setDuration(1500); 63 | style.setSectionCount(3); 64 | style.setShape(AtomStyle.Shape.DRAWABLE); 65 | style.setInterpolator(null); 66 | style.setPaint(mPaint); 67 | initialize(style); 68 | } 69 | 70 | @Override 71 | protected void drawDrawable(Canvas canvas, AtomStyle style, Atom atom) { 72 | Paint paint = style.getPaint(); 73 | paint.setStrokeWidth(atom.getDelta()); 74 | switch (atom.getId()) { 75 | case 0: 76 | paint.setColor(Color.RED); 77 | break; 78 | case 1: 79 | paint.setColor(Color.BLUE); 80 | break; 81 | case 2: 82 | paint.setColor(Color.GRAY); 83 | break; 84 | default: 85 | } 86 | mRectF.set(mRectLeft + atom.getLocationX(), mRectTop + atom.getLocationX(), 87 | mRectRight - atom.getLocationX(), 88 | mRectBottom - atom.getLocationX()); 89 | canvas.drawArc(mRectF, atom.getRotate(), atom.getLength(), false, paint);//小弧形 90 | } 91 | 92 | @Override 93 | public ObjectAnimator[] getAtomAnimator(Atom atom, Rect bound) { 94 | ObjectAnimator[] result = new ObjectAnimator[4]; 95 | result[0] = ObjectAnimator.ofFloat(atom, "delta", 4f, 9f); 96 | result[0].setInterpolator(mPaintInterpolator); 97 | switch (atom.getId()) { 98 | case 0: 99 | result[1] = ObjectAnimator.ofInt(atom, "rotate", 0, 360); 100 | break; 101 | case 1: 102 | result[1] = ObjectAnimator.ofInt(atom, "rotate", 120, 480); 103 | break; 104 | case 2: 105 | result[1] = ObjectAnimator.ofInt(atom, "rotate", 240, 600); 106 | break; 107 | default: 108 | throw new RuntimeException(); 109 | } 110 | result[1].setInterpolator(mRotateInterpolator); 111 | result[2] = ObjectAnimator.ofFloat(atom, "length", 80f, 59f); 112 | result[2].setInterpolator(mPaintInterpolator); 113 | /** 114 | * 并不是location,改变绘制圆弧的半径 115 | * */ 116 | result[3] = ObjectAnimator 117 | .ofFloat(atom, "locationX", 0, mScaleFactor * getIntrinsicWidth()); 118 | result[3].setInterpolator(mPaintInterpolator); 119 | return result; 120 | } 121 | 122 | @Override 123 | protected void onBoundsChange(Rect bounds) { 124 | super.onBoundsChange(bounds); 125 | int height = mBounds.height(); 126 | mRectLeft = mBounds.centerX() - height / 2 + mPadding; 127 | mRectTop = mPadding; 128 | mRectRight = mBounds.centerX() + height / 2 - mPadding; 129 | mRectBottom = height - mPadding; 130 | } 131 | 132 | @Override 133 | public boolean isAtomRunning(Atom atom) { 134 | return true; 135 | } 136 | 137 | @Override 138 | public int getIntrinsicWidth() { 139 | return (int) (mSize * mDensity + 0.5f); 140 | } 141 | 142 | @Override 143 | public int getIntrinsicHeight() { 144 | return (int) (mSize * mDensity + 0.5f); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /fancydrawable/src/main/java/ivonhoe/android/fancydrawable/StreakDrawable.java: -------------------------------------------------------------------------------- 1 | package ivonhoe.android.fancydrawable; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Color; 5 | import android.graphics.Paint; 6 | import android.graphics.drawable.ColorDrawable; 7 | import android.util.Log; 8 | import android.view.animation.AccelerateDecelerateInterpolator; 9 | import android.view.animation.Interpolator; 10 | 11 | /** 12 | * Created by ivonhoe on 14-12-15. 13 | * Based on http://antoine-merle.com/blog/2013/11/12/make-your-progressbar-more-smooth/ 14 | */ 15 | public class StreakDrawable extends FancyDrawable { 16 | 17 | private static final float OFFSET_PER_FRAME = 0.01f; 18 | 19 | private Interpolator mInterpolator = new AccelerateDecelerateInterpolator(); 20 | private int mSectionsCount = 6; 21 | private int mSeparatorWidth = 10; 22 | private int mStrokeWidth = 9; 23 | private float mSpeed = 0.5f; 24 | private float mCurrentOffset; 25 | private float mMaxOffset = 1f / mSectionsCount; 26 | 27 | private float xSectionWidth = 1f / mSectionsCount; 28 | private int boundWidth; 29 | private int boundCenterY; 30 | 31 | public StreakDrawable() { 32 | mPaint.setStrokeWidth(mStrokeWidth); 33 | mPaint.setStyle(Paint.Style.STROKE); 34 | mPaint.setDither(false); 35 | mPaint.setAntiAlias(false); 36 | mPaint.setColor(Color.BLUE); 37 | 38 | setBackground(new ColorDrawable(0xFFFFF)); 39 | } 40 | 41 | @Override 42 | public void onDraw(Canvas canvas) { 43 | boundWidth = mBounds.width(); 44 | boundCenterY = mBounds.centerY(); 45 | 46 | drawStrokes(canvas); 47 | } 48 | 49 | @Override 50 | public void onFrame() { 51 | mCurrentOffset += (OFFSET_PER_FRAME * mSpeed); 52 | 53 | if (mCurrentOffset >= mMaxOffset) { 54 | mCurrentOffset -= mMaxOffset; 55 | } 56 | } 57 | 58 | private void drawStrokes(Canvas canvas) { 59 | int width = mBounds.width() + mSeparatorWidth; 60 | int prev; 61 | int end; 62 | int spaceLength; 63 | for (int i = 0; i <= mSectionsCount; i++) { 64 | float xOffset = i * xSectionWidth + mCurrentOffset; 65 | xOffset = Math.max(0f, xOffset - xSectionWidth); 66 | prev = (int) (mInterpolator.getInterpolation(xOffset) * width); 67 | 68 | float ratioSectionWidth = 69 | Math.abs(mInterpolator.getInterpolation(xOffset) - 70 | mInterpolator.getInterpolation(Math.min(xOffset + xSectionWidth, 1f))); 71 | 72 | //separator between each piece of line 73 | int sectionWidth = (int) (width * ratioSectionWidth); 74 | if (sectionWidth + prev < width) 75 | spaceLength = Math.min(sectionWidth, mSeparatorWidth); 76 | else { 77 | spaceLength = 0; 78 | 79 | } 80 | 81 | int drawLength = sectionWidth > spaceLength ? sectionWidth - spaceLength : 0; 82 | end = prev + drawLength; 83 | 84 | if (end > prev) { 85 | canvas.drawLine(prev, boundCenterY, end, boundCenterY, mPaint); 86 | } 87 | } 88 | } 89 | 90 | public void setInterpolator(Interpolator interpolator) { 91 | mInterpolator = interpolator; 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /fancydrawable/src/main/java/ivonhoe/android/fancydrawable/TaoBaoDrawable.java: -------------------------------------------------------------------------------- 1 | package ivonhoe.android.fancydrawable; 2 | 3 | import android.animation.ObjectAnimator; 4 | import android.content.Context; 5 | import android.graphics.Canvas; 6 | import android.graphics.Matrix; 7 | import android.graphics.Rect; 8 | import android.util.Log; 9 | import android.view.View; 10 | 11 | /** 12 | * @auther Ivonhoe 13 | */ 14 | public class TaoBaoDrawable extends AtomDrawable { 15 | 16 | private Matrix mMatrix; 17 | 18 | public TaoBaoDrawable(Context context) { 19 | mMatrix = new Matrix(); 20 | } 21 | 22 | @Override 23 | public void onDraw(Canvas canvas) { 24 | super.onDraw(canvas); 25 | /* mMatrix.setRotate(90); 26 | Log.d("simply", "setRotate 90 degree, matrix:" + mMatrix.toString()); 27 | canvas.setMatrix(mMatrix);*/ 28 | /* mMatrix.setTranslate(10, 10);*/ 29 | mMatrix.setScale(2, 2); 30 | } 31 | 32 | @Override 33 | public ObjectAnimator[] getAtomAnimator(Atom atom, Rect bound) { 34 | return new ObjectAnimator[0]; 35 | } 36 | 37 | @Override 38 | public boolean isAtomRunning(Atom atom) { 39 | return false; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /fancydrawable/src/main/java/ivonhoe/android/fancydrawable/TextDrawable.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2012 Wireless Designs, LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be 13 | * included in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | package ivonhoe.android.fancydrawable; 25 | 26 | import android.content.Context; 27 | import android.content.res.ColorStateList; 28 | import android.content.res.Resources; 29 | import android.content.res.TypedArray; 30 | import android.graphics.*; 31 | import android.graphics.drawable.Drawable; 32 | import android.text.Layout; 33 | import android.text.StaticLayout; 34 | import android.text.TextPaint; 35 | import android.util.TypedValue; 36 | 37 | /** 38 | * A Drawable object that draws text. 39 | * A TextDrawable accepts most of the same parameters that can be applied to 40 | * {@link android.widget.TextView} for displaying and formatting text. 41 | * 42 | * Optionally, a {@link Path} may be supplied on which to draw the text. 43 | * 44 | * A TextDrawable has an intrinsic size equal to that required to draw all 45 | * the text it has been supplied, when possible. In cases where a {@link Path} 46 | * has been supplied, the caller must explicitly call 47 | * {@link #setBounds(Rect) setBounds()} to provide the Drawable 48 | * size based on the Path constraints. 49 | */ 50 | public class TextDrawable extends Drawable { 51 | 52 | /* Platform XML constants for typeface */ 53 | private static final int SANS = 1; 54 | private static final int SERIF = 2; 55 | private static final int MONOSPACE = 3; 56 | 57 | /* Resources for scaling values to the given device */ 58 | private Resources mResources; 59 | /* Paint to hold most drawing primitives for the text */ 60 | private TextPaint mTextPaint; 61 | /* Layout is used to measure and draw the text */ 62 | private StaticLayout mTextLayout; 63 | /* Alignment of the text inside its bounds */ 64 | private Layout.Alignment mTextAlignment = Layout.Alignment.ALIGN_NORMAL; 65 | /* Optional path on which to draw the text */ 66 | private Path mTextPath; 67 | /* Stateful text color list */ 68 | private ColorStateList mTextColors; 69 | /* Container for the bounds to be reported to widgets */ 70 | private Rect mTextBounds; 71 | /* Text string to draw */ 72 | private CharSequence mText = ""; 73 | 74 | /* Attribute lists to pull default values from the current theme */ 75 | private static final int[] themeAttributes = { 76 | android.R.attr.textAppearance 77 | }; 78 | private static final int[] appearanceAttributes = { 79 | android.R.attr.textSize, 80 | android.R.attr.typeface, 81 | android.R.attr.textStyle, 82 | android.R.attr.textColor 83 | }; 84 | 85 | 86 | public TextDrawable(Context context) { 87 | super(); 88 | //Used to load and scale resource items 89 | mResources = context.getResources(); 90 | //Definition of this drawables size 91 | mTextBounds = new Rect(); 92 | //Paint to use for the text 93 | mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); 94 | mTextPaint.density = mResources.getDisplayMetrics().density; 95 | mTextPaint.setDither(true); 96 | 97 | int textSize = 15; 98 | ColorStateList textColor = null; 99 | int styleIndex = -1; 100 | int typefaceIndex = -1; 101 | 102 | //Set default parameters from the current theme 103 | TypedArray a = context.getTheme().obtainStyledAttributes(themeAttributes); 104 | int appearanceId = a.getResourceId(0, -1); 105 | a.recycle(); 106 | 107 | TypedArray ap = null; 108 | if (appearanceId != -1) { 109 | ap = context.obtainStyledAttributes(appearanceId, appearanceAttributes); 110 | } 111 | if (ap != null) { 112 | for (int i=0; i < ap.getIndexCount(); i++) { 113 | int attr = ap.getIndex(i); 114 | switch (attr) { 115 | case 0: //Text Size 116 | textSize = a.getDimensionPixelSize(attr, textSize); 117 | break; 118 | case 1: //Typeface 119 | typefaceIndex = a.getInt(attr, typefaceIndex); 120 | break; 121 | case 2: //Text Style 122 | styleIndex = a.getInt(attr, styleIndex); 123 | break; 124 | case 3: //Text Color 125 | textColor = a.getColorStateList(attr); 126 | break; 127 | default: 128 | break; 129 | } 130 | } 131 | 132 | ap.recycle(); 133 | } 134 | 135 | setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000)); 136 | setRawTextSize(textSize); 137 | 138 | Typeface tf = null; 139 | switch (typefaceIndex) { 140 | case SANS: 141 | tf = Typeface.SANS_SERIF; 142 | break; 143 | 144 | case SERIF: 145 | tf = Typeface.SERIF; 146 | break; 147 | 148 | case MONOSPACE: 149 | tf = Typeface.MONOSPACE; 150 | break; 151 | } 152 | 153 | setTypeface(tf, styleIndex); 154 | } 155 | 156 | 157 | /** 158 | * Set the text that will be displayed 159 | * @param text Text to display 160 | */ 161 | public void setText(CharSequence text) { 162 | if (text == null) text = ""; 163 | 164 | mText = text; 165 | 166 | measureContent(); 167 | } 168 | 169 | /** 170 | * Return the text currently being displayed 171 | */ 172 | public CharSequence getText() { 173 | return mText; 174 | } 175 | 176 | /** 177 | * Return the current text size, in pixels 178 | */ 179 | public float getTextSize() { 180 | return mTextPaint.getTextSize(); 181 | } 182 | 183 | /** 184 | * Set the text size. The value will be interpreted in "sp" units 185 | * @param size Text size value, in sp 186 | */ 187 | public void setTextSize(float size) { 188 | setTextSize(TypedValue.COMPLEX_UNIT_SP, size); 189 | } 190 | 191 | /** 192 | * Set the text size, using the supplied complex units 193 | * @param unit Units for the text size, such as dp or sp 194 | * @param size Text size value 195 | */ 196 | public void setTextSize(int unit, float size) { 197 | float dimension = TypedValue.applyDimension(unit, size, 198 | mResources.getDisplayMetrics()); 199 | setRawTextSize(dimension); 200 | } 201 | 202 | /* 203 | * Set the text size, in raw pixels 204 | */ 205 | private void setRawTextSize(float size) { 206 | if (size != mTextPaint.getTextSize()) { 207 | mTextPaint.setTextSize(size); 208 | 209 | measureContent(); 210 | } 211 | } 212 | 213 | /** 214 | * Return the horizontal stretch factor of the text 215 | */ 216 | public float getTextScaleX() { 217 | return mTextPaint.getTextScaleX(); 218 | } 219 | 220 | /** 221 | * Set the horizontal stretch factor of the text 222 | * @param size Text scale factor 223 | */ 224 | public void setTextScaleX(float size) { 225 | if (size != mTextPaint.getTextScaleX()) { 226 | mTextPaint.setTextScaleX(size); 227 | measureContent(); 228 | } 229 | } 230 | 231 | /** 232 | * Return the current text alignment setting 233 | */ 234 | public Layout.Alignment getTextAlign() { 235 | return mTextAlignment; 236 | } 237 | 238 | /** 239 | * Set the text alignment. The alignment itself is based on the text layout direction. 240 | * For LTR text NORMAL is left aligned and OPPOSITE is right aligned. 241 | * For RTL text, those alignments are reversed. 242 | * @param align Text alignment value. Should be set to one of: 243 | * 244 | * {@link Layout.Alignment#ALIGN_NORMAL}, 245 | * {@link Layout.Alignment#ALIGN_NORMAL}, 246 | * {@link Layout.Alignment#ALIGN_OPPOSITE}. 247 | */ 248 | public void setTextAlign(Layout.Alignment align) { 249 | if (mTextAlignment != align) { 250 | mTextAlignment = align; 251 | measureContent(); 252 | } 253 | } 254 | 255 | /** 256 | * Sets the typeface and style in which the text should be displayed. 257 | * Note that not all Typeface families actually have bold and italic 258 | * variants, so you may need to use 259 | * {@link #setTypeface(Typeface, int)} to get the appearance 260 | * that you actually want. 261 | */ 262 | public void setTypeface(Typeface tf) { 263 | if (mTextPaint.getTypeface() != tf) { 264 | mTextPaint.setTypeface(tf); 265 | 266 | measureContent(); 267 | } 268 | } 269 | 270 | /** 271 | * Sets the typeface and style in which the text should be displayed, 272 | * and turns on the fake bold and italic bits in the Paint if the 273 | * Typeface that you provided does not have all the bits in the 274 | * style that you specified. 275 | * 276 | */ 277 | public void setTypeface(Typeface tf, int style) { 278 | if (style > 0) { 279 | if (tf == null) { 280 | tf = Typeface.defaultFromStyle(style); 281 | } else { 282 | tf = Typeface.create(tf, style); 283 | } 284 | 285 | setTypeface(tf); 286 | // now compute what (if any) algorithmic styling is needed 287 | int typefaceStyle = tf != null ? tf.getStyle() : 0; 288 | int need = style & ~typefaceStyle; 289 | mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0); 290 | mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0); 291 | } else { 292 | mTextPaint.setFakeBoldText(false); 293 | mTextPaint.setTextSkewX(0); 294 | setTypeface(tf); 295 | } 296 | } 297 | 298 | /** 299 | * Return the current typeface and style that the Paint 300 | * using for display. 301 | */ 302 | public Typeface getTypeface() { 303 | return mTextPaint.getTypeface(); 304 | } 305 | 306 | /** 307 | * Set a single text color for all states 308 | * @param color Color value such as {@link Color#WHITE} or {@link Color#argb(int, int, int, int)} 309 | */ 310 | public void setTextColor(int color) { 311 | setTextColor(ColorStateList.valueOf(color)); 312 | } 313 | 314 | /** 315 | * Set the text color as a state list 316 | * @param colorStateList ColorStateList of text colors, such as inflated from an R.color resource 317 | */ 318 | public void setTextColor(ColorStateList colorStateList) { 319 | mTextColors = colorStateList; 320 | updateTextColors(getState()); 321 | } 322 | 323 | /** 324 | * Optional Path object on which to draw the text. If this is set, 325 | * TextDrawable cannot properly measure the bounds this drawable will need. 326 | * You must call {@link #setBounds(int, int, int, int) setBounds()} before 327 | * applying this TextDrawable to any View. 328 | * 329 | * Calling this method with null will remove any Path currently attached. 330 | */ 331 | public void setTextPath(Path path) { 332 | if (mTextPath != path) { 333 | mTextPath = path; 334 | measureContent(); 335 | } 336 | } 337 | 338 | /** 339 | * Internal method to take measurements of the current contents and apply 340 | * the correct bounds when possible. 341 | */ 342 | private void measureContent() { 343 | //If drawing to a path, we cannot measure intrinsic bounds 344 | //We must resly on setBounds being called externally 345 | if (mTextPath != null) { 346 | //Clear any previous measurement 347 | mTextLayout = null; 348 | mTextBounds.setEmpty(); 349 | } else { 350 | //Measure text bounds 351 | double desired = Math.ceil( Layout.getDesiredWidth(mText, mTextPaint) ); 352 | mTextLayout = new StaticLayout(mText, mTextPaint, (int)desired, 353 | mTextAlignment, 1.0f, 0.0f, false); 354 | mTextBounds.set(0, 0, mTextLayout.getWidth(), mTextLayout.getHeight()); 355 | } 356 | 357 | //We may need to be redrawn 358 | invalidateSelf(); 359 | } 360 | 361 | /** 362 | * Internal method to apply the correct text color based on the drawable's state 363 | */ 364 | private boolean updateTextColors(int[] stateSet) { 365 | int newColor = mTextColors.getColorForState(stateSet, Color.WHITE); 366 | if (mTextPaint.getColor() != newColor) { 367 | mTextPaint.setColor(newColor); 368 | return true; 369 | } 370 | 371 | return false; 372 | } 373 | 374 | @Override 375 | protected void onBoundsChange(Rect bounds) { 376 | //Update the internal bounds in response to any external requests 377 | mTextBounds.set(bounds); 378 | } 379 | 380 | @Override 381 | public boolean isStateful() { 382 | /* 383 | * The drawable's ability to represent state is based on 384 | * the text color list set 385 | */ 386 | return mTextColors.isStateful(); 387 | } 388 | 389 | @Override 390 | protected boolean onStateChange(int[] state) { 391 | //Upon state changes, grab the correct text color 392 | return updateTextColors(state); 393 | } 394 | 395 | @Override 396 | public int getIntrinsicHeight() { 397 | //Return the vertical bounds measured, or -1 if none 398 | if (mTextBounds.isEmpty()) { 399 | return -1; 400 | } else { 401 | return (mTextBounds.bottom - mTextBounds.top); 402 | } 403 | } 404 | 405 | @Override 406 | public int getIntrinsicWidth() { 407 | //Return the horizontal bounds measured, or -1 if none 408 | if (mTextBounds.isEmpty()) { 409 | return -1; 410 | } else { 411 | return (mTextBounds.right - mTextBounds.left); 412 | } 413 | } 414 | 415 | @Override 416 | public void draw(Canvas canvas) { 417 | final Rect bounds = getBounds(); 418 | final int count = canvas.save(); 419 | canvas.translate(bounds.left, bounds.top); 420 | if (mTextPath == null) { 421 | //Allow the layout to draw the text 422 | mTextLayout.draw(canvas); 423 | } else { 424 | //Draw directly on the canvas using the supplied path 425 | canvas.drawTextOnPath(mText.toString(), mTextPath, 0, 0, mTextPaint); 426 | } 427 | canvas.restoreToCount(count); 428 | } 429 | 430 | @Override 431 | public void setAlpha(int alpha) { 432 | if (mTextPaint.getAlpha() != alpha) { 433 | mTextPaint.setAlpha(alpha); 434 | } 435 | } 436 | 437 | @Override 438 | public int getOpacity() { 439 | return mTextPaint.getAlpha(); 440 | } 441 | 442 | @Override 443 | public void setColorFilter(ColorFilter cf) { 444 | if (mTextPaint.getColorFilter() != cf) { 445 | mTextPaint.setColorFilter(cf); 446 | } 447 | } 448 | 449 | } 450 | -------------------------------------------------------------------------------- /fancydrawable/src/main/java/ivonhoe/android/fancydrawable/YouZanDrawable.java: -------------------------------------------------------------------------------- 1 | package ivonhoe.android.fancydrawable; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.ObjectAnimator; 6 | import android.animation.ValueAnimator; 7 | import android.graphics.Canvas; 8 | import android.graphics.Color; 9 | import android.graphics.Paint; 10 | import android.graphics.Rect; 11 | import android.graphics.drawable.Drawable; 12 | import android.graphics.drawable.ShapeDrawable; 13 | import android.graphics.drawable.shapes.RectShape; 14 | import android.graphics.drawable.shapes.Shape; 15 | import android.util.Log; 16 | import android.view.animation.Interpolator; 17 | import android.view.animation.LinearInterpolator; 18 | 19 | import java.util.ArrayList; 20 | 21 | /** 22 | * Created by ivonhoe on 15-8-25. 23 | */ 24 | public class YouZanDrawable extends AtomDrawable { 25 | 26 | private static final int RECT_SIZE = 40; 27 | private static final double PI = 180f * Math.PI; 28 | ShapeDrawable mRectDrawable; 29 | Interpolator mInterpolator; 30 | Interpolator mLinearInterpolator; 31 | 32 | public YouZanDrawable() { 33 | mRectDrawable = new ShapeDrawable(new RectShape()); 34 | mRectDrawable.setBounds(0, 0, RECT_SIZE, RECT_SIZE); 35 | mRectDrawable.getPaint().setColor(Color.BLUE); 36 | 37 | mLinearInterpolator = new LinearInterpolator(); 38 | 39 | // 过(0,0),(0.5,1),(1,0)的一元二次方程 40 | mInterpolator = new Interpolator() { 41 | @Override 42 | public float getInterpolation(float input) { 43 | return -4 * input * input + 4 * input; 44 | } 45 | }; 46 | AtomStyle style = new AtomStyle(); 47 | style.setDelay(0); 48 | style.setDuration(1000); 49 | style.setSectionCount(4); 50 | style.setShape(AtomStyle.Shape.DRAWABLE); 51 | style.setDrawable(mRectDrawable); 52 | //style.setInterpolator(null); 53 | initialize(style); 54 | } 55 | 56 | @Override 57 | public void onDraw(Canvas canvas) { 58 | canvas.save(); 59 | // 将绘制原点平移到中心 60 | canvas.translate(mBounds.width() / 2, mBounds.height() / 2); 61 | super.onDraw(canvas); 62 | canvas.restore(); 63 | } 64 | 65 | @Override 66 | public ObjectAnimator[] getAtomAnimator(Atom atom, Rect bound) { 67 | ObjectAnimator[] result = new ObjectAnimator[2]; 68 | float centerHeight = (float) Math.ceil(getIntrinsicHeight() / 2); 69 | float diagonal = RECT_SIZE * 1.414f; 70 | float animationDistance = centerHeight - diagonal; 71 | switch (atom.getId()) { 72 | case 0: 73 | result[0] = ObjectAnimator.ofFloat(atom, "locationY", 1f, animationDistance - 30); 74 | break; 75 | case 1: 76 | atom.setLocationY(-diagonal / 2); 77 | result[0] = ObjectAnimator 78 | .ofFloat(atom, "locationX", -diagonal / 2 - 1, 79 | -(animationDistance + diagonal / 2 - 30)); 80 | break; 81 | case 2: 82 | atom.setLocationX(0); 83 | result[0] = ObjectAnimator 84 | .ofFloat(atom, "locationY", -diagonal - 1, -(centerHeight - 30)); 85 | break; 86 | case 3: 87 | atom.setLocationY(-diagonal / 2); 88 | result[0] = ObjectAnimator 89 | .ofFloat(atom, "locationX", diagonal / 2 + 1, 90 | centerHeight - diagonal / 2 - 30); 91 | break; 92 | default: 93 | throw new RuntimeException(); 94 | } 95 | 96 | result[0].setInterpolator(mInterpolator); 97 | int start = 45 + atom.getId() * 90; 98 | int end = start + 180; 99 | result[1] = ObjectAnimator.ofInt(atom, "rotate", start, end); 100 | result[1].setInterpolator(mLinearInterpolator); 101 | return result; 102 | } 103 | 104 | @Override 105 | public boolean isAtomRunning(Atom atom) { 106 | return true; 107 | } 108 | 109 | @Override 110 | public int getIntrinsicWidth() { 111 | return (int) (RECT_SIZE * 1.414f * 4); 112 | } 113 | 114 | @Override 115 | public int getIntrinsicHeight() { 116 | return (int) (RECT_SIZE * 1.414f * 4); 117 | } 118 | 119 | /** 120 | * 在以四个矩形的中心为原点的坐标系里绘制Rect 121 | */ 122 | private void drawRect(Canvas canvas, Drawable drawable, int degree, float x, float y) { 123 | canvas.save(); 124 | canvas.translate( 125 | (float) (x + (1 - 1.414f * Math.sin((45 - degree) / 180f * Math.PI)) * 20f), 126 | (float) (y - (1.414f * Math.cos((45 - degree) / 180f * Math.PI) - 1) * 20f)); 127 | canvas.rotate(degree); 128 | drawable.draw(canvas); 129 | canvas.restore(); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /fancydrawable/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | FancyDrawable 3 | 4 | -------------------------------------------------------------------------------- /fancydrawable/src/test/java/ivonhoe/android/fancydrawable/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package ivonhoe.android.fancydrawable; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ivonhoe/FancyDrawable/006586eecf26be8fdfcc922a7b4739b849e9e77a/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Jun 20 00:21:27 CST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /libs/BezierInterpolator.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ivonhoe/FancyDrawable/006586eecf26be8fdfcc922a7b4739b849e9e77a/libs/BezierInterpolator.jar -------------------------------------------------------------------------------- /local.properties: -------------------------------------------------------------------------------- 1 | ## This file is automatically generated by Android Studio. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must *NOT* be checked into Version Control Systems, 5 | # as it contains information specific to your local configuration. 6 | # 7 | # Location of the SDK. This is only used by Gradle. 8 | # For customization when using a Version Control System, please read the 9 | # header note. 10 | #Sun Nov 15 19:14:39 HKT 2015 11 | ndk.dir=/Users/Ivonhoe/Library/Android/sdk/ndk-bundle 12 | sdk.dir=/Users/Ivonhoe/Library/Android/sdk 13 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.2" 6 | 7 | defaultConfig { 8 | applicationId "com.ivonhoe.demo" 9 | minSdkVersion 15 10 | targetSdkVersion 25 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:25.3.1' 26 | 27 | compile project(':fancydrawable') 28 | compile project(':bezierinterpolator') 29 | } 30 | -------------------------------------------------------------------------------- /sample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/Ivonhoe/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /sample/sample.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /sample/src/androidTest/java/com/ivonhoe/demo/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.ivonhoe.demo; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /sample/src/main/java/ivonhoe/android/fancyDrawable/MyActivity.java: -------------------------------------------------------------------------------- 1 | package ivonhoe.android.fancyDrawable; 2 | 3 | import android.app.ActionBar; 4 | import android.app.Activity; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.drawable.Drawable; 8 | import android.os.Bundle; 9 | import android.util.Log; 10 | import android.view.View; 11 | import android.widget.ImageView; 12 | import com.ivonhoe.drawable.*; 13 | 14 | public class MyActivity extends Activity { 15 | 16 | private ColorfulDrawable mColorfulDrawable; 17 | 18 | private ImageView mGridImage; 19 | private ImageView mStreakImage; 20 | private ImageView mBallsLineImage; 21 | private ImageView mTextLineImage; 22 | private ImageView mBallsCircleImage; 23 | private ImageView mYouZanImage; 24 | private ImageView mWangYiImage; 25 | private ImageView mTaoBaoImage; 26 | 27 | /** 28 | * Called when the activity is first created. 29 | */ 30 | @Override 31 | public void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.main); 34 | 35 | loadDrawables(); 36 | } 37 | 38 | private void loadDrawables() { 39 | /** 40 | * 41 | * */ 42 | mGridImage = (ImageView) findViewById(R.id.gridImage); 43 | GridDrawable gridDrawable = new GridDrawable(); 44 | Drawable itemDrawable = getResources().getDrawable(R.drawable.ic_launcher); 45 | for (int i = 0; i < 10; i++) { 46 | gridDrawable.addDrawable(itemDrawable); 47 | } 48 | int size = getResources().getDimensionPixelSize(R.dimen.itemSize); 49 | int padding = getResources().getDimensionPixelOffset(R.dimen.itemPadding); 50 | gridDrawable.setPadding(padding, padding, padding, padding); 51 | gridDrawable.setDefaultChildSize(size, size); 52 | mGridImage.setImageDrawable(gridDrawable); 53 | 54 | /** 55 | * 56 | * */ 57 | Drawable streakDrawable = new StreakDrawable(); 58 | mStreakImage = (ImageView) findViewById(R.id.streakImage); 59 | mStreakImage.setImageDrawable(streakDrawable); 60 | 61 | /** 62 | * 63 | * */ 64 | ActionBar actionBar = getActionBar(); 65 | if (actionBar != null) { 66 | mColorfulDrawable = new ColorfulDrawable(); 67 | actionBar.setBackgroundDrawable(mColorfulDrawable); 68 | } 69 | 70 | /** 71 | * 72 | * */ 73 | Drawable ballsDrawable = new BallsLineDrawable(); 74 | mBallsLineImage = (ImageView) findViewById(R.id.ballsLineImage); 75 | mBallsLineImage.setImageDrawable(ballsDrawable); 76 | 77 | /** 78 | * 79 | * */ 80 | Drawable textDrawable = new BallsLineDrawable(); 81 | ((AtomDrawable) textDrawable).setAtomText("LOADING"); 82 | Paint paint = new Paint(); 83 | paint.setDither(false); 84 | paint.setAntiAlias(false); 85 | paint.setStrokeWidth(5); 86 | paint.setTextSize(40); 87 | paint.setColor(Color.BLUE); 88 | ((AtomDrawable) textDrawable).setPaint(paint); 89 | mTextLineImage = (ImageView) findViewById(R.id.textLineImage); 90 | mTextLineImage.setImageDrawable(textDrawable); 91 | 92 | /** 93 | * 94 | * */ 95 | Drawable cycleDrawable = new BallsCircleDrawable(); 96 | 97 | /** 98 | * 有赞客户端加载进度条 99 | * */ 100 | Drawable youZanDrawable = new YouZanDrawable(); 101 | mYouZanImage = (ImageView) findViewById(R.id.youZanImage); 102 | mYouZanImage.setImageDrawable(youZanDrawable); 103 | 104 | mYouZanImage.setOnClickListener(new View.OnClickListener() { 105 | @Override 106 | public void onClick(View v) { 107 | stopDrawableAnimation(mYouZanImage.getDrawable()); 108 | startDrawableAnimation(mYouZanImage.getDrawable()); 109 | stopDrawableAnimation(mWangYiImage.getDrawable()); 110 | startDrawableAnimation(mWangYiImage.getDrawable()); 111 | } 112 | }); 113 | 114 | /** 115 | * 网易邮箱客户端加载进度条 116 | * */ 117 | Drawable wangYiDrawable = new NetEaseDrawable(this); 118 | mWangYiImage = (ImageView) findViewById(R.id.wangYiImage); 119 | mWangYiImage.setImageDrawable(wangYiDrawable); 120 | 121 | /** 122 | * 手机淘宝客户端加载进度条 123 | * */ 124 | Drawable taoBaoImage = new TaoBaoDrawable(this); 125 | mTaoBaoImage = (ImageView) findViewById(R.id.taoBaoImage); 126 | mTaoBaoImage.setImageDrawable(taoBaoImage); 127 | } 128 | 129 | @Override 130 | protected void onResume() { 131 | super.onResume(); 132 | 133 | startDrawableAnimation(mColorfulDrawable); 134 | startDrawableAnimation(mStreakImage.getDrawable()); 135 | startDrawableAnimation(mBallsLineImage.getDrawable()); 136 | startDrawableAnimation(mTextLineImage.getDrawable()); 137 | startDrawableAnimation(mYouZanImage.getDrawable()); 138 | startDrawableAnimation(mWangYiImage.getDrawable()); 139 | startDrawableAnimation(mTaoBaoImage.getDrawable()); 140 | } 141 | 142 | @Override 143 | protected void onStop() { 144 | super.onStop(); 145 | 146 | stopDrawableAnimation(mColorfulDrawable); 147 | stopDrawableAnimation(mStreakImage.getDrawable()); 148 | stopDrawableAnimation(mBallsLineImage.getDrawable()); 149 | stopDrawableAnimation(mTextLineImage.getDrawable()); 150 | stopDrawableAnimation(mYouZanImage.getDrawable()); 151 | stopDrawableAnimation(mWangYiImage.getDrawable()); 152 | stopDrawableAnimation(mTaoBaoImage.getDrawable()); 153 | } 154 | 155 | private void startDrawableAnimation(Drawable drawable) { 156 | if (drawable instanceof FancyDrawable) { 157 | ((FancyDrawable) drawable).start(); 158 | } 159 | } 160 | 161 | private void stopDrawableAnimation(Drawable drawable) { 162 | if (drawable instanceof FancyDrawable) { 163 | ((FancyDrawable) drawable).stop(); 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 19 | 20 | 27 | 30 | 31 | 32 | 33 | 34 | 37 | 38 | 39 | 40 | 41 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 55 | 56 | 60 | 64 | 65 | 69 | 73 | 77 | 81 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ivonhoe/FancyDrawable/006586eecf26be8fdfcc922a7b4739b849e9e77a/sample/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ivonhoe/FancyDrawable/006586eecf26be8fdfcc922a7b4739b849e9e77a/sample/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ivonhoe/FancyDrawable/006586eecf26be8fdfcc922a7b4739b849e9e77a/sample/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ivonhoe/FancyDrawable/006586eecf26be8fdfcc922a7b4739b849e9e77a/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ivonhoe/FancyDrawable/006586eecf26be8fdfcc922a7b4739b849e9e77a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | > 2 | 8 | 9 | -------------------------------------------------------------------------------- /sample/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /sample/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /sample/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 16dp 6 | 7 | 7dp 8 | 40dp 9 | 10 | 11 | -------------------------------------------------------------------------------- /sample/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | FancyDrawable 4 | 5 | -------------------------------------------------------------------------------- /sample/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 14 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /sample/src/test/java/com/ivonhoe/demo/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.ivonhoe.demo; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':sample', ':bezierinterpolator', ':fancydrawable' 2 | --------------------------------------------------------------------------------