LAPTOP-K69FCNBP\crius 2 years ago
commit
0763927ea8
100 changed files with 4809 additions and 0 deletions
  1. 74 0
      .gitignore
  2. 201 0
      LICENSE
  3. 493 0
      README.md
  4. 1 0
      app/.gitignore
  5. 42 0
      app/build.gradle
  6. 21 0
      app/proguard-rules.pro
  7. 26 0
      app/src/androidTest/java/com/yxf/clippathlayout/sample/ExampleInstrumentedTest.java
  8. 25 0
      app/src/main/AndroidManifest.xml
  9. 101 0
      app/src/main/java/com/yxf/clippathlayout/sample/CirclePathFragment.java
  10. 49 0
      app/src/main/java/com/yxf/clippathlayout/sample/ControlButtonFragment.java
  11. 144 0
      app/src/main/java/com/yxf/clippathlayout/sample/MainActivity.java
  12. 38 0
      app/src/main/java/com/yxf/clippathlayout/sample/MyApplication.java
  13. 51 0
      app/src/main/java/com/yxf/clippathlayout/sample/RemoteControllerFragment.java
  14. 103 0
      app/src/main/java/com/yxf/clippathlayout/sample/ScrollTransitionFragment.java
  15. 65 0
      app/src/main/java/com/yxf/clippathlayout/sample/ViewTransitionFragment.java
  16. 111 0
      app/src/main/java/com/yxf/clippathlayout/sample/YinYangFishFragment.java
  17. 12 0
      app/src/main/res/drawable-v21/ic_menu_camera.xml
  18. 9 0
      app/src/main/res/drawable-v21/ic_menu_gallery.xml
  19. 9 0
      app/src/main/res/drawable-v21/ic_menu_manage.xml
  20. 9 0
      app/src/main/res/drawable-v21/ic_menu_send.xml
  21. 9 0
      app/src/main/res/drawable-v21/ic_menu_share.xml
  22. 9 0
      app/src/main/res/drawable-v21/ic_menu_slideshow.xml
  23. 34 0
      app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  24. 12 0
      app/src/main/res/drawable/button_oval.xml
  25. 12 0
      app/src/main/res/drawable/button_rectangle_black.xml
  26. 13 0
      app/src/main/res/drawable/button_rectangle_blue.xml
  27. 12 0
      app/src/main/res/drawable/button_rectangle_green.xml
  28. 12 0
      app/src/main/res/drawable/button_rectangle_orange.xml
  29. 12 0
      app/src/main/res/drawable/button_rectangle_white.xml
  30. 170 0
      app/src/main/res/drawable/ic_launcher_background.xml
  31. 9 0
      app/src/main/res/drawable/side_nav_bar.xml
  32. 25 0
      app/src/main/res/layout/activity_main.xml
  33. 25 0
      app/src/main/res/layout/app_bar_main.xml
  34. 12 0
      app/src/main/res/layout/content_main.xml
  35. 43 0
      app/src/main/res/layout/fragment_circle_path.xml
  36. 48 0
      app/src/main/res/layout/fragment_control_button.xml
  37. 47 0
      app/src/main/res/layout/fragment_remote_controller.xml
  38. 48 0
      app/src/main/res/layout/fragment_scroll_transition.xml
  39. 24 0
      app/src/main/res/layout/fragment_view_transition.xml
  40. 24 0
      app/src/main/res/layout/fragment_yin_yang_fish.xml
  41. 36 0
      app/src/main/res/layout/nav_header_main.xml
  42. 32 0
      app/src/main/res/menu/activity_main_drawer.xml
  43. 9 0
      app/src/main/res/menu/main.xml
  44. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  45. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  46. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher.png
  47. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher_round.png
  48. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher.png
  49. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher_round.png
  50. BIN
      app/src/main/res/mipmap-xhdpi/dmr_gn_portrait.jpg
  51. BIN
      app/src/main/res/mipmap-xhdpi/gn_dmr.jpg
  52. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher.png
  53. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
  54. BIN
      app/src/main/res/mipmap-xhdpi/image.jpg
  55. BIN
      app/src/main/res/mipmap-xhdpi/tm_gy.jpg
  56. BIN
      app/src/main/res/mipmap-xhdpi/zhang_liang.jpg
  57. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  58. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
  59. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  60. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
  61. 8 0
      app/src/main/res/values-v21/styles.xml
  62. 6 0
      app/src/main/res/values/colors.xml
  63. 8 0
      app/src/main/res/values/dimens.xml
  64. 8 0
      app/src/main/res/values/drawables.xml
  65. 9 0
      app/src/main/res/values/strings.xml
  66. 20 0
      app/src/main/res/values/styles.xml
  67. 17 0
      app/src/test/java/com/yxf/clippathlayout/sample/ExampleUnitTest.java
  68. 32 0
      build.gradle
  69. 1 0
      clippathlayout/.gitignore
  70. 76 0
      clippathlayout/build.gradle
  71. 21 0
      clippathlayout/proguard-rules.pro
  72. 26 0
      clippathlayout/src/androidTest/java/com/yxf/clippathlayout/ExampleInstrumentedTest.java
  73. 2 0
      clippathlayout/src/main/AndroidManifest.xml
  74. 51 0
      clippathlayout/src/main/java/com/yxf/clippathlayout/BitmapPathRegion.java
  75. 29 0
      clippathlayout/src/main/java/com/yxf/clippathlayout/ClipPathLayout.java
  76. 462 0
      clippathlayout/src/main/java/com/yxf/clippathlayout/ClipPathLayoutDelegate.java
  77. 9 0
      clippathlayout/src/main/java/com/yxf/clippathlayout/Config.java
  78. 26 0
      clippathlayout/src/main/java/com/yxf/clippathlayout/NativePathRegion.java
  79. 186 0
      clippathlayout/src/main/java/com/yxf/clippathlayout/PathInfo.java
  80. 8 0
      clippathlayout/src/main/java/com/yxf/clippathlayout/PathRegion.java
  81. 9 0
      clippathlayout/src/main/java/com/yxf/clippathlayout/PathRegionGenerator.java
  82. 36 0
      clippathlayout/src/main/java/com/yxf/clippathlayout/PathRegionGenerators.java
  83. 116 0
      clippathlayout/src/main/java/com/yxf/clippathlayout/Utils.java
  84. 87 0
      clippathlayout/src/main/java/com/yxf/clippathlayout/impl/ClipPathFrameLayout.java
  85. 87 0
      clippathlayout/src/main/java/com/yxf/clippathlayout/impl/ClipPathLinearLayout.java
  86. 88 0
      clippathlayout/src/main/java/com/yxf/clippathlayout/impl/ClipPathRelativeLayout.java
  87. 146 0
      clippathlayout/src/main/java/com/yxf/clippathlayout/pathgenerator/AnimTransition.java
  88. 55 0
      clippathlayout/src/main/java/com/yxf/clippathlayout/pathgenerator/CirclePathGenerator.java
  89. 23 0
      clippathlayout/src/main/java/com/yxf/clippathlayout/pathgenerator/OvalPathGenerator.java
  90. 66 0
      clippathlayout/src/main/java/com/yxf/clippathlayout/pathgenerator/OvalRingPathGenerator.java
  91. 19 0
      clippathlayout/src/main/java/com/yxf/clippathlayout/pathgenerator/PathGenerator.java
  92. 21 0
      clippathlayout/src/main/java/com/yxf/clippathlayout/pathgenerator/RhombusPathGenerator.java
  93. 12 0
      clippathlayout/src/main/java/com/yxf/clippathlayout/transition/ProgressController.java
  94. 256 0
      clippathlayout/src/main/java/com/yxf/clippathlayout/transition/TransitionAdapter.java
  95. 191 0
      clippathlayout/src/main/java/com/yxf/clippathlayout/transition/TransitionFragmentContainer.java
  96. 209 0
      clippathlayout/src/main/java/com/yxf/clippathlayout/transition/TransitionFrameLayout.java
  97. 17 0
      clippathlayout/src/main/java/com/yxf/clippathlayout/transition/TransitionLayout.java
  98. 73 0
      clippathlayout/src/main/java/com/yxf/clippathlayout/transition/generator/RandomTransitionPathGenerator.java
  99. 19 0
      clippathlayout/src/main/java/com/yxf/clippathlayout/transition/generator/TransitionPathGenerator.java
  100. 3 0
      clippathlayout/src/main/res/values/strings.xml

+ 74 - 0
.gitignore

@@ -0,0 +1,74 @@
+# Built application files
+*.apk
+*.ap_
+
+# Files for the ART/Dalvik VM
+*.dex
+
+# Java class files
+*.class
+
+# Generated files
+bin/
+gen/
+out/
+
+# Gradle files
+.gradle/
+build/
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Proguard folder generated by Eclipse
+proguard/
+
+# Log Files
+*.log
+
+# Android Studio Navigation editor temp files
+.navigation/
+
+# Android Studio captures folder
+captures/
+
+# IntelliJ
+*.iml
+.idea/workspace.xml
+.idea/tasks.xml
+.idea/gradle.xml
+.idea/assetWizardSettings.xml
+.idea/dictionaries
+.idea/libraries
+.idea/caches
+
+# Keystore files
+# Uncomment the following line if you do not want to check your keystore files in.
+#*.jks
+
+# External native build folder generated in Android Studio 2.2 and later
+.externalNativeBuild
+
+# Google Services (e.g. APIs or Firebase)
+google-services.json
+
+# Freeline
+freeline.py
+freeline/
+freeline_project_description.json
+
+# fastlane
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots
+fastlane/test_output
+fastlane/readme.md
+
+# others
+.idea/
+gradle.properties
+gradle/
+gradlew
+gradlew.bat
+
+image/

+ 201 - 0
LICENSE

@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 493 - 0
README.md

@@ -0,0 +1,493 @@
+# ClipPathLayout
+[![](https://www.jitpack.io/v/dqh147258/ClipPathLayout.svg)](https://www.jitpack.io/#dqh147258/ClipPathLayout)
+[![Platform](https://img.shields.io/badge/platform-android-blue.svg)]()
+[![License](https://img.shields.io/hexpm/l/plug.svg)](https://www.apache.org/licenses/LICENSE-2.0)
+
+Android中实现不规则图形的布局
+
+及由此扩展的转场动画布局
+
+
+## 效果展示
+
+### 不规则图形
+
+将方形图片裁剪成圆形并且让圆形View的4角不接收触摸事件
+
+![](https://github.com/dqh147258/ClipPathLayout/blob/master/image/circle.gif)
+
+很多游戏都会有方向键,曾经我也做过一个小游戏,但是在做方向键的时候遇到一个问题,4个方向按钮的位置会有重叠,导致局部地方会发生误触.
+当时没有特别好的解决办法,只能做自定义View,而自定义View特别麻烦,需要重写onTouchEvent和onDraw计算落点属于哪个方向,并增加点击效果.
+简单的自定义View会丧失很多Android自带的一些特性,要支持这些特性又繁琐而复杂.
+下面借助于ClipPathLayout用4个菱形按钮实现的方向控制键很好的解决了这个问题
+
+![](https://github.com/dqh147258/ClipPathLayout/blob/master/image/control_button.gif)
+
+对于遥控器的按键的模拟同样有上述问题,一般只能采用自定义View实现,较为繁琐.
+以下是借助于ClipPathLayout实现的遥控器按钮,由于没有美工切图,比较丑,将就下吧
+
+![](https://github.com/dqh147258/ClipPathLayout/blob/master/image/remote_controller.gif)
+
+甚至我们可以将不连续的图形变成一个View,比如做一个阴阳鱼的按钮
+
+![](https://github.com/dqh147258/ClipPathLayout/blob/master/image/yin_yang_fish.gif)
+
+
+### 转场动画
+
+两个View的场景切换效果,Android原生自带的场景切换效果大部分是由动画实现的平移,缩小,暗淡.
+原生比较少带有那种PPT播放的切换效果,一些第三方库实现的效果一般是由在DecorView中添加一层View来实现较为和谐的切换,
+沪江开心词场里使用的就是这种动画,这种动画很棒,但是也有一个小缺点,就是在切换的过程中,切换用的View和即将要切换的View没有什么关系.
+借助于ClipPathLayout扩展的TransitionFrameLayout也可以实现较为和谐的切换效果,由于是示例,不写太复杂的场景,以下仅用两个TextView作为展示
+
+![](https://github.com/dqh147258/ClipPathLayout/blob/master/image/view_transition.gif)
+
+在浏览QQ空间和使用QQ浏览器的过程看到腾讯的广告切换效果也是很不错的,这里借助于TransitionFrameLayout也可以实现这种效果
+
+![](https://github.com/dqh147258/ClipPathLayout/blob/master/image/scroll_transition_2.gif)
+
+其实大部分的场景切换应该是用在Fragment中,这里也用TransitionFragmentContainer实现了Fragment的场景切换效果
+
+![](https://github.com/dqh147258/ClipPathLayout/blob/master/image/fragment_transition_2.gif)
+
+## 使用
+
+### 添加依赖
+
+```
+	allprojects {
+		repositories {
+			...
+			maven { url 'https://www.jitpack.io' }
+		}
+	}
+```
+
+在app module中的build.gradle中添加依赖
+```
+	dependencies {
+	        implementation 'com.github.dqh147258:ClipPathLayout:1.1.0'
+	}
+```
+
+如果依然使用Jcenter版本则是
+```
+implementation 'com.yxf:clippathlayout:1.0.+'
+```
+
+### 不规则图形布局的使用
+
+当前实现了三个不规则图形的布局
+
+- ClipPathFrameLayout
+- ClipPathLinearLayout
+- ClipPathRelativeLayout
+
+如果有其他布局要求,请自定义,参见[自定义ClipPathLayout](#自定义clippathlayout)
+
+ClipPathLayout是一个接口,以上布局都实现了ClipPathLayout接口,并且具备父类的功能.
+
+要实现不规则图形,其实要操作的并不是父布局,而是子View.
+我们需要给子View添加一些信息,这样父布局才知道应该如何去实现这个不规则图形.
+
+这里以最简单的圆形View为例.
+
+在一个实现了ClipPathLayout接口的ViewGroup(以ClipPathFrameLayout为例)中添加一个子View(ImageView).
+```
+<com.yxf.clippathlayout.impl.ClipPathFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/clip_path_frame_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <ImageView
+        android:id="@+id/image"
+        android:layout_width="300dp"
+        android:layout_height="300dp"
+        android:layout_gravity="center"
+        android:src="@mipmap/image" />
+
+</com.yxf.clippathlayout.impl.ClipPathFrameLayout>
+
+```
+
+```
+mImageView = mLayout.findViewById(R.id.image);
+```
+
+然后构建一个PathInfo对象
+
+```
+new PathInfo.Builder(new CirclePathGenerator(), mImageView)
+    .setApplyFlag(mApplyFlag)
+    .setClipType(mClipType)
+    .setAntiAlias(false)
+    .create()
+    .apply();
+```
+
+搞定!运行就可以看到一个圆形的View.
+
+![](https://github.com/dqh147258/ClipPathLayout/blob/master/image/circle.gif)
+
+和效果展示上的这个图差不多,不过这张图多了几个按钮,然后那个圆形View有个绿色背景,那个是用来做对比的,在那个View之下添加了一个绿色的View,不要在意这些细节......
+
+对其中使用到的参数和方法做下说明
+
+#### PathInfo.Builder
+PathInfo创建器,用于配置和生成PathInfo.
+
+构造方法定义如下
+```
+        /**
+         * @param generator Path生成器
+         * @param view 实现了ClipPathLayout接口的ViewGroup的子View
+         */
+        public Builder(PathGenerator generator, View view) {
+
+        }
+```
+
+#### PathGenerator
+
+CirclePathGenerator是一个PathGenerator接口的实现类,用于生成圆形的Path.
+
+PathGenerator定义如下
+```
+public interface PathGenerator {
+
+    /**
+     * @param old 以前使用过的Path,如果以前为null,则可能为null
+     * @param view Path关联的子View对象
+     * @param width 生成Path所限定的范围宽度,一般是子View宽度
+     * @param height 生成Path所限定的范围高度,一般是子View高度
+     * @return 返回一个Path对象,必须为闭合的Path,将用于裁剪子View
+     *
+     * 其中Path的范围即left : 0 , top : 0 , right : width , bottom : height
+     */
+    Path generatePath(Path old, View view, int width, int height);
+
+}
+```
+PathGenerator是使用的核心,父布局将根据这个来对子View进行裁剪来实现不规则图形.
+
+此库内置了4种Path生成器
+- CirclePathGenerator(圆形Path生成器)
+- OvalPathGenerator(椭圆Path生成器)
+- RhombusPathGenerator(菱形Path生成器)
+- OvalRingPathGenerator(椭圆环Path生成器)
+
+如果有其他复杂的Path,可以自己实现PathGenerator,可以参考示例中的阴阳鱼Path的生成.
+
+#### ApplyFlag
+
+Path的应用标志,有如下几种
+
+- APPLY_FLAG_DRAW_ONLY(只用于绘制)
+- APPLY_FLAG_TOUCH_ONLY(只用于触摸事件)
+- APPLY_FLAG_DRAW_AND_TOUCH(绘制和触摸事件一起应用)
+
+默认不设置的话是APPLY_FLAG_DRAW_AND_TOUCH.
+
+切换效果如下
+
+![](https://github.com/dqh147258/ClipPathLayout/blob/master/image/select_apply_flag.gif)
+
+#### ClipType
+
+Path的裁剪模式,有如下两种
+
+- CLIP_TYPE_IN(取Path内范围作为不规则图形子View)
+- CLIP_TYPE_OUT(取Path外范围作为不规则图形子View)
+
+默认不设置为CLIP_TYPE_IN.
+
+切换效果如下
+
+![](https://github.com/dqh147258/ClipPathLayout/blob/master/image/select_clip_mode.gif)
+
+#### AntiAlias
+
+抗锯齿,true表示开启,false关闭,默认关闭.
+
+请慎用此功能,此功能会关闭硬件加速并且会新建图层,在View绘制期间还有一个图片生成过程,所以此功能开启会严重降低绘制性能,并且如果频繁刷新界面会导致内存抖动.所以这个功能只建议在静态而且不常刷新的情况下使用.
+
+### 自定义ClipPathLayout
+
+自定义一个ClipPathLayout很简单,首先选择一个ViewGroup,然后实现ClipPathLayout接口.
+
+然后再在自定义的ViewGroup中创建一个ClipPathLayoutDelegate对象.
+
+```
+ClipPathLayoutDelegate mClipPathLayoutDelegate = new ClipPathLayoutDelegate(this);
+```
+
+并将所有ClipPathLayout接口的实现都委派给ClipPathLayoutDelegate去实现.
+
+这里需要注意两点:
+
+- 需要重写ViewGroup的drawChild,按如下实现即可
+
+```
+    @Override
+    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+        beforeDrawChild(canvas, child, drawingTime);
+        boolean result = super.drawChild(canvas, child, drawingTime);
+        afterDrawChild(canvas, child, drawingTime);
+        return result;
+    }
+```
+
+- requestLayout方法也需要重写,这属于ViewGroup和ClipPathLayout共有的方法,这个方法会在父类的ViewGroup的构造方法中调用,在父类构造方法被调用时,mClipPathLayoutDelegate还没有初始化,如果直接调用会报空指针,所以需要添加空判断.
+
+```
+    @Override
+    public void requestLayout() {
+        super.requestLayout();
+        // the request layout method would be invoked in the constructor of super class
+        if (mClipPathLayoutDelegate == null) {
+            return;
+        }
+        mClipPathLayoutDelegate.requestLayout();
+    }
+```
+
+这里将整个ClipPathFrameLayout源码贴出作为参考
+```
+public class ClipPathFrameLayout extends FrameLayout implements ClipPathLayout {
+
+    ClipPathLayoutDelegate mClipPathLayoutDelegate = new ClipPathLayoutDelegate(this);
+
+    public ClipPathFrameLayout(@NonNull Context context) {
+        this(context, null);
+    }
+
+    public ClipPathFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ClipPathFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    public boolean isTransformedTouchPointInView(float x, float y, View child, PointF outLocalPoint) {
+        return mClipPathLayoutDelegate.isTransformedTouchPointInView(x, y, child, outLocalPoint);
+    }
+
+    @Override
+    public void applyPathInfo(PathInfo info) {
+        mClipPathLayoutDelegate.applyPathInfo(info);
+    }
+
+    @Override
+    public void cancelPathInfo(View child) {
+        mClipPathLayoutDelegate.cancelPathInfo(child);
+    }
+
+    @Override
+    public void beforeDrawChild(Canvas canvas, View child, long drawingTime) {
+        mClipPathLayoutDelegate.beforeDrawChild(canvas, child, drawingTime);
+    }
+
+    @Override
+    public void afterDrawChild(Canvas canvas, View child, long drawingTime) {
+        mClipPathLayoutDelegate.afterDrawChild(canvas, child, drawingTime);
+    }
+
+    //the drawChild method is not belong to ClipPathLayout ,
+    //but you should rewrite it without changing the return value of the method
+    @Override
+    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+        beforeDrawChild(canvas, child, drawingTime);
+        boolean result = super.drawChild(canvas, child, drawingTime);
+        afterDrawChild(canvas, child, drawingTime);
+        return result;
+    }
+
+    //do not forget to rewrite the method
+    @Override
+    public void requestLayout() {
+        super.requestLayout();
+        // the request layout method would be invoked in the constructor of super class
+        if (mClipPathLayoutDelegate == null) {
+            return;
+        }
+        mClipPathLayoutDelegate.requestLayout();
+    }
+
+    @Override
+    public void notifyPathChanged(View child) {
+        mClipPathLayoutDelegate.notifyPathChanged(child);
+    }
+
+    @Override
+    public void notifyAllPathChanged() {
+        mClipPathLayoutDelegate.notifyAllPathChanged();
+    }
+}
+```
+
+
+### 转场动画布局的使用
+
+转场动画布局这里做了两个,一个用于普通的View(TransitionFrameLayout),一个是针对Fragment的容器(TransitionFragmentContainer).
+
+#### TransitionFrameLayout
+
+这个布局继承于FrameLayout,用于两个View的场景切换.
+
+要求两个子View大小宽高需要一致,位置也一致.一般不做什么特殊设置的话,FrameLayout默认就是如此的.
+
+**这个ViewGroup限定只显示一个View**,如果在xml中添加了多个View,**只有最后一个View会显示出来**.
+
+如果需要添加一个View或者将其中隐藏的View显示出来请调用TransitionFrameLayout的switchView方法,**不要直接调用addView或者setVisibility**,可能会造成不太友好的界面效果.
+
+##### 使用
+
+以两个TextView的切换为例
+
+```
+<com.yxf.clippathlayout.transition.TransitionFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/blue_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="#880000ff"
+        android:gravity="center"
+        android:text="蓝色界面"
+        android:textSize="30sp" />
+
+    <TextView
+        android:id="@+id/green_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="#8800ff00"
+        android:gravity="center"
+        android:text="绿色界面"
+        android:textSize="30sp" />
+
+</com.yxf.clippathlayout.transition.TransitionFrameLayout>
+```
+
+```
+mLayout = (TransitionFrameLayout) inflater.inflate(R.layout.fragment_view_transition, null);
+```
+
+现在绿色界面在上面显示,蓝色隐藏.
+
+如果需要将蓝色界面切换出来,可以调用如下代码.
+
+```
+TransitionAdapter adapter = mLayout.switchView(mBlueView);
+```
+switchView有两个方法
+```
+    @Override
+    public TransitionAdapter switchView(View view) {
+        return switchView(view, false);
+    }
+
+    /**
+     * if you want add a view , just invoke switchView directly ,
+     * do not invoke addView , it may cause some problem .
+     *
+     * @param view
+     * @return
+     */
+    @Override
+    public TransitionAdapter switchView(final View view, boolean reverse) {
+        //.................
+    }
+```
+reverse为false表示动画扩张,为true表示收缩.
+
+在switchView后获得一个adapter对象,此时蓝色界面还没有展示出来.
+
+可以通过adapter获得一个ValueAnimator对象或者一个Controller对象.
+可以直接调用
+```
+adapter.animate();
+```
+来启动场景切换动画效果.
+
+也可以通过
+```
+adapter.getAnimator();
+```
+获得一个属性动画,自己控制动画过程.
+
+还可以获得一个Controller对象
+```
+mController = adapter.getController();
+```
+然后通过
+```
+mController.setProgress
+```
+来控制动画的实现进度.当到达1时(进度范围0~1),即动画结束时,调用
+```
+adapter.finish();
+```
+来通知转场结束了.
+
+直接使用adapter.animate()的效果如下
+
+![](https://github.com/dqh147258/ClipPathLayout/blob/master/image/view_transition.gif)
+
+#### TransitionFragmentContainer
+
+这个布局作为Fragment的容器来实现Fragment的场景切换效果.
+
+直接像FrameLayout作为Fragment容器做动态添加删除即可.
+
+效果如下
+
+![](https://github.com/dqh147258/ClipPathLayout/blob/master/image/fragment_transition_2.gif)
+
+#### TransitionAdapter
+
+这个类是一个Path适配器,构造方法如下
+
+```
+public TransitionAdapter(PathGenerator generator)
+```
+
+适配器需要获得一个Path内所能容下的最大矩形区域来确定一个最小的放大Scale,以获得最好的视觉效果,
+当前采用了一种二分查找的方式去获得这个矩形区域,不过这种方式有个弊端,对于中心有镂空的Path,
+这种方式是不可行的,所以针对这种情况,添加了一个TransitionPathGenerator的接口,定义如下
+
+```
+public interface TransitionPathGenerator extends PathGenerator {
+
+    /**
+     * @param similar 相似矩形参考
+     * @param boundWidth Path的范围区域宽
+     * @param boundHeight Path的范围区域高
+     * @return 返回最大的和@param similar相似的的矩形区域,
+     * 返回的矩形区域中心必须是Path的中心,即(boundWidth/2,boundHeight/2),
+     * 为了尽量减少内存抖动,建议使用参数传入的矩形修改数值后返回
+     */
+    Rect maxContainSimilarRange(Rect similar, int boundWidth, int boundHeight);
+
+}
+```
+
+如果有比较特殊的Path(比如有镂空)需要自定义包含的矩形区域范围,可以实现这个接口,然后作为TransitionAdapter的构造参数传入.
+
+回到TransitionAdapter上
+
+以上两种转场动画容器都有setAdapter方法,可以替换掉默认的TransitionAdapter.
+
+从TransitionFrameLayout.switchView中获得Adapter后,还可以通过setPathCenter来控制Path的扩张和收缩中心,默认PathCenter是View中心.
+
+## 原理解析
+
+[Android中不规则形状View的布局实现](https://www.jianshu.com/p/178c9efcdb44)
+
+[基于ClipPathLayout转场动画布局的实现](https://www.jianshu.com/p/0c7d4214fabe)
+
+---
+

+ 1 - 0
app/.gitignore

@@ -0,0 +1 @@
+/build

+ 42 - 0
app/build.gradle

@@ -0,0 +1,42 @@
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 28
+    defaultConfig {
+        applicationId "com.yxf.clippathlayout.sample"
+        minSdkVersion 19
+        targetSdkVersion 28
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+}
+
+dependencies {
+    implementation fileTree(include: ['*.jar'], dir: 'libs')
+    implementation 'com.android.support:appcompat-v7:28.0.0'
+    implementation 'com.android.support:design:28.0.0'
+    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
+    testImplementation 'junit:junit:4.12'
+    androidTestImplementation 'com.android.support.test:runner:1.0.2'
+    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+    implementation project(':clippathlayout')
+//    implementation 'com.yxf:clippathlayout:1.0.+'
+
+    //LeakCanary
+    debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
+    releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
+    // Optional, if you use support library fragments:
+    debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'
+
+}

+ 21 - 0
app/proguard-rules.pro

@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

+ 26 - 0
app/src/androidTest/java/com/yxf/clippathlayout/sample/ExampleInstrumentedTest.java

@@ -0,0 +1,26 @@
+package com.yxf.clippathlayout.sample;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+    @Test
+    public void useAppContext() {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getTargetContext();
+
+        assertEquals("com.yxf.clippathlayout.sample", appContext.getPackageName());
+    }
+}

+ 25 - 0
app/src/main/AndroidManifest.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.yxf.clippathlayout.sample">
+
+    <application
+        android:name=".MyApplication"
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme">
+        <activity
+            android:name=".MainActivity"
+            android:label="@string/app_name"
+            android:theme="@style/AppTheme.NoActionBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>

+ 101 - 0
app/src/main/java/com/yxf/clippathlayout/sample/CirclePathFragment.java

@@ -0,0 +1,101 @@
+package com.yxf.clippathlayout.sample;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.yxf.clippathlayout.PathInfo;
+import com.yxf.clippathlayout.impl.ClipPathFrameLayout;
+import com.yxf.clippathlayout.pathgenerator.CirclePathGenerator;
+import com.yxf.clippathlayout.pathgenerator.OvalPathGenerator;
+
+public class CirclePathFragment extends Fragment {
+
+    private ImageView mImageView;
+    private ClipPathFrameLayout mLayout;
+
+    private TextView mPathFunctionSwitchView;
+    private TextView mClipTypeSwitchView;
+
+    private int mApplyFlag = PathInfo.APPLY_FLAG_DRAW_AND_TOUCH;
+
+    private int mClipType = PathInfo.CLIP_TYPE_IN;
+
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+        mLayout = (ClipPathFrameLayout) inflater.inflate(R.layout.fragment_circle_path, null);
+
+        mImageView = mLayout.findViewById(R.id.image);
+        mImageView.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                MyApplication.displayToast("View内");
+            }
+        });
+        updatePathInfo();
+        mLayout.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                MyApplication.displayToast("View外");
+            }
+        });
+
+        mPathFunctionSwitchView = mLayout.findViewById(R.id.switch_path_function);
+        mPathFunctionSwitchView.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                switch (mApplyFlag) {
+                    case PathInfo.APPLY_FLAG_DRAW_ONLY:
+                        mApplyFlag = PathInfo.APPLY_FLAG_TOUCH_ONLY;
+                        break;
+                    case PathInfo.APPLY_FLAG_TOUCH_ONLY:
+                        mApplyFlag = PathInfo.APPLY_FLAG_DRAW_AND_TOUCH;
+                        break;
+                    case PathInfo.APPLY_FLAG_DRAW_AND_TOUCH:
+                        mApplyFlag = PathInfo.APPLY_FLAG_DRAW_ONLY;
+                        break;
+                }
+                updatePathInfo();
+            }
+        });
+        mClipTypeSwitchView = mLayout.findViewById(R.id.switch_clip_type);
+        mClipTypeSwitchView.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                switch (mClipType) {
+                    case PathInfo.CLIP_TYPE_IN:
+                        mClipType = PathInfo.CLIP_TYPE_OUT;
+                        break;
+                    case PathInfo.CLIP_TYPE_OUT:
+                        mClipType = PathInfo.CLIP_TYPE_IN;
+                        break;
+                }
+                updatePathInfo();
+            }
+        });
+
+        new PathInfo.Builder(new OvalPathGenerator(), mClipTypeSwitchView)
+                .setApplyFlag(PathInfo.APPLY_FLAG_DRAW_AND_TOUCH)
+                .setClipType(PathInfo.CLIP_TYPE_IN)
+                .setAntiAlias(true)
+                .create()
+                .apply(mLayout);
+
+        return mLayout;
+    }
+
+    private void updatePathInfo() {
+        new PathInfo.Builder(new CirclePathGenerator(), mImageView)
+                .setApplyFlag(mApplyFlag)
+                .setClipType(mClipType)
+                .create()
+                .apply();
+    }
+}

+ 49 - 0
app/src/main/java/com/yxf/clippathlayout/sample/ControlButtonFragment.java

@@ -0,0 +1,49 @@
+package com.yxf.clippathlayout.sample;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.yxf.clippathlayout.PathInfo;
+import com.yxf.clippathlayout.impl.ClipPathRelativeLayout;
+import com.yxf.clippathlayout.pathgenerator.PathGenerator;
+import com.yxf.clippathlayout.pathgenerator.RhombusPathGenerator;
+
+public class ControlButtonFragment extends Fragment {
+
+    private ClipPathRelativeLayout mLayout;
+
+    private View mLeftView, mTopView, mRightView, mBottomView;
+
+
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+        mLayout = (ClipPathRelativeLayout) inflater.inflate(R.layout.fragment_control_button, null);
+        mLeftView = mLayout.findViewById(R.id.left_view);
+        mTopView = mLayout.findViewById(R.id.top_view);
+        mRightView = mLayout.findViewById(R.id.right_view);
+        mBottomView = mLayout.findViewById(R.id.bottom_view);
+
+        PathGenerator generator = new RhombusPathGenerator();
+
+        new PathInfo.Builder(generator, mLeftView)
+                .create()
+                .apply();
+        new PathInfo.Builder(generator, mTopView)
+                .create()
+                .apply();
+        new PathInfo.Builder(generator, mRightView)
+                .create()
+                .apply();
+        new PathInfo.Builder(generator, mBottomView)
+                .create()
+                .apply();
+
+        return mLayout;
+    }
+}

+ 144 - 0
app/src/main/java/com/yxf/clippathlayout/sample/MainActivity.java

@@ -0,0 +1,144 @@
+package com.yxf.clippathlayout.sample;
+
+import android.os.Bundle;
+import android.support.design.widget.NavigationView;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.app.ActionBarDrawerToggle;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import com.yxf.clippathlayout.pathgenerator.AnimTransition;
+import com.yxf.clippathlayout.pathgenerator.CirclePathGenerator;
+import com.yxf.clippathlayout.pathgenerator.OvalPathGenerator;
+import com.yxf.clippathlayout.pathgenerator.RhombusPathGenerator;
+import com.yxf.clippathlayout.transition.TransitionAdapter;
+import com.yxf.clippathlayout.transition.TransitionFragmentContainer;
+import com.yxf.clippathlayout.transition.generator.RandomTransitionPathGenerator;
+
+import java.lang.ref.WeakReference;
+
+public class MainActivity extends AppCompatActivity
+        implements NavigationView.OnNavigationItemSelectedListener {
+
+
+    private TransitionFragmentContainer mContainer;
+
+    private WeakReference<Fragment> mLastFragmentReference;
+
+    FragmentManager mFragmentManager = getSupportFragmentManager();
+
+    private TransitionAdapter mTransitionAdapter;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+        setSupportActionBar(toolbar);
+
+        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
+        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
+                this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
+        drawer.addDrawerListener(toggle);
+        toggle.syncState();
+
+        NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
+        navigationView.setNavigationItemSelectedListener(this);
+
+        mContainer = findViewById(R.id.fragment_container);
+        RandomTransitionPathGenerator generator =
+                new RandomTransitionPathGenerator(new AnimTransition());
+        mTransitionAdapter = new TransitionAdapter(generator);
+        mTransitionAdapter.setImmediately(true);
+        mContainer.setAdapter(mTransitionAdapter);
+        switchFragment(new ScrollTransitionFragment(), false);
+    }
+
+    @Override
+    public void onBackPressed() {
+        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
+        if (drawer.isDrawerOpen(GravityCompat.START)) {
+            drawer.closeDrawer(GravityCompat.START);
+        } else {
+            if (mFragmentManager.getBackStackEntryCount() > 0) {
+                mFragmentManager.popBackStack();
+            } else {
+                super.onBackPressed();
+            }
+        }
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        // Inflate the menu; this adds items to the action bar if it is present.
+        getMenuInflater().inflate(R.menu.main, menu);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        // Handle action bar item clicks here. The action bar will
+        // automatically handle clicks on the Home/Up button, so long
+        // as you specify a parent activity in AndroidManifest.xml.
+        int id = item.getItemId();
+
+        //noinspection SimplifiableIfStatement
+        if (id == R.id.action_settings) {
+            return true;
+        }
+
+        return super.onOptionsItemSelected(item);
+    }
+
+    private void switchFragment(Fragment fragment) {
+        if (mTransitionAdapter.isImmediately()) {
+            mTransitionAdapter.setImmediately(false);
+        }
+        switchFragment(fragment, true);
+    }
+
+    private void switchFragment(Fragment fragment, boolean addToBackStack) {
+        FragmentTransaction transaction = mFragmentManager.beginTransaction();
+        Fragment f;
+        if (mLastFragmentReference != null && (f = mLastFragmentReference.get()) != null) {
+            transaction.hide(f);
+        }
+        if (addToBackStack) {
+            transaction.addToBackStack(null);
+        }
+        transaction.add(R.id.fragment_container, fragment).commit();
+        mLastFragmentReference = new WeakReference<Fragment>(fragment);
+    }
+
+
+    @SuppressWarnings("StatementWithEmptyBody")
+    @Override
+    public boolean onNavigationItemSelected(MenuItem item) {
+        // Handle navigation view item clicks here.
+        int id = item.getItemId();
+
+        if (id == R.id.nav_circle_path) {
+            switchFragment(new CirclePathFragment());
+        } else if (id == R.id.nav_control_button) {
+            switchFragment(new ControlButtonFragment());
+        } else if (id == R.id.nav_remote_controller) {
+            switchFragment(new RemoteControllerFragment());
+        } else if (id == R.id.yin_yang_fish) {
+            switchFragment(new YinYangFishFragment());
+        } else if (id == R.id.nav_view_transition) {
+            switchFragment(new ViewTransitionFragment());
+        } else if (id == R.id.nav_scroll_transition) {
+            switchFragment(new ScrollTransitionFragment());
+        }
+
+        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
+        drawer.closeDrawer(GravityCompat.START);
+        return true;
+    }
+}

+ 38 - 0
app/src/main/java/com/yxf/clippathlayout/sample/MyApplication.java

@@ -0,0 +1,38 @@
+package com.yxf.clippathlayout.sample;
+
+import android.app.Application;
+import android.content.Context;
+import android.widget.Toast;
+
+import com.squareup.leakcanary.LeakCanary;
+
+public class MyApplication extends Application {
+
+    private static Context sAppContext;
+    private static Toast sToast;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        if (LeakCanary.isInAnalyzerProcess(this)) {
+            // This process is dedicated to LeakCanary for heap analysis.
+            // You should not init your app in this process.
+            return;
+        }
+        LeakCanary.install(this);
+        sAppContext = getApplicationContext();
+    }
+
+    public static void displayToast(String message) {
+        if (sToast == null) {
+            sToast = Toast.makeText(sAppContext, message, Toast.LENGTH_SHORT);
+        } else {
+            sToast.setText(message);
+        }
+        sToast.show();
+    }
+
+    public static Context getAppContext() {
+        return sAppContext;
+    }
+}

+ 51 - 0
app/src/main/java/com/yxf/clippathlayout/sample/RemoteControllerFragment.java

@@ -0,0 +1,51 @@
+package com.yxf.clippathlayout.sample;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.yxf.clippathlayout.PathInfo;
+import com.yxf.clippathlayout.impl.ClipPathFrameLayout;
+import com.yxf.clippathlayout.pathgenerator.CirclePathGenerator;
+import com.yxf.clippathlayout.pathgenerator.OvalPathGenerator;
+import com.yxf.clippathlayout.pathgenerator.OvalRingPathGenerator;
+
+public class RemoteControllerFragment extends Fragment {
+
+    ClipPathFrameLayout mLayout;
+    View mCenterView, mLeftView, mRightView, mTopView, mBottomView;
+
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+        mLayout = (ClipPathFrameLayout) inflater.inflate(R.layout.fragment_remote_controller, null);
+        mCenterView = mLayout.findViewById(R.id.center_view);
+        mLeftView = mLayout.findViewById(R.id.left_view);
+        mRightView = mLayout.findViewById(R.id.right_view);
+        mTopView = mLayout.findViewById(R.id.top_view);
+        mBottomView = mLayout.findViewById(R.id.bottom_view);
+
+        new PathInfo.Builder(new OvalPathGenerator(), mCenterView)
+                .create()
+                .apply();
+
+        new PathInfo.Builder(new OvalRingPathGenerator(0.45f, 44, 88), mBottomView)
+                .create()
+                .apply();
+        new PathInfo.Builder(new OvalRingPathGenerator(0.45f, 134, 88), mLeftView)
+                .create()
+                .apply();
+        new PathInfo.Builder(new OvalRingPathGenerator(0.45f, 224, 88), mTopView)
+                .create()
+                .apply();
+        new PathInfo.Builder(new OvalRingPathGenerator(0.45f, 314, 88), mRightView)
+                .create()
+                .apply();
+
+        return mLayout;
+    }
+}

+ 103 - 0
app/src/main/java/com/yxf/clippathlayout/sample/ScrollTransitionFragment.java

@@ -0,0 +1,103 @@
+package com.yxf.clippathlayout.sample;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v4.widget.NestedScrollView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import com.yxf.clippathlayout.Utils;
+import com.yxf.clippathlayout.transition.ProgressController;
+import com.yxf.clippathlayout.transition.TransitionAdapter;
+import com.yxf.clippathlayout.transition.TransitionFrameLayout;
+
+public class ScrollTransitionFragment extends Fragment implements NestedScrollView.OnScrollChangeListener {
+
+    private static final String TAG = Utils.getTAG(ScrollTransitionFragment.class);
+
+    private NestedScrollView mLayout;
+
+    private TransitionFrameLayout mImageContainer;
+
+    private ImageView mBelowView, mAboveView;
+
+    private ProgressController mController;
+    private TransitionAdapter mTransitionAdapter;
+
+    private boolean mAbove = true;
+
+    private boolean mOver = true;
+
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+        mLayout = (NestedScrollView) inflater.inflate(R.layout.fragment_scroll_transition, null);
+        mImageContainer = mLayout.findViewById(R.id.image_container);
+        mBelowView = mLayout.findViewById(R.id.below_image);
+        mAboveView = mLayout.findViewById(R.id.above_image);
+        mLayout.setOnScrollChangeListener(this);
+        mLayout.post(new Runnable() {
+            @Override
+            public void run() {
+                if (mController == null) {
+                    initController();
+                }
+            }
+        });
+        return mLayout;
+    }
+
+    @Override
+    public void onScrollChange(NestedScrollView nestedScrollView, int i, int i1, int i2, int i3) {
+        int top = mImageContainer.getTop();
+        int height = mImageContainer.getHeight();
+        int scrollCenter = i1 + nestedScrollView.getHeight() / 2;
+        if (scrollCenter < top && mAbove) {
+            mOver = true;
+        } else if (scrollCenter > top + height && !mAbove) {
+            mOver = true;
+        } else {
+            mOver = false;
+        }
+        controlProgress(scrollCenter - top, height);
+    }
+
+    private void controlProgress(int current, int total) {
+        if (mOver) {
+            if (mController != null) {
+                mTransitionAdapter.finish();
+                mController = null;
+            }
+        } else {
+            if (mController == null) {
+                initController();
+            }
+            if (mAbove) {
+                current = total - current;
+            }
+            mController.setProgress(current / (total * 1f));
+        }
+    }
+
+    private void initController() {
+        if (mAbove) {
+            int x = mImageContainer.getWidth() * 3 / 4;
+            int y = mImageContainer.getHeight() * 3 / 4;
+            mTransitionAdapter = mImageContainer.switchView(mBelowView);
+            mTransitionAdapter.setPathCenter(x, y);
+            mController = mTransitionAdapter.getController();
+            mAbove = false;
+        } else {
+            int x = mImageContainer.getWidth() / 4;
+            int y = mImageContainer.getHeight() / 4;
+            mTransitionAdapter = mImageContainer.switchView(mAboveView);
+            mTransitionAdapter.setPathCenter(x, y);
+            mController = mTransitionAdapter.getController();
+            mAbove = true;
+        }
+    }
+}

+ 65 - 0
app/src/main/java/com/yxf/clippathlayout/sample/ViewTransitionFragment.java

@@ -0,0 +1,65 @@
+package com.yxf.clippathlayout.sample;
+
+import android.animation.ValueAnimator;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.yxf.clippathlayout.pathgenerator.AnimTransition;
+import com.yxf.clippathlayout.pathgenerator.CirclePathGenerator;
+import com.yxf.clippathlayout.pathgenerator.OvalPathGenerator;
+import com.yxf.clippathlayout.pathgenerator.RhombusPathGenerator;
+import com.yxf.clippathlayout.transition.TransitionAdapter;
+import com.yxf.clippathlayout.transition.TransitionFrameLayout;
+import com.yxf.clippathlayout.transition.generator.RandomTransitionPathGenerator;
+
+public class ViewTransitionFragment extends Fragment {
+
+    TransitionFrameLayout mLayout;
+    View mBlueView, mGreenView;
+
+    private int mLastX, mLastY;
+
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+        mLayout = (TransitionFrameLayout) inflater.inflate(R.layout.fragment_view_transition, null);
+        RandomTransitionPathGenerator generator =
+                new RandomTransitionPathGenerator(new AnimTransition());
+        mLayout.setAdapter(new TransitionAdapter(generator));
+        mBlueView = mLayout.findViewById(R.id.blue_view);
+        mGreenView = mLayout.findViewById(R.id.green_view);
+        View.OnTouchListener listener = new View.OnTouchListener() {
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+                mLastX = (int) event.getX();
+                mLastY = (int) event.getY();
+                return false;
+            }
+        };
+        mBlueView.setOnTouchListener(listener);
+        mGreenView.setOnTouchListener(listener);
+        mBlueView.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                TransitionAdapter adapter = mLayout.switchView(mGreenView);
+                adapter.setPathCenter(mLastX, mLastY);
+                adapter.animate();
+            }
+        });
+        mGreenView.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                TransitionAdapter adapter = mLayout.switchView(mBlueView);
+                adapter.setPathCenter(mLastX, mLastY);
+                adapter.animate();
+            }
+        });
+        return mLayout;
+    }
+}

+ 111 - 0
app/src/main/java/com/yxf/clippathlayout/sample/YinYangFishFragment.java

@@ -0,0 +1,111 @@
+package com.yxf.clippathlayout.sample;
+
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.yxf.clippathlayout.PathInfo;
+import com.yxf.clippathlayout.impl.ClipPathFrameLayout;
+import com.yxf.clippathlayout.pathgenerator.PathGenerator;
+
+public class YinYangFishFragment extends Fragment {
+
+    private ClipPathFrameLayout mLayout;
+    private View mYinFishView, mYangFishView;
+
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+        mLayout = (ClipPathFrameLayout) inflater.inflate(R.layout.fragment_yin_yang_fish, null);
+        mYinFishView = mLayout.findViewById(R.id.yin_fish_view);
+        mYangFishView = mLayout.findViewById(R.id.yang_fish_view);
+        mYinFishView.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                MyApplication.displayToast("阴");
+            }
+        });
+        mYangFishView.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                MyApplication.displayToast("阳");
+            }
+        });
+
+        new PathInfo.Builder(new YinYangFishPathGenerator(270), mYinFishView)
+                .setAntiAlias(true)
+                .create()
+                .apply();
+
+        new PathInfo.Builder(new YinYangFishPathGenerator(90), mYangFishView)
+                .create()
+                .apply();
+        return mLayout;
+    }
+
+
+    private static class YinYangFishPathGenerator implements PathGenerator {
+
+        private int mDegree;
+
+        private RectF mRectF = new RectF();
+
+        private Path mPath = new Path();
+
+        public YinYangFishPathGenerator(int degree) {
+            mDegree = degree;
+        }
+
+        @Override
+        public Path generatePath(Path old, View view, int width, int height) {
+            double radians = Math.toRadians(mDegree);
+            int radius = Math.min(width, height) / 2;
+            int centerX = width / 2;
+            int centerY = height / 2;
+
+            if (old == null) {
+                old = new Path();
+            } else {
+                old.reset();
+            }
+
+            mRectF.set(centerX - radius, centerY - radius, centerX + radius, centerY + radius);
+            old.moveTo(centerX + (float) (radius * Math.cos(radians)),
+                    centerY + (float) (radius * Math.sin(radians)));
+            old.arcTo(mRectF, mDegree, 180);
+
+            int degree = mDegree + 180;
+            double cRadians = Math.toRadians(degree);
+            int cX = centerX + (int) (radius * Math.cos(cRadians) / 2);
+            int cY = centerY + (int) (radius * Math.sin(cRadians) / 2);
+            int cR = radius / 2;
+            mRectF.set(cX - cR, cY - cR, cX + cR, cY + cR);
+            old.arcTo(mRectF, degree, 180);
+            old.close();
+            mPath.reset();
+            mPath.addCircle(cX, cY, cR / 3, Path.Direction.CW);
+            old.op(mPath, Path.Op.DIFFERENCE);
+
+            cX = centerX + (int) (radius * Math.cos(radians) / 2);
+            cY = centerY + (int) (radius * Math.sin(radians) / 2);
+            mRectF.set(cX - cR, cY - cR, cX + cR, cY + cR);
+            mPath.reset();
+            mPath.addArc(mRectF, mDegree, 180);
+            mPath.close();
+            old.op(mPath, Path.Op.DIFFERENCE);
+
+            mPath.reset();
+            mPath.moveTo(centerX + (float) (radius * Math.cos(radians)),
+                    centerY + (float) (radius * Math.sin(radians)));
+            mPath.addCircle(cX, cY, cR / 3, Path.Direction.CW);
+            old.op(mPath, Path.Op.UNION);
+            return old;
+        }
+    }
+}

+ 12 - 0
app/src/main/res/drawable-v21/ic_menu_camera.xml

@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0" />
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M9,2L7.17,4H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2H9zm3,15c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z" />
+</vector>

+ 9 - 0
app/src/main/res/drawable-v21/ic_menu_gallery.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M22,16V4c0,-1.1 -0.9,-2 -2,-2H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2zm-11,-4l2.03,2.71L16,11l4,5H8l3,-4zM2,6v14c0,1.1 0.9,2 2,2h14v-2H4V6H2z" />
+</vector>

+ 9 - 0
app/src/main/res/drawable-v21/ic_menu_manage.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M22.7,19l-9.1,-9.1c0.9,-2.3 0.4,-5 -1.5,-6.9 -2,-2 -5,-2.4 -7.4,-1.3L9,6 6,9 1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1c1.9,1.9 4.6,2.4 6.9,1.5l9.1,9.1c0.4,0.4 1,0.4 1.4,0l2.3,-2.3c0.5,-0.4 0.5,-1.1 0.1,-1.4z" />
+</vector>

+ 9 - 0
app/src/main/res/drawable-v21/ic_menu_send.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z" />
+</vector>

+ 9 - 0
app/src/main/res/drawable-v21/ic_menu_share.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z" />
+</vector>

+ 9 - 0
app/src/main/res/drawable-v21/ic_menu_slideshow.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M4,6H2v14c0,1.1 0.9,2 2,2h14v-2H4V6zm16,-4H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-8,12.5v-9l6,4.5 -6,4.5z" />
+</vector>

+ 34 - 0
app/src/main/res/drawable-v24/ic_launcher_foreground.xml

@@ -0,0 +1,34 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportHeight="108"
+    android:viewportWidth="108">
+    <path
+        android:fillType="evenOdd"
+        android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
+        android:strokeColor="#00000000"
+        android:strokeWidth="1">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="78.5885"
+                android:endY="90.9159"
+                android:startX="48.7653"
+                android:startY="61.0927"
+                android:type="linear">
+                <item
+                    android:color="#44000000"
+                    android:offset="0.0" />
+                <item
+                    android:color="#00000000"
+                    android:offset="1.0" />
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path
+        android:fillColor="#FFFFFF"
+        android:fillType="nonZero"
+        android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
+        android:strokeColor="#00000000"
+        android:strokeWidth="1" />
+</vector>

+ 12 - 0
app/src/main/res/drawable/button_oval.xml

@@ -0,0 +1,12 @@
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true">
+        <shape android:shape="oval">
+            <solid android:color="@android:color/holo_green_light" />
+        </shape>
+    </item>
+    <item android:state_pressed="false">
+        <shape android:shape="oval">
+            <solid android:color="@android:color/holo_green_dark" />
+        </shape>
+    </item>
+</selector>

+ 12 - 0
app/src/main/res/drawable/button_rectangle_black.xml

@@ -0,0 +1,12 @@
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true">
+        <shape android:shape="rectangle">
+            <solid android:color="@android:color/darker_gray" />
+        </shape>
+    </item>
+    <item android:state_pressed="false">
+        <shape android:shape="rectangle">
+            <solid android:color="@android:color/black" />
+        </shape>
+    </item>
+</selector>

+ 13 - 0
app/src/main/res/drawable/button_rectangle_blue.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true">
+        <shape android:shape="rectangle">
+            <solid android:color="@android:color/holo_blue_light" />
+        </shape>
+    </item>
+    <item android:state_pressed="false">
+        <shape android:shape="rectangle">
+            <solid android:color="@android:color/holo_blue_dark" />
+        </shape>
+    </item>
+</selector>

+ 12 - 0
app/src/main/res/drawable/button_rectangle_green.xml

@@ -0,0 +1,12 @@
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true">
+        <shape android:shape="rectangle">
+            <solid android:color="@android:color/holo_green_light" />
+        </shape>
+    </item>
+    <item android:state_pressed="false">
+        <shape android:shape="rectangle">
+            <solid android:color="@android:color/holo_green_dark" />
+        </shape>
+    </item>
+</selector>

+ 12 - 0
app/src/main/res/drawable/button_rectangle_orange.xml

@@ -0,0 +1,12 @@
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true">
+        <shape android:shape="rectangle">
+            <solid android:color="@android:color/holo_orange_light" />
+        </shape>
+    </item>
+    <item android:state_pressed="false">
+        <shape android:shape="rectangle">
+            <solid android:color="@android:color/holo_orange_dark" />
+        </shape>
+    </item>
+</selector>

+ 12 - 0
app/src/main/res/drawable/button_rectangle_white.xml

@@ -0,0 +1,12 @@
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true">
+        <shape android:shape="rectangle">
+            <solid android:color="@android:color/darker_gray" />
+        </shape>
+    </item>
+    <item android:state_pressed="false">
+        <shape android:shape="rectangle">
+            <solid android:color="@android:color/white" />
+        </shape>
+    </item>
+</selector>

+ 170 - 0
app/src/main/res/drawable/ic_launcher_background.xml

@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportHeight="108"
+    android:viewportWidth="108">
+    <path
+        android:fillColor="#26A69A"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeColor="#33FFFFFF"
+        android:strokeWidth="0.8" />
+</vector>

+ 9 - 0
app/src/main/res/drawable/side_nav_bar.xml

@@ -0,0 +1,9 @@
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <gradient
+        android:angle="135"
+        android:centerColor="#009688"
+        android:endColor="#00695C"
+        android:startColor="#4DB6AC"
+        android:type="linear" />
+</shape>

+ 25 - 0
app/src/main/res/layout/activity_main.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/drawer_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true"
+    tools:openDrawer="start">
+
+    <include
+        layout="@layout/app_bar_main"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+    <android.support.design.widget.NavigationView
+        android:id="@+id/nav_view"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_gravity="start"
+        android:fitsSystemWindows="true"
+        app:headerLayout="@layout/nav_header_main"
+        app:menu="@menu/activity_main_drawer" />
+
+</android.support.v4.widget.DrawerLayout>

+ 25 - 0
app/src/main/res/layout/app_bar_main.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".MainActivity">
+
+    <android.support.design.widget.AppBarLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:theme="@style/AppTheme.AppBarOverlay">
+
+        <android.support.v7.widget.Toolbar
+            android:id="@+id/toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="?attr/actionBarSize"
+            android:background="?attr/colorPrimary"
+            app:popupTheme="@style/AppTheme.PopupOverlay" />
+
+    </android.support.design.widget.AppBarLayout>
+
+    <include layout="@layout/content_main" />
+
+</android.support.design.widget.CoordinatorLayout>

+ 12 - 0
app/src/main/res/layout/content_main.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.yxf.clippathlayout.transition.TransitionFragmentContainer xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/fragment_container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    app:layout_behavior="@string/appbar_scrolling_view_behavior"
+    tools:context=".MainActivity"
+    tools:showIn="@layout/app_bar_main">
+
+</com.yxf.clippathlayout.transition.TransitionFragmentContainer>

+ 43 - 0
app/src/main/res/layout/fragment_circle_path.xml

@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.yxf.clippathlayout.impl.ClipPathFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/clip_path_frame_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/switch_path_function"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="top|left"
+        android:layout_margin="30dp"
+        android:background="@drawable/button_oval"
+        android:gravity="center"
+        android:padding="20dp"
+        android:text="切换Path功能" />
+
+    <TextView
+        android:id="@+id/switch_clip_type"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="top|right"
+        android:layout_margin="30dp"
+        android:background="@drawable/button_rectangle_green"
+        android:gravity="center"
+        android:padding="20dp"
+        android:text="切换切割方式"
+        />
+
+    <TextView
+        android:layout_width="300dp"
+        android:layout_height="300dp"
+        android:layout_gravity="center"
+        android:background="@android:color/holo_green_light" />
+
+    <ImageView
+        android:id="@+id/image"
+        android:layout_width="300dp"
+        android:layout_height="300dp"
+        android:layout_gravity="center"
+        android:src="@mipmap/image" />
+
+</com.yxf.clippathlayout.impl.ClipPathFrameLayout>

+ 48 - 0
app/src/main/res/layout/fragment_control_button.xml

@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.yxf.clippathlayout.impl.ClipPathRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <View
+        android:id="@+id/center_view"
+        android:layout_width="0px"
+        android:layout_height="0px"
+        android:layout_centerInParent="true" />
+
+    <View
+        android:id="@+id/left_view"
+        android:layout_width="180dp"
+        android:layout_height="135dp"
+        android:layout_centerVertical="true"
+        android:layout_toLeftOf="@id/center_view"
+        android:background="@drawable/button_rectangle_green"
+        android:clickable="true" />
+
+    <View
+        android:id="@+id/right_view"
+        android:layout_width="180dp"
+        android:layout_height="135dp"
+        android:layout_centerVertical="true"
+        android:layout_toRightOf="@id/center_view"
+        android:background="@drawable/button_rectangle_green"
+        android:clickable="true" />
+
+    <View
+        android:id="@+id/top_view"
+        android:layout_width="135dp"
+        android:layout_height="180dp"
+        android:layout_above="@id/center_view"
+        android:layout_centerHorizontal="true"
+        android:background="@drawable/button_rectangle_green"
+        android:clickable="true" />
+
+    <View
+        android:id="@+id/bottom_view"
+        android:layout_width="135dp"
+        android:layout_height="180dp"
+        android:layout_below="@id/center_view"
+        android:layout_centerHorizontal="true"
+        android:background="@drawable/button_rectangle_green"
+        android:clickable="true" />
+
+</com.yxf.clippathlayout.impl.ClipPathRelativeLayout>

+ 47 - 0
app/src/main/res/layout/fragment_remote_controller.xml

@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.yxf.clippathlayout.impl.ClipPathFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <View
+        android:id="@+id/center_view"
+        android:layout_width="100dp"
+        android:layout_height="80dp"
+        android:layout_gravity="center"
+        android:background="@drawable/button_rectangle_orange"
+        android:clickable="true" />
+
+    <View
+        android:id="@+id/left_view"
+        android:layout_width="300dp"
+        android:layout_height="240dp"
+        android:layout_gravity="center"
+        android:background="@drawable/button_rectangle_green"
+        android:clickable="true" />
+
+    <View
+        android:id="@+id/right_view"
+        android:layout_width="300dp"
+        android:layout_height="240dp"
+        android:layout_gravity="center"
+        android:background="@drawable/button_rectangle_green"
+        android:clickable="true" />
+
+    <View
+        android:id="@+id/top_view"
+        android:layout_width="300dp"
+        android:layout_height="240dp"
+        android:layout_gravity="center"
+        android:background="@drawable/button_rectangle_blue"
+        android:clickable="true" />
+
+    <View
+        android:id="@+id/bottom_view"
+        android:layout_width="300dp"
+        android:layout_height="240dp"
+        android:layout_gravity="center"
+        android:background="@drawable/button_rectangle_blue"
+        android:clickable="true" />
+
+
+</com.yxf.clippathlayout.impl.ClipPathFrameLayout>

+ 48 - 0
app/src/main/res/layout/fragment_scroll_transition.xml

@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/scroll_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <ImageView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:adjustViewBounds="true"
+            android:scaleType="fitXY"
+            android:src="@mipmap/dmr_gn_portrait" />
+
+        <com.yxf.clippathlayout.transition.TransitionFrameLayout
+            android:id="@+id/image_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <ImageView
+                android:id="@+id/below_image"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:adjustViewBounds="true"
+                android:scaleType="fitXY"
+                android:src="@mipmap/tm_gy" />
+
+            <ImageView
+                android:id="@+id/above_image"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:adjustViewBounds="true"
+                android:scaleType="fitXY"
+                android:src="@mipmap/gn_dmr" />
+        </com.yxf.clippathlayout.transition.TransitionFrameLayout>
+
+        <ImageView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:adjustViewBounds="true"
+            android:scaleType="fitXY"
+            android:src="@mipmap/zhang_liang" />
+    </LinearLayout>
+</android.support.v4.widget.NestedScrollView>

+ 24 - 0
app/src/main/res/layout/fragment_view_transition.xml

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.yxf.clippathlayout.transition.TransitionFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/blue_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="#880000ff"
+        android:gravity="center"
+        android:text="蓝色界面"
+        android:textSize="30sp" />
+
+    <TextView
+        android:id="@+id/green_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="#8800ff00"
+        android:gravity="center"
+        android:text="绿色界面"
+        android:textSize="30sp" />
+
+</com.yxf.clippathlayout.transition.TransitionFrameLayout>

+ 24 - 0
app/src/main/res/layout/fragment_yin_yang_fish.xml

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.yxf.clippathlayout.impl.ClipPathFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@android:color/darker_gray"
+    >
+
+    <View
+        android:id="@+id/yang_fish_view"
+        android:layout_width="300dp"
+        android:layout_height="300dp"
+        android:layout_gravity="center"
+        android:background="@drawable/button_rectangle_white"
+        android:clickable="true" />
+
+    <View
+        android:id="@+id/yin_fish_view"
+        android:layout_width="300dp"
+        android:layout_height="300dp"
+        android:layout_gravity="center"
+        android:background="@drawable/button_rectangle_black"
+        android:clickable="true" />
+
+</com.yxf.clippathlayout.impl.ClipPathFrameLayout>

+ 36 - 0
app/src/main/res/layout/nav_header_main.xml

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/nav_header_height"
+    android:background="@drawable/side_nav_bar"
+    android:gravity="bottom"
+    android:orientation="vertical"
+    android:paddingBottom="@dimen/activity_vertical_margin"
+    android:paddingLeft="@dimen/activity_horizontal_margin"
+    android:paddingRight="@dimen/activity_horizontal_margin"
+    android:paddingTop="@dimen/activity_vertical_margin"
+    android:theme="@style/ThemeOverlay.AppCompat.Dark">
+
+    <ImageView
+        android:id="@+id/imageView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:contentDescription="@string/nav_header_desc"
+        android:paddingTop="@dimen/nav_header_vertical_spacing"
+        app:srcCompat="@mipmap/ic_launcher_round" />
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="@dimen/nav_header_vertical_spacing"
+        android:text="@string/nav_header_title"
+        android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
+
+    <TextView
+        android:id="@+id/textView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/nav_header_subtitle" />
+
+</LinearLayout>

+ 32 - 0
app/src/main/res/menu/activity_main_drawer.xml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:showIn="navigation_view">
+
+    <group android:checkableBehavior="single">
+        <item
+            android:id="@+id/nav_circle_path"
+            android:icon="@drawable/ic_menu_gallery"
+            android:title="圆形" />
+        <item
+            android:id="@+id/nav_control_button"
+            android:icon="@drawable/ic_menu_gallery"
+            android:title="方向键" />
+        <item
+            android:id="@+id/nav_remote_controller"
+            android:icon="@drawable/ic_menu_gallery"
+            android:title="遥控器" />
+        <item
+            android:id="@+id/yin_yang_fish"
+            android:icon="@drawable/ic_menu_gallery"
+            android:title="阴阳鱼" />
+        <item
+            android:id="@+id/nav_view_transition"
+            android:icon="@drawable/ic_menu_gallery"
+            android:title="场景切换" />
+        <item
+            android:id="@+id/nav_scroll_transition"
+            android:icon="@drawable/ic_menu_gallery"
+            android:title="滚动切换" />
+    </group>
+</menu>

+ 9 - 0
app/src/main/res/menu/main.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item
+        android:id="@+id/action_settings"
+        android:orderInCategory="100"
+        android:title="@string/action_settings"
+        app:showAsAction="never" />
+</menu>

+ 5 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

+ 5 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-mdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-xhdpi/dmr_gn_portrait.jpg


BIN
app/src/main/res/mipmap-xhdpi/gn_dmr.jpg


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-xhdpi/image.jpg


BIN
app/src/main/res/mipmap-xhdpi/tm_gy.jpg


BIN
app/src/main/res/mipmap-xhdpi/zhang_liang.jpg


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png


+ 8 - 0
app/src/main/res/values-v21/styles.xml

@@ -0,0 +1,8 @@
+<resources>
+
+    <style name="AppTheme.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+        <item name="android:statusBarColor">@android:color/transparent</item>
+    </style>
+</resources>

+ 6 - 0
app/src/main/res/values/colors.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#3F51B5</color>
+    <color name="colorPrimaryDark">#303F9F</color>
+    <color name="colorAccent">#FF4081</color>
+</resources>

+ 8 - 0
app/src/main/res/values/dimens.xml

@@ -0,0 +1,8 @@
+<resources>
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+    <dimen name="nav_header_vertical_spacing">8dp</dimen>
+    <dimen name="nav_header_height">176dp</dimen>
+    <dimen name="fab_margin">16dp</dimen>
+</resources>

+ 8 - 0
app/src/main/res/values/drawables.xml

@@ -0,0 +1,8 @@
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <item name="ic_menu_camera" type="drawable">@android:drawable/ic_menu_camera</item>
+    <item name="ic_menu_gallery" type="drawable">@android:drawable/ic_menu_gallery</item>
+    <item name="ic_menu_slideshow" type="drawable">@android:drawable/ic_menu_slideshow</item>
+    <item name="ic_menu_manage" type="drawable">@android:drawable/ic_menu_manage</item>
+    <item name="ic_menu_share" type="drawable">@android:drawable/ic_menu_share</item>
+    <item name="ic_menu_send" type="drawable">@android:drawable/ic_menu_send</item>
+</resources>

+ 9 - 0
app/src/main/res/values/strings.xml

@@ -0,0 +1,9 @@
+<resources>
+    <string name="app_name">ClipPathLayout</string>
+    <string name="navigation_drawer_open">Open navigation drawer</string>
+    <string name="navigation_drawer_close">Close navigation drawer</string>
+    <string name="nav_header_title">忆_析风</string>
+    <string name="nav_header_subtitle">dqh147258@gmail.com</string>
+    <string name="nav_header_desc">Navigation header</string>
+    <string name="action_settings">Settings</string>
+</resources>

+ 20 - 0
app/src/main/res/values/styles.xml

@@ -0,0 +1,20 @@
+<resources>
+
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+
+    <style name="AppTheme.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+    </style>
+
+    <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
+
+    <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
+
+</resources>

+ 17 - 0
app/src/test/java/com/yxf/clippathlayout/sample/ExampleUnitTest.java

@@ -0,0 +1,17 @@
+package com.yxf.clippathlayout.sample;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+public class ExampleUnitTest {
+    @Test
+    public void addition_isCorrect() {
+        assertEquals(4, 2 + 2);
+    }
+}

+ 32 - 0
build.gradle

@@ -0,0 +1,32 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    
+    repositories {
+        google()
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:4.0.0'
+        classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
+        classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3'
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        jcenter()
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
+
+ext {
+    CLIP_PATH_LAYOUT_VERSION = '1.0.4'
+}

+ 1 - 0
clippathlayout/.gitignore

@@ -0,0 +1 @@
+/build

+ 76 - 0
clippathlayout/build.gradle

@@ -0,0 +1,76 @@
+apply plugin: 'com.android.library'
+
+
+apply plugin: 'com.jfrog.bintray'
+Properties properties = new Properties()
+properties.load(project.rootProject.file('local.properties').newDataInputStream())
+version = rootProject.ext.CLIP_PATH_LAYOUT_VERSION //发布版本号
+group = "com.yxf" //最终引用形式
+
+
+
+android {
+    compileSdkVersion 28
+
+
+
+    defaultConfig {
+        minSdkVersion 19
+        targetSdkVersion 28
+        versionCode 1
+        versionName rootProject.ext.CLIP_PATH_LAYOUT_VERSION
+
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+
+}
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+    implementation 'com.android.support:appcompat-v7:28.0.0'
+    testImplementation 'junit:junit:4.12'
+    androidTestImplementation 'com.android.support.test:runner:1.0.2'
+    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+}
+
+
+task sourcesJar(type: Jar) {
+    from android.sourceSets.main.java.srcDirs
+    classifier = 'sources'
+}
+
+task javadoc(type: Javadoc) {
+    failOnError false //必须添加以免出错
+    source = android.sourceSets.main.java.srcDirs
+    classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
+}
+
+task javadocJar(type: Jar, dependsOn: javadoc) {
+    classifier = 'javadoc'
+    from javadoc.destinationDir
+}
+
+artifacts {
+    archives javadocJar
+    archives sourcesJar
+}
+
+javadoc {
+    options{
+        encoding "UTF-8"
+        charSet 'UTF-8'
+        author true
+        version true
+        links "http://docs.oracle.com/javase/7/docs/api"
+        title "ClipPathLayoutJavaDoc"
+    }
+}

+ 21 - 0
clippathlayout/proguard-rules.pro

@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

+ 26 - 0
clippathlayout/src/androidTest/java/com/yxf/clippathlayout/ExampleInstrumentedTest.java

@@ -0,0 +1,26 @@
+package com.yxf.clippathlayout;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+    @Test
+    public void useAppContext() {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getTargetContext();
+
+        assertEquals("com.yxf.clippathlayout.test", appContext.getPackageName());
+    }
+}

+ 2 - 0
clippathlayout/src/main/AndroidManifest.xml

@@ -0,0 +1,2 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.yxf.clippathlayout" />

+ 51 - 0
clippathlayout/src/main/java/com/yxf/clippathlayout/BitmapPathRegion.java

@@ -0,0 +1,51 @@
+package com.yxf.clippathlayout;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Path;
+
+import static com.yxf.clippathlayout.Config.DEFAULT_IN_SAMPLE_SIZE;
+
+
+public class BitmapPathRegion implements PathRegion {
+
+    private Bitmap mBitmap;
+
+    private final int mInSampleSize;
+
+    public BitmapPathRegion(Path path, int clipType, int width, int height) {
+        this(path, clipType, width, height, DEFAULT_IN_SAMPLE_SIZE);
+    }
+
+    public BitmapPathRegion(Path path, int clipType, int width, int height, int inSampleSize) {
+        mInSampleSize = inSampleSize;
+        if (width > 0 && height > 0) {
+            Bitmap in = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
+            Canvas canvas = new Canvas(in);
+            Utils.clipPath(canvas, path, clipType);
+            canvas.drawColor(Color.GREEN);
+            Matrix matrix = new Matrix();
+            matrix.setScale(1 / (inSampleSize * 1f), 1 / (inSampleSize * 1f));
+            mBitmap = Bitmap.createBitmap(in, 0, 0, in.getWidth(), in.getHeight(), matrix, false);
+        }
+    }
+
+
+    @Override
+    public boolean isInRegion(float x, float y) {
+        if (x < 0 || y < 0) {
+            return false;
+        }
+        if (mBitmap == null) {
+            return false;
+        }
+        int px = (int) (x / mInSampleSize);
+        int py = (int) (y / mInSampleSize);
+        if (px >= mBitmap.getWidth() || py >= mBitmap.getHeight()) {
+            return false;
+        }
+        return mBitmap.getPixel(px, py) == Color.GREEN;
+    }
+}

+ 29 - 0
clippathlayout/src/main/java/com/yxf/clippathlayout/ClipPathLayout.java

@@ -0,0 +1,29 @@
+package com.yxf.clippathlayout;
+
+import android.graphics.Canvas;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.util.Log;
+import android.view.View;
+
+public interface ClipPathLayout {
+
+
+    boolean isTransformedTouchPointInView(float x, float y, View child, PointF outLocalPoint);
+
+    void applyPathInfo(PathInfo info);
+
+    void cancelPathInfo(View child);
+
+    void beforeDrawChild(Canvas canvas, View child, long drawingTime);
+
+    void afterDrawChild(Canvas canvas, View child, long drawingTime);
+
+    void notifyPathChanged(View child);
+
+    void notifyAllPathChanged();
+
+    void requestLayout();
+
+
+}

+ 462 - 0
clippathlayout/src/main/java/com/yxf/clippathlayout/ClipPathLayoutDelegate.java

@@ -0,0 +1,462 @@
+package com.yxf.clippathlayout;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.DrawFilter;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PaintFlagsDrawFilter;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.os.Build;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Queue;
+
+public class ClipPathLayoutDelegate implements ClipPathLayout {
+
+    private static final String TAG = Utils.getTAG(ClipPathLayoutDelegate.class);
+
+    ViewGroup mParent;
+
+    private HashMap<ViewKey, PathInfo> mPathInfoMap = new HashMap<ViewKey, PathInfo>();
+
+    private PathRegionGenerator mPathRegionGenerator = PathRegionGenerators.createNativePathRegionGenerator();
+
+    // Lazily-created holder for point computations.
+    private float[] mTempPoint;
+
+    private Matrix mTempMatrix;
+
+    private ViewGetKey mTempViewGetKey;
+
+    private Paint mPathPaint;
+    private Paint mBitmapPaint;
+
+    private PorterDuffXfermode mDstInMode;
+    private PorterDuffXfermode mDstOutMode;
+
+    private DrawFilter mOriginalDrawFilter;
+    private Integer mOriginalLayerType;
+
+    private int mCanvasSavedCount;
+
+    private DrawFilter mAntiAliasDrawFilter;
+
+    private boolean mHasLayoutRequest = false;
+
+    private boolean mInBeforeDrawChild = false;
+
+    private Queue<Runnable> mPendingTaskQueue = new LinkedList<Runnable>();
+
+    private Runnable mBeforeDrawTask = new Runnable() {
+        @Override
+        public void run() {
+            executePendingTask();
+        }
+    };
+
+    public ClipPathLayoutDelegate(ViewGroup parent) {
+        if (parent == null) {
+            throw new NullPointerException("parent is a null value");
+        }
+        mParent = parent;
+    }
+
+    public ClipPathLayoutDelegate(ViewGroup parent, PathRegionGenerator generator) {
+        this(parent);
+        if (generator != null) {
+            mPathRegionGenerator = generator;
+        }
+    }
+
+    @Override
+    public boolean isTransformedTouchPointInView(float x, float y, View child, PointF outLocalPoint) {
+        final float[] point = getTempPoint();
+        point[0] = x;
+        point[1] = y;
+        transformPointToViewLocal(point, child);
+        boolean isInView = pointInView(child, point[0], point[1]);
+        if (isInView) {
+            ViewGetKey key = getTempViewGetKey(child.hashCode(), child);
+            PathInfo info = mPathInfoMap.get(key);
+            if (info != null) {
+                if ((info.getApplyFlag() & PathInfo.APPLY_FLAG_TOUCH_ONLY) != 0) {
+                    PathRegion region = info.getPathRegion();
+                    if (region != null) {
+                        if (!region.isInRegion(point[0], point[1])) {
+                            isInView = false;
+                        }
+                    }
+                }
+            }
+            resetTempViewGetKey();
+        }
+
+        if (isInView && outLocalPoint != null) {
+            outLocalPoint.set(point[0], point[1]);
+        }
+        return isInView;
+    }
+
+    private boolean pointInView(View child, float x, float y) {
+        return x > 0 && y > 0 && x < (child.getRight() - child.getLeft()) &&
+                y < (child.getBottom() - child.getTop());
+    }
+
+    private void transformPointToViewLocal(float[] point, View child) {
+        point[0] += mParent.getScrollX() - child.getLeft();
+        point[1] += mParent.getScrollY() - child.getTop();
+        Matrix matrix = child.getMatrix();
+        if (!matrix.isIdentity()) {
+            Matrix invert = getTempMatrix();
+            boolean result = matrix.invert(invert);
+            if (result) {
+                invert.mapPoints(point);
+            }
+        }
+    }
+
+    @Override
+    public void applyPathInfo(final PathInfo info) {
+        runBeforeDrawChild(new Runnable() {
+            @Override
+            public void run() {
+                removeDeletedViewPathInfo();
+                if (info.getView() == null) {
+                    Log.e(TAG, "applyPathInfo: apply path info failed ,the view of info is null");
+                    return;
+                }
+                mPathInfoMap.put(new ViewKey(info.hashCode(), info.getView()), info);
+                notifyPathChangedInternal(info.getView());
+            }
+        });
+    }
+
+    @Override
+    public void cancelPathInfo(View child) {
+        cancelPathInfoInternal(child);
+    }
+
+    private void cancelPathInfoInternal(final View child) {
+        if (child == null) {
+            Log.e(TAG, "cancelPathInfo: child is null");
+            return;
+        }
+        runBeforeDrawChild(new Runnable() {
+            @Override
+            public void run() {
+                ViewGetKey key = getTempViewGetKey(child.hashCode(), child);
+                mPathInfoMap.remove(key);
+                mParent.invalidate();
+            }
+        });
+    }
+
+    @Override
+    public void beforeDrawChild(Canvas canvas, View child, long drawingTime) {
+        if (mInBeforeDrawChild) {
+            Log.e(TAG, "beforeDrawChild: can not recursive call this method");
+            return;
+        }
+        mInBeforeDrawChild = true;
+        executePendingTask();
+        if (mHasLayoutRequest) {
+            mHasLayoutRequest = false;
+            notifyAllPathChangedInternal(false);
+        }
+
+        ViewGetKey key = getTempViewGetKey(child.hashCode(), child);
+        PathInfo info = mPathInfoMap.get(key);
+        if (info != null && info.isAntiAlias()) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+                mCanvasSavedCount = canvas.saveLayer(child.getLeft(), child.getTop(), child.getRight(), child.getBottom(), null);
+            } else {
+                mCanvasSavedCount = canvas.saveLayer(child.getLeft(), child.getTop(), child.getRight(), child.getBottom(), null, Canvas.ALL_SAVE_FLAG);
+            }
+        } else {
+            mCanvasSavedCount = canvas.save();
+        }
+        if (info != null) {
+            if (!info.isAntiAlias()) {
+                if ((info.getApplyFlag() & PathInfo.APPLY_FLAG_DRAW_ONLY) != 0) {
+                    Path path = info.getPath();
+                    if (path != null) {
+                        canvas.translate(child.getLeft(), child.getTop());
+                        Utils.clipPath(canvas, path, info.getClipType());
+                        canvas.translate(-child.getLeft(), -child.getTop());
+                    } else {
+                        Log.d(TAG, "beforeDrawChild: path is null , hash code : " + info.hashCode());
+                    }
+                }
+            } else {
+                mOriginalDrawFilter = canvas.getDrawFilter();
+                mOriginalLayerType = mParent.getLayerType();
+                mParent.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+                if (mAntiAliasDrawFilter == null) {
+                    mAntiAliasDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+                }
+                canvas.setDrawFilter(mAntiAliasDrawFilter);
+            }
+        }
+        resetTempViewGetKey();
+        mInBeforeDrawChild = false;
+    }
+
+    @Override
+    public void afterDrawChild(Canvas canvas, View child, long drawingTime) {
+        ViewGetKey key = getTempViewGetKey(child.hashCode(), child);
+        PathInfo info = mPathInfoMap.get(key);
+        if (info != null && info.isAntiAlias()) {
+            if ((info.getApplyFlag() & PathInfo.APPLY_FLAG_DRAW_ONLY) != 0) {
+                Path path = info.getPath();
+                if (path != null) {
+                    Bitmap bitmap = Bitmap.createBitmap(child.getWidth(), child.getHeight(), Bitmap.Config.ARGB_8888);
+                    Canvas c = new Canvas(bitmap);
+                    if (mPathPaint == null) {
+                        mPathPaint = new Paint();
+                        mPathPaint.setAntiAlias(true);
+                    }
+                    if (mBitmapPaint == null) {
+                        mBitmapPaint = new Paint();
+                        mPathPaint.setAntiAlias(true);
+                    }
+                    c.drawPath(path, mPathPaint);
+                    if (info.getClipType() == PathInfo.CLIP_TYPE_IN) {
+                        if (mDstInMode == null) {
+                            mDstInMode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
+                        }
+                        mBitmapPaint.setXfermode(mDstInMode);
+                    } else {
+                        if (mDstOutMode == null) {
+                            mDstOutMode = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);
+                        }
+                        mBitmapPaint.setXfermode(mDstOutMode);
+                    }
+                    canvas.drawBitmap(bitmap, child.getLeft(), child.getTop(), mBitmapPaint);
+                    //if use drawPath may cause some strange problems
+                    /*canvas.translate(child.getLeft(), child.getTop());
+                    canvas.drawPath(path, mBitmapPaint);
+                    canvas.translate(-child.getLeft(),-child.getTop());*/
+                } else {
+                    Log.d(TAG, "beforeDrawChild: path is null , hash code : " + info.hashCode());
+                }
+            }
+            mParent.setLayerType(mOriginalLayerType, null);
+            canvas.setDrawFilter(mOriginalDrawFilter);
+        }
+        resetTempViewGetKey();
+        canvas.restoreToCount(mCanvasSavedCount);
+    }
+
+    @Override
+    public void notifyPathChanged(View child) {
+        notifyPathChangedInternal(child);
+    }
+
+    @Override
+    public void notifyAllPathChanged() {
+        notifyAllPathChangedInternal(true);
+    }
+
+    private void notifyAllPathChangedInternal(final boolean reDraw) {
+        runBeforeDrawChild(new Runnable() {
+            @Override
+            public void run() {
+                Iterator<Map.Entry<ViewKey, PathInfo>> iterator = mPathInfoMap.entrySet().iterator();
+                while (iterator.hasNext()) {
+                    PathInfo info = iterator.next().getValue();
+                    if (info != null) {
+                        if (info.getView() != null) {
+                            updatePath(info);
+                            if (reDraw) {
+                                //info.getView().invalidate() the method can not refresh view info of path ,
+                                //mParent.invalidate() instead
+                                mParent.invalidate();
+                            }
+                        } else {
+                            iterator.remove();
+                        }
+                    } else {
+                        iterator.remove();
+                    }
+                }
+            }
+        });
+    }
+
+    @Override
+    public void requestLayout() {
+        mHasLayoutRequest = true;
+    }
+
+    private void notifyPathChangedInternal(final View child) {
+        runBeforeDrawChild(new Runnable() {
+            @Override
+            public void run() {
+                ViewGetKey key = getTempViewGetKey(child.hashCode(), child);
+                PathInfo info = mPathInfoMap.get(key);
+                if (info != null) {
+                    if (info.getView() != null) {
+                        updatePath(info);
+                        //info.getView().invalidate() the method can not refresh view info of path ,
+                        //mParent.invalidate() instead
+                        mParent.invalidate();
+                    } else {
+                        Log.e(TAG, "notifyPathChangedInternal: update path failed , the view is null");
+                        mPathInfoMap.remove(key);
+                    }
+                } else {
+                    Log.d(TAG, "notifyPathChangedInternal: notify path changed failed , the info is null");
+                    mPathInfoMap.remove(key);
+                }
+                resetTempViewGetKey();
+            }
+        });
+    }
+
+    private float[] getTempPoint() {
+        if (mTempPoint == null) {
+            mTempPoint = new float[2];
+        }
+        return mTempPoint;
+    }
+
+    private Matrix getTempMatrix() {
+        if (mTempMatrix == null) {
+            mTempMatrix = new Matrix();
+        }
+        return mTempMatrix;
+    }
+
+    private ViewGetKey getTempViewGetKey(int hashCode, View view) {
+        if (mTempViewGetKey == null) {
+            mTempViewGetKey = new ViewGetKey();
+        }
+        mTempViewGetKey.set(hashCode, view);
+        return mTempViewGetKey;
+    }
+
+    // avoid memory leak
+    private void resetTempViewGetKey() {
+        mTempViewGetKey.set(-1, null);
+    }
+
+    private void updatePath(PathInfo info) {
+        View view = info.getView();
+        if (view == null) {
+            Log.e(TAG, "updatePath: view is null ,update failed");
+            return;
+        }
+        if (view.getVisibility() != View.VISIBLE) {
+            Log.v(TAG, "updatePath: view is invisible or gone");
+            return;
+        }
+        int width = view.getWidth();
+        int height = view.getHeight();
+        if (width == 0 || height == 0) {
+            Log.v(TAG, "updatePath: the width or height of view is zero");
+            return;
+        }
+        info.setPath(info.getPathGenerator().generatePath(info.getPath(), view, width, height));
+        if ((info.getApplyFlag() & PathInfo.APPLY_FLAG_TOUCH_ONLY) != 0) {
+            info.setPathRegion(mPathRegionGenerator.generatorPathRegion(info.getPath(), info.getClipType(), width, height));
+        }
+    }
+
+    private void removeDeletedViewPathInfo() {
+        Iterator<Map.Entry<ViewKey, PathInfo>> iterator = mPathInfoMap.entrySet().iterator();
+        while (iterator.hasNext()) {
+            PathInfo info = iterator.next().getValue();
+            if (info.getView() == null) {
+                iterator.remove();
+            }
+        }
+    }
+
+    private void runBeforeDrawChild(Runnable runnable) {
+        if (mInBeforeDrawChild) {
+            runnable.run();
+        } else {
+            mPendingTaskQueue.add(runnable);
+            mParent.removeCallbacks(mBeforeDrawTask);
+            Utils.runOnUiThreadAfterViewCanUse(mParent, mBeforeDrawTask);
+        }
+    }
+
+    private void executePendingTask() {
+        while (mPendingTaskQueue.size() > 0) {
+            mPendingTaskQueue.poll().run();
+        }
+    }
+
+
+    private static class ViewKey {
+
+        private final int mHashCode;
+        private final WeakReference<View> mViewWeakReference;
+
+        public ViewKey(int hashCode, View view) {
+            mHashCode = hashCode;
+            mViewWeakReference = new WeakReference<View>(view);
+        }
+
+        @Override
+        public int hashCode() {
+            return mHashCode;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof ViewKey) {
+                ViewKey key = (ViewKey) obj;
+                return mViewWeakReference.get() == key.mViewWeakReference.get();
+            } else if (obj instanceof ViewGetKey) {
+                ViewGetKey key = (ViewGetKey) obj;
+                return mViewWeakReference.get() == key.mView;
+            }
+            return false;
+        }
+    }
+
+    private static class ViewGetKey {
+
+        private int mHashCode = -1;
+        private View mView;
+
+        public ViewGetKey() {
+
+        }
+
+        public void set(int hashCode, View view) {
+            mHashCode = hashCode;
+            mView = view;
+        }
+
+        @Override
+        public int hashCode() {
+            return mHashCode;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof ViewKey) {
+                ViewKey key = (ViewKey) obj;
+                if (key.mViewWeakReference.get() == mView) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+}

+ 9 - 0
clippathlayout/src/main/java/com/yxf/clippathlayout/Config.java

@@ -0,0 +1,9 @@
+package com.yxf.clippathlayout;
+
+public interface Config {
+
+    String libTAG = "ClipPath";
+
+    int DEFAULT_IN_SAMPLE_SIZE = 16;
+
+}

+ 26 - 0
clippathlayout/src/main/java/com/yxf/clippathlayout/NativePathRegion.java

@@ -0,0 +1,26 @@
+package com.yxf.clippathlayout;
+
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+
+public class NativePathRegion implements PathRegion {
+
+    Region mRegion = new Region();
+
+    private int mCLipType;
+
+    public NativePathRegion(Path path, int clipType) {
+        mCLipType = clipType;
+        RectF bounds = new RectF();
+        path.computeBounds(bounds, true);
+        mRegion.setPath(path, new Region((int) bounds.left,
+                (int) bounds.top, (int) bounds.right, (int) bounds.bottom));
+    }
+
+    @Override
+    public boolean isInRegion(float x, float y) {
+        return mRegion.contains((int) x, (int) y) ^ (mCLipType == PathInfo.CLIP_TYPE_OUT);
+    }
+}

+ 186 - 0
clippathlayout/src/main/java/com/yxf/clippathlayout/PathInfo.java

@@ -0,0 +1,186 @@
+package com.yxf.clippathlayout;
+
+import android.graphics.Path;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewParent;
+
+import com.yxf.clippathlayout.pathgenerator.PathGenerator;
+
+import java.lang.ref.WeakReference;
+
+public class PathInfo {
+
+    private static final String TAG = Utils.getTAG(PathInfo.class);
+
+    public static final int APPLY_FLAG_DRAW_ONLY = 1;
+    public static final int APPLY_FLAG_TOUCH_ONLY = 1 << 1;
+    public static final int APPLY_FLAG_DRAW_AND_TOUCH = APPLY_FLAG_DRAW_ONLY | APPLY_FLAG_TOUCH_ONLY;
+
+    public static final int CLIP_TYPE_IN = 0;
+    public static final int CLIP_TYPE_OUT = 1;
+
+    private PathGenerator mPathGenerator;
+    private WeakReference<View> mViewReference;
+
+    private final int mSavedHashCode;
+
+    private int mApplyFlag;
+
+    private int mClipType;
+
+    private Path mPath;
+    private PathRegion mPathRegion;
+
+    private boolean mAntiAlias;
+
+    private PathInfo(PathGenerator generator, View view) {
+        mPathGenerator = generator;
+        mViewReference = new WeakReference<View>(view);
+        mSavedHashCode = view.hashCode();
+        mPath = new Path();
+    }
+
+    public PathInfo apply(ClipPathLayout layout) {
+        layout.applyPathInfo(this);
+        return this;
+    }
+
+    public PathInfo apply() {
+        View view = mViewReference.get();
+        if (view == null) {
+            throw new NullPointerException("view is null");
+        }
+        if (view.getParent() instanceof ClipPathLayout) {
+            apply((ClipPathLayout) view.getParent());
+        } else if (view.getParent() != null) {
+            throw new UnsupportedOperationException(
+                    String.format("the parent(%s) of view(%s) does not implement ClipPathLayout",
+                            view.getParent().getClass().getCanonicalName(), view.getClass().getCanonicalName()));
+        } else {
+            throw new UnsupportedOperationException(
+                    String.format("the parent of view(%s) is null", view.getClass().getCanonicalName()));
+        }
+        return this;
+    }
+
+    public PathInfo cancel() {
+        View view = mViewReference.get();
+        if (view == null) {
+            Log.d(TAG, "cancel: view is null");
+            return this;
+        }
+        ViewParent parent = view.getParent();
+        if (parent == null) {
+            Log.d(TAG, "cancel: the parent of view is null");
+            return this;
+        }
+        if (parent instanceof ClipPathLayout) {
+            ((ClipPathLayout) parent).cancelPathInfo(view);
+        } else {
+            throw new UnsupportedOperationException(String.format("the parent(%s) of view(%s) does not implement ClipPathLayout",
+                    view.getParent().getClass().getCanonicalName(), view.getClass().getCanonicalName()));
+        }
+        return this;
+    }
+
+    @Override
+    public int hashCode() {
+        return mSavedHashCode;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof PathInfo) {
+            PathInfo info = (PathInfo) obj;
+            if (info.mViewReference.get() == mViewReference.get()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    PathGenerator getPathGenerator() {
+        return mPathGenerator;
+    }
+
+    public int getApplyFlag() {
+        return mApplyFlag;
+    }
+
+    public int getClipType() {
+        return mClipType;
+    }
+
+    View getView() {
+        return mViewReference.get();
+    }
+
+    Path getPath() {
+        return mPath;
+    }
+
+    void setPath(Path path) {
+        mPath = path;
+    }
+
+    PathRegion getPathRegion() {
+        return mPathRegion;
+    }
+
+    void setPathRegion(PathRegion pathRegion) {
+        mPathRegion = pathRegion;
+    }
+
+    public boolean isAntiAlias() {
+        return mAntiAlias;
+    }
+
+    public static class Builder {
+        private PathGenerator mPathGenerator;
+        private View mView;
+        private int mApplyFlag = APPLY_FLAG_DRAW_AND_TOUCH;
+        private int mClipType = CLIP_TYPE_IN;
+        private boolean mAntiAlias = false;
+
+        /**
+         * @param generator Path生成器
+         * @param view      实现了ClipPathLayout接口的ViewGroup的子View
+         */
+        public Builder(PathGenerator generator, View view) {
+            if (generator == null) {
+                throw new NullPointerException("PathGenerator is null");
+            }
+            if (view == null) {
+                throw new NullPointerException("view is null");
+            }
+            this.mPathGenerator = generator;
+            this.mView = view;
+        }
+
+        public Builder setApplyFlag(int flag) {
+            mApplyFlag = flag;
+            return this;
+        }
+
+        public Builder setClipType(int type) {
+            mClipType = type;
+            return this;
+        }
+
+        public Builder setAntiAlias(boolean antiAlias) {
+            mAntiAlias = antiAlias;
+            return this;
+        }
+
+        public PathInfo create() {
+            PathInfo info = new PathInfo(mPathGenerator, mView);
+            info.mApplyFlag = mApplyFlag;
+            info.mClipType = mClipType;
+            info.mAntiAlias = mAntiAlias;
+            return info;
+        }
+    }
+
+
+}

+ 8 - 0
clippathlayout/src/main/java/com/yxf/clippathlayout/PathRegion.java

@@ -0,0 +1,8 @@
+package com.yxf.clippathlayout;
+
+public interface PathRegion {
+
+
+    boolean isInRegion(float x, float y);
+
+}

+ 9 - 0
clippathlayout/src/main/java/com/yxf/clippathlayout/PathRegionGenerator.java

@@ -0,0 +1,9 @@
+package com.yxf.clippathlayout;
+
+import android.graphics.Path;
+
+public interface PathRegionGenerator {
+
+    PathRegion generatorPathRegion(Path path, int clipType, int width, int height);
+
+}

+ 36 - 0
clippathlayout/src/main/java/com/yxf/clippathlayout/PathRegionGenerators.java

@@ -0,0 +1,36 @@
+package com.yxf.clippathlayout;
+
+import android.graphics.Path;
+
+public class PathRegionGenerators {
+
+    public static PathRegionGenerator createBitmapPathRegionGenerator() {
+
+        return new PathRegionGenerator() {
+
+            @Override
+            public PathRegion generatorPathRegion(Path path, int clipType, int width, int height) {
+                return new BitmapPathRegion(path, clipType, width, height);
+            }
+        };
+    }
+
+    public static PathRegionGenerator createBitmapPathRegionGenerator(final int inSampleSize) {
+        return new PathRegionGenerator() {
+            @Override
+            public PathRegion generatorPathRegion(Path path, int clipType, int width, int height) {
+                return new BitmapPathRegion(path, width, height, inSampleSize);
+            }
+        };
+    }
+
+    public static PathRegionGenerator createNativePathRegionGenerator() {
+        return new PathRegionGenerator() {
+            @Override
+            public PathRegion generatorPathRegion(Path path, int clipType, int width, int height) {
+                return new NativePathRegion(path, clipType);
+            }
+        };
+    }
+
+}

+ 116 - 0
clippathlayout/src/main/java/com/yxf/clippathlayout/Utils.java

@@ -0,0 +1,116 @@
+package com.yxf.clippathlayout;
+
+import android.graphics.Canvas;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.Build;
+import android.os.Looper;
+import android.util.Log;
+import android.view.View;
+
+public class Utils {
+
+    private static final String TAG = getTAG(Utils.class);
+
+    public static boolean DEUBG = false;
+
+    public static void clipOutPath(Canvas canvas, Path path) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            canvas.clipOutPath(path);
+        } else {
+            canvas.clipPath(path, Region.Op.DIFFERENCE);
+        }
+    }
+
+    public static void clipPath(Canvas canvas, Path path, int clipType) {
+        if (clipType == PathInfo.CLIP_TYPE_IN) {
+            canvas.clipPath(path);
+        } else if (clipType == PathInfo.CLIP_TYPE_OUT) {
+            clipOutPath(canvas, path);
+        } else {
+            Log.e(TAG, "clipPath: unsupported clip type : " + clipType);
+        }
+    }
+
+    public static boolean isInUiThread() {
+        return Thread.currentThread() == Looper.getMainLooper().getThread();
+    }
+
+    public static boolean isViewCanUse(View view) {
+        return isInUiThread() && (view.getWidth() > 0);
+    }
+
+    public static void runOnUiThreadAfterViewCanUse(View view, Runnable runnable) {
+        if (isViewCanUse(view)) {
+            runnable.run();
+        } else {
+            view.post(runnable);
+        }
+    }
+
+    public static String getTAG(Class c) {
+        return Config.libTAG + "." + c.getSimpleName();
+    }
+
+
+    public static Rect maxContainSimilarRange(Path path, Rect similar, int boundWidth, int boundHeight) {
+        PathRegion region = new NativePathRegion(path, PathInfo.CLIP_TYPE_IN);
+        if (isRectInRegion(region, similar)) {
+            return similar;
+        }
+        Rect result = similar;
+        int centerX = boundWidth / 2;
+        int centerY = boundHeight / 2;
+
+        int outLeft, outTop, outRight, outBottom;
+        int inLeft, inTop, inRight, inBottom;
+        int left, top, right, bottom;
+        outLeft = similar.left;
+        outTop = similar.top;
+        outRight = similar.right;
+        outBottom = similar.bottom;
+        inLeft = centerX;
+        inTop = centerY;
+        inRight = centerX;
+        inBottom = centerY;
+        while (true) {
+            left = (outLeft + inLeft) / 2;
+            top = (outTop + inTop) / 2;
+            right = (outRight + inRight) / 2;
+            bottom = (outBottom + inBottom) / 2;
+            if (isRectInRegion(region, left, top, right, bottom)) {
+                inLeft = left;
+                inTop = top;
+                inRight = right;
+                inBottom = bottom;
+            } else {
+                outLeft = left;
+                outTop = top;
+                outRight = right;
+                outBottom = bottom;
+            }
+            if (Math.abs(outLeft - inLeft) <= 1 &&
+                    Math.abs(outTop - inTop) <= 1 &&
+                    Math.abs(outRight - inRight) <= 1 &&
+                    Math.abs(outBottom - inBottom) <= 1) {
+                result.set(inLeft, inTop, inRight, inBottom);
+                break;
+            }
+        }
+        return result;
+    }
+
+    public static boolean isRectInRegion(PathRegion region, Rect rect) {
+        return isRectInRegion(region, rect.left, rect.top, rect.right, rect.bottom);
+    }
+
+    public static boolean isRectInRegion(PathRegion region, int left, int top, int right, int bottom) {
+        return (region.isInRegion(left, top) &&
+                region.isInRegion(right, top) &&
+                region.isInRegion(left, bottom) &&
+                region.isInRegion(right, bottom));
+    }
+
+
+}

+ 87 - 0
clippathlayout/src/main/java/com/yxf/clippathlayout/impl/ClipPathFrameLayout.java

@@ -0,0 +1,87 @@
+package com.yxf.clippathlayout.impl;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.PointF;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.yxf.clippathlayout.ClipPathLayout;
+import com.yxf.clippathlayout.ClipPathLayoutDelegate;
+import com.yxf.clippathlayout.PathInfo;
+
+public class ClipPathFrameLayout extends FrameLayout implements ClipPathLayout {
+
+    ClipPathLayoutDelegate mClipPathLayoutDelegate = new ClipPathLayoutDelegate(this);
+
+    public ClipPathFrameLayout(@NonNull Context context) {
+        this(context, null);
+    }
+
+    public ClipPathFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ClipPathFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    public boolean isTransformedTouchPointInView(float x, float y, View child, PointF outLocalPoint) {
+        return mClipPathLayoutDelegate.isTransformedTouchPointInView(x, y, child, outLocalPoint);
+    }
+
+    @Override
+    public void applyPathInfo(PathInfo info) {
+        mClipPathLayoutDelegate.applyPathInfo(info);
+    }
+
+    @Override
+    public void cancelPathInfo(View child) {
+        mClipPathLayoutDelegate.cancelPathInfo(child);
+    }
+
+    @Override
+    public void beforeDrawChild(Canvas canvas, View child, long drawingTime) {
+        mClipPathLayoutDelegate.beforeDrawChild(canvas, child, drawingTime);
+    }
+
+    @Override
+    public void afterDrawChild(Canvas canvas, View child, long drawingTime) {
+        mClipPathLayoutDelegate.afterDrawChild(canvas, child, drawingTime);
+    }
+
+    //the drawChild method is not belong to ClipPathLayout ,
+    //but you should rewrite it without changing the return value of the method
+    @Override
+    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+        beforeDrawChild(canvas, child, drawingTime);
+        boolean result = super.drawChild(canvas, child, drawingTime);
+        afterDrawChild(canvas, child, drawingTime);
+        return result;
+    }
+
+    //do not forget to rewrite the method
+    @Override
+    public void requestLayout() {
+        super.requestLayout();
+        // the request layout method would be invoked in the constructor of super class
+        if (mClipPathLayoutDelegate == null) {
+            return;
+        }
+        mClipPathLayoutDelegate.requestLayout();
+    }
+
+    @Override
+    public void notifyPathChanged(View child) {
+        mClipPathLayoutDelegate.notifyPathChanged(child);
+    }
+
+    @Override
+    public void notifyAllPathChanged() {
+        mClipPathLayoutDelegate.notifyAllPathChanged();
+    }
+}

+ 87 - 0
clippathlayout/src/main/java/com/yxf/clippathlayout/impl/ClipPathLinearLayout.java

@@ -0,0 +1,87 @@
+package com.yxf.clippathlayout.impl;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.PointF;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.yxf.clippathlayout.ClipPathLayout;
+import com.yxf.clippathlayout.ClipPathLayoutDelegate;
+import com.yxf.clippathlayout.PathInfo;
+
+public class ClipPathLinearLayout extends LinearLayout implements ClipPathLayout {
+
+    ClipPathLayoutDelegate mClipPathLayoutDelegate = new ClipPathLayoutDelegate(this);
+
+
+    public ClipPathLinearLayout(Context context) {
+        this(context, null);
+    }
+
+    public ClipPathLinearLayout(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ClipPathLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    public boolean isTransformedTouchPointInView(float x, float y, View child, PointF outLocalPoint) {
+        return mClipPathLayoutDelegate.isTransformedTouchPointInView(x, y, child, outLocalPoint);
+    }
+
+    @Override
+    public void applyPathInfo(PathInfo info) {
+        mClipPathLayoutDelegate.applyPathInfo(info);
+    }
+
+    @Override
+    public void cancelPathInfo(View child) {
+        mClipPathLayoutDelegate.cancelPathInfo(child);
+    }
+
+    @Override
+    public void beforeDrawChild(Canvas canvas, View child, long drawingTime) {
+        mClipPathLayoutDelegate.beforeDrawChild(canvas, child, drawingTime);
+    }
+
+    @Override
+    public void afterDrawChild(Canvas canvas, View child, long drawingTime) {
+        mClipPathLayoutDelegate.afterDrawChild(canvas, child, drawingTime);
+    }
+
+    //the drawChild method is not belong to ClipPathLayout ,
+    //but you should rewrite it without changing the return value of the method
+    @Override
+    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+        beforeDrawChild(canvas, child, drawingTime);
+        boolean result = super.drawChild(canvas, child, drawingTime);
+        afterDrawChild(canvas, child, drawingTime);
+        return result;
+    }
+
+    //do not forget to rewrite the method
+    @Override
+    public void requestLayout() {
+        super.requestLayout();
+        // the request layout method would be invoked in the constructor of super class
+        if (mClipPathLayoutDelegate == null) {
+            return;
+        }
+        mClipPathLayoutDelegate.requestLayout();
+    }
+
+    @Override
+    public void notifyPathChanged(View child) {
+        mClipPathLayoutDelegate.notifyPathChanged(child);
+    }
+
+    @Override
+    public void notifyAllPathChanged() {
+        mClipPathLayoutDelegate.notifyAllPathChanged();
+    }
+}

+ 88 - 0
clippathlayout/src/main/java/com/yxf/clippathlayout/impl/ClipPathRelativeLayout.java

@@ -0,0 +1,88 @@
+package com.yxf.clippathlayout.impl;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.PointF;
+import android.os.Build;
+import android.support.annotation.RequiresApi;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.RelativeLayout;
+
+import com.yxf.clippathlayout.ClipPathLayout;
+import com.yxf.clippathlayout.ClipPathLayoutDelegate;
+import com.yxf.clippathlayout.PathInfo;
+
+public class ClipPathRelativeLayout extends RelativeLayout implements ClipPathLayout {
+
+    ClipPathLayoutDelegate mClipPathLayoutDelegate = new ClipPathLayoutDelegate(this);
+
+    public ClipPathRelativeLayout(Context context) {
+        this(context, null);
+    }
+
+    public ClipPathRelativeLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ClipPathRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    public boolean isTransformedTouchPointInView(float x, float y, View child, PointF outLocalPoint) {
+        return mClipPathLayoutDelegate.isTransformedTouchPointInView(x, y, child, outLocalPoint);
+    }
+
+    @Override
+    public void applyPathInfo(PathInfo info) {
+        mClipPathLayoutDelegate.applyPathInfo(info);
+    }
+
+    @Override
+    public void cancelPathInfo(View child) {
+        mClipPathLayoutDelegate.cancelPathInfo(child);
+    }
+
+    @Override
+    public void beforeDrawChild(Canvas canvas, View child, long drawingTime) {
+        mClipPathLayoutDelegate.beforeDrawChild(canvas, child, drawingTime);
+    }
+
+    @Override
+    public void afterDrawChild(Canvas canvas, View child, long drawingTime) {
+        mClipPathLayoutDelegate.afterDrawChild(canvas, child, drawingTime);
+    }
+
+    //the drawChild method is not belong to ClipPathLayout ,
+    //but you should rewrite it without changing the return value of the method
+    @Override
+    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+        beforeDrawChild(canvas, child, drawingTime);
+        boolean result = super.drawChild(canvas, child, drawingTime);
+        afterDrawChild(canvas, child, drawingTime);
+        return result;
+    }
+
+
+    //do not forget to rewrite the method
+    @Override
+    public void requestLayout() {
+        super.requestLayout();
+        // the request layout method would be invoked in the constructor of super class
+        if (mClipPathLayoutDelegate == null) {
+            return;
+        }
+        mClipPathLayoutDelegate.requestLayout();
+    }
+
+    @Override
+    public void notifyPathChanged(View child) {
+        mClipPathLayoutDelegate.notifyPathChanged(child);
+    }
+
+    @Override
+    public void notifyAllPathChanged() {
+        mClipPathLayoutDelegate.notifyAllPathChanged();
+    }
+}

+ 146 - 0
clippathlayout/src/main/java/com/yxf/clippathlayout/pathgenerator/AnimTransition.java

@@ -0,0 +1,146 @@
+package com.yxf.clippathlayout.pathgenerator;
+
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.util.Log;
+import android.view.View;
+
+import com.yxf.clippathlayout.transition.generator.TransitionPathGenerator;
+
+public class AnimTransition implements TransitionPathGenerator {
+    private final static String TAG = "AnimTransition";
+
+    @Override
+    public Path generatePath(Path old, View view, int width, int height) {
+        if (old == null) {
+            old = new Path();
+        } else {
+            old.reset();
+        }
+        int radius = Math.min(width, height) / 2;
+
+        float degA, degB;
+        if (5 % 2 == 1) {//奇数和偶数角区别对待
+            degA = 360 / 5 / 2 / 2;
+            degB = 180 - degA - 360 / 5 / 2;
+        } else {
+            degA = 360 / 5 / 2;
+            degB = 180 - degA - 360 / 5 / 2;
+        }
+        float r = (float) (radius * Math.sin(rad(degA)) / Math.sin(rad(degB)));
+
+        float perDeg = 360 / 5;
+        float degAn = perDeg / 2 / 2;
+        float degBn = 360 / (5 - 1) / 2 - degAn / 2 + degAn;
+//        old.moveTo(
+//                (float) (Math.cos(rad(degAn + perDeg * 0)) * radius + radius * Math.cos(rad(degAn))),
+//                (float) (-Math.sin(rad(degAn + perDeg * 0)) * radius + radius));
+//        for (int i = 0; i < 5; i++) {
+//            old.lineTo(
+//                    (float) (Math.cos(rad(degAn + perDeg * i)) * radius + radius * Math.cos(rad(degAn))),
+//                    (float) (-Math.sin(rad(degAn + perDeg * i)) * radius + radius));
+//            old.lineTo(
+//                    (float) (Math.cos(rad(degBn + perDeg * i)) * r + radius * Math.cos(rad(degAn))),
+//                    (float) (-Math.sin(rad(degBn + perDeg * i)) * r + radius));
+//        }
+//        old.close();
+        Log.i(TAG, "外接圆半径:" + radius);
+        Log.i(TAG, "内接圆半径:" + r);
+        Log.i(TAG, "宽:" +width);
+        Log.i(TAG, "高:" + height);
+        Log.i(TAG, "点1:(" + (float) (Math.cos(rad(degAn + perDeg * 0)) * radius + radius * Math.cos(rad(degAn))) +
+                        "," + (float) (-Math.sin(rad(degAn + perDeg * 0)) * radius + radius) + ")\n" +
+                "点2:(" + (float) (Math.cos(rad(degAn + perDeg * 0)) * radius + radius * Math.cos(rad(degAn))) +
+                "," + (float) (-Math.sin(rad(degAn + perDeg * 0)) * radius + radius) + ")\n" +
+                "点3:(" + (float) (Math.cos(rad(degBn + perDeg * 0)) * r + radius * Math.cos(rad(degAn))) +
+                "," + (float) (-Math.sin(rad(degBn + perDeg * 0)) * r + radius) + ")\n" +
+                "点4:(" + (float) (Math.cos(rad(degAn + perDeg * 1)) * radius + radius * Math.cos(rad(degAn))) +
+                "," + (float) (-Math.sin(rad(degAn + perDeg * 1)) * radius + radius) + ")\n" +
+                "点5:(" + (float) (Math.cos(rad(degBn + perDeg * 1)) * r + radius * Math.cos(rad(degAn))) +
+                "," + (float) (-Math.sin(rad(degBn + perDeg * 1)) * r + radius) + ")\n" +
+                "点6:(" + (float) (Math.cos(rad(degAn + perDeg * 2)) * radius + radius * Math.cos(rad(degAn))) +
+                "," + (float) (-Math.sin(rad(degAn + perDeg * 2)) * radius + radius) + ")\n" +
+                "点7:(" + (float) (Math.cos(rad(degBn + perDeg * 2)) * r + radius * Math.cos(rad(degAn))) +
+                "," + (float) (-Math.sin(rad(degBn + perDeg * 2)) * r + radius) + ")\n" +
+                "点8:(" + (float) (Math.cos(rad(degAn + perDeg * 3)) * radius + radius * Math.cos(rad(degAn))) +
+                "," + (float) (-Math.sin(rad(degAn + perDeg * 3)) * radius + radius) + ")\n" +
+                "点9:(" + (float) (Math.cos(rad(degBn + perDeg * 3)) * r + radius * Math.cos(rad(degAn))) +
+                "," + (float) (-Math.sin(rad(degBn + perDeg * 3)) * r + radius) + ")\n" +
+                "点10:(" + (float) (Math.cos(rad(degAn + perDeg * 4)) * radius + radius * Math.cos(rad(degAn))) +
+                "," + (float) (-Math.sin(rad(degAn + perDeg * 4)) * radius + radius) + ")\n" +
+                "点11:(" + (float) (Math.cos(rad(degBn + perDeg * 4)) * r + radius * Math.cos(rad(degAn))) +
+                "," + (float) (-Math.sin(rad(degBn + perDeg * 4)) * r + radius) + ")\n"
+        );
+        return regularStarPath(old, 5, radius);
+    }
+
+    /**
+     * 画正n角星的路径:
+     *
+     * @param num 角数
+     * @param R   外接圆半径
+     * @return 画正n角星的路径
+     */
+    public static Path regularStarPath(Path old, int num, float R) {
+        float degA, degB;
+        if (num % 2 == 1) {//奇数和偶数角区别对待
+            degA = 360 / num / 2 / 2;
+            degB = 180 - degA - 360 / num / 2;
+        } else {
+            degA = 360 / num / 2;
+            degB = 180 - degA - 360 / num / 2;
+        }
+        float r = (float) (R * Math.sin(rad(degA)) / Math.sin(rad(degB)));
+        return nStarPath(old, num, R, r);
+    }
+
+    /**
+     * n角星路径
+     *
+     * @param num 几角星
+     * @param R   外接圆半径
+     * @param r   内接圆半径
+     * @return n角星路径
+     */
+    public static Path nStarPath(Path old, int num, float R, float r) {
+        float perDeg = 360 / num;  // 72
+        float degAn = perDeg / 2 / 2;  // 18
+        float degBn = 360 / (num - 1) / 2 - degAn / 2 + degAn;  // 54
+        old.moveTo(
+                (float) (Math.cos(rad(degAn)) * R * 2 + (2 * R - Math.cos(rad(degAn)) * R * 2) / 2),
+                (float) (-Math.sin(rad(degAn)) * R + R + R));
+//        old.moveTo(
+//                (float) (Math.cos(rad(degAn + perDeg * 0)) * R + R * Math.cos(rad(degAn))),
+//                (float) (-Math.sin(rad(degAn + perDeg * 0)) * R + R + R));
+        for (int i = 0; i < num; i++) {
+            old.lineTo(
+                    (float) (Math.cos(rad(degAn + perDeg * i)) * R + R * Math.cos(rad(degAn)) + (2 * R - Math.cos(rad(degAn)) * R * 2) / 2),
+                    (float) (-Math.sin(rad(degAn + perDeg * i)) * R + R + R));
+            old.lineTo(
+                    (float) (Math.cos(rad(degBn + perDeg * i)) * r + R * Math.cos(rad(degAn)) + (2 * R - Math.cos(rad(degAn)) * R * 2) / 2),
+                    (float) (-Math.sin(rad(degBn + perDeg * i)) * r + R + R));
+        }
+        old.close();
+        return old;
+    }
+    /**
+     * 角度制化为弧度制
+     *
+     * @param deg 角度
+     * @return 弧度
+     */
+    public static float rad(float deg) {
+        return (float) (deg * Math.PI / 180);
+    }
+
+    @Override
+    public Rect maxContainSimilarRange(Rect similar, int boundWidth, int boundHeight) {
+
+        int centerX = boundWidth / 2;
+        int centerY = boundHeight / 2;
+        int radius = Math.min(boundWidth, boundHeight) / 2;
+
+        return new Rect(centerX - radius - radius,centerY - radius - radius,
+                centerX + radius + radius, centerY + radius + radius);
+    }
+}

+ 55 - 0
clippathlayout/src/main/java/com/yxf/clippathlayout/pathgenerator/CirclePathGenerator.java

@@ -0,0 +1,55 @@
+package com.yxf.clippathlayout.pathgenerator;
+
+import android.graphics.Path;
+import android.view.Gravity;
+import android.view.View;
+
+public class CirclePathGenerator implements PathGenerator {
+
+    private int mGravity;
+
+    public CirclePathGenerator() {
+        this(Gravity.CENTER);
+    }
+
+    public CirclePathGenerator(int gravity) {
+        mGravity = gravity;
+    }
+
+    @Override
+    public Path generatePath(Path old, View view, int width, int height) {
+        if (old == null) {
+            old = new Path();
+        }
+        old.reset();
+        int centerX = width / 2;
+        int centerY = height / 2;
+        int radius = Math.min(width, height) / 2;
+        if (height > width) {
+            switch (mGravity) {
+                case Gravity.TOP:
+                    centerY = centerX;
+                    break;
+                case Gravity.BOTTOM:
+                    centerY = height - centerX;
+                    break;
+
+            }
+        } else if (height < width) {
+            switch (mGravity) {
+                case Gravity.LEFT:
+                    centerX = centerY;
+                    break;
+                case Gravity.RIGHT:
+                    centerX = width - centerY;
+                    break;
+            }
+        }
+        return generateCirclePath(old, centerX, centerY, radius);
+    }
+
+    private Path generateCirclePath(Path old, int centerX, int centerY, int radius) {
+        old.addCircle(centerX, centerY, radius, Path.Direction.CW);
+        return old;
+    }
+}

+ 23 - 0
clippathlayout/src/main/java/com/yxf/clippathlayout/pathgenerator/OvalPathGenerator.java

@@ -0,0 +1,23 @@
+package com.yxf.clippathlayout.pathgenerator;
+
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.os.Build;
+import android.view.View;
+
+public class OvalPathGenerator implements PathGenerator {
+    @Override
+    public Path generatePath(Path old, View view, int width, int height) {
+        if (old == null) {
+            old = new Path();
+        } else {
+            old.reset();
+        }
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            old.addOval(0, 0, width, height, Path.Direction.CW);
+        } else {
+            old.addOval(new RectF(0, 0, width, height), Path.Direction.CW);
+        }
+        return old;
+    }
+}

+ 66 - 0
clippathlayout/src/main/java/com/yxf/clippathlayout/pathgenerator/OvalRingPathGenerator.java

@@ -0,0 +1,66 @@
+package com.yxf.clippathlayout.pathgenerator;
+
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.util.Log;
+import android.view.View;
+
+public class OvalRingPathGenerator implements PathGenerator {
+
+    private float mInSideRadiusRate;
+    private int mStartAngle;
+    private int mSweepAngle;
+
+    private RectF mRectF = new RectF();
+
+    private Path mPath = new Path();
+    private float[] mPoint = new float[2];
+
+    public OvalRingPathGenerator(float inSideRadiusRate, int startAngle, int sweepAngle) {
+        mInSideRadiusRate = inSideRadiusRate;
+        mStartAngle = startAngle;
+        mSweepAngle = sweepAngle;
+    }
+
+
+    @Override
+    public Path generatePath(Path old, View view, int width, int height) {
+        if (old == null) {
+            old = new Path();
+        } else {
+            old.reset();
+        }
+        float centerX = width / 2;
+        float centerY = height / 2;
+        mRectF.set(0, 0, width, height);
+        calculateCoordinate(width, height, mStartAngle, mPoint);
+        if (mSweepAngle < 360) {
+            old.moveTo(centerX, centerY);
+            old.lineTo(mPoint[0] + centerX, mPoint[1] + centerY);
+        } else {
+            mSweepAngle = 360;
+            old.moveTo(mPoint[0] + centerX, mPoint[1] + centerY);
+        }
+        old.arcTo(mRectF, mStartAngle, mSweepAngle);
+        if (mSweepAngle < 360) {
+            old.lineTo(centerX, centerY);
+        }
+        float radiusX = centerX * mInSideRadiusRate;
+        float radiusY = centerY * mInSideRadiusRate;
+        mRectF.set(centerX - radiusX, centerY - radiusY, centerX + radiusX, centerY + radiusY);
+        mPath.addOval(mRectF, Path.Direction.CW);
+        old.op(mPath, Path.Op.DIFFERENCE);
+        return old;
+    }
+
+    private void calculateCoordinate(int width, int height, int angle, float[] point) {
+        if (angle % 180 == 90) {
+            point[0] = 0;
+            point[1] = (float) (height * Math.sin(Math.toRadians(angle)));
+            return;
+        }
+        double radians = Math.toRadians(angle);
+        point[0] = (float) (width / 2 * Math.cos(radians));
+        point[1] = (float) (width / 2 * Math.sin(radians)) / width * height;
+    }
+}

+ 19 - 0
clippathlayout/src/main/java/com/yxf/clippathlayout/pathgenerator/PathGenerator.java

@@ -0,0 +1,19 @@
+package com.yxf.clippathlayout.pathgenerator;
+
+import android.graphics.Path;
+import android.view.View;
+
+public interface PathGenerator {
+
+    /**
+     * @param old 以前使用过的Path,如果以前为null,则可能为null
+     * @param view Path关联的子View对象
+     * @param width 生成Path所限定的范围宽度,一般是子View宽度
+     * @param height 生成Path所限定的范围高度,一般是子View高度
+     * @return 返回一个Path对象,必须为闭合的Path,将用于裁剪子View
+     *
+     * 其中Path的范围即left : 0 , top : 0 , right : width , bottom : height
+     */
+    Path generatePath(Path old, View view, int width, int height);
+
+}

+ 21 - 0
clippathlayout/src/main/java/com/yxf/clippathlayout/pathgenerator/RhombusPathGenerator.java

@@ -0,0 +1,21 @@
+package com.yxf.clippathlayout.pathgenerator;
+
+import android.graphics.Path;
+import android.view.View;
+
+public class RhombusPathGenerator implements PathGenerator {
+    @Override
+    public Path generatePath(Path old, View view, int width, int height) {
+        if (old == null) {
+            old = new Path();
+        } else {
+            old.reset();
+        }
+        old.moveTo(width / 2, 0);
+        old.lineTo(width, height / 2);
+        old.lineTo(width / 2, height);
+        old.lineTo(0, height / 2);
+        old.close();
+        return old;
+    }
+}

+ 12 - 0
clippathlayout/src/main/java/com/yxf/clippathlayout/transition/ProgressController.java

@@ -0,0 +1,12 @@
+package com.yxf.clippathlayout.transition;
+
+public interface ProgressController {
+
+    /**
+     * @param percent 0 <= percent <= 1
+     */
+    void setProgress(float percent);
+
+    float getProgress();
+
+}

+ 256 - 0
clippathlayout/src/main/java/com/yxf/clippathlayout/transition/TransitionAdapter.java

@@ -0,0 +1,256 @@
+package com.yxf.clippathlayout.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.Interpolator;
+
+import com.yxf.clippathlayout.Utils;
+import com.yxf.clippathlayout.pathgenerator.PathGenerator;
+import com.yxf.clippathlayout.transition.generator.TransitionPathGenerator;
+
+public class TransitionAdapter implements PathGenerator, ProgressController {
+
+    private static final String TAG = Utils.getTAG(TransitionFrameLayout.class);
+
+    public static final int PATH_CENTER_VIEW_CENTER = -1;
+
+    private static final float INVALID_SCALE = -1f;
+
+    public static final int DEFAULT_ANIMATOR_DURATION = 1800;
+
+    private int mDefaultDuration = DEFAULT_ANIMATOR_DURATION;
+
+    private Interpolator mDefaultInterpolator = new AccelerateInterpolator();
+
+    private PathGenerator mPathGenerator;
+    private TransitionLayout mTransitionLayout;
+
+    private int mPathCenterX = PATH_CENTER_VIEW_CENTER, mPathCenterY = PATH_CENTER_VIEW_CENTER;
+
+    public static final float FAILED_PERCENT = 0f;
+    public static final float SUCCESSFAULLY_PERCENT = 1f;
+
+    private float mPercent = 0f;
+
+    private float mScale = INVALID_SCALE;
+
+    private boolean mInvalidateOriginPath = true;
+
+    private Path mOriginPath = new Path();
+    private Path mPath = new Path();
+
+    private Matrix mMatrix = new Matrix();
+
+    private Rect mRect = new Rect();
+
+    private ValueAnimator mValueAnimator;
+
+    private boolean mReverse = false;
+
+    private boolean immediately = false;
+
+    public TransitionAdapter(PathGenerator generator) {
+        if (generator == null) {
+            throw new NullPointerException("TransitionPathGenerator is null");
+        }
+        mPathGenerator = generator;
+    }
+
+    void setTransitionLayout(TransitionLayout transitionLayout) {
+        if (transitionLayout == null) {
+            throw new NullPointerException("TransitionLayout is null");
+        }
+        mTransitionLayout = transitionLayout;
+    }
+
+    void setReverse(boolean reverse) {
+        if (mReverse == reverse) {
+            return;
+        }
+        mReverse = reverse;
+    }
+
+    void reset() {
+        mPercent = 0f;
+        mScale = INVALID_SCALE;
+        mInvalidateOriginPath = true;
+        mReverse = false;
+        if (mOriginPath != null) {
+            mOriginPath.reset();
+        }
+    }
+
+    public void setPathCenter(int x, int y) {
+        mPathCenterX = x;
+        mPathCenterY = y;
+        mInvalidateOriginPath = true;
+    }
+
+    public void update() {
+        mInvalidateOriginPath = true;
+    }
+
+    public void setDefaultDuration(int defaultDuration) {
+        mDefaultDuration = defaultDuration;
+    }
+
+    public void setDefaultInterpolator(Interpolator defaultInterpolator) {
+        if (defaultInterpolator == null) {
+            throw new NullPointerException("Interpolator is null");
+        }
+        mDefaultInterpolator = defaultInterpolator;
+    }
+
+    @Override
+    public Path generatePath(Path old, View view, int width, int height) {
+        if (mPathCenterX == PATH_CENTER_VIEW_CENTER) {
+            mPathCenterX = width / 2;
+        }
+        if (mPathCenterY == PATH_CENTER_VIEW_CENTER) {
+            mPathCenterY = height / 2;
+        }
+        if (mInvalidateOriginPath) {
+            mOriginPath = mPathGenerator.generatePath(mOriginPath, view, width, height);
+            calculateScale(width, height);
+        }
+        transformPath(width, height);
+        mInvalidateOriginPath = false;
+        return mPath;
+    }
+
+    private void transformPath(int width, int height) {
+        mMatrix.reset();
+        mMatrix.postTranslate(-width / 2, -height / 2);
+//        float scale = mScale * mPercent;
+        float scale = (float) (6 * mPercent);
+        float rotate = 720f * mPercent;
+        mMatrix.postScale(scale, scale);
+        mMatrix.postRotate(rotate);
+        mMatrix.postTranslate(mPathCenterX, mPathCenterY);
+        mOriginPath.transform(mMatrix, mPath);
+    }
+
+    private void calculateScale(int width, int height) {
+        int radiusX, radiusY;
+        radiusX = Math.max(mPathCenterX, width - mPathCenterX);
+        radiusY = Math.max(mPathCenterY, height - mPathCenterY);
+        int centerX = width / 2;
+        int centerY = height / 2;
+
+        int x, y;
+        if (width / (height * 1f) > radiusX / (radiusY * 1f)) {
+            x = radiusY * width / height;
+            y = height / 2;
+        } else {
+            x = width / 2;
+            y = radiusX * height / width;
+        }
+        mRect.set(centerX - x, centerY - y, centerX + x, centerY + y);
+        Rect rect;
+        if (mPathGenerator instanceof TransitionPathGenerator) {
+            rect = ((TransitionPathGenerator) mPathGenerator).maxContainSimilarRange(mRect, width, height);
+        } else {
+            rect = Utils.maxContainSimilarRange(mOriginPath, mRect, width, height);
+        }
+        if (rect.width() <= 0 || rect.height() <= 0) {
+            throw new RuntimeException("calculateScale: the width or height of the rect get from maxContainSimilarRange is illegal , rect : " + rect);
+        }
+        mScale = Math.max(radiusX * 2 / (rect.width() * 1f), radiusY * 2 / (rect.height() * 1f));
+    }
+
+
+    @Override
+    public void setProgress(float percent) {
+        if (percent < 0f) {
+            percent = 0f;
+        }
+        if (Float.compare(mPercent, percent) == 0) {
+            //percent not change ,return
+            return;
+        }
+        mPercent = percent;
+        mTransitionLayout.update(false);
+    }
+
+    public void finish() {
+        finishInternal();
+    }
+
+    private void finishInternal() {
+        mTransitionLayout.update(true);
+    }
+
+    @Override
+    public float getProgress() {
+        return mPercent;
+    }
+
+    public ProgressController getController() {
+        return this;
+    }
+
+    public boolean isImmediately() {
+        return immediately;
+    }
+
+    public void setImmediately(boolean immediately) {
+        this.immediately = immediately;
+        if (mValueAnimator != null && !mValueAnimator.isRunning()) {
+            mValueAnimator.setDuration(0);
+        }
+    }
+
+    public void animate() {
+        ValueAnimator animator = getAnimatorInternal();
+        animator.setInterpolator(mDefaultInterpolator);
+        animator.start();
+    }
+
+    public ValueAnimator getAnimator() {
+        return getAnimatorInternal();
+    }
+
+    void cancelAnimator() {
+        if (mValueAnimator != null) {
+            mValueAnimator.cancel();
+        }
+    }
+
+    void updateAnimator() {
+        float start = mReverse ? 1f : 0f;
+        float end = mReverse ? 0f : 1f;
+        mValueAnimator = ValueAnimator.ofFloat(start, end);
+        mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                setProgress((Float) animation.getAnimatedValue());
+            }
+        });
+        mValueAnimator.addListener(new AnimatorListenerAdapter() {
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                finishInternal();
+            }
+        });
+        if (immediately) {
+            mValueAnimator.setDuration(0);
+        } else {
+            mValueAnimator.setDuration(mDefaultDuration);
+        }
+    }
+
+    private ValueAnimator getAnimatorInternal() {
+        if (mValueAnimator == null) {
+            updateAnimator();
+        }
+        return mValueAnimator;
+    }
+}

+ 191 - 0
clippathlayout/src/main/java/com/yxf/clippathlayout/transition/TransitionFragmentContainer.java

@@ -0,0 +1,191 @@
+package com.yxf.clippathlayout.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+
+import java.lang.ref.WeakReference;
+
+public class TransitionFragmentContainer extends TransitionFrameLayout implements Handler.Callback {
+
+    private static final int MESSAGE_REMOVE_VIEW = 1;
+
+    private static final int MESSAGE_START_ANIMATOR = 2;
+
+    private MyHandler mHandler = new MyHandler(this);
+
+    private Runnable mRemoveViewTask = new Runnable() {
+        @Override
+        public void run() {
+            View view = mRemoveViewReference.get();
+            if (view != null) {
+                TransitionFragmentContainer.super.removeView(view);
+            }
+            mRemoveViewReference = null;
+        }
+    };
+
+    private WeakReference<View> mRemoveViewReference;
+
+    private ValueAnimator mValueAnimator;
+
+
+    public TransitionFragmentContainer(@NonNull Context context) {
+        this(context, null);
+    }
+
+    public TransitionFragmentContainer(@NonNull Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public TransitionFragmentContainer(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    protected View findPreviousView(View current) {
+        return findNextTopView(current);
+    }
+
+    @Override
+    public void addView(View child) {
+        TransitionAdapter adapter = switchView(child);
+        if (mHandler.hasMessages(MESSAGE_REMOVE_VIEW)) {
+            mHandler.removeMessages(MESSAGE_REMOVE_VIEW);
+        }
+        mValueAnimator = adapter.getAnimator();
+        mValueAnimator.setInterpolator(new AccelerateInterpolator());
+        mValueAnimator.addListener(new AnimatorListenerAdapter() {
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                sendRemoveViewMessage();
+            }
+        });
+        mValueAnimator.setInterpolator(new AccelerateInterpolator());
+        startAnimator();
+    }
+
+    private void startAnimator() {
+        View previous = getPreviousView();
+        if (previous != null) {
+            previous.setVisibility(VISIBLE);
+        }
+        View current = getCurrentView();
+        if (current != null) {
+            current.setVisibility(VISIBLE);
+        }
+        mValueAnimator.start();
+    }
+
+    @Override
+    public void removeView(View view) {
+        if (indexOfChild(view) == getChildCount() - 1) {
+            view.setVisibility(VISIBLE);
+        }
+        removeViewInternal(view);
+    }
+
+    @Override
+    public void removeViewAt(int index) {
+        removeViewInternal(getChildAt(index));
+    }
+
+    private void executeRemoveViewTask() {
+        if (mRemoveViewTask != null && mRemoveViewReference != null) {
+            mRemoveViewTask.run();
+        }
+    }
+
+    private void removeViewInternal(final View child) {
+        if (child.getVisibility() != VISIBLE) {
+            super.removeView(child);
+            return;
+        }
+        executeRemoveViewTask();
+        View current = findNextTopView(child);
+        if (current == null) {
+            mRemoveViewReference = new WeakReference<View>(child);
+            sendRemoveViewMessage();
+            return;
+        }
+        final TransitionAdapter adapter = switchView(current, true);
+        if (mHandler.hasMessages(MESSAGE_REMOVE_VIEW)) {
+            mHandler.removeMessages(MESSAGE_REMOVE_VIEW);
+        }
+        mRemoveViewReference = new WeakReference<View>(child);
+        mValueAnimator = adapter.getAnimator();
+        mValueAnimator.setInterpolator(new DecelerateInterpolator());
+        mValueAnimator.addListener(new AnimatorListenerAdapter() {
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                sendRemoveViewMessage();
+            }
+        });
+        startAnimator();
+        return;
+    }
+
+    private void sendRemoveViewMessage() {
+        mHandler.sendEmptyMessage(MESSAGE_REMOVE_VIEW);
+    }
+
+    private View findNextTopView(View child) {
+        int index = getChildCount() - 1;
+        if (index < 0) {
+            return null;
+        }
+        for (int i = index; i >= 0; i--) {
+            View view = getChildAt(i);
+            if (child != view) {
+                return view;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public boolean handleMessage(Message msg) {
+        switch (msg.what) {
+            case MESSAGE_REMOVE_VIEW:
+                executeRemoveViewTask();
+                break;
+            case MESSAGE_START_ANIMATOR:
+                startAnimator();
+                break;
+        }
+        return true;
+    }
+
+    private static class MyHandler extends Handler {
+
+        private WeakReference<Callback> mCallbackWeakReference;
+
+
+        public MyHandler(Callback callback) {
+            super(Looper.getMainLooper());
+            mCallbackWeakReference = new WeakReference<Callback>(callback);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            super.handleMessage(msg);
+            Callback callback = mCallbackWeakReference.get();
+            if (callback != null) {
+                callback.handleMessage(msg);
+            }
+        }
+    }
+}

+ 209 - 0
clippathlayout/src/main/java/com/yxf/clippathlayout/transition/TransitionFrameLayout.java

@@ -0,0 +1,209 @@
+package com.yxf.clippathlayout.transition;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+import com.yxf.clippathlayout.PathInfo;
+import com.yxf.clippathlayout.Utils;
+import com.yxf.clippathlayout.impl.ClipPathFrameLayout;
+import com.yxf.clippathlayout.pathgenerator.CirclePathGenerator;
+
+import java.lang.ref.WeakReference;
+
+public class TransitionFrameLayout extends ClipPathFrameLayout implements TransitionLayout {
+
+    private static final String TAG = Utils.getTAG(TransitionFrameLayout.class);
+
+    private WeakReference<View> mPreviousViewReference, mCurrentViewReference;
+    private PathInfo mPreviousInfo, mCurrentInfo;
+
+    private TransitionAdapter mTransitionAdapter;
+
+    private int mApplyFlag = PathInfo.APPLY_FLAG_DRAW_ONLY;
+
+    public TransitionFrameLayout(@NonNull Context context) {
+        this(context, null);
+    }
+
+    public TransitionFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public TransitionFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        setAdapterInternal(new TransitionAdapter(new CirclePathGenerator()));
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        int count = getChildCount();
+        if (count > 1) {
+            for (int i = count - 2; i >= 0; i--) {
+                getChildAt(i).setVisibility(GONE);
+            }
+        }
+    }
+
+    @Override
+    public void setAdapter(TransitionAdapter adapter) {
+        setAdapterInternal(adapter);
+    }
+
+    private void setAdapterInternal(TransitionAdapter adapter) {
+        if (adapter == null) {
+            Log.e(TAG, "setAdapter: adapter is null");
+            return;
+        }
+        mTransitionAdapter = adapter;
+        mTransitionAdapter.setTransitionLayout(this);
+    }
+
+    @Override
+    public TransitionAdapter switchView(View view) {
+        return switchView(view, false);
+    }
+
+    /**
+     * if you want add a view , just invoke switchView directly ,
+     * do not invoke addView , it may cause some problem .
+     *
+     * @param view
+     * @return
+     */
+    @Override
+    public TransitionAdapter switchView(final View view, boolean reverse) {
+        if (view == null) {
+            throw new NullPointerException("view is null");
+        }
+        View previous = findPreviousView(view);
+        if (previous == view) {
+            Log.w(TAG, "switchView: the top visible view is the same as the view switched");
+            new Throwable().printStackTrace();
+            return mTransitionAdapter;
+        }
+
+        cancelPreviousTransition();
+
+        if (view.getParent() == this) {
+            view.setVisibility(VISIBLE);
+            view.bringToFront();
+        } else if (view.getParent() != null) {
+            throw new IllegalArgumentException(String.format("the view(%s) switched has another parent(%s)",
+                    view.getClass().getCanonicalName(), view.getParent().getClass().getCanonicalName()));
+        } else {
+            super.addView(view);
+        }
+        updateViewReference(previous, view);
+        applySwitch(reverse);
+        return mTransitionAdapter;
+    }
+
+    @Override
+    public void update(boolean finished) {
+        if (finished) {
+            if (mPreviousInfo != null) {
+                mPreviousInfo.cancel();
+            }
+            if (mCurrentInfo != null) {
+                mCurrentInfo.cancel();
+            }
+            if (mPreviousViewReference != null) {
+                final View previous = mPreviousViewReference.get();
+                if (previous != null) {
+                    previous.setVisibility(GONE);
+                }
+            }
+        } else {
+            if (mPreviousViewReference != null) {
+                final View previous = mPreviousViewReference.get();
+                if (previous != null) {
+                    notifyPathChanged(previous);
+                }
+            }
+            final View current;
+            if (mCurrentViewReference != null && (current = mCurrentViewReference.get()) != null) {
+                notifyPathChanged(current);
+            }
+        }
+    }
+
+    public void setApplyFlag(int applyFlag) {
+        mApplyFlag = applyFlag;
+    }
+
+    private void cancelPreviousTransition() {
+        mTransitionAdapter.cancelAnimator();
+        if (mPreviousInfo != null) {
+            mPreviousInfo.cancel();
+        }
+        if (mCurrentInfo != null) {
+            mCurrentInfo.cancel();
+        }
+        mTransitionAdapter.reset();
+    }
+
+    private void applySwitch(boolean reverse) {
+        mTransitionAdapter.setReverse(reverse);
+        mTransitionAdapter.updateAnimator();
+        if (mPreviousViewReference != null && mPreviousViewReference.get() != null) {
+            mPreviousInfo = new PathInfo.Builder(mTransitionAdapter, mPreviousViewReference.get())
+                    .setClipType(reverse ? PathInfo.CLIP_TYPE_IN : PathInfo.CLIP_TYPE_OUT)
+                    .setApplyFlag(mApplyFlag)
+                    .create()
+                    .apply();
+        }
+        mCurrentInfo = new PathInfo.Builder(mTransitionAdapter, mCurrentViewReference.get())
+                .setClipType(reverse ? PathInfo.CLIP_TYPE_OUT : PathInfo.CLIP_TYPE_IN)
+                .setApplyFlag(mApplyFlag)
+                .create()
+                .apply();
+    }
+
+    protected View findPreviousView(View current) {
+        int frontIndex = getChildCount() - 1;
+        if (frontIndex < 0) {
+            return null;
+        }
+        for (int i = frontIndex; i >= 0; i--) {
+            View view = getChildAt(i);
+            if (view.getVisibility() == VISIBLE) {
+                return view;
+            }
+        }
+        return null;
+    }
+
+    private void updateViewReference(View previous, View current) {
+        if (previous != null) {
+            mPreviousViewReference = new WeakReference<View>(previous);
+        } else {
+            mPreviousViewReference = null;
+        }
+        mCurrentViewReference = new WeakReference<View>(current);
+    }
+
+    protected View getPreviousView() {
+        if (mPreviousViewReference != null) {
+            return mPreviousViewReference.get();
+        }
+        return null;
+    }
+
+    protected View getCurrentView() {
+        if (mCurrentViewReference != null) {
+            return mCurrentViewReference.get();
+        }
+        return null;
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        cancelPreviousTransition();
+    }
+}

+ 17 - 0
clippathlayout/src/main/java/com/yxf/clippathlayout/transition/TransitionLayout.java

@@ -0,0 +1,17 @@
+package com.yxf.clippathlayout.transition;
+
+import android.view.View;
+
+import com.yxf.clippathlayout.ClipPathLayout;
+
+public interface TransitionLayout extends ClipPathLayout {
+
+    void setAdapter(TransitionAdapter adapter);
+
+    TransitionAdapter switchView(View view);
+
+    TransitionAdapter switchView(View view, boolean reverse);
+
+    void update(boolean finished);
+
+}

+ 73 - 0
clippathlayout/src/main/java/com/yxf/clippathlayout/transition/generator/RandomTransitionPathGenerator.java

@@ -0,0 +1,73 @@
+package com.yxf.clippathlayout.transition.generator;
+
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.util.Log;
+import android.view.View;
+
+import com.yxf.clippathlayout.Utils;
+import com.yxf.clippathlayout.pathgenerator.PathGenerator;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class RandomTransitionPathGenerator implements TransitionPathGenerator {
+
+    private static final String TAG = Utils.getTAG(RandomTransitionPathGenerator.class);
+
+
+    private List<PathGenerator> mPathGeneratorList = new ArrayList<PathGenerator>();
+
+    private PathGenerator mPathGenerator;
+
+    private Path mPath;
+
+    public RandomTransitionPathGenerator(PathGenerator defaultGenerator) {
+        if (defaultGenerator == null) {
+            throw new NullPointerException("default generator is null");
+        }
+        addInternal(defaultGenerator);
+    }
+
+    public void add(PathGenerator generator) {
+        addInternal(generator);
+    }
+
+    private void addInternal(PathGenerator generator) {
+        if (generator == null) {
+            Log.e(TAG, "add: generator is null");
+            return;
+        }
+        mPathGeneratorList.add(generator);
+    }
+
+    public void clear(PathGenerator defaultGenerator) {
+        if (defaultGenerator == null) {
+            throw new NullPointerException("default generator is null");
+        }
+        mPathGeneratorList.clear();
+        addInternal(defaultGenerator);
+    }
+
+    @Override
+    public Rect maxContainSimilarRange(Rect similar, int boundWidth, int boundHeight) {
+        if (mPathGenerator instanceof TransitionPathGenerator) {
+            return ((TransitionPathGenerator) mPathGenerator).maxContainSimilarRange(similar, boundWidth, boundHeight);
+        } else {
+            return Utils.maxContainSimilarRange(mPath, similar, boundWidth, boundHeight);
+        }
+    }
+
+    @Override
+    public Path generatePath(Path old, View view, int width, int height) {
+        updateGenerator();
+        mPath = mPathGenerator.generatePath(old, view, width, height);
+        return mPath;
+    }
+
+    private void updateGenerator() {
+        int size = mPathGeneratorList.size();
+        int index = (int) (Math.random() * size);
+        mPathGenerator = mPathGeneratorList.get(index);
+    }
+}

+ 19 - 0
clippathlayout/src/main/java/com/yxf/clippathlayout/transition/generator/TransitionPathGenerator.java

@@ -0,0 +1,19 @@
+package com.yxf.clippathlayout.transition.generator;
+
+import android.graphics.Rect;
+
+import com.yxf.clippathlayout.pathgenerator.PathGenerator;
+
+public interface TransitionPathGenerator extends PathGenerator {
+
+    /**
+     * @param similar 相似矩形参考
+     * @param boundWidth Path的范围区域宽
+     * @param boundHeight Path的范围区域高
+     * @return 返回最大的和@param similar相似的的矩形区域,
+     * 返回的矩形区域中心必须是Path的中心,即(boundWidth/2,boundHeight/2),
+     * 为了尽量减少内存抖动,建议使用参数传入的矩形修改数值后返回
+     */
+    Rect maxContainSimilarRange(Rect similar, int boundWidth, int boundHeight);
+
+}

+ 3 - 0
clippathlayout/src/main/res/values/strings.xml

@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">ClipPathLayout</string>
+</resources>

Some files were not shown because too many files changed in this diff