ソースを参照

2021年11月20日 提交

yanghuaxiong 3 年 前
コミット
78baebe33a
100 ファイル変更9443 行追加72 行削除
  1. 14 6
      app/src/main/AndroidManifest.xml
  2. 48 4
      app/src/main/java/com/tjzhxx/union/GroupFragment.java
  3. 12 3
      app/src/main/java/com/tjzhxx/union/HomeFragment.java
  4. 15 4
      app/src/main/java/com/tjzhxx/union/MainActivity.java
  5. 39 21
      app/src/main/java/com/tjzhxx/union/MyFragment.java
  6. 73 0
      app/src/main/java/com/tjzhxx/union/activity/AddressMapActivity.java
  7. 46 1
      app/src/main/java/com/tjzhxx/union/activity/AuthenticationActivity.java
  8. 39 3
      app/src/main/java/com/tjzhxx/union/activity/LoginActivity.java
  9. 2 2
      app/src/main/java/com/tjzhxx/union/activity/NewListActivity.java
  10. 5 13
      app/src/main/java/com/tjzhxx/union/activity/PayActivity.java
  11. 33 0
      app/src/main/java/com/tjzhxx/union/activity/PaySuccessActivity.java
  12. 39 0
      app/src/main/java/com/tjzhxx/union/activity/PromptActivity.java
  13. 133 0
      app/src/main/java/com/tjzhxx/union/activity/WelfareListActivity.java
  14. 117 0
      app/src/main/java/com/tjzhxx/union/activity/WelfareSignatureSureActivity.java
  15. 102 0
      app/src/main/java/com/tjzhxx/union/adapter/WelfareAdapter.java
  16. 125 0
      app/src/main/java/com/tjzhxx/union/dialog/PrivacyRemarkDialog.java
  17. 242 0
      app/src/main/java/com/tjzhxx/union/entity/WelfareResponse.java
  18. 39 0
      app/src/main/java/com/tjzhxx/union/entity/request/GetWelfareRequest.java
  19. 39 0
      app/src/main/java/com/tjzhxx/union/entity/request/WelfareListRequest.java
  20. 1 1
      app/src/main/java/com/tjzhxx/union/fragment/NewListFragment.java
  21. 1 1
      app/src/main/java/com/tjzhxx/union/public_store/ConstDefine.java
  22. 11 0
      app/src/main/java/com/tjzhxx/union/public_store/retrofit/UnionServices.java
  23. 8 0
      app/src/main/res/drawable/select_pic_dialog_bg.xml
  24. 16 0
      app/src/main/res/layout/activity_address_map.xml
  25. 6 1
      app/src/main/res/layout/activity_login.xml
  26. 2 2
      app/src/main/res/layout/activity_new_list.xml
  27. 56 0
      app/src/main/res/layout/activity_pay_success.xml
  28. 48 0
      app/src/main/res/layout/activity_prompt.xml
  29. 23 0
      app/src/main/res/layout/activity_welfare_list.xml
  30. 110 0
      app/src/main/res/layout/activity_welfare_signature_sure.xml
  31. 79 0
      app/src/main/res/layout/dialog_privacy_remark.xml
  32. 86 1
      app/src/main/res/layout/fragment_group.xml
  33. 1 1
      app/src/main/res/layout/fragment_home.xml
  34. 66 5
      app/src/main/res/layout/fragment_my.xml
  35. 51 0
      app/src/main/res/layout/item_welfare.xml
  36. BIN
      app/src/main/res/mipmap-xxhdpi/icon_address_remark.png
  37. BIN
      app/src/main/res/mipmap-xxhdpi/icon_get_welfare.png
  38. BIN
      app/src/main/res/mipmap-xxhdpi/icon_member_bg.png
  39. BIN
      app/src/main/res/mipmap-xxhdpi/icon_noget_welfare.png
  40. BIN
      app/src/main/res/mipmap-xxhdpi/icon_nojoin_prompt.png
  41. BIN
      app/src/main/res/mipmap-xxhdpi/icon_pay_success.png
  42. BIN
      app/src/main/res/mipmap-xxhdpi/icon_welfare.png
  43. 9 2
      app/src/main/res/values/strings.xml
  44. 35 1
      app/src/main/res/values/styles.xml
  45. 17 0
      app/src/test/java/com/yhx/union/ExampleUnitTest.java
  46. 73 0
      easyPhotos/.gitignore
  47. 55 0
      easyPhotos/build.gradle
  48. 25 0
      easyPhotos/proguard-rules.pro
  49. 42 0
      easyPhotos/src/main/AndroidManifest.xml
  50. 625 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/Builder/AlbumBuilder.java
  51. 345 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/EasyPhotos.java
  52. 22 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/callback/PuzzleCallback.java
  53. 26 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/callback/SelectCallback.java
  54. 21 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/constant/Code.java
  55. 23 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/constant/Key.java
  56. 10 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/constant/Type.java
  57. 63 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/engine/ImageEngine.java
  58. 18 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/ad/AdEntity.java
  59. 11 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/ad/AdListener.java
  60. 20 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/ad/AdViewHolder.java
  61. 362 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/album/AlbumModel.java
  62. 49 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/album/entity/Album.java
  63. 34 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/album/entity/AlbumItem.java
  64. 114 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/album/entity/Photo.java
  65. 65 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/Area.java
  66. 353 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/DegreeSeekBar.java
  67. 52 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/Line.java
  68. 167 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/MatrixUtils.java
  69. 88 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzleLayout.java
  70. 432 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzlePiece.java
  71. 94 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzleUtils.java
  72. 791 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzleView.java
  73. 32 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/SquarePuzzleView.java
  74. 34 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/CrossoverPointF.java
  75. 294 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantArea.java
  76. 173 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantLine.java
  77. 334 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantPuzzleLayout.java
  78. 482 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantUtils.java
  79. 231 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightArea.java
  80. 215 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightLine.java
  81. 359 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightPuzzleLayout.java
  82. 236 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightUtils.java
  83. 33 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/NumberSlantLayout.java
  84. 36 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/OneSlantLayout.java
  85. 70 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/SlantLayoutHelper.java
  86. 48 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/ThreeSlantLayout.java
  87. 30 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/TwoSlantLayout.java
  88. 89 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/EightStraightLayout.java
  89. 94 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/FiveStraightLayout.java
  90. 52 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/FourStraightLayout.java
  91. 76 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/NineStraightLayout.java
  92. 31 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/NumberStraightLayout.java
  93. 45 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/OneStraightLayout.java
  94. 77 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/SevenStraightLayout.java
  95. 93 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/SixStraightLayout.java
  96. 69 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/StraightLayoutHelper.java
  97. 50 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/ThreeStraightLayout.java
  98. 58 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/TwoStraightLayout.java
  99. 220 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/sticker/StickerModel.java
  100. 114 0
      easyPhotos/src/main/java/com/huantansheng/easyphotos/models/sticker/cache/StickerCache.java

+ 14 - 6
app/src/main/AndroidManifest.xml

@@ -15,7 +15,8 @@
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
     <!--定位权限-->
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
     <application
         android:name=".system.MyApplication"
         android:allowBackup="true"
@@ -27,9 +28,10 @@
         android:supportsRtl="true"
         android:theme="@style/AppTheme"
         android:usesCleartextTraffic="true">
-        <activity android:name=".WellcomActivity"
-            android:theme="@style/SplashTheme"
-            >
+        <activity
+            android:name=".WellcomActivity"
+            android:exported="true"
+            android:theme="@style/SplashTheme">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
 
@@ -53,6 +55,12 @@
         <activity android:name=".activity.SetActivity" />
         <activity android:name=".activity.HomeGuanaiActivity" />
         <activity android:name=".activity.PolicyListActivity" />
+        <activity android:name=".activity.PromptActivity" />
+        <activity android:name=".activity.PaySuccessActivity" />
+        <activity android:name=".activity.AddressMapActivity" />
+        <activity android:name=".activity.WelfareListActivity" />
+        <activity android:name=".activity.WelfareSignatureSureActivity" />
+
         <provider
             android:name="androidx.core.content.FileProvider"
             android:authorities="com.tjzhxx.union.FileProvider"
@@ -61,8 +69,8 @@
             tools:replace="android:authorities">
             <meta-data
                 android:name="android.support.FILE_PROVIDER_PATHS"
-                tools:replace="android:resource"
-                android:resource="@xml/provider_paths" />
+                android:resource="@xml/provider_paths"
+                tools:replace="android:resource" />
         </provider>
         <!-- wx pay begin -->
         <activity

+ 48 - 4
app/src/main/java/com/tjzhxx/union/GroupFragment.java

@@ -1,13 +1,25 @@
 package com.tjzhxx.union;
 
+import android.content.Intent;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.ImageView;
 
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
+import com.tjzhxx.union.activity.AddressMapActivity;
+import com.tjzhxx.union.entity.BannerEntity;
 import com.tjzhxx.union.system.BaseFragment;
 
+import java.util.ArrayList;
+import java.util.List;
+
+import butterknife.BindView;
 import butterknife.ButterKnife;
+import butterknife.OnClick;
+import cn.bingoogolapple.bgabanner.BGABanner;
 import io.reactivex.annotations.Nullable;
 
 /**
@@ -20,6 +32,10 @@ import io.reactivex.annotations.Nullable;
 public class GroupFragment extends BaseFragment {
 
 
+    @BindView(R.id.bga_banner)
+    BGABanner bgaBanner;
+    private List<BannerEntity> bannerEntities = new ArrayList<>();
+
     @Override
     public void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -31,11 +47,39 @@ public class GroupFragment extends BaseFragment {
         setHasOptionsMenu(true);
         View view = inflater.inflate(R.layout.fragment_group, container, false);
         ButterKnife.bind(this, view);
-
-
+        bgaBanner.setAdapter(new BGABanner.Adapter<ImageView, String>() {
+            @Override
+            public void fillBannerItem(BGABanner banner, ImageView itemView, String model, int position) {
+                Glide.with(getActivity())
+                        .load(model)
+                        .placeholder(R.mipmap.bg_placeholder)
+                        .error(R.mipmap.bg_placeholder)
+                        .diskCacheStrategy(DiskCacheStrategy.ALL)
+                        .centerCrop()
+                        .dontAnimate()
+                        .into(itemView);
+            }
+        });
+        String[] url = new String[]{
+                "https://app.tjzhxx.cn:1443/Uploads/sysimage/p1.jpg",
+                "https://app.tjzhxx.cn:1443/Uploads/sysimage/p2.jpg",
+                "https://app.tjzhxx.cn:1443/Uploads/sysimage/p3.jpg",
+                "https://app.tjzhxx.cn:1443/Uploads/sysimage/p4.jpg",
+                "https://app.tjzhxx.cn:1443/Uploads/sysimage/p5.jpg",
+                "https://app.tjzhxx.cn:1443/Uploads/sysimage/p6.jpg"};
+        List<String> urls = new ArrayList<>();
+        List<String> titles = new ArrayList<>();
+        for (String entity : url) {
+            urls.add(entity);
+            titles.add("");
+        }
+        bgaBanner.setData(urls, titles);
         return view;
     }
 
-
-
+    @OnClick(R.id.address)
+    public void onClick() {
+        Intent intent = new Intent(getActivity(), AddressMapActivity.class);
+        startActivity(intent);
+    }
 }

+ 12 - 3
app/src/main/java/com/tjzhxx/union/HomeFragment.java

@@ -18,6 +18,7 @@ import com.tjzhxx.union.activity.HomeGuanaiActivity;
 import com.tjzhxx.union.activity.NewListActivity;
 import com.tjzhxx.union.activity.PayActivity;
 import com.tjzhxx.union.activity.PolicyListActivity;
+import com.tjzhxx.union.activity.PromptActivity;
 import com.tjzhxx.union.adapter.NewAdapter;
 import com.tjzhxx.union.entity.BannerEntity;
 import com.tjzhxx.union.entity.BaseResponse;
@@ -99,7 +100,7 @@ public class HomeFragment extends BaseFragment {
                     Intent intent = new Intent();
                     intent.setClass(getActivity(), WebviewActivity.class);
                     intent.putExtra("url", entity.getWxurl());
-                    intent.putExtra("title", "工会闻");
+                    intent.putExtra("title", "工会闻");
                     startActivity(intent);
                 }
             }
@@ -113,7 +114,7 @@ public class HomeFragment extends BaseFragment {
                 Intent intent = new Intent();
                 intent.setClass(getActivity(), WebviewActivity.class);
                 intent.putExtra("url", entity.getWxurl());
-                intent.putExtra("title", "工会闻");
+                intent.putExtra("title", "工会闻");
                 startActivity(intent);
             }
         });
@@ -142,7 +143,10 @@ public class HomeFragment extends BaseFragment {
                         showSnackBar("请先授权获取位置信息");
                         return;
                     }else if (!ConstDefine.city.contains("天津")){
-                        showSnackBar("你不是天津人");
+                        intent.setClass(getActivity(), PromptActivity.class);
+                        intent.putExtra("content","因为您现在所在区域不在天津市,暂时工会限定天津市工作人员加入因此您没有成为零散务工人员工会会员,但是恭喜您成为水猫平台会员,您可享受平台提供的会员服务。");
+                        startActivity(intent);
+//                        showSnackBar("你不是天津人");
                         return;
                     }
                     intent.setClass(getActivity(), WebviewActivity.class);
@@ -203,9 +207,14 @@ public class HomeFragment extends BaseFragment {
                 startActivity(intent);
                 break;
             case R.id.function08:
+                if (ConstDefine.userInfo == null){
+                    showSnackBar("请先登录");
+                    return;
+                }
                 intent.setClass(getActivity(), WebviewActivity.class);
                 intent.putExtra("url", ConstDefine.HttpAdressH5+"/index.html#/legalAid");
                 intent.putExtra("title", "法律援助");
+                intent.putExtra("UserInfo",ConstDefine.userInfo);
                 startActivity(intent);
                 break;
         }

+ 15 - 4
app/src/main/java/com/tjzhxx/union/MainActivity.java

@@ -12,6 +12,7 @@ import com.amap.api.location.AMapLocation;
 import com.amap.api.location.AMapLocationClient;
 import com.amap.api.location.AMapLocationClientOption;
 import com.amap.api.location.AMapLocationListener;
+import com.tjzhxx.union.dialog.PrivacyRemarkDialog;
 import com.tjzhxx.union.permission.PermissionHelper;
 import com.tjzhxx.union.permission.PermissionInterface;
 import com.tjzhxx.union.public_store.ConstDefine;
@@ -69,7 +70,19 @@ public class MainActivity extends BaseActivity implements FragmentVisitor.Fragme
                 .setFragmentContiner(R.id.fragment_continer)
                 .setTabAndFragmentData(majorTabs, fragments);
         fragmentVisitor.setCurrentItem(0);
-        mPermissionHelper.requestPermissions();
+        //第一次进入 提示隐私权限对话框
+        if ((Boolean) SpUtils.get(this, "PrivacyRemark", true)) {
+            PrivacyRemarkDialog dialog = new PrivacyRemarkDialog(this);
+            dialog.setmOnItemClickListener(new PrivacyRemarkDialog.OnItemClickListener() {
+                @Override
+                public void onRight() {
+                    mPermissionHelper.requestPermissions();
+                }
+            });
+            dialog.show();
+        } else {
+            mPermissionHelper.requestPermissions();
+        }
     }
 
     @Override
@@ -152,9 +165,6 @@ public class MainActivity extends BaseActivity implements FragmentVisitor.Fragme
     @Override
     public String[] getPermissions() {
         return new String[]{
-                Manifest.permission.WRITE_EXTERNAL_STORAGE,
-                Manifest.permission.READ_EXTERNAL_STORAGE,
-                Manifest.permission.CAMERA,
                 Manifest.permission.ACCESS_FINE_LOCATION
         };
     }
@@ -168,6 +178,7 @@ public class MainActivity extends BaseActivity implements FragmentVisitor.Fragme
     public void requestPermissionsFail() {
         showSnackBar("权限被拒绝");
     }
+
     @Override
     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                            @NonNull int[]

+ 39 - 21
app/src/main/java/com/tjzhxx/union/MyFragment.java

@@ -6,10 +6,12 @@ import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ImageView;
+import android.widget.RelativeLayout;
 import android.widget.TextView;
 
 import com.tjzhxx.union.activity.LoginActivity;
 import com.tjzhxx.union.activity.SetActivity;
+import com.tjzhxx.union.activity.WelfareListActivity;
 import com.tjzhxx.union.entity.UserInfo;
 import com.tjzhxx.union.public_store.ConstDefine;
 import com.tjzhxx.union.system.BaseFragment;
@@ -35,8 +37,11 @@ public class MyFragment extends BaseFragment {
     @BindView(R.id.image_member)
     ImageView imageMember;
     @BindView(R.id.image_member2)
-    ImageView imageMember2;
+    RelativeLayout imageMember2;
+    @BindView(R.id.phone)
+    TextView phone;
     private UserInfo userInfo;
+
     @Override
     public void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -55,20 +60,21 @@ public class MyFragment extends BaseFragment {
     public void onResume() {
         super.onResume();
         userInfo = ConstDefine.userInfo;
-        if (userInfo != null){
+        if (userInfo != null) {
             name.setText(ConstDefine.userInfo.getWname());
             if (userInfo.getIsactive().equals("1")) {
                 imageMember.setVisibility(View.VISIBLE);
-            }else {
+                phone.setText(ConstDefine.userInfo.getTelno());
+            } else {
                 imageMember.setVisibility(View.GONE);
             }
-        }else {
+        } else {
             name.setText("点击登录/注册");
             imageMember.setVisibility(View.GONE);
         }
     }
 
-    @OnClick({R.id.image_member, R.id.image_member2, R.id.set, R.id.personal, R.id.jz, R.id.px, R.id.js, R.id.wt, R.id.fl})
+    @OnClick({R.id.image_member, R.id.image_member2, R.id.set, R.id.personal, R.id.welfare, R.id.jz, R.id.px, R.id.js, R.id.wt, R.id.fl})
     public void onClick(View view) {
         Intent intent = new Intent();
         switch (view.getId()) {
@@ -85,64 +91,76 @@ public class MyFragment extends BaseFragment {
                 startActivity(intent);
                 break;
             case R.id.personal:
-                if (userInfo == null){
+                if (userInfo == null) {
                     intent.setClass(getActivity(), LoginActivity.class);
                     startActivity(intent);
                 }
                 break;
+            case R.id.welfare:
+                if (userInfo == null) {
+                    showSnackBar("请先登录");
+                    return;
+                }
+                if (!userInfo.getIsactive().equals("1")) {
+                    showSnackBar("非会员不能领取");
+                    return;
+                }
+                intent.setClass(getActivity(), WelfareListActivity.class);
+                startActivity(intent);
+                break;
             case R.id.jz:
-                if (userInfo == null){
+                if (userInfo == null) {
                     showSnackBar("请先登录");
                     return;
                 }
                 intent.setClass(getActivity(), WebviewActivity.class);
-                intent.putExtra("url", ConstDefine.HttpAdressH5+"/index.html#/userrescuelist");
+                intent.putExtra("url", ConstDefine.HttpAdressH5 + "/index.html#/userrescuelist");
                 intent.putExtra("title", "我的救助");
-                intent.putExtra("UserInfo",userInfo);
+                intent.putExtra("UserInfo", userInfo);
                 startActivity(intent);
                 break;
             case R.id.px:
-                if (userInfo == null){
+                if (userInfo == null) {
                     showSnackBar("请先登录");
                     return;
                 }
                 intent.setClass(getActivity(), WebviewActivity.class);
-                intent.putExtra("url", ConstDefine.HttpAdressH5+"/index.html#/usertrainlist?type=jnpx");
+                intent.putExtra("url", ConstDefine.HttpAdressH5 + "/index.html#/usertrainlist?type=jnpx");
                 intent.putExtra("title", "我的培训");
-                intent.putExtra("UserInfo",userInfo);
+                intent.putExtra("UserInfo", userInfo);
                 startActivity(intent);
                 break;
             case R.id.js:
-                if (userInfo == null){
+                if (userInfo == null) {
                     showSnackBar("请先登录");
                     return;
                 }
                 intent.setClass(getActivity(), WebviewActivity.class);
-                intent.putExtra("url", ConstDefine.HttpAdressH5+"/index.html#/usertrainlist?type=ldjs");
+                intent.putExtra("url", ConstDefine.HttpAdressH5 + "/index.html#/usertrainlist?type=ldjs");
                 intent.putExtra("title", "我的竞赛");
-                intent.putExtra("UserInfo",userInfo);
+                intent.putExtra("UserInfo", userInfo);
                 startActivity(intent);
                 break;
             case R.id.wt:
-                if (userInfo == null){
+                if (userInfo == null) {
                     showSnackBar("请先登录");
                     return;
                 }
                 intent.setClass(getActivity(), WebviewActivity.class);
-                intent.putExtra("url", ConstDefine.HttpAdressH5+"/index.html#/usertrainlist?type=wthd");
+                intent.putExtra("url", ConstDefine.HttpAdressH5 + "/index.html#/usertrainlist?type=wthd");
                 intent.putExtra("title", "我的文体活动");
-                intent.putExtra("UserInfo",userInfo);
+                intent.putExtra("UserInfo", userInfo);
                 startActivity(intent);
                 break;
             case R.id.fl:
-                if (userInfo == null){
+                if (userInfo == null) {
                     showSnackBar("请先登录");
                     return;
                 }
                 intent.setClass(getActivity(), WebviewActivity.class);
-                intent.putExtra("url", ConstDefine.HttpAdressH5+"/index.html#/usertrainlist?type=flyz");
+                intent.putExtra("url", ConstDefine.HttpAdressH5 + "/index.html#/usertrainlist?type=flyz");
                 intent.putExtra("title", "我的法律援助");
-                intent.putExtra("UserInfo",userInfo);
+                intent.putExtra("UserInfo", userInfo);
                 startActivity(intent);
                 break;
         }

+ 73 - 0
app/src/main/java/com/tjzhxx/union/activity/AddressMapActivity.java

@@ -0,0 +1,73 @@
+package com.tjzhxx.union.activity;
+
+import android.os.Bundle;
+
+import com.amap.api.maps.AMap;
+import com.amap.api.maps.CameraUpdateFactory;
+import com.amap.api.maps.MapView;
+import com.amap.api.maps.model.LatLng;
+import com.amap.api.maps.model.MarkerOptions;
+import com.tjzhxx.union.R;
+import com.tjzhxx.union.system.BaseActivity;
+
+import butterknife.BindView;
+
+/**
+ * 描述:
+ *
+ * @author yhx
+ * @create 2021/10/9  10:01 AM
+ */
+public class AddressMapActivity extends BaseActivity {
+    @BindView(R.id.map)
+    MapView mapView;
+    private AMap aMap;
+    @Override
+    public int getContentViewLayoutID() {
+        return R.layout.activity_address_map;
+    }
+
+    @Override
+    public void bindViewAndEvent(Bundle savedInstanceState) {
+        initTitle("位置信息");
+        mapView.onCreate(savedInstanceState);
+        aMap = mapView.getMap();
+        aMap.getUiSettings().setScrollGesturesEnabled(true);
+        aMap.getUiSettings().setRotateGesturesEnabled(false);//禁止地图旋转手势.
+        aMap.getUiSettings().setTiltGesturesEnabled(false);//禁止倾斜手势.
+        LatLng latLng = new LatLng(39.043066, 117.634121);
+        aMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 18));
+        MarkerOptions marker = new MarkerOptions();
+        marker.position(latLng);
+        aMap.addMarker(marker);
+    }
+
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        //在activity执行onDestroy时执行mMapView.onDestroy(),销毁地图
+        mapView.onDestroy();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        //在activity执行onResume时执行mMapView.onResume (),重新绘制加载地图
+        mapView.onResume();
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        //在activity执行onPause时执行mMapView.onPause (),暂停地图的绘制
+        mapView.onPause();
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        //在activity执行onSaveInstanceState时执行mMapView.onSaveInstanceState (outState),保存地图当前的状态
+        mapView.onSaveInstanceState(outState);
+    }
+}

+ 46 - 1
app/src/main/java/com/tjzhxx/union/activity/AuthenticationActivity.java

@@ -40,6 +40,7 @@ import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider;
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.Calendar;
 
 import butterknife.BindView;
 import butterknife.OnClick;
@@ -74,6 +75,7 @@ public class AuthenticationActivity extends BaseActivity implements PermissionIn
     private PermissionHelper mPermissionHelper;
     private Boolean isPositive = true;
     private AuthenticationInfo info;
+    private Faceid faceid;
     @Override
     public int getContentViewLayoutID() {
         return R.layout.activity_authentication;
@@ -113,6 +115,18 @@ public class AuthenticationActivity extends BaseActivity implements PermissionIn
                     showSnackBar("请上传正确的身份证反面");
                     return;
                 }
+                if (idCardToAge(faceid.getIdNum())> 60){
+                    intent.setClass(this, PromptActivity.class);
+                    intent.putExtra("content","因为您的年龄超出了,工会成员年龄范围因此您没有成为零散务工人员工会会员,但是恭喜您成为水猫平台会员,您可享受平台提供的会员服务。");
+                    startActivity(intent);
+                    return;
+                }
+                if (idCardToAge(faceid.getIdNum()) < 18){
+                    intent.setClass(this, PromptActivity.class);
+                    intent.putExtra("content","因为您的年龄未达到,工会成员年龄范围 因此您没有成为零散务工人员工会会员, 但是恭喜您成为水猫平台会员,您可享受平台提供的会员服务。");
+                    startActivity(intent);
+                    return;
+                }
                 querysfzrepeatfromapp(info.getSfzid());
                 break;
         }
@@ -153,7 +167,7 @@ public class AuthenticationActivity extends BaseActivity implements PermissionIn
                     @Override
                     public void success(BaseResponse<Faceid> value) {
                         if (value.getData() != null){
-                            Faceid faceid = value.getData();
+                            faceid = value.getData();
                             if (isPositive) {
                                 name.setText(faceid.getName());
                                 sex.setText(faceid.getSex());
@@ -210,6 +224,37 @@ public class AuthenticationActivity extends BaseActivity implements PermissionIn
                     }
                 });
     }
+
+    /**
+     * 根据身份证计算年龄
+     *
+     * @param idcard
+     * @return
+     */
+    public Integer idCardToAge(String idcard) {
+        Integer selectYear = Integer.valueOf(idcard.substring(6, 10));         //出生的年份
+        Integer selectMonth = Integer.valueOf(idcard.substring(10, 12));       //出生的月份
+        Integer selectDay = Integer.valueOf(idcard.substring(12, 14));         //出生的日期
+        Calendar cal = Calendar.getInstance();
+        Integer yearMinus = cal.get(Calendar.YEAR) - selectYear;
+        Integer monthMinus = cal.get(Calendar.MONTH) + 1 - selectMonth;
+        Integer dayMinus = cal.get(Calendar.DATE) - selectDay;
+        Integer age = yearMinus;
+        if (yearMinus < 0) {
+            age = 0;
+        } else if (yearMinus == 0) {
+            age = 0;
+        } else if (yearMinus > 0) {
+            if (monthMinus == 0) {
+                if (dayMinus < 0) {
+                    age = age - 1;
+                }
+            } else if (monthMinus > 0) {
+                age = age + 1;
+            }
+        }
+        return yearMinus;
+    }
     @Override
     public int getPermissionsRequestCode() {
         return 999;

+ 39 - 3
app/src/main/java/com/tjzhxx/union/activity/LoginActivity.java

@@ -6,6 +6,7 @@ import android.text.TextUtils;
 import android.view.View;
 import android.widget.EditText;
 import android.widget.ImageView;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import androidx.lifecycle.Lifecycle;
@@ -54,8 +55,11 @@ public class LoginActivity extends BaseActivity {
     TextView newLogin;
     @BindView(R.id.wx)
     ImageView wx;
+    @BindView(R.id.icon_back)
+    LinearLayout iconBack;
     private UnionServices services;
     private static int m = 60;
+
     @Override
     public int getContentViewLayoutID() {
         return R.layout.activity_login;
@@ -64,10 +68,12 @@ public class LoginActivity extends BaseActivity {
     @Override
     public void bindViewAndEvent(Bundle savedInstanceState) {
         services = RetrofitUtils.createApi(UnionServices.class, ConstDefine.HttpAdress);
+        check.setSelected(false);
+
     }
 
 
-    @OnClick({R.id.icon_back, R.id.get_code, R.id.login, R.id.new_login, R.id.wx})
+    @OnClick({R.id.icon_back, R.id.get_code, R.id.login, R.id.new_login, R.id.wx,R.id.privacy, R.id.agress})
     public void onClick(View view) {
         Intent intent = new Intent();
         switch (view.getId()) {
@@ -84,13 +90,26 @@ public class LoginActivity extends BaseActivity {
                 }
                 break;
             case R.id.login:
-                if (TextUtils.isEmpty(code.getText().toString())){
+                if (TextUtils.isEmpty(code.getText().toString())) {
                     showSnackBar("请输入验证码");
                     return;
                 }
+                if (!check.isSelected()) {
+                    showSnackBar("请先阅读并同意《用户协议和隐私政策》");
+                    return;
+                }
                 smsLogin();
                 break;
             case R.id.new_login:
+                if (TextUtils.isEmpty(ConstDefine.city)){
+                    showSnackBar("请先授权获取位置信息");
+                    return;
+                }else if (!ConstDefine.city.contains("天津")){
+                    intent.setClass(this, PromptActivity.class);
+                    intent.putExtra("content","因为您现在所在区域不在天津市,暂时工会限定天津市工作人员加入因此您没有成为零散务工人员工会会员,但是恭喜您成为水猫平台会员,您可享受平台提供的会员服务。");
+                    startActivity(intent);
+                    return;
+                }
                 intent.setClass(this, WebviewActivity.class);
                 intent.putExtra("url", "http://47.94.42.74:9100/zhghsjd/index.html#/guidepage");
                 intent.putExtra("title", "会员入会");
@@ -98,8 +117,23 @@ public class LoginActivity extends BaseActivity {
                 break;
             case R.id.wx:
                 break;
+            case R.id.privacy:
+                intent.setClass(this, WebviewActivity.class);
+                intent.putExtra("url", ConstDefine.HttpAdress+"Uploads/privacyagreement/privacyagreement.html");
+                startActivity(intent);
+                break;
+            case R.id.agress:
+                if (check.isSelected()){
+                    check.setSelected(false);
+                    check.setImageResource(R.mipmap.icon_checkbox_normal);
+                }else {
+                    check.setSelected(true);
+                    check.setImageResource(R.mipmap.icon_checkbox_select);
+                }
+                break;
         }
     }
+
     private void sendSmsCode() {
         SmsCodeRequest request = new SmsCodeRequest();
         request.setTel(phone.getText().toString());
@@ -145,10 +179,12 @@ public class LoginActivity extends BaseActivity {
                     @Override
                     public void success(BaseResponse<UserInfo> value) {
                         ConstDefine.userInfo = value.getData();
-                        SpUtils.putUserInfo(LoginActivity.this,value.getData());
+                        SpUtils.putUserInfo(LoginActivity.this, value.getData());
                         finish();
                     }
 
                 });
     }
+
+
 }

+ 2 - 2
app/src/main/java/com/tjzhxx/union/activity/NewListActivity.java

@@ -76,7 +76,7 @@ public class NewListActivity extends BaseActivity implements FragmentPager.Fragm
 
     @Override
     public void bindViewAndEvent(Bundle savedInstanceState) {
-        initTitle("工会闻");
+        initTitle("工会闻");
         fragmentTab = new FragmentTab(this);
         majorTabs = new FragmentTab[]{fragmentTab, fragmentTab};
         fragments = new Class[]{NewListFragment.class, ActivityListFragment.class};
@@ -113,7 +113,7 @@ public class NewListActivity extends BaseActivity implements FragmentPager.Fragm
                     Intent intent = new Intent();
                     intent.setClass(NewListActivity.this, WebviewActivity.class);
                     intent.putExtra("url", entity.getWxurl());
-                    intent.putExtra("title", "工会闻");
+                    intent.putExtra("title", "工会闻");
                     startActivity(intent);
                 }
             }

+ 5 - 13
app/src/main/java/com/tjzhxx/union/activity/PayActivity.java

@@ -1,5 +1,6 @@
 package com.tjzhxx.union.activity;
 
+import android.content.Intent;
 import android.os.Bundle;
 
 import androidx.lifecycle.Lifecycle;
@@ -17,9 +18,6 @@ import com.tjzhxx.union.weixinapi.WxPay;
 import com.uber.autodispose.AutoDispose;
 import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider;
 
-import java.util.Timer;
-import java.util.TimerTask;
-
 import butterknife.OnClick;
 import io.reactivex.android.schedulers.AndroidSchedulers;
 import io.reactivex.schedulers.Schedulers;
@@ -50,16 +48,10 @@ public class PayActivity extends BaseActivity {
         wxPay.setOnPayResultListener(new WxPay.OnPayResultListener() {
             @Override
             public void onSuccess() {
-                showSnackBar("支付成功");
-                new Timer().schedule(new TimerTask() {
-                    @Override
-                    public void run() {
-                        PayActivity.this.finish();
-                        cancel();
-                        ConstDefine.userInfo.setIsactive("1");
-                    }
-                }, 1000);
-
+                PayActivity.this.finish();
+                ConstDefine.userInfo.setIsactive("1");
+                Intent intent = new Intent(PayActivity.this,PaySuccessActivity.class);
+                startActivity(intent);
             }
 
             @Override

+ 33 - 0
app/src/main/java/com/tjzhxx/union/activity/PaySuccessActivity.java

@@ -0,0 +1,33 @@
+package com.tjzhxx.union.activity;
+
+import android.os.Bundle;
+
+import com.tjzhxx.union.R;
+import com.tjzhxx.union.system.BaseActivity;
+
+import butterknife.OnClick;
+
+/**
+ * 描述:
+ *
+ * @author yhx
+ * @create 2021/9/11  8:58 PM
+ */
+public class PaySuccessActivity extends BaseActivity {
+    @Override
+    public int getContentViewLayoutID() {
+        return R.layout.activity_pay_success;
+    }
+
+    @Override
+    public void bindViewAndEvent(Bundle savedInstanceState) {
+        initTitle("缴费成功");
+    }
+
+
+    @OnClick(R.id.share)
+    public void onClick() {
+       finish();
+    }
+
+}

+ 39 - 0
app/src/main/java/com/tjzhxx/union/activity/PromptActivity.java

@@ -0,0 +1,39 @@
+package com.tjzhxx.union.activity;
+
+import android.os.Bundle;
+import android.widget.TextView;
+
+import com.tjzhxx.union.R;
+import com.tjzhxx.union.system.BaseActivity;
+
+import butterknife.BindView;
+import butterknife.OnClick;
+
+/**
+ * 描述:
+ *
+ * @author yhx
+ * @create 2021/9/11  8:58 PM
+ */
+public class PromptActivity extends BaseActivity {
+    @BindView(R.id.prompt)
+    TextView prompt;
+
+    @Override
+    public int getContentViewLayoutID() {
+        return R.layout.activity_prompt;
+    }
+
+    @Override
+    public void bindViewAndEvent(Bundle savedInstanceState) {
+        String content = getIntent().getStringExtra("content");
+        prompt.setText(content);
+    }
+
+
+
+    @OnClick(R.id.sure)
+    public void onClick() {
+        finish();
+    }
+}

+ 133 - 0
app/src/main/java/com/tjzhxx/union/activity/WelfareListActivity.java

@@ -0,0 +1,133 @@
+package com.tjzhxx.union.activity;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.scwang.smart.refresh.layout.SmartRefreshLayout;
+import com.scwang.smart.refresh.layout.api.RefreshLayout;
+import com.scwang.smart.refresh.layout.listener.OnLoadMoreListener;
+import com.scwang.smart.refresh.layout.listener.OnRefreshListener;
+import com.tjzhxx.union.R;
+import com.tjzhxx.union.adapter.WelfareAdapter;
+import com.tjzhxx.union.entity.BaseResponse;
+import com.tjzhxx.union.entity.WelfareResponse;
+import com.tjzhxx.union.entity.request.WelfareListRequest;
+import com.tjzhxx.union.public_store.ConstDefine;
+import com.tjzhxx.union.public_store.retrofit.RetrofitUtils;
+import com.tjzhxx.union.public_store.retrofit.UnionServices;
+import com.tjzhxx.union.public_store.rx.DefaultSubscriber;
+import com.tjzhxx.union.system.BaseActivity;
+import com.uber.autodispose.AutoDispose;
+import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import butterknife.BindView;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.schedulers.Schedulers;
+
+/**
+ * 描述:
+ *
+ * @author yhx
+ * @create 2021/10/18  11:47 AM
+ */
+public class WelfareListActivity extends BaseActivity {
+    @BindView(R.id.recyclerView)
+    RecyclerView recyclerView;
+    @BindView(R.id.refreshLayout)
+    SmartRefreshLayout refreshLayout;
+    private UnionServices services;
+    private int pageNo = 1, stepSize = 10;
+    private boolean isHasMore = false;
+    private List<WelfareResponse> list = new ArrayList<>();
+    private WelfareAdapter adapter;
+    @Override
+    public int getContentViewLayoutID() {
+        return R.layout.activity_welfare_list;
+    }
+
+    @Override
+    public void bindViewAndEvent(Bundle savedInstanceState) {
+        initTitle("会员福利");
+        services = RetrofitUtils.createApi(UnionServices.class, ConstDefine.HttpAdress);
+        recyclerView.setLayoutManager(new LinearLayoutManager(this));
+        adapter = new WelfareAdapter(this, list);
+        adapter.setOnItemClickListener(new WelfareAdapter.OnItemClickListener() {
+            @Override
+            public void onItemClick(View view, WelfareResponse entity) {
+                if (entity.getStatus() != 1){
+                    Intent intent = new Intent(WelfareListActivity.this,WelfareSignatureSureActivity.class);
+                    intent.putExtra("wfid",entity.getWfinfo().getId());
+                    startActivity(intent);
+                }
+            }
+        });
+        recyclerView.setAdapter(adapter);
+        refreshLayout.setOnRefreshListener(new OnRefreshListener() {
+            @Override
+            public void onRefresh(RefreshLayout refreshLayout) {
+                pageNo = 1;
+                loadDate();
+            }
+        });
+
+        refreshLayout.setOnLoadMoreListener(new OnLoadMoreListener() {
+            @Override
+            public void onLoadMore(RefreshLayout refreshLayout) {
+                loadDate();
+            }
+        });
+
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        refreshLayout.autoRefresh();
+    }
+
+    private void loadDate() {
+        WelfareListRequest request = new WelfareListRequest();
+        request.setPage(pageNo);
+        request.setSize(stepSize);
+        request.setWid(Integer.valueOf(ConstDefine.userInfo.getWid()));
+        services.getalllistbywid(request)
+                .subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread())
+                .as(AutoDispose.<BaseResponse<List<WelfareResponse>>>autoDisposable(AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY)))
+                .subscribe(new DefaultSubscriber<BaseResponse<List<WelfareResponse>>>(this) {
+                    @Override
+                    public void success(BaseResponse<List<WelfareResponse>> value) {
+                        if (value.getData() != null) {
+                            List<WelfareResponse> beanList = value.getData();
+                            if (beanList != null) {
+                                if (pageNo == 1) {
+                                    list.clear();
+                                }
+                                if (beanList.size() == stepSize) {
+                                    isHasMore = true;
+                                    pageNo++;
+                                } else {
+                                    isHasMore = false;
+                                }
+                                list.addAll(beanList);
+                            } else {
+                                isHasMore = false;
+                            }
+                        } else {
+                            showSnackBar("加载失败");
+                        }
+                        adapter.notifyDataSetChanged();
+                        refreshComplete(refreshLayout, isHasMore);
+                    }
+                });
+    }
+
+}

+ 117 - 0
app/src/main/java/com/tjzhxx/union/activity/WelfareSignatureSureActivity.java

@@ -0,0 +1,117 @@
+package com.tjzhxx.union.activity;
+
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.lifecycle.Lifecycle;
+
+import com.tjzhxx.union.R;
+import com.tjzhxx.union.custom_view.LinePathView;
+import com.tjzhxx.union.entity.BaseResponse;
+import com.tjzhxx.union.entity.request.GetWelfareRequest;
+import com.tjzhxx.union.public_store.ConstDefine;
+import com.tjzhxx.union.public_store.FileUtils;
+import com.tjzhxx.union.public_store.retrofit.RetrofitUtils;
+import com.tjzhxx.union.public_store.retrofit.UnionServices;
+import com.tjzhxx.union.public_store.rx.DefaultSubscriber;
+import com.tjzhxx.union.system.BaseActivity;
+import com.uber.autodispose.AutoDispose;
+import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import butterknife.BindView;
+import butterknife.OnClick;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.schedulers.Schedulers;
+import okhttp3.MediaType;
+import okhttp3.MultipartBody;
+import okhttp3.RequestBody;
+
+/**
+ * 描述:
+ *
+ * @author yhx
+ * @create 2021/9/2  8:57 AM
+ */
+public class WelfareSignatureSureActivity extends BaseActivity {
+
+
+    @BindView(R.id.linepath)
+    LinePathView linepath;
+    private UnionServices services;
+    private int wfid;
+    @Override
+    public int getContentViewLayoutID() {
+        return R.layout.activity_welfare_signature_sure;
+    }
+
+    @Override
+    public void bindViewAndEvent(Bundle savedInstanceState) {
+        initTitle("签名领取");
+        wfid = getIntent().getIntExtra("wfid",0);
+        services = RetrofitUtils.createApi(UnionServices.class, ConstDefine.HttpAdress);
+    }
+
+    @OnClick({R.id.clear, R.id.sure})
+    public void onClick(View view) {
+
+        switch (view.getId()) {
+            case R.id.clear:
+                linepath.clear();
+                break;
+            case R.id.sure:
+                if (!linepath.getTouched()){
+                    showSnackBar("请输入签名");
+                    return;
+                }
+                try {
+                    String tiemStamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
+                    String path = FileUtils.getCompressFilePath(WelfareSignatureSureActivity.this)  + "/" + tiemStamp + ".png";
+                    linepath.save(path);
+                    uploadImage(path);
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+                break;
+        }
+    }
+
+    //上传图片
+    private void uploadImage(String path) {
+        File file = new File(path);
+        RequestBody imageBody = RequestBody.create(MediaType.parse("multipart/form-data"), file);
+        MultipartBody.Part imageBodyPart = MultipartBody.Part.createFormData("file", file.getName(), imageBody);
+        services.uploadImage(imageBodyPart)
+                .subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread())
+                .as(AutoDispose.<BaseResponse<String>>autoDisposable(AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY)))
+                .subscribe(new DefaultSubscriber<BaseResponse<String>>(this) {
+                    @Override
+                    public void success(BaseResponse<String> value) {
+                        getwelfarebywid(value.getData());
+                    }
+
+                });
+    }
+
+    private void getwelfarebywid(String url) {
+        GetWelfareRequest request = new GetWelfareRequest();
+        request.setWfid(wfid);
+        request.setWid(Integer.valueOf(ConstDefine.userInfo.getWid()));
+        request.setSignpicurl(url);
+        services.getwelfarebywid(request)
+                .subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread())
+                .as(AutoDispose.<BaseResponse>autoDisposable(AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY)))
+                .subscribe(new DefaultSubscriber<BaseResponse>(this) {
+                    @Override
+                    public void success(BaseResponse value) {
+                       finish();
+                    }
+                });
+    }
+}

+ 102 - 0
app/src/main/java/com/tjzhxx/union/adapter/WelfareAdapter.java

@@ -0,0 +1,102 @@
+package com.tjzhxx.union.adapter;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bumptech.glide.Glide;
+import com.tjzhxx.union.R;
+import com.tjzhxx.union.custom_view.RoundImageView;
+import com.tjzhxx.union.entity.WelfareResponse;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+
+/**
+ * 描述:
+ *
+ * @author yanghuaxiong
+ * @create 2018/5/3 下午4:40
+ */
+public class WelfareAdapter extends RecyclerView.Adapter<WelfareAdapter.ViewHolder> {
+
+    Context mContext;
+    List<WelfareResponse> mList;
+    OnItemClickListener mOnItemClickListener;
+
+
+    public void setOnItemClickListener(OnItemClickListener listener) {
+        this.mOnItemClickListener = listener;
+    }
+
+    public WelfareAdapter(Context mContext, List<WelfareResponse> list) {
+        this.mContext = mContext;
+        this.mList = (list == null ? new ArrayList<WelfareResponse>() : list);
+    }
+
+    @Override
+    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_welfare, parent, false);
+        v.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                if (mOnItemClickListener != null)
+                    mOnItemClickListener.onItemClick(view, (WelfareResponse) view.getTag());
+            }
+        });
+        return new ViewHolder(v);
+    }
+
+    @Override
+    public void onBindViewHolder(ViewHolder holder, int position) {
+        WelfareResponse bean = mList.get(position);
+        holder.itemView.setTag(bean);
+        Glide.with(mContext)
+                .load(bean.getWfinfo().getPicurl())
+                .placeholder(R.mipmap.bg_placeholder)
+                .error(R.mipmap.bg_placeholder)
+                .into(holder.image);
+        holder.title.setText(bean.getWfinfo().getTitle());
+        holder.descrble.setText(bean.getWfinfo().getDisstr());
+        if (bean.getStatus() == 1){
+            holder.item.setBackgroundResource(R.mipmap.icon_get_welfare);
+        }else {
+            holder.item.setBackgroundResource(R.mipmap.icon_noget_welfare);
+        }
+    }
+
+    @Override
+    public int getItemCount() {
+        return mList.size();
+    }
+
+    public static class ViewHolder extends RecyclerView.ViewHolder {
+        @BindView(R.id.image)
+        RoundImageView image;
+        @BindView(R.id.title)
+        TextView title;
+        @BindView(R.id.descrble)
+        TextView descrble;
+        @BindView(R.id.item)
+        LinearLayout item;
+
+
+        public ViewHolder(View itemView) {
+            super(itemView);
+            ButterKnife.bind(this, itemView);
+        }
+    }
+
+    public interface OnItemClickListener {
+        void onItemClick(View view, WelfareResponse entity);
+    }
+
+}

+ 125 - 0
app/src/main/java/com/tjzhxx/union/dialog/PrivacyRemarkDialog.java

@@ -0,0 +1,125 @@
+package com.tjzhxx.union.dialog;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.Intent;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.TextPaint;
+import android.text.method.LinkMovementMethod;
+import android.text.style.ClickableSpan;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+import com.tjzhxx.union.MainActivity;
+import com.tjzhxx.union.R;
+import com.tjzhxx.union.public_store.ConstDefine;
+import com.tjzhxx.union.public_store.SpUtils;
+import com.tjzhxx.union.system.BaseActivity;
+import com.tjzhxx.union.system.WebviewActivity;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+
+public class PrivacyRemarkDialog extends Dialog {
+    @BindView(R.id.checkbox)
+    CheckBox checkbox;
+    @BindView(R.id.remark)
+    TextView remark;
+    private Context context;
+
+    OnItemClickListener mOnItemClickListener;
+
+    public void setmOnItemClickListener(OnItemClickListener mOnItemClickListener) {
+        this.mOnItemClickListener = mOnItemClickListener;
+    }
+
+    public PrivacyRemarkDialog(Context context) {
+        super(context, R.style.Dialog);
+        this.context = context;
+        LayoutInflater inflater = LayoutInflater.from(context);
+        View view = inflater.inflate(R.layout.dialog_privacy_remark, null);
+        setContentView(view);
+        ButterKnife.bind(this);
+        setCancelable(false);
+        initString();
+    }
+
+
+    @OnClick({R.id.ll_agree, R.id.left, R.id.right})
+    public void onClick(View view) {
+        switch (view.getId()) {
+            case R.id.ll_agree:
+                checkbox.setChecked(!checkbox.isChecked());
+                break;
+            case R.id.left:
+                dismiss();
+                ((MainActivity) context).finish();
+                break;
+            case R.id.right:
+                if (checkbox.isChecked()) {
+                    dismiss();
+                    SpUtils.put(context, "PrivacyRemark", false);
+                    if (mOnItemClickListener != null) {
+                        mOnItemClickListener.onRight();
+                    }
+                } else {
+                    ((BaseActivity) context).showSnackBar("请先勾选同意事项");
+                }
+                break;
+        }
+    }
+
+    private void initString() {
+        String s01 = context.getResources().getString(R.string.privacy_ramark_02);
+        String s02 = context.getResources().getString(R.string.privacy_ramark_03);
+        String s03 = context.getResources().getString(R.string.privacy_ramark_04);
+        String s04 = context.getResources().getString(R.string.privacy_ramark_05);
+        String s05 = context.getResources().getString(R.string.privacy_ramark_06);
+        final SpannableString spannableTip1 = new SpannableString(s02);
+        spannableTip1.setSpan(new ClickableSpan() {
+            @Override
+            public void onClick(View widget) {
+                Intent intent = new Intent();
+                intent.setClass(context, WebviewActivity.class);
+                intent.putExtra("url", ConstDefine.HttpAdress + "Uploads/privacyagreement/privacyagreement.html");
+                context.startActivity(intent);
+            }
+
+            @Override
+            public void updateDrawState(TextPaint ds) {
+                super.updateDrawState(ds);
+                ds.setColor(context.getResources().getColor(R.color.colorAccent)); //设置颜色
+            }
+        }, 0, s02.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        final SpannableString spannableTip2 = new SpannableString(s04);
+        spannableTip2.setSpan(new ClickableSpan() {
+            @Override
+            public void onClick(View widget) {
+                Intent intent = new Intent();
+                intent.setClass(context, WebviewActivity.class);
+                intent.putExtra("url", ConstDefine.HttpAdress + "Uploads/privacyagreement/privacyagreement.html");
+                context.startActivity(intent);
+            }
+
+            @Override
+            public void updateDrawState(TextPaint ds) {
+                super.updateDrawState(ds);
+                ds.setColor(context.getResources().getColor(R.color.colorAccent)); //设置颜色
+            }
+        }, 0, s04.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        remark.setText(s01);
+        remark.append(spannableTip1);
+        remark.append(s03);
+        remark.append(spannableTip2);
+        remark.append(s05);
+        remark.setMovementMethod(LinkMovementMethod.getInstance());//开始响应点击事件
+    }
+
+    public interface OnItemClickListener {
+        void onRight();
+    }
+}

+ 242 - 0
app/src/main/java/com/tjzhxx/union/entity/WelfareResponse.java

@@ -0,0 +1,242 @@
+package com.tjzhxx.union.entity;
+
+import java.io.Serializable;
+
+/**
+ * 描述:
+ *
+ * @author yhx
+ * @create 2021/10/18  2:03 PM
+ */
+public class WelfareResponse implements Serializable {
+
+    /**
+     * id : 1034
+     * wfid : 2
+     * wid : 13
+     * wname : 徐子翔
+     * sfzid : 430402197708073018
+     * telno : 13512442396
+     * status : 0
+     * createdate : null
+     * getdate : null
+     * signpicurl : null
+     * wfinfo : {"id":2,"code":"wf20210928","title":"会员九月福利","disstr":"方便面","picurl":"https://app.tjzhxx.cn:1443/Uploads/welfarephoto/fb.jpg","startdate":null,"enddate":null,"status":1,"count":1000,"createdate":null,"isautosend":1}
+     */
+
+    private int id;
+    private int wfid;
+    private int wid;
+    private String wname;
+    private String sfzid;
+    private String telno;
+    private int status;
+    private Object createdate;
+    private Object getdate;
+    private Object signpicurl;
+    /**
+     * id : 2
+     * code : wf20210928
+     * title : 会员九月福利
+     * disstr : 方便面
+     * picurl : https://app.tjzhxx.cn:1443/Uploads/welfarephoto/fb.jpg
+     * startdate : null
+     * enddate : null
+     * status : 1
+     * count : 1000
+     * createdate : null
+     * isautosend : 1
+     */
+
+    private WfinfoBean wfinfo;
+
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    public int getWfid() {
+        return wfid;
+    }
+
+    public void setWfid(int wfid) {
+        this.wfid = wfid;
+    }
+
+    public int getWid() {
+        return wid;
+    }
+
+    public void setWid(int wid) {
+        this.wid = wid;
+    }
+
+    public String getWname() {
+        return wname;
+    }
+
+    public void setWname(String wname) {
+        this.wname = wname;
+    }
+
+    public String getSfzid() {
+        return sfzid;
+    }
+
+    public void setSfzid(String sfzid) {
+        this.sfzid = sfzid;
+    }
+
+    public String getTelno() {
+        return telno;
+    }
+
+    public void setTelno(String telno) {
+        this.telno = telno;
+    }
+
+    public int getStatus() {
+        return status;
+    }
+
+    public void setStatus(int status) {
+        this.status = status;
+    }
+
+    public Object getCreatedate() {
+        return createdate;
+    }
+
+    public void setCreatedate(Object createdate) {
+        this.createdate = createdate;
+    }
+
+    public Object getGetdate() {
+        return getdate;
+    }
+
+    public void setGetdate(Object getdate) {
+        this.getdate = getdate;
+    }
+
+    public Object getSignpicurl() {
+        return signpicurl;
+    }
+
+    public void setSignpicurl(Object signpicurl) {
+        this.signpicurl = signpicurl;
+    }
+
+    public WfinfoBean getWfinfo() {
+        return wfinfo;
+    }
+
+    public void setWfinfo(WfinfoBean wfinfo) {
+        this.wfinfo = wfinfo;
+    }
+
+    public static class WfinfoBean  implements Serializable{
+        private int id;
+        private String code;
+        private String title;
+        private String disstr;
+        private String picurl;
+        private Object startdate;
+        private Object enddate;
+        private int status;
+        private int count;
+        private Object createdate;
+        private int isautosend;
+
+        public int getId() {
+            return id;
+        }
+
+        public void setId(int id) {
+            this.id = id;
+        }
+
+        public String getCode() {
+            return code;
+        }
+
+        public void setCode(String code) {
+            this.code = code;
+        }
+
+        public String getTitle() {
+            return title;
+        }
+
+        public void setTitle(String title) {
+            this.title = title;
+        }
+
+        public String getDisstr() {
+            return disstr;
+        }
+
+        public void setDisstr(String disstr) {
+            this.disstr = disstr;
+        }
+
+        public String getPicurl() {
+            return picurl;
+        }
+
+        public void setPicurl(String picurl) {
+            this.picurl = picurl;
+        }
+
+        public Object getStartdate() {
+            return startdate;
+        }
+
+        public void setStartdate(Object startdate) {
+            this.startdate = startdate;
+        }
+
+        public Object getEnddate() {
+            return enddate;
+        }
+
+        public void setEnddate(Object enddate) {
+            this.enddate = enddate;
+        }
+
+        public int getStatus() {
+            return status;
+        }
+
+        public void setStatus(int status) {
+            this.status = status;
+        }
+
+        public int getCount() {
+            return count;
+        }
+
+        public void setCount(int count) {
+            this.count = count;
+        }
+
+        public Object getCreatedate() {
+            return createdate;
+        }
+
+        public void setCreatedate(Object createdate) {
+            this.createdate = createdate;
+        }
+
+        public int getIsautosend() {
+            return isautosend;
+        }
+
+        public void setIsautosend(int isautosend) {
+            this.isautosend = isautosend;
+        }
+    }
+}

+ 39 - 0
app/src/main/java/com/tjzhxx/union/entity/request/GetWelfareRequest.java

@@ -0,0 +1,39 @@
+package com.tjzhxx.union.entity.request;
+
+import java.io.Serializable;
+
+/**
+ * 描述:
+ * @author yanghuaxiong
+ * @create 2021/9/2 8:07 AM
+ * 
+ */
+public class GetWelfareRequest implements Serializable {
+    private int wid;
+    private int wfid;
+    private String signpicurl;
+
+    public int getWid() {
+        return wid;
+    }
+
+    public void setWid(int wid) {
+        this.wid = wid;
+    }
+
+    public int getWfid() {
+        return wfid;
+    }
+
+    public void setWfid(int wfid) {
+        this.wfid = wfid;
+    }
+
+    public String getSignpicurl() {
+        return signpicurl;
+    }
+
+    public void setSignpicurl(String signpicurl) {
+        this.signpicurl = signpicurl;
+    }
+}

+ 39 - 0
app/src/main/java/com/tjzhxx/union/entity/request/WelfareListRequest.java

@@ -0,0 +1,39 @@
+package com.tjzhxx.union.entity.request;
+
+import java.io.Serializable;
+
+/**
+ * 描述:
+ * @author yanghuaxiong
+ * @create 2021/9/2 8:07 AM
+ * 
+ */
+public class WelfareListRequest implements Serializable {
+    private int wid;
+    private int page;
+    private int size;
+
+    public int getWid() {
+        return wid;
+    }
+
+    public void setWid(int wid) {
+        this.wid = wid;
+    }
+
+    public int getPage() {
+        return page;
+    }
+
+    public void setPage(int page) {
+        this.page = page;
+    }
+
+    public int getSize() {
+        return size;
+    }
+
+    public void setSize(int size) {
+        this.size = size;
+    }
+}

+ 1 - 1
app/src/main/java/com/tjzhxx/union/fragment/NewListFragment.java

@@ -68,7 +68,7 @@ public class NewListFragment extends BaseFragment {
                 Intent intent = new Intent();
                 intent.setClass(getActivity(), WebviewActivity.class);
                 intent.putExtra("url", entity.getWxurl());
-                intent.putExtra("title", "工会闻");
+                intent.putExtra("title", "工会闻");
                 startActivity(intent);
             }
         });

+ 1 - 1
app/src/main/java/com/tjzhxx/union/public_store/ConstDefine.java

@@ -10,7 +10,7 @@ public class ConstDefine {
 //    public static String HttpAdress = "https://app.tjzhxx.com/labphp/";
     public static String HttpAdress = "https://app.tjzhxx.cn:1443/";//正式环境
 
-    public static String HttpAdressH5 = "http://47.94.42.74:9100/zhghsjd";
+    public static String HttpAdressH5 = "http://app.tjzhxx.cn:9100/zhghsjd";
     public static UserInfo userInfo = null;
     public static String city = "";
     public static String district = "";

+ 11 - 0
app/src/main/java/com/tjzhxx/union/public_store/retrofit/UnionServices.java

@@ -7,17 +7,20 @@ import com.tjzhxx.union.entity.Faceid;
 import com.tjzhxx.union.entity.NewBean;
 import com.tjzhxx.union.entity.Rescuestyle;
 import com.tjzhxx.union.entity.UserInfo;
+import com.tjzhxx.union.entity.WelfareResponse;
 import com.tjzhxx.union.entity.request.DataDbqzPhotoRequest;
 import com.tjzhxx.union.entity.request.DataDbqzRequest;
 import com.tjzhxx.union.entity.request.DataXcjzRequest;
 import com.tjzhxx.union.entity.request.DataZywwRequest;
 import com.tjzhxx.union.entity.request.FaceidRequest;
 import com.tjzhxx.union.entity.request.GetPayInfoRequest;
+import com.tjzhxx.union.entity.request.GetWelfareRequest;
 import com.tjzhxx.union.entity.request.IsRepeatRequest;
 import com.tjzhxx.union.entity.request.NewListRequest;
 import com.tjzhxx.union.entity.request.SmsCodeRequest;
 import com.tjzhxx.union.entity.request.SmsLoginRequest;
 import com.tjzhxx.union.entity.request.SubnewworkerRequest;
+import com.tjzhxx.union.entity.request.WelfareListRequest;
 import com.tjzhxx.union.entity.request.WxPayReq;
 
 import java.util.List;
@@ -100,4 +103,12 @@ public interface UnionServices {
     //用户支付入会费接口 使用微信统一下单接口 仅app用
     @POST("index.php/index/pay/getOrderInfoForApp")
     Observable<BaseResponse<WxPayReq>> getOrderInfoForApp(@Body GetPayInfoRequest request);
+
+    //获取用户全部福利 通过wid
+    @POST("index.php/index/newwelfare/getalllistbywid")
+    Observable<BaseResponse<List<WelfareResponse>>> getalllistbywid(@Body WelfareListRequest request);
+
+    //获取用户全部福利 通过wid
+    @POST("index.php/index/newwelfare/getwelfarebywid")
+    Observable<BaseResponse> getwelfarebywid(@Body GetWelfareRequest request);
 }

+ 8 - 0
app/src/main/res/drawable/select_pic_dialog_bg.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+
+    <solid android:color="@color/white" />
+    <corners android:radius="12dp" />
+
+</shape>

+ 16 - 0
app/src/main/res/layout/activity_address_map.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/bg"
+    android:clipToPadding="true"
+    android:fitsSystemWindows="true"
+    android:orientation="vertical">
+
+    <include layout="@layout/include_title" />
+
+    <com.amap.api.maps.MapView
+        android:id="@+id/map"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+</LinearLayout>

+ 6 - 1
app/src/main/res/layout/activity_login.xml

@@ -136,6 +136,7 @@
         android:background="@color/separated" />
 
     <LinearLayout
+        android:id="@+id/agress"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginLeft="40dp"
@@ -148,16 +149,18 @@
             android:id="@+id/check"
             android:layout_width="12dp"
             android:layout_height="12dp"
-            android:src="@mipmap/icon_checkbox_select" />
+            android:src="@mipmap/icon_checkbox_normal" />
 
         <TextView
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:text="我已阅读并同意"
+            android:layout_marginLeft="5dp"
             android:textColor="@color/text_black_666"
             android:textSize="@dimen/textSize_12" />
 
         <TextView
+            android:id="@+id/privacy"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:text="《用户协议和隐私政策》"
@@ -191,6 +194,7 @@
         android:layout_height="wrap_content"
         android:layout_marginLeft="35dp"
         android:layout_marginRight="35dp"
+        android:visibility="gone"
         android:layout_marginTop="40dp"
         android:gravity="center_vertical"
         android:orientation="horizontal">
@@ -221,6 +225,7 @@
         android:layout_width="40dp"
         android:layout_height="40dp"
         android:layout_marginTop="16dp"
+        android:visibility="gone"
         android:layout_gravity="center_horizontal"
         android:src="@mipmap/icon_wx" />
 </LinearLayout>

+ 2 - 2
app/src/main/res/layout/activity_new_list.xml

@@ -32,7 +32,7 @@
                 android:layout_height="match_parent"
                 android:layout_weight="1"
                 android:gravity="center"
-                android:text="工会闻"
+                android:text="工会闻"
                 android:textColor="@color/red"
                 android:textSize="@dimen/textSize_18" />
 
@@ -48,7 +48,7 @@
                 android:layout_height="match_parent"
                 android:layout_weight="1"
                 android:gravity="center"
-                android:text="活动报告"
+                android:text="身边人,身边事"
                 android:textColor="@color/text_black_333"
                 android:textSize="@dimen/textSize_14" />
 

+ 56 - 0
app/src/main/res/layout/activity_pay_success.xml

@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/bg"
+    android:gravity="center_horizontal"
+    android:orientation="vertical">
+
+
+    <include layout="@layout/include_title" />
+
+    <ImageView
+        android:layout_width="90dp"
+        android:layout_height="90dp"
+        android:src="@mipmap/icon_pay_success"
+        android:layout_marginTop="45dp"
+        />
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="7dp"
+        android:text="缴费成功"
+        android:textColor="@color/text_black_333"
+        android:textStyle="bold"
+        android:textSize="23sp" />
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="60dp"
+        android:text="恭喜您成功加入零散务工人员公会"
+        android:textColor="@color/text_black_333"
+        android:textStyle="bold"
+        android:textSize="@dimen/textSize_18" />
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="10dp"
+        android:layout_marginLeft="55dp"
+        android:layout_marginRight="55dp"
+        android:text="好的东西要分享出去,把零散务工人员工会分享给身边的朋友和家人,邀请他们一起加零散务工人员入工会"
+        android:gravity="center"
+        android:textColor="@color/text_black_333"
+        android:textSize="@dimen/textSize_14" />
+    <TextView
+        android:id="@+id/share"
+        android:layout_width="100dp"
+        android:layout_height="30dp"
+        android:text="分享"
+        android:layout_marginTop="60dp"
+        android:gravity="center"
+        android:layout_gravity="center_horizontal"
+        android:background="@drawable/drawable_red_radius_10"
+        android:textColor="@color/white"
+        android:textSize="@dimen/textSize_18" />
+
+</LinearLayout>

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

@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/bg"
+    android:gravity="center_horizontal"
+    android:orientation="vertical">
+
+
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="50dp"
+        android:text="加入工会出了点问题~"
+        android:textColor="@color/text_black_333"
+        android:textStyle="bold"
+        android:textSize="20sp" />
+
+    <TextView
+        android:id="@+id/prompt"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="10dp"
+        android:layout_marginLeft="45dp"
+        android:layout_marginRight="45dp"
+        android:text=""
+        android:gravity="center"
+        android:textColor="@color/text_black_333"
+        android:textSize="@dimen/textSize_16" />
+    <ImageView
+        android:layout_width="340dp"
+        android:layout_height="252dp"
+        android:layout_marginTop="25dp"
+        android:src="@mipmap/icon_nojoin_prompt" />
+    <TextView
+        android:id="@+id/sure"
+        android:layout_width="140dp"
+        android:layout_height="30dp"
+        android:text="确定"
+        android:layout_marginTop="30dp"
+        android:gravity="center"
+        android:layout_gravity="center_horizontal"
+        android:background="@drawable/drawable_red_radius_10"
+        android:textColor="@color/white"
+        android:textSize="@dimen/textSize_18" />
+
+</LinearLayout>

+ 23 - 0
app/src/main/res/layout/activity_welfare_list.xml

@@ -0,0 +1,23 @@
+<?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="match_parent"
+    android:orientation="vertical">
+
+    <include layout="@layout/include_title" />
+
+    <com.scwang.smart.refresh.layout.SmartRefreshLayout
+        android:id="@+id/refreshLayout"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:srlEnableFooterFollowWhenLoadFinished="true">
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/recyclerView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:overScrollMode="never"
+            android:scrollbars="none" />
+    </com.scwang.smart.refresh.layout.SmartRefreshLayout>
+</LinearLayout>

+ 110 - 0
app/src/main/res/layout/activity_welfare_signature_sure.xml

@@ -0,0 +1,110 @@
+<?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="match_parent"
+    android:background="@color/bg"
+    android:orientation="vertical">
+
+    <include layout="@layout/include_title" />
+
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_margin="12dp"
+            android:orientation="vertical">
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="4dp"
+                android:background="@drawable/drawable_gray_stroke_radius_10"
+                android:gravity="center_horizontal"
+                android:orientation="vertical"
+                android:padding="15dp">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="4dp"
+                    android:text="福利领取"
+                    android:textColor="@color/text_black_333"
+                    android:textSize="@dimen/textSize_16"
+                    android:textStyle="bold" />
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="4dp"
+                    android:paddingLeft="30dp"
+                    android:paddingRight="30dp"
+                    android:text="您将领取福利,本福利只能领取一次,请你签名"
+                    android:textColor="@color/text_black_333"
+                    android:textSize="@dimen/textSize_14" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="4dp"
+                android:background="@drawable/drawable_gray_stroke_radius_10"
+                android:gravity="center_horizontal"
+                android:orientation="vertical"
+                android:padding="15dp">
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:gravity="bottom"
+                    android:orientation="horizontal">
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="4dp"
+                        android:text="请在下方签名确认:"
+                        android:textColor="@color/text_black_333"
+                        android:textSize="@dimen/textSize_14"
+                        android:textStyle="bold" />
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="(签名人工审核留底,请规范签名)"
+                        android:textColor="@color/text_black_999"
+                        android:textSize="@dimen/textSize_10" />
+                </LinearLayout>
+                <com.tjzhxx.union.custom_view.LinePathView
+                    android:id="@+id/linepath"
+                    android:layout_width="match_parent"
+                    android:layout_height="160dp"
+                    android:background="@drawable/drawable_bg_radius_10" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="16dp"
+                android:gravity="center_horizontal"
+                android:orientation="horizontal">
+                <TextView
+                    android:id="@+id/clear"
+                    android:layout_width="100dp"
+                    android:layout_height="30dp"
+                    android:text="清除重签"
+                    android:gravity="center"
+                    android:textSize="@dimen/textSize_16"
+                    android:textColor="@color/white"
+                    android:background="@drawable/drawable_cccccc_radius_10" />
+                <TextView
+                    android:id="@+id/sure"
+                    android:layout_width="100dp"
+                    android:layout_height="30dp"
+                    android:text="确认签名"
+                    android:layout_marginLeft="20dp"
+                    android:gravity="center"
+                    android:textSize="@dimen/textSize_16"
+                    android:textColor="@color/white"
+                    android:background="@drawable/drawable_red_radius_10" />
+            </LinearLayout>
+        </LinearLayout>
+</LinearLayout>

+ 79 - 0
app/src/main/res/layout/dialog_privacy_remark.xml

@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_margin="30dp"
+    android:background="@drawable/select_pic_dialog_bg"
+    android:orientation="vertical">
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="20dp"
+        android:textStyle="bold"
+        android:text="@string/privacy_ramark_01"
+        android:textColor="@color/text_black_333"
+        android:textSize="16sp" />
+    <LinearLayout
+        android:id="@+id/ll_agree"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="10dp"
+        android:layout_marginLeft="20dp"
+        android:layout_marginRight="20dp"
+        android:layout_marginBottom="20dp"
+        android:orientation="horizontal">
+        <CheckBox
+            android:id="@+id/checkbox"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginRight="5dp"
+            />
+        <TextView
+            android:id="@+id/remark"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textStyle="bold"
+            android:text="@string/privacy_ramark_02"
+            android:textColor="@color/text_black_333"
+            android:textSize="16sp" />
+    </LinearLayout>
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="1dp"
+        android:background="@color/separated" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="50dp"
+        android:orientation="horizontal">
+
+        <TextView
+            android:id="@+id/left"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:textStyle="bold"
+            android:text="@string/privacy_ramark_07"
+            android:textColor="@color/text_black_666"
+            android:textSize="16sp" />
+
+        <View
+            android:layout_width="1dp"
+            android:layout_height="match_parent"
+            android:background="@color/separated" />
+
+        <TextView
+            android:id="@+id/right"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:textStyle="bold"
+            android:text="@string/privacy_ramark_08"
+            android:textColor="#20B6B6"
+            android:textSize="16sp" />
+    </LinearLayout>
+</LinearLayout>

+ 86 - 1
app/src/main/res/layout/fragment_group.xml

@@ -5,12 +5,96 @@
     android:layout_height="match_parent"
     android:clickable="true"
     android:background="@color/bg"
-    android:gravity="center"
     android:orientation="vertical">
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="44dp"
+        android:background="@color/red"
+        android:gravity="center"
+        android:text="找活"
+        android:visibility="gone"
+        android:textColor="@color/white"
+        android:textSize="@dimen/textSize_18" />
+    <androidx.core.widget.NestedScrollView
+        android:id="@+id/nestedscrollview"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:overScrollMode="never"
+        android:scrollbars="none">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:descendantFocusability="blocksDescendants"
+            android:orientation="vertical">
+
+            <cn.bingoogolapple.bgabanner.BGABanner
+                android:id="@+id/bga_banner"
+                style="@style/BannerDefaultStyle2"
+                android:layout_height="210dp"
+                android:layout_centerInParent="true"
+                app:banner_placeholderDrawable="@mipmap/bg_placeholder"
+                app:banner_transitionEffect="depth" />
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:text="零散务工人员综合服务平台"
+                android:textStyle="bold"
+                android:textColor="@color/text_black_333"
+                android:textSize="20sp"
+                android:layout_marginTop="20dp"
+                />
+            <LinearLayout
+                android:id="@+id/address"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="10dp"
+                android:layout_gravity="center_horizontal"
+                android:gravity="center_vertical"
+                android:orientation="horizontal">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_horizontal"
+                    android:text="市场位置"
+                    android:textStyle="bold"
+                    android:textColor="@color/text_black_333"
+                    android:textSize="18sp"
 
+                    />
+                <ImageView
+                    android:layout_width="20dp"
+                    android:layout_height="20dp"
+                    android:layout_marginLeft="5dp"
+                    android:src="@mipmap/icon_address_remark" />
+            </LinearLayout>
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="         水猫工匠于2021年5月份正式成立了天津市第一家“零散务工人员综合劳动服务市场”,从4月份开建到5月份运营,在各级政府、各级领导的大力支持下,仅用了不到一个月时间就建设成了占地一万多平米的集服务大厅、生活中心、培训大厅、招工企业办公用房、务工人员交易场地为一体的综合化劳动服务市场。"
+                android:textColor="@color/text_black_333"
+                android:textSize="16sp"
+                android:layout_marginLeft="30dp"
+                android:layout_marginTop="30dp"
+                android:layout_marginRight="30dp"
+                />
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="         通过平台公司合理化运营,将散落在周边的城市零散务工人员和招工企业和个人,全部集结到市场内,市场每天有近百家招工企业和2000多务工人员进行招工找活交易,彻底解决了企业招工难、城市零散务工人员找活难、城市管理难的突出难点痛点问题。为了彻底把市场内的务工人员留住,我们还深入到务工人员当中去,为他们排忧解难,在三个月的时间里,为他们解决欠薪问题200多人次。同时,公司还主动联系招工企业,及时在市场内发布招工信息,并牵头撮合成交招工找活3000余人次。"
+                android:textColor="@color/text_black_333"
+                android:textSize="16sp"
+                android:layout_marginLeft="30dp"
+                android:layout_marginBottom="30dp"
+                android:layout_marginRight="30dp"
+                />
+        </LinearLayout>
+    </androidx.core.widget.NestedScrollView>
    <ImageView
        android:layout_width="292dp"
        android:layout_height="243dp"
+       android:visibility="gone"
        android:src="@mipmap/icon_default_empty_photo"
        />
     <TextView
@@ -18,6 +102,7 @@
         android:layout_height="wrap_content"
         android:text="功能正在开发中,敬请期待~"
         android:textColor="#DA251C"
+        android:visibility="gone"
         android:layout_marginTop="20dp"
         android:textSize="23sp"
         />

+ 1 - 1
app/src/main/res/layout/fragment_home.xml

@@ -66,7 +66,7 @@
                             android:layout_width="wrap_content"
                             android:layout_height="wrap_content"
                             android:layout_marginTop="@dimen/vertical_margin"
-                            android:text="工会新闻"
+                            android:text="思想阵地"
                             android:textColor="@color/text_black_333"
                             android:textSize="@dimen/textSize_14" />
                     </LinearLayout>

+ 66 - 5
app/src/main/res/layout/fragment_my.xml

@@ -31,9 +31,11 @@
 
             <ImageView
                 android:id="@+id/set"
-                android:layout_width="20dp"
-                android:layout_height="20dp"
+                android:layout_width="13dp"
+                android:visibility="gone"
+                android:layout_height="11dp"
                 android:layout_alignParentRight="true"
+                android:layout_marginRight="7dp"
                 android:src="@mipmap/icon_set" />
         </RelativeLayout>
 
@@ -82,6 +84,37 @@
         android:orientation="vertical"
         android:padding="12dp">
 
+
+        <LinearLayout
+            android:id="@+id/welfare"
+            android:layout_width="match_parent"
+            android:layout_height="50dp"
+            android:gravity="center_vertical"
+            android:orientation="horizontal">
+
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:src="@mipmap/icon_welfare" />
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="12dp"
+                android:layout_weight="1"
+                android:text="会员福利"
+                android:textColor="@color/text_black_333"
+                android:textSize="@dimen/textSize_14" />
+
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:src="@mipmap/icon_next" />
+        </LinearLayout>
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="1dp"
+            android:background="@color/separated" />
         <LinearLayout
             android:id="@+id/jz"
             android:layout_width="match_parent"
@@ -238,12 +271,40 @@
         </LinearLayout>
     </LinearLayout>
 
-    <ImageView
+    <RelativeLayout
         android:id="@+id/image_member2"
         android:layout_width="275dp"
         android:layout_height="150dp"
+        android:visibility="gone"
         android:layout_centerHorizontal="true"
         android:layout_marginTop="110dp"
-        android:visibility="gone"
-        android:src="@mipmap/icon_member_bg" />
+        >
+        <ImageView
+            android:layout_width="275dp"
+            android:layout_height="150dp"
+            android:layout_centerHorizontal="true"
+            android:src="@mipmap/icon_member_bg" />
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="20dp"
+            android:textSize="16sp"
+            android:textStyle="bold"
+            android:layout_marginTop="80dp"
+            android:textColor="@color/white"
+            android:text="您已是会员,您的电话"
+            />
+        <TextView
+            android:id="@+id/phone"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="20dp"
+            android:textSize="16sp"
+            android:textStyle="bold"
+            android:layout_marginTop="100dp"
+            android:textColor="@color/white"
+            android:text="1370843786"
+            />
+    </RelativeLayout>
+
 </RelativeLayout>

+ 51 - 0
app/src/main/res/layout/item_welfare.xml

@@ -0,0 +1,51 @@
+<?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:id="@+id/item"
+    android:layout_width="match_parent"
+    android:layout_height="130dp"
+    android:layout_marginLeft="20dp"
+    android:layout_marginRight="20dp"
+    android:layout_marginTop="20dp"
+    android:gravity="center_vertical"
+    android:background="@mipmap/icon_noget_welfare"
+    android:orientation="horizontal">
+
+
+    <com.tjzhxx.union.custom_view.RoundImageView
+        android:id="@+id/image"
+        android:layout_width="65dp"
+        android:layout_height="65dp"
+        android:layout_marginLeft="36dp"
+        android:scaleType="centerCrop"
+        app:radius="10dp"
+        app:type="round" />
+
+    <LinearLayout
+        android:layout_width="170dp"
+        android:layout_height="60dp"
+        android:orientation="vertical"
+        android:paddingLeft="8dp">
+
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:ellipsize="end"
+            android:maxLines="2"
+            android:textStyle="bold"
+            android:text="福利阿达来伐啦发是理发就是登陆发时间两份都是"
+            android:textColor="@color/white"
+            android:textSize="14sp" />
+
+        <TextView
+            android:id="@+id/descrble"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textColor="@color/white"
+            android:textStyle="bold"
+            android:text="efsfs"
+            android:textSize="12sp" />
+    </LinearLayout>
+</LinearLayout>

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


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


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


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


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


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


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


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

@@ -1,5 +1,5 @@
 <resources>
-    <string name="app_name">众惠工会</string>
+    <string name="app_name">零散务工人员工会</string>
 
     <string name="global_tips_network_error">当前网络不可用,请检查网络情况</string>
     <string name="loading_error">网络不稳定,请重试</string>
@@ -21,7 +21,14 @@
     <string name="current_time">当前时间</string>
     <string name="start_date">1900-12-12 23:59</string>
     <string name="end_date">2020-12-12 23:59</string>
-
+    <string name="privacy_ramark_01">当您使用零散务工人员工会前,请仔细阅读《隐私政策》、《服务条款》,了解我们对您个人信息的处理规则和申请权限的目的。</string>
+    <string name="privacy_ramark_02">同意</string>
+    <string name="privacy_ramark_03">《隐私政策》</string>
+    <string name="privacy_ramark_04">、</string>
+    <string name="privacy_ramark_05">《服务条款》</string>
+    <string name="privacy_ramark_06"></string>
+    <string name="privacy_ramark_07">放弃并退出</string>
+    <string name="privacy_ramark_08">同意</string>
 
     <!-- 重疾 -->
     <array name="type">

+ 35 - 1
app/src/main/res/values/styles.xml

@@ -86,6 +86,40 @@
         <!-- 加载网络数据时覆盖在BGABanner最上层的占位图 -->
         <item name="banner_placeholderDrawable">@mipmap/bg_placeholder</item>
     </style>
-
+    <style name="BannerDefaultStyle2">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">210dp</item>
+        <!-- 自定义属性在styles.xml中不需要命名空间,直接用属性名就可以了 -->
+        <!-- 开启自动轮播 -->
+        <item name="banner_pointAutoPlayAble">true</item>
+        <!-- 自动轮播的时间间隔 -->
+        <item name="banner_pointAutoPlayInterval">3000</item>
+        <!-- 指示点容器背景 -->
+        <item name="banner_pointContainerBackground">@android:color/transparent</item>
+        <!-- 指示点背景 -->
+        <item name="banner_pointDrawable">@drawable/bga_banner_selector_point_hollow</item>
+        <!-- 指示点容器左右内间距 -->
+        <item name="banner_pointContainerLeftRightPadding">10dp</item>
+        <!-- 指示点上下外间距 -->
+        <item name="banner_pointTopBottomMargin">6dp</item>
+        <!-- 指示点左右外间距 -->
+        <item name="banner_pointLeftRightMargin">3dp</item>
+        <!-- 指示器的位置 -->
+        <item name="banner_indicatorGravity">bottom|center_horizontal</item>
+        <!-- 页码切换过程的时间长度 -->
+        <item name="banner_pageChangeDuration">800</item>
+        <!-- 提示文案的文字大小 -->
+        <item name="banner_tipTextSize">12sp</item>
+        <!-- 提示文案的文字颜色 -->
+        <item name="banner_tipTextColor">@android:color/white</item>
+        <!-- 加载网络数据时覆盖在BGABanner最上层的占位图 -->
+        <item name="banner_placeholderDrawable">@mipmap/bg_placeholder</item>
+    </style>
+    <style name="Dialog" parent="android:style/Theme.Dialog">
+        <item name="android:background">#00000000</item>
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowIsFloating">true</item>
+    </style>
 
 </resources>

+ 17 - 0
app/src/test/java/com/yhx/union/ExampleUnitTest.java

@@ -0,0 +1,17 @@
+package com.yhx.union;
+
+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);
+    }
+}

+ 73 - 0
easyPhotos/.gitignore

@@ -0,0 +1,73 @@
+.DS_Store
+
+# 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
+.idea/workspace.xml
+.idea/modules.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
+
+# test
+/src/test
+/src/androidTest

+ 55 - 0
easyPhotos/build.gradle

@@ -0,0 +1,55 @@
+apply plugin: 'com.android.library'
+apply plugin: 'com.github.dcendents.android-maven'
+group = 'com.github.HuanTanSheng'
+
+android {
+    compileSdkVersion 30
+
+    defaultConfig {
+        minSdkVersion 15
+        targetSdkVersion 30
+        versionCode 1
+        versionName "1.0"
+        vectorDrawables.useSupportLibrary = true
+    }
+}
+
+dependencies {
+    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
+    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
+    implementation 'androidx.appcompat:appcompat:1.2.0'
+    implementation 'com.google.android.material:material:1.1.0'
+    implementation 'androidx.recyclerview:recyclerview:1.1.0'
+    implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0'
+    implementation 'com.github.chrisbanes:PhotoView:2.3.0'
+}
+
+//// 指定编码
+//tasks.withType(JavaCompile) {
+//    options.encoding = "UTF-8"
+//}
+
+// 打包源码
+task sourcesJar(type: Jar) {
+    from android.sourceSets.main.java.srcDirs
+    classifier = 'sources'
+}
+
+//制作文档
+//task javadoc(type: Javadoc) {
+//    failOnError  false
+//    source = android.sourceSets.main.java.sourceFiles
+//    classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
+//    classpath += configurations.compile
+//}
+
+// 打包文档
+//task javadocJar(type: Jar, dependsOn: javadoc) {
+//    classifier = 'javadoc'
+//    from javadoc.destinationDir
+//}
+
+artifacts {
+    archives sourcesJar
+//    archives javadocJar
+}

+ 25 - 0
easyPhotos/proguard-rules.pro

@@ -0,0 +1,25 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in C:\androidSpace\android\sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# 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

+ 42 - 0
easyPhotos/src/main/AndroidManifest.xml

@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="com.huantansheng.easyphotos">
+
+
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission
+        android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
+        tools:ignore="ProtectedPermissions" />
+
+
+    <application
+        android:allowBackup="true"
+        android:supportsRtl="true">
+        <activity
+            android:name=".ui.EasyPhotosActivity"
+            android:configChanges="orientation|keyboardHidden|screenSize"
+            android:screenOrientation="portrait"
+            android:theme="@style/EasyPhotosTheme" />
+        <activity
+            android:name=".ui.PreviewActivity"
+            android:configChanges="orientation|keyboardHidden|screenSize"
+            android:screenOrientation="portrait"
+            android:theme="@style/EasyPhotosFullscreenTheme" />
+        <activity
+            android:name=".ui.PuzzleActivity"
+            android:configChanges="orientation|keyboardHidden|screenSize"
+            android:screenOrientation="portrait"
+            android:theme="@style/EasyPhotosTheme"
+            android:windowSoftInputMode="adjustPan" />
+
+        <activity
+            android:name=".ui.PuzzleSelectorActivity"
+            android:configChanges="orientation|keyboardHidden|screenSize"
+            android:screenOrientation="portrait"
+            android:theme="@style/EasyPhotosTheme" />
+    </application>
+
+</manifest>

+ 625 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/Builder/AlbumBuilder.java

@@ -0,0 +1,625 @@
+package com.huantansheng.easyphotos.Builder;
+
+import android.app.Activity;
+import android.net.Uri;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+
+import com.huantansheng.easyphotos.callback.SelectCallback;
+import com.huantansheng.easyphotos.constant.Type;
+import com.huantansheng.easyphotos.engine.ImageEngine;
+import com.huantansheng.easyphotos.models.ad.AdListener;
+import com.huantansheng.easyphotos.models.album.entity.Photo;
+import com.huantansheng.easyphotos.result.Result;
+import com.huantansheng.easyphotos.setting.Setting;
+import com.huantansheng.easyphotos.ui.EasyPhotosActivity;
+import com.huantansheng.easyphotos.utils.result.EasyResult;
+import com.huantansheng.easyphotos.utils.uri.UriUtils;
+
+import java.io.File;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * EasyPhotos的启动管理器
+ * Created by huan on 2017/10/18.
+ */
+public class AlbumBuilder {
+
+    /**
+     * 启动模式
+     * CAMERA-相机
+     * ALBUM-相册专辑
+     * ALBUM_CAMERA-带有相机按钮的相册专辑
+     */
+    private enum StartupType {
+        CAMERA,
+        ALBUM,
+        ALBUM_CAMERA
+    }
+
+    private static final String TAG = "com.huantansheng.easyphotos";
+    private static AlbumBuilder instance;
+    private WeakReference<Activity> mActivity;
+    private WeakReference<Fragment> mFragmentV;
+    private WeakReference<android.app.Fragment> mFragment;
+    private StartupType startupType;
+    private WeakReference<AdListener> adListener;
+
+    //私有构造函数,不允许外部调用,真正实例化通过静态方法实现
+    private AlbumBuilder(Activity activity, StartupType startupType) {
+        mActivity = new WeakReference<Activity>(activity);
+        this.startupType = startupType;
+    }
+
+    private AlbumBuilder(android.app.Fragment fragment, StartupType startupType) {
+        mFragment = new WeakReference<android.app.Fragment>(fragment);
+        this.startupType = startupType;
+    }
+
+    private AlbumBuilder(FragmentActivity activity, StartupType startupType) {
+        mActivity = new WeakReference<Activity>(activity);
+        this.startupType = startupType;
+    }
+
+    private AlbumBuilder(Fragment fragment, StartupType startupType) {
+        mFragmentV = new WeakReference<Fragment>(fragment);
+        this.startupType = startupType;
+    }
+
+    /**
+     * 内部处理相机和相册的实例
+     *
+     * @param activity Activity的实例
+     * @return AlbumBuilder EasyPhotos的实例
+     */
+
+    private static AlbumBuilder with(Activity activity, StartupType startupType) {
+        clear();
+        instance = new AlbumBuilder(activity, startupType);
+        return instance;
+    }
+
+
+    private static AlbumBuilder with(android.app.Fragment fragment, StartupType startupType) {
+        clear();
+        instance = new AlbumBuilder(fragment, startupType);
+        return instance;
+    }
+
+    private static AlbumBuilder with(FragmentActivity activity, StartupType startupType) {
+        clear();
+        instance = new AlbumBuilder(activity, startupType);
+        return instance;
+    }
+
+    private static AlbumBuilder with(Fragment fragmentV, StartupType startupType) {
+        clear();
+        instance = new AlbumBuilder(fragmentV, startupType);
+        return instance;
+    }
+
+
+    /**
+     * 创建相机
+     *
+     * @param activity 上下文
+     * @return AlbumBuilder
+     */
+
+    public static AlbumBuilder createCamera(Activity activity) {
+        return AlbumBuilder.with(activity, StartupType.CAMERA);
+    }
+
+
+    public static AlbumBuilder createCamera(android.app.Fragment fragment) {
+        return AlbumBuilder.with(fragment, StartupType.CAMERA);
+    }
+
+    public static AlbumBuilder createCamera(FragmentActivity activity) {
+        return AlbumBuilder.with(activity, StartupType.CAMERA);
+    }
+
+    public static AlbumBuilder createCamera(Fragment fragmentV) {
+        return AlbumBuilder.with(fragmentV, StartupType.CAMERA);
+    }
+
+    /**
+     * 创建相册
+     *
+     * @param activity     上下文
+     * @param isShowCamera 是否显示相机按钮
+     * @param imageEngine  图片加载引擎的具体实现
+     * @return
+     */
+    public static AlbumBuilder createAlbum(Activity activity, boolean isShowCamera,
+                                           @NonNull ImageEngine imageEngine) {
+        if (Setting.imageEngine != imageEngine) {
+            Setting.imageEngine = imageEngine;
+        }
+        if (isShowCamera) {
+            return AlbumBuilder.with(activity, StartupType.ALBUM_CAMERA);
+        } else {
+            return AlbumBuilder.with(activity, StartupType.ALBUM);
+        }
+    }
+
+    public static AlbumBuilder createAlbum(android.app.Fragment fragment, boolean isShowCamera,
+                                           @NonNull ImageEngine imageEngine) {
+        if (Setting.imageEngine != imageEngine) {
+            Setting.imageEngine = imageEngine;
+        }
+        if (isShowCamera) {
+            return AlbumBuilder.with(fragment, StartupType.ALBUM_CAMERA);
+        } else {
+            return AlbumBuilder.with(fragment, StartupType.ALBUM);
+        }
+    }
+
+    public static AlbumBuilder createAlbum(FragmentActivity activity, boolean isShowCamera,
+                                           @NonNull ImageEngine imageEngine) {
+        if (Setting.imageEngine != imageEngine) {
+            Setting.imageEngine = imageEngine;
+        }
+        if (isShowCamera) {
+            return AlbumBuilder.with(activity, StartupType.ALBUM_CAMERA);
+        } else {
+            return AlbumBuilder.with(activity, StartupType.ALBUM);
+        }
+    }
+
+    public static AlbumBuilder createAlbum(Fragment fragmentV, boolean isShowCamera,
+                                           @NonNull ImageEngine imageEngine) {
+        if (Setting.imageEngine != imageEngine) {
+            Setting.imageEngine = imageEngine;
+        }
+        if (isShowCamera) {
+            return AlbumBuilder.with(fragmentV, StartupType.ALBUM_CAMERA);
+        } else {
+            return AlbumBuilder.with(fragmentV, StartupType.ALBUM);
+        }
+    }
+
+    /**
+     * 设置fileProvider字段
+     *
+     * @param fileProviderAuthority fileProvider字段
+     * @return AlbumBuilder
+     */
+    public AlbumBuilder setFileProviderAuthority(String fileProviderAuthority) {
+        Setting.fileProviderAuthority = fileProviderAuthority;
+        return AlbumBuilder.this;
+    }
+
+    /**
+     * 设置选择数
+     *
+     * @param selectorMaxCount 最大选择数
+     * @return AlbumBuilder
+     */
+    public AlbumBuilder setCount(int selectorMaxCount) {
+        if (Setting.complexSelector) return AlbumBuilder.this;
+        Setting.count = selectorMaxCount;
+        return AlbumBuilder.this;
+    }
+
+    /**
+     * 设置是否使用宽高数据
+     *
+     * @param useWidth 是否使用宽高数据,需要使用写true,不用写false。
+     *                 true:会保证宽高数据的正确性,返回速度慢,耗时。
+     *                 false:宽高数据为0。
+     * @return AlbumBuilder
+     */
+    public AlbumBuilder setUseWidth(boolean useWidth) {
+        Setting.useWidth = useWidth;
+        return AlbumBuilder.this;
+    }
+
+    /**
+     * 支持复杂选择情况
+     *
+     * @param singleType   是否只能选择一种文件类型,如用户选择视频后不可以选择图片,若false则可以同时选择
+     * @param videoCount   可选择视频类型文件的最大数
+     * @param pictureCount 可选择图片类型文件的最大数
+     * @return
+     */
+    public AlbumBuilder complexSelector(boolean singleType, int videoCount, int pictureCount) {
+        Setting.complexSelector = true;
+        Setting.complexSingleType = singleType;
+        Setting.complexVideoCount = videoCount;
+        Setting.complexPictureCount = pictureCount;
+        Setting.count = videoCount + pictureCount;
+        Setting.showVideo = true;
+        return AlbumBuilder.this;
+    }
+
+    /**
+     * 设置相机按钮位置
+     *
+     * @param cLocation 使用Material Design风格相机按钮 默认 BOTTOM_RIGHT
+     * @return AlbumBuilder
+     */
+    public AlbumBuilder setCameraLocation(@Setting.Location int cLocation) {
+        Setting.cameraLocation = cLocation;
+        return AlbumBuilder.this;
+    }
+
+    /**
+     * 设置显示照片的最小文件大小
+     *
+     * @param minFileSize 最小文件大小,单位Bytes
+     * @return AlbumBuilder
+     */
+    public AlbumBuilder setMinFileSize(long minFileSize) {
+        Setting.minSize = minFileSize;
+        return AlbumBuilder.this;
+    }
+
+    /**
+     * 设置显示照片的最小宽度
+     *
+     * @param minWidth 照片的最小宽度,单位Px
+     * @return AlbumBuilder
+     */
+    public AlbumBuilder setMinWidth(int minWidth) {
+        Setting.minWidth = minWidth;
+        return AlbumBuilder.this;
+    }
+
+    /**
+     * 设置显示照片的最小高度
+     *
+     * @param minHeight 显示照片的最小高度,单位Px
+     * @return AlbumBuilder
+     */
+    public AlbumBuilder setMinHeight(int minHeight) {
+        Setting.minHeight = minHeight;
+        return AlbumBuilder.this;
+    }
+
+    /**
+     * 设置默认选择图片集合
+     *
+     * @param selectedPhotos 默认选择图片集合
+     * @return AlbumBuilder
+     */
+    public AlbumBuilder setSelectedPhotos(ArrayList<Photo> selectedPhotos) {
+        Setting.selectedPhotos.clear();
+        if (selectedPhotos.isEmpty()) {
+            return AlbumBuilder.this;
+        }
+        Setting.selectedPhotos.addAll(selectedPhotos);
+        Setting.selectedOriginal = selectedPhotos.get(0).selectedOriginal;
+        return AlbumBuilder.this;
+    }
+
+    /**
+     * 设置默认选择图片地址集合
+     *
+     * @param selectedPhotoPaths 默认选择图片地址集合
+     * @return AlbumBuilder
+     * @Deprecated android 10 不推荐使用直接使用Path方式,推荐使用Photo类
+     */
+    @Deprecated
+    public AlbumBuilder setSelectedPhotoPaths(ArrayList<String> selectedPhotoPaths) {
+        Setting.selectedPhotos.clear();
+        ArrayList<Photo> selectedPhotos = new ArrayList<>();
+        for (String path : selectedPhotoPaths) {
+            File file = new File(path);
+            Uri uri = null;
+            if (null != mActivity && null != mActivity.get()) {
+                uri = UriUtils.getUri(mActivity.get(), file);
+            }
+            if (null != mFragment && null != mFragment.get()) {
+                uri = UriUtils.getUri(mFragment.get().getActivity(), file);
+            }
+            if (null != mFragmentV && null != mFragmentV.get()) {
+                uri = UriUtils.getUri(mFragmentV.get().getActivity(), file);
+            }
+            if (uri == null) {
+                uri = Uri.fromFile(file);
+            }
+            Photo photo = new Photo(null, uri, path, 0, 0, 0, 0, 0, 0, null);
+            selectedPhotos.add(photo);
+        }
+        Setting.selectedPhotos.addAll(selectedPhotos);
+        return AlbumBuilder.this;
+    }
+
+
+    /**
+     * 原图按钮设置,不调用该方法不显示原图按钮
+     *
+     * @param isChecked    原图选项默认状态是否为选中状态
+     * @param usable       原图按钮是否可使用
+     * @param unusableHint 原图按钮不可使用时给用户的文字提示
+     * @return AlbumBuilder
+     */
+    public AlbumBuilder setOriginalMenu(boolean isChecked, boolean usable, String unusableHint) {
+        Setting.showOriginalMenu = true;
+        Setting.selectedOriginal = isChecked;
+        Setting.originalMenuUsable = usable;
+        Setting.originalMenuUnusableHint = unusableHint;
+        return AlbumBuilder.this;
+    }
+
+
+    /**
+     * 是否显示拼图按钮
+     *
+     * @param shouldShow 是否显示
+     * @return AlbumBuilder
+     */
+    public AlbumBuilder setPuzzleMenu(boolean shouldShow) {
+        Setting.showPuzzleMenu = shouldShow;
+        return AlbumBuilder.this;
+    }
+
+    /**
+     * 只显示Video
+     *
+     * @return @return AlbumBuilder
+     */
+
+    public AlbumBuilder onlyVideo() {
+        return filter(Type.VIDEO);
+    }
+
+    /**
+     * 过滤
+     *
+     * @param types {@link Type}
+     * @return @return AlbumBuilder
+     */
+    public AlbumBuilder filter(String... types) {
+        Setting.filterTypes = Arrays.asList(types);
+        return AlbumBuilder.this;
+    }
+
+    /**
+     * 是否显示gif图
+     *
+     * @param shouldShow 是否显示
+     * @return @return AlbumBuilder
+     */
+    public AlbumBuilder setGif(boolean shouldShow) {
+        Setting.showGif = shouldShow;
+        return AlbumBuilder.this;
+    }
+
+    /**
+     * 是否显示video
+     *
+     * @param shouldShow 是否显示
+     * @return @return AlbumBuilder
+     */
+    public AlbumBuilder setVideo(boolean shouldShow) {
+        Setting.showVideo = shouldShow;
+        return AlbumBuilder.this;
+    }
+
+    /**
+     * 显示最少多少秒的视频
+     *
+     * @param second 秒
+     * @return @return AlbumBuilder
+     */
+    public AlbumBuilder setVideoMinSecond(int second) {
+        Setting.videoMinSecond = second * 1000;
+        return AlbumBuilder.this;
+    }
+
+    /**
+     * 显示最多多少秒的视频
+     *
+     * @param second 秒
+     * @return @return AlbumBuilder
+     */
+    public AlbumBuilder setVideoMaxSecond(int second) {
+        Setting.videoMaxSecond = second * 1000;
+        return AlbumBuilder.this;
+    }
+
+    /**
+     * 相册选择页是否显示清空按钮
+     *
+     * @param shouldShow
+     * @return
+     */
+    public AlbumBuilder setCleanMenu(boolean shouldShow) {
+        Setting.showCleanMenu = shouldShow;
+        return AlbumBuilder.this;
+    }
+
+    private void setSettingParams() {
+        switch (startupType) {
+            case CAMERA:
+                Setting.onlyStartCamera = true;
+                Setting.isShowCamera = true;
+                break;
+            case ALBUM:
+                Setting.isShowCamera = false;
+                break;
+            case ALBUM_CAMERA:
+                Setting.isShowCamera = true;
+                break;
+        }
+        if (!Setting.filterTypes.isEmpty()) {
+            if (Setting.isFilter(Type.GIF)) {
+                Setting.showGif = true;
+            }
+            if (Setting.isFilter(Type.VIDEO)) {
+                Setting.showVideo = true;
+            }
+        }
+        if (Setting.isOnlyVideo()) {
+            //只选择视频 暂不支持拍照/拼图等
+            Setting.isShowCamera = false;
+            Setting.showPuzzleMenu = false;
+            Setting.showGif = false;
+            Setting.showVideo = true;
+        }
+    }
+
+    /**
+     * 启动,onActivityResult方式
+     *
+     * @param requestCode startActivityForResult的请求码
+     */
+
+    public void start(int requestCode) {
+        setSettingParams();
+        launchEasyPhotosActivity(requestCode);
+    }
+
+    /**
+     * 启动,链式调用
+     */
+    public void start(SelectCallback callback) {
+        setSettingParams();
+        if (null != mActivity && null != mActivity.get() && mActivity.get() instanceof FragmentActivity) {
+            EasyResult.get((FragmentActivity) mActivity.get()).startEasyPhoto(callback);
+            return;
+        }
+        if (null != mFragmentV && null != mFragmentV.get()) {
+            EasyResult.get(mFragmentV.get()).startEasyPhoto(callback);
+            return;
+        }
+        throw new RuntimeException("mActivity or mFragmentV maybe null, you can not use this " +
+                "method... ");
+    }
+
+    /**
+     * 正式启动
+     *
+     * @param requestCode startActivityForResult的请求码
+     */
+    private void launchEasyPhotosActivity(int requestCode) {
+        if (null != mActivity && null != mActivity.get()) {
+            EasyPhotosActivity.start(mActivity.get(), requestCode);
+            return;
+        }
+        if (null != mFragment && null != mFragment.get()) {
+            EasyPhotosActivity.start(mFragment.get(), requestCode);
+            return;
+        }
+        if (null != mFragmentV && null != mFragmentV.get()) {
+            EasyPhotosActivity.start(mFragmentV.get(), requestCode);
+        }
+    }
+
+    /**
+     * 清除所有数据
+     */
+    private static void clear() {
+        Result.clear();
+        Setting.clear();
+        instance = null;
+    }
+
+//*********************AD************************************
+
+    /**
+     * 设置广告(不设置该选项则表示不使用广告)
+     *
+     * @param photosAdView         使用图片列表的广告View
+     * @param photosAdIsLoaded     图片列表广告是否加载完毕
+     * @param albumItemsAdView     使用专辑项目列表的广告View
+     * @param albumItemsAdIsLoaded 专辑项目列表广告是否加载完毕
+     * @return AlbumBuilder
+     */
+    public AlbumBuilder setAdView(View photosAdView, boolean photosAdIsLoaded,
+                                  View albumItemsAdView, boolean albumItemsAdIsLoaded) {
+        Setting.photosAdView = new WeakReference<View>(photosAdView);
+        Setting.albumItemsAdView = new WeakReference<View>(albumItemsAdView);
+        Setting.photoAdIsOk = photosAdIsLoaded;
+        Setting.albumItemsAdIsOk = albumItemsAdIsLoaded;
+        return AlbumBuilder.this;
+    }
+
+    /**
+     * 设置广告监听
+     * 内部使用,无需关心
+     *
+     * @param adListener 广告监听
+     */
+    public static void setAdListener(AdListener adListener) {
+        if (null == instance) return;
+        if (instance.startupType == StartupType.CAMERA) return;
+        instance.adListener = new WeakReference<AdListener>(adListener);
+    }
+
+    /**
+     * 刷新图片列表广告数据
+     */
+    public static void notifyPhotosAdLoaded() {
+        if (Setting.photoAdIsOk) {
+            return;
+        }
+        if (null == instance) {
+            return;
+        }
+        if (instance.startupType == StartupType.CAMERA) {
+            return;
+        }
+        if (null == instance.adListener) {
+            new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        Thread.sleep(2000);
+                    } catch (InterruptedException e) {
+                        e.printStackTrace();
+                    }
+                    if (null != instance && null != instance.adListener) {
+                        Setting.photoAdIsOk = true;
+                        instance.adListener.get().onPhotosAdLoaded();
+                    }
+                }
+            }).start();
+            return;
+        }
+        Setting.photoAdIsOk = true;
+        instance.adListener.get().onPhotosAdLoaded();
+    }
+
+    /**
+     * 刷新专辑项目列表广告
+     */
+    public static void notifyAlbumItemsAdLoaded() {
+        if (Setting.albumItemsAdIsOk) {
+            return;
+        }
+        if (null == instance) {
+            return;
+        }
+        if (instance.startupType == StartupType.CAMERA) {
+            return;
+        }
+        if (null == instance.adListener) {
+            new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        Thread.sleep(2000);
+                    } catch (InterruptedException e) {
+                        e.printStackTrace();
+                    }
+                    if (null != instance && null != instance.adListener) {
+                        Setting.albumItemsAdIsOk = true;
+                        instance.adListener.get().onAlbumItemsAdLoaded();
+                    }
+                }
+            }).start();
+            return;
+        }
+        Setting.albumItemsAdIsOk = true;
+        instance.adListener.get().onAlbumItemsAdLoaded();
+    }
+
+}

+ 345 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/EasyPhotos.java

@@ -0,0 +1,345 @@
+package com.huantansheng.easyphotos;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.FragmentActivity;
+
+import com.huantansheng.easyphotos.Builder.AlbumBuilder;
+import com.huantansheng.easyphotos.callback.PuzzleCallback;
+import com.huantansheng.easyphotos.engine.ImageEngine;
+import com.huantansheng.easyphotos.models.ad.AdListener;
+import com.huantansheng.easyphotos.models.album.AlbumModel;
+import com.huantansheng.easyphotos.models.album.entity.Photo;
+import com.huantansheng.easyphotos.models.sticker.StickerModel;
+import com.huantansheng.easyphotos.models.sticker.entity.TextStickerData;
+import com.huantansheng.easyphotos.ui.PuzzleActivity;
+import com.huantansheng.easyphotos.utils.bitmap.BitmapUtils;
+import com.huantansheng.easyphotos.utils.bitmap.SaveBitmapCallBack;
+import com.huantansheng.easyphotos.utils.media.MediaScannerConnectionUtils;
+import com.huantansheng.easyphotos.utils.result.EasyResult;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * EasyPhotos的启动管理器
+ * Created by huan on 2017/10/18.
+ */
+public class EasyPhotos {
+
+    //easyPhotos的返回数据Key
+    public static final String RESULT_PHOTOS = "keyOfEasyPhotosResult";
+    public static final String RESULT_SELECTED_ORIGINAL = "keyOfEasyPhotosResultSelectedOriginal";
+
+    /**
+     * 预加载
+     * 调不调用该方法都可以,不调用不影响EasyPhotos正常使用
+     * 第一次扫描媒体库可能会慢,调用预加载会使真正打开相册的速度加快
+     * 若调用该方法,建议自行判断代码书写位置,建议在用户打开相册的3秒前调用,比如app主页面或调用相册的上一页
+     * 该方法如果没有授权读取权限的话,是无效的,所以外部加不加权限控制都可以,加的话保证执行,不加也不影响程序正常使用
+     *
+     * @param cxt 上下文
+     */
+    public static void preLoad(Context cxt) {
+        AlbumModel.getInstance().query(cxt, null);
+    }
+
+    /**
+     * 预加载
+     * 调不调用该方法都可以,不调用不影响EasyPhotos正常使用
+     * 第一次扫描媒体库可能会慢,调用预加载会使真正打开相册的速度加快
+     * 若调用该方法,建议自行判断代码书写位置,建议在用户打开相册的3秒前调用,比如app主页面或调用相册的上一页
+     * 该方法如果没有授权读取权限的话,是无效的,所以外部加不加权限控制都可以,加的话保证执行,不加也不影响程序正常使用
+     *
+     * @param cxt      上下文
+     * @param callBack 预加载完成的回调,若进行UI操作,需自行切回主线程。
+     */
+    public static void preLoad(Context cxt, AlbumModel.CallBack callBack) {
+        AlbumModel.getInstance().query(cxt, callBack);
+    }
+
+
+    /**
+     * 创建相机
+     *
+     * @param activity 上下文
+     * @param useWidth 是否使用宽高数据
+     * @return AlbumBuilder
+     */
+    public static AlbumBuilder createCamera(Activity activity,
+                                            boolean useWidth) {
+        return AlbumBuilder.createCamera(activity).setUseWidth(useWidth);
+    }
+
+    public static AlbumBuilder createCamera(Fragment fragment,
+                                            boolean useWidth) {
+        return AlbumBuilder.createCamera(fragment).setUseWidth(useWidth);
+    }
+
+    public static AlbumBuilder createCamera(FragmentActivity activity,
+                                            boolean useWidth) {
+        return AlbumBuilder.createCamera(activity).setUseWidth(useWidth);
+    }
+
+    public static AlbumBuilder createCamera(androidx.fragment.app.Fragment fragmentV,
+                                            boolean useWidth) {
+        return AlbumBuilder.createCamera(fragmentV).setUseWidth(useWidth);
+    }
+
+    /**
+     * 创建相册
+     *
+     * @param activity     上下文
+     * @param isShowCamera 是否显示相机按钮
+     * @param useWidth     是否使用宽高数据。
+     *                     true:会保证宽高数据的正确性,返回速度慢,耗时,尤其在华为mate30上,可能点击完成后会加载三四秒才能返回。
+     *                     false:有宽高数据但不保证正确性,点击完成后秒回,但可能有因旋转问题导致的宽高相反的情况,以及极少数的宽高为0情况。
+     * @param imageEngine  图片加载引擎的具体实现
+     * @return AlbumBuilder 建造者模式配置其他选项
+     */
+    public static AlbumBuilder createAlbum(Activity activity, boolean isShowCamera,
+                                           boolean useWidth, @NonNull ImageEngine imageEngine) {
+        return AlbumBuilder.createAlbum(activity, isShowCamera, imageEngine).setUseWidth(useWidth);
+    }
+
+    public static AlbumBuilder createAlbum(Fragment fragment, boolean isShowCamera,
+                                           boolean useWidth, @NonNull ImageEngine imageEngine) {
+        return AlbumBuilder.createAlbum(fragment, isShowCamera, imageEngine).setUseWidth(useWidth);
+    }
+
+    public static AlbumBuilder createAlbum(FragmentActivity activity, boolean isShowCamera,
+                                           boolean useWidth, @NonNull ImageEngine imageEngine) {
+        return AlbumBuilder.createAlbum(activity, isShowCamera, imageEngine).setUseWidth(useWidth);
+    }
+
+    public static AlbumBuilder createAlbum(androidx.fragment.app.Fragment fragmentV,
+                                           boolean isShowCamera, boolean useWidth,
+                                           @NonNull ImageEngine imageEngine) {
+        return AlbumBuilder.createAlbum(fragmentV, isShowCamera, imageEngine).setUseWidth(useWidth);
+    }
+
+
+//*********************AD************************************
+
+
+    /**
+     * 设置广告监听
+     * 内部使用,无需关心
+     *
+     * @param adListener 广告监听
+     */
+    public static void setAdListener(AdListener adListener) {
+        AlbumBuilder.setAdListener(adListener);
+    }
+
+    /**
+     * 刷新图片列表广告数据
+     */
+    public static void notifyPhotosAdLoaded() {
+        AlbumBuilder.notifyPhotosAdLoaded();
+    }
+
+    /**
+     * 刷新专辑项目列表广告
+     */
+    public static void notifyAlbumItemsAdLoaded() {
+        AlbumBuilder.notifyAlbumItemsAdLoaded();
+    }
+
+
+//*************************bitmap功能***********************************/
+
+    /**
+     * 回收bitmap
+     *
+     * @param bitmap 要回收的bitmap
+     */
+    public static void recycle(Bitmap bitmap) {
+        BitmapUtils.recycle(bitmap);
+    }
+
+    /**
+     * 回收bitmap数组中的所有图片
+     *
+     * @param bitmaps 要回收的bitmap数组
+     */
+    public static void recycle(Bitmap... bitmaps) {
+        BitmapUtils.recycle(bitmaps);
+    }
+
+    /**
+     * 回收bitmap集合中的所有图片
+     *
+     * @param bitmaps 要回收的bitmap集合
+     */
+    public static void recycle(List<Bitmap> bitmaps) {
+        BitmapUtils.recycle(bitmaps);
+    }
+
+    /**
+     * 给图片添加水印,水印会根据图片宽高自动缩放处理
+     *
+     * @param watermark     水印
+     * @param image         添加水印的图片
+     * @param srcImageWidth 水印对应的原图片宽度,即ui制作水印时参考的要添加水印的图片的宽度
+     * @param offsetX       添加水印的X轴偏移量
+     * @param offsetY       添加水印的Y轴偏移量
+     * @param addInLeft     true 在左下角添加水印,false 在右下角添加水印
+     * @param orientation   Bitmap的旋转角度。当useWidth为true时,Photo实体类中会有orientation,若bitmap
+     *                      不是用户手机内图片,填0即可。
+     * @return 添加水印后的bitmap
+     */
+    public static Bitmap addWatermark(Bitmap watermark, Bitmap image, int srcImageWidth,
+                                      int offsetX, int offsetY, boolean addInLeft,
+                                      int orientation) {
+        return BitmapUtils.addWatermark(watermark, image, srcImageWidth, offsetX, offsetY,
+                addInLeft, orientation);
+    }
+
+    /**
+     * 给图片添加带文字和图片的水印,水印会根据图片宽高自动缩放处理
+     *
+     * @param watermark     水印图片
+     * @param image         要加水印的图片
+     * @param srcImageWidth 水印对应的原图片宽度,即ui制作水印时参考的要添加水印的图片的宽度
+     * @param text          要添加的文字
+     * @param offsetX       添加水印的X轴偏移量
+     * @param offsetY       添加水印的Y轴偏移量
+     * @param addInLeft     true 在左下角添加水印,false 在右下角添加水印
+     * @param orientation   Bitmap的旋转角度。当useWidth为true时,Photo实体类中会有orientation,若bitmap
+     *                      不是用户手机内图片,填0即可。
+     * @return 添加水印后的bitmap
+     */
+    public static Bitmap addWatermarkWithText(Bitmap watermark, Bitmap image, int srcImageWidth,
+                                              @NonNull String text, int offsetX, int offsetY,
+                                              boolean addInLeft, int orientation) {
+        return BitmapUtils.addWatermarkWithText(watermark, image, srcImageWidth, text, offsetX,
+                offsetY,
+                addInLeft, orientation);
+    }
+
+    /**
+     * 保存Bitmap到指定文件夹
+     *
+     * @param act         上下文
+     * @param dirPath     文件夹全路径
+     * @param bitmap      bitmap
+     * @param namePrefix  保存文件的前缀名,文件最终名称格式为:前缀名+自动生成的唯一数字字符+.png
+     * @param notifyMedia 是否更新到媒体库
+     * @param callBack    保存图片后的回调,回调已经处于UI线程
+     */
+    public static void saveBitmapToDir(Activity act, String dirPath, String namePrefix,
+                                       Bitmap bitmap, boolean notifyMedia,
+                                       SaveBitmapCallBack callBack) {
+        BitmapUtils.saveBitmapToDir(act, dirPath, namePrefix, bitmap, notifyMedia, callBack);
+    }
+
+
+    /**
+     * 把View画成Bitmap
+     *
+     * @param view 要处理的View
+     * @return Bitmap
+     */
+    public static Bitmap createBitmapFromView(View view) {
+        return BitmapUtils.createBitmapFromView(view);
+    }
+
+    /**
+     * 启动拼图(最多对9张图片进行拼图)
+     *
+     * @param act                  上下文
+     * @param photos               图片集合(最多对9张图片进行拼图)
+     * @param puzzleSaveDirPath    拼图完成保存的文件夹全路径
+     * @param puzzleSaveNamePrefix 拼图完成保存的文件名前缀,最终格式:前缀+默认生成唯一数字标识+.png
+     * @param requestCode          请求code
+     * @param replaceCustom        单击替换拼图中的某张图片时,是否以startForResult的方式启动你的自定义界面,该界面与传进来的act
+     *                             为同一界面。false则在EasyPhotos内部完成,正常需求直接写false即可。
+     *                             true的情况适用于:用于拼图的图片集合中包含网络图片,是在你的act界面中获取并下载的(也可以直接用网络地址,不用下载后的本地地址,也就是可以不下载下来),而非单纯本地相册。举例:你的act中有两个按钮,一个指向本地相册,一个指向网络相册,用户在该界面任意选择,选择好图片后跳转到拼图界面,用户在拼图界面点击替换按钮,将会启动一个新的act界面,这时,act只让用户在网络相册和本地相册选择一张图片,选择好执行
+     *                             Intent intent = new Intent();
+     *                             intent.putParcelableArrayListExtra(AlbumBuilder.RESULT_PHOTOS
+     *                             , photos);
+     *                             act.setResult(RESULT_OK,intent); 并关闭act,回到拼图界面,完成替换。
+     * @param imageEngine          图片加载引擎的具体实现
+     */
+
+    public static void startPuzzleWithPhotos(Activity act, ArrayList<Photo> photos,
+                                             String puzzleSaveDirPath,
+                                             String puzzleSaveNamePrefix, int requestCode,
+                                             boolean replaceCustom,
+                                             @NonNull ImageEngine imageEngine) {
+        act.setResult(Activity.RESULT_OK);
+        PuzzleActivity.startWithPhotos(act, photos, puzzleSaveDirPath, puzzleSaveNamePrefix,
+                requestCode, replaceCustom, imageEngine);
+    }
+
+    public static void startPuzzleWithPhotos(FragmentActivity act, ArrayList<Photo> photos,
+                                             String puzzleSaveDirPath,
+                                             String puzzleSaveNamePrefix, boolean replaceCustom,
+                                             @NonNull ImageEngine imageEngine,
+                                             PuzzleCallback callback) {
+        act.setResult(Activity.RESULT_OK);
+        EasyResult.get(act).startPuzzleWithPhotos(photos, puzzleSaveDirPath, puzzleSaveNamePrefix
+                , replaceCustom, imageEngine, callback);
+    }
+
+
+    //**************更新媒体库***********************
+
+    /**
+     * 更新媒体文件到媒体库
+     *
+     * @param cxt       上下文
+     * @param filePaths 更新的文件地址
+     */
+    public static void notifyMedia(Context cxt, String... filePaths) {
+        MediaScannerConnectionUtils.refresh(cxt, filePaths);
+    }
+
+    /**
+     * 更新媒体文件到媒体库
+     *
+     * @param cxt   上下文
+     * @param files 更新的文件
+     */
+    public static void notifyMedia(Context cxt, File... files) {
+        MediaScannerConnectionUtils.refresh(cxt, files);
+    }
+
+    /**
+     * 更新媒体文件到媒体库
+     *
+     * @param cxt      上下文
+     * @param fileList 更新的文件地址集合
+     */
+    public static void notifyMedia(Context cxt, List<String> fileList) {
+        MediaScannerConnectionUtils.refresh(cxt, fileList);
+    }
+
+
+    //*********************************贴纸***************************
+
+
+    /**
+     * 添加文字贴纸的文字数据
+     *
+     * @param textStickerData 文字贴纸的文字数据
+     */
+    public static void addTextStickerData(TextStickerData... textStickerData) {
+        StickerModel.textDataList.addAll(Arrays.asList(textStickerData));
+    }
+
+    /**
+     * 清空文字贴纸的数据
+     */
+    public static void clearTextStickerDataList() {
+        StickerModel.textDataList.clear();
+    }
+}

+ 22 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/callback/PuzzleCallback.java

@@ -0,0 +1,22 @@
+package com.huantansheng.easyphotos.callback;
+
+import com.huantansheng.easyphotos.models.album.entity.Photo;
+
+/**
+ * PuzzleCallback
+ *
+ * @author joker
+ * @date 2019/4/9.
+ */
+public abstract class PuzzleCallback {
+    /**
+     * 选择结果回调
+     *
+     * @param photo 返回对象:如果你需要了解图片的宽、高、大小、用户是否选中原图选项等信息,可以用这个
+     */
+    public abstract void onResult(Photo photo);
+    /**
+     * 什么都没选,取消选择回调
+     */
+    public abstract void onCancel();
+}

+ 26 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/callback/SelectCallback.java

@@ -0,0 +1,26 @@
+package com.huantansheng.easyphotos.callback;
+
+import com.huantansheng.easyphotos.models.album.entity.Photo;
+
+import java.util.ArrayList;
+
+/**
+ * SelectCallback
+ *
+ * @author joker
+ * @date 2019/4/9.
+ */
+public abstract class SelectCallback {
+    /**
+     * 选择结果回调
+     *
+     * @param photos     返回对象集合:如果你需要了解图片的宽、高、大小、用户是否选中原图选项等信息,可以用这个
+     * @param isOriginal 返回图片地址集合时如果你需要知道用户选择图片时是否选择了原图选项,用如下方法获取
+     */
+    public abstract void onResult(ArrayList<Photo> photos,  boolean isOriginal);
+
+    /**
+     * 什么都没选,取消选择回调
+     */
+    public abstract void onCancel();
+}

+ 21 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/constant/Code.java

@@ -0,0 +1,21 @@
+package com.huantansheng.easyphotos.constant;
+
+/**
+ * Code常量
+ * Created by huan on 2017/10/19.
+ */
+
+public class Code {
+    //相机请求码
+    public static final int REQUEST_CAMERA = 11;
+    //权限请求码
+    public static final int REQUEST_PERMISSION = 12;
+    //预览activity请求码
+    public static final int REQUEST_PREVIEW_ACTIVITY = 13;
+    //请求应用详情
+    public static final int REQUEST_SETTING_APP_DETAILS = 14;
+    //拼图activity请求吗
+    public static final int REQUEST_PUZZLE = 15;
+    //拼图选择activity请求吗
+    public static final int REQUEST_PUZZLE_SELECTOR = 16;
+}

+ 23 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/constant/Key.java

@@ -0,0 +1,23 @@
+package com.huantansheng.easyphotos.constant;
+
+/**
+ * key的常量
+ * Created by huan on 2017/10/19.
+ */
+
+public class Key {
+    //预览图片的当前角标
+    public static final String PREVIEW_PHOTO_INDEX = "keyOfPreviewPhotoIndex";
+    //当前预览界面的专辑index
+    public static final String PREVIEW_ALBUM_ITEM_INDEX = "keyOfPreviewAlbumItemIndex";
+    //预览界面是否点击完成
+    public static final String PREVIEW_CLICK_DONE = "keyOfPreviewClickDone";
+    //拼图界面图片类型,true-Photo,false-String
+    public static final String PUZZLE_FILE_IS_PHOTO = "keyOfPuzzleFilesTypeIsPhoto";
+    //拼图界面图片结合
+    public static final String PUZZLE_FILES = "keyOfPuzzleFiles";
+    //拼图界面图片保存文件夹地址
+    public static final String PUZZLE_SAVE_DIR = "keyOfPuzzleSaveDir";
+    //拼图界面图片保存文件名前缀
+    public static final String PUZZLE_SAVE_NAME_PREFIX = "keyOfPuzzleSaveNamePrefix";
+}

+ 10 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/constant/Type.java

@@ -0,0 +1,10 @@
+package com.huantansheng.easyphotos.constant;
+
+/**
+ * Created by huan on 2018/1/9.
+ */
+
+public class Type {
+    public static final String GIF = "gif";
+    public static final String VIDEO = "video";
+}

+ 63 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/engine/ImageEngine.java

@@ -0,0 +1,63 @@
+package com.huantansheng.easyphotos.engine;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.widget.ImageView;
+
+import androidx.annotation.NonNull;
+
+/**
+ * 自定义图片加载方式
+ * Created by huan on 2018/1/15.
+ */
+public interface ImageEngine {
+    /**
+     * 加载图片到ImageView
+     *
+     * @param context   上下文
+     * @param uri 图片Uri
+     * @param imageView 加载到的ImageView
+     */
+    //安卓10推荐uri,并且path的方式不再可用
+    void loadPhoto(@NonNull Context context, @NonNull Uri uri,@NonNull ImageView imageView);
+
+    /**
+     * 加载gif动图图片到ImageView,gif动图不动
+     *
+     * @param context   上下文
+     * @param gifUri  gif动图路径Uri
+     * @param imageView 加载到的ImageView
+     *                  <p>
+     *                  备注:不支持动图显示的情况下可以不写
+     */
+    //安卓10推荐uri,并且path的方式不再可用
+    void loadGifAsBitmap(@NonNull Context context,@NonNull Uri gifUri,@NonNull ImageView imageView);
+
+    /**
+     * 加载gif动图到ImageView,gif动图动
+     *
+     * @param context   上下文
+     * @param gifUri   gif动图路径Uri
+     * @param imageView 加载动图的ImageView
+     *                  <p>
+     *                  备注:不支持动图显示的情况下可以不写
+     */
+    //安卓10推荐uri,并且path的方式不再可用
+    void loadGif(@NonNull Context context,@NonNull Uri gifUri,@NonNull ImageView imageView);
+
+    /**
+     * 获取图片加载框架中的缓存Bitmap,不用拼图功能可以直接返回null
+     *
+     * @param context 上下文
+     * @param uri    图片路径
+     * @param width   图片宽度
+     * @param height  图片高度
+     * @return Bitmap
+     * @throws Exception 异常直接抛出,EasyPhotos内部处理
+     */
+    //安卓10推荐uri,并且path的方式不再可用
+    Bitmap getCacheBitmap(@NonNull Context context,@NonNull Uri uri, int width, int height) throws Exception;
+
+
+}

+ 18 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/ad/AdEntity.java

@@ -0,0 +1,18 @@
+package com.huantansheng.easyphotos.models.ad;
+
+import android.view.View;
+
+/**
+ * 广告实体
+ * Created by huan on 2017/10/24.
+ */
+
+public class AdEntity {
+    public View adView;
+    public int lineIndex;
+
+    public AdEntity(View adView, int lineIndex) {
+        this.adView = adView;
+        this.lineIndex = lineIndex;
+    }
+}

+ 11 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/ad/AdListener.java

@@ -0,0 +1,11 @@
+package com.huantansheng.easyphotos.models.ad;
+
+/**
+ * 广告监听
+ * Created by huan on 2017/10/24.
+ */
+
+public interface AdListener {
+    void onPhotosAdLoaded();
+    void onAlbumItemsAdLoaded();
+}

+ 20 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/ad/AdViewHolder.java

@@ -0,0 +1,20 @@
+package com.huantansheng.easyphotos.models.ad;
+
+import androidx.recyclerview.widget.RecyclerView;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.huantansheng.easyphotos.R;
+
+/**
+ * 广告viewolder
+ * Created by huan on 2017/10/28.
+ */
+
+public class AdViewHolder extends RecyclerView.ViewHolder {
+    public FrameLayout adFrame;
+    public AdViewHolder(View itemView) {
+        super(itemView);
+        adFrame = (FrameLayout) itemView.findViewById(R.id.ad_frame_easy_photos);
+    }
+}

+ 362 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/album/AlbumModel.java

@@ -0,0 +1,362 @@
+package com.huantansheng.easyphotos.models.album;
+
+import android.Manifest;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.core.content.PermissionChecker;
+
+import com.huantansheng.easyphotos.R;
+import com.huantansheng.easyphotos.constant.Type;
+import com.huantansheng.easyphotos.models.album.entity.Album;
+import com.huantansheng.easyphotos.models.album.entity.AlbumItem;
+import com.huantansheng.easyphotos.models.album.entity.Photo;
+import com.huantansheng.easyphotos.result.Result;
+import com.huantansheng.easyphotos.setting.Setting;
+import com.huantansheng.easyphotos.utils.String.StringUtils;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 专辑模型
+ * Created by huan on 2017/10/20.
+ * <p>
+ * Modified by Eagle on 2018/08/31.
+ * 修改内容:将AlbumModel的实例化与数据查询分开
+ */
+public class AlbumModel {
+    private static final String TAG = "AlbumModel";
+    public static AlbumModel instance;
+    public Album album;
+    private String[] projections;
+
+    private AlbumModel() {
+        album = new Album();
+    }
+
+    public static AlbumModel getInstance() {
+        if (null == instance) {
+            synchronized (AlbumModel.class) {
+                if (null == instance) {
+                    instance = new AlbumModel();
+                }
+            }
+        }
+        return instance;
+    }
+
+    /**
+     * 专辑查询
+     *
+     * @param context  调用查询方法的context
+     * @param callBack 查询完成后的回调
+     */
+    public volatile boolean canRun = true;
+
+    public void query(Context context, final CallBack callBack) {
+        final Context appCxt = context.getApplicationContext();
+        if (PermissionChecker.checkSelfPermission(context,
+                Manifest.permission.READ_EXTERNAL_STORAGE) != PermissionChecker.PERMISSION_GRANTED) {
+            if (null != callBack) callBack.onAlbumWorkedCallBack();
+            return;
+        }
+        canRun = true;
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                initAlbum(appCxt);
+                if (null != callBack) callBack.onAlbumWorkedCallBack();
+            }
+        }).start();
+    }
+
+    public void stopQuery() {
+        canRun = false;
+    }
+
+    private synchronized void initAlbum(Context context) {
+        album.clear();
+//        long now = System.currentTimeMillis();
+        if (Setting.selectedPhotos.size() > Setting.count) {
+            throw new RuntimeException("AlbumBuilder: 默认勾选的图片张数不能大于设置的选择数!" + "|默认勾选图片张数:" + Setting.selectedPhotos.size() + "|设置的选择数:" + Setting.count);
+        }
+        boolean canReadWidth =
+                android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN;
+//        boolean isQ = android.os.Build.VERSION.SDK_INT == Build.VERSION_CODES.Q;
+        final String sortOrder = MediaStore.Files.FileColumns.DATE_MODIFIED + " DESC";
+
+        Uri contentUri;
+        String selection = null;
+        String[] selectionAllArgs = null;
+
+        if (Setting.isOnlyVideo()) {
+            contentUri = MediaStore.Video.Media.getContentUri("external");
+
+        } else if (!Setting.showVideo) {
+            contentUri = MediaStore.Images.Media.getContentUri("external");
+
+        } else {
+            contentUri = MediaStore.Files.getContentUri("external");
+            selection =
+                    "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?" + " OR " + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?)";
+            selectionAllArgs =
+                    new String[]{String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE),
+                            String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO)};
+        }
+
+        ContentResolver contentResolver = context.getContentResolver();
+
+
+        List<String> projectionList = new ArrayList<String>();
+        projectionList.add(MediaStore.Files.FileColumns._ID);
+//        if (isQ) {
+//            projectionList.add(MediaStore.MediaColumns.RELATIVE_PATH);
+//        }else {
+        projectionList.add(MediaStore.MediaColumns.DATA);
+//        }
+        projectionList.add(MediaStore.MediaColumns.DISPLAY_NAME);
+        projectionList.add(MediaStore.MediaColumns.DATE_MODIFIED);
+        projectionList.add(MediaStore.MediaColumns.MIME_TYPE);
+        projectionList.add(MediaStore.MediaColumns.SIZE);
+        projectionList.add(MediaStore.MediaColumns.BUCKET_DISPLAY_NAME);
+
+        if (!Setting.useWidth) {
+            if (Setting.minWidth != 1 && Setting.minHeight != 1)
+                Setting.useWidth = true;
+        }
+        if (canReadWidth) {
+            if (Setting.useWidth) {
+                projectionList.add(MediaStore.MediaColumns.WIDTH);
+                projectionList.add(MediaStore.MediaColumns.HEIGHT);
+                if (!Setting.isOnlyVideo())
+                    projectionList.add(MediaStore.MediaColumns.ORIENTATION);
+            }
+        }
+
+        if (Setting.showVideo) {
+            projectionList.add(MediaStore.MediaColumns.DURATION);
+        }
+
+        projections = projectionList.toArray(new String[0]);
+
+        Cursor cursor = contentResolver.query(contentUri, projections, selection,
+                selectionAllArgs, sortOrder);
+        if (cursor == null) {
+//            Log.d(TAG, "call: " + "Empty photos");
+        } else if (cursor.moveToFirst()) {
+            String albumItem_all_name = getAllAlbumName(context);
+            String albumItem_video_name =
+                    context.getString(R.string.selector_folder_video_easy_photos);
+
+            int albumNameCol = cursor.getColumnIndex(MediaStore.MediaColumns.BUCKET_DISPLAY_NAME);
+            int durationCol = cursor.getColumnIndex(MediaStore.MediaColumns.DURATION);
+            int WidthCol = 0;
+            int HeightCol = 0;
+            int orientationCol = -1;
+            if (canReadWidth && Setting.useWidth) {
+                WidthCol = cursor.getColumnIndex(MediaStore.MediaColumns.WIDTH);
+                HeightCol = cursor.getColumnIndex(MediaStore.MediaColumns.HEIGHT);
+                orientationCol = cursor.getColumnIndex(MediaStore.MediaColumns.ORIENTATION);
+            }
+            boolean hasTime = durationCol > 0;
+
+            do {
+                long id = cursor.getLong(0);
+                String path = cursor.getString(1);
+                String name = cursor.getString(2);
+                long dateTime = cursor.getLong(3);
+                String type = cursor.getString(4);
+                long size = cursor.getLong(5);
+                long duration = 0;
+
+
+                if (TextUtils.isEmpty(path) || TextUtils.isEmpty(type)) {
+                    continue;
+                }
+
+                if (size < Setting.minSize) {
+                    continue;
+                }
+
+                boolean isVideo = type.contains(Type.VIDEO);// 是否是视频
+
+                int width = 0;
+                int height = 0;
+                int orientation = 0;
+                if (isVideo) {
+                    if (hasTime)
+                        duration = cursor.getLong(durationCol);
+                    if (duration <= Setting.videoMinSecond || duration >= Setting.videoMaxSecond) {
+                        continue;
+                    }
+                } else {
+                    if (orientationCol != -1) {
+                        orientation = cursor.getInt(orientationCol);
+                    }
+                    if (!Setting.showGif) {
+                        if (path.endsWith(Type.GIF) || type.endsWith(Type.GIF)) {
+                            continue;
+                        }
+                    }
+                    if (Setting.useWidth) {
+                        if (canReadWidth) {
+                            width = cursor.getInt(WidthCol);
+                            height = cursor.getInt(HeightCol);
+                        }
+                        if (width == 0 || height == 0) {
+                            BitmapFactory.Options options = new BitmapFactory.Options();
+                            options.inJustDecodeBounds = true;
+                            BitmapFactory.decodeFile(path, options);
+                            width = options.outWidth;
+                            height = options.outHeight;
+                        }
+
+                        if (orientation == 90 || orientation == 270) {
+                            int temp = width;
+                            width = height;
+                            height = temp;
+                        }
+
+                        if (width < Setting.minWidth || height < Setting.minHeight) {
+                            continue;
+                        }
+
+                    }
+                }
+
+                Uri uri = ContentUris.withAppendedId(isVideo ?
+                        MediaStore.Video.Media.getContentUri("external") :
+                        MediaStore.Images.Media.getContentUri("external"), id);
+
+//某些机型,特定情况下三方应用或用户操作删除媒体文件时,没有通知媒体库,导致媒体库表中还有其数据,但真实文件已经不存在
+                File file = new File(path);
+                if (!file.isFile()) {
+                    continue;
+                }
+
+                Photo imageItem = new Photo(name, uri, path, dateTime, width, height, orientation
+                        , size,
+                        duration, type);
+                if (!Setting.selectedPhotos.isEmpty()) {
+                    int selectSize = Setting.selectedPhotos.size();
+                    for (int i = 0; i < selectSize; i++) {
+                        Photo selectedPhoto = Setting.selectedPhotos.get(i);
+                        if (path.equals(selectedPhoto.path)) {
+                            imageItem.selectedOriginal = Setting.selectedOriginal;
+                            Result.addPhoto(imageItem);
+                        }
+                    }
+                }
+
+                // 初始化“全部”专辑
+                if (album.isEmpty()) {
+                    // 用第一个图片作为专辑的封面
+                    album.addAlbumItem(albumItem_all_name, "", path, uri);
+                }
+                // 把图片全部放进“全部”专辑
+                album.getAlbumItem(albumItem_all_name).addImageItem(imageItem);
+
+                if (Setting.showVideo && isVideo && !albumItem_video_name.equals(albumItem_all_name)) {
+                    album.addAlbumItem(albumItem_video_name, "", path, uri);
+                    album.getAlbumItem(albumItem_video_name).addImageItem(imageItem);
+                }
+
+                // 添加当前图片的专辑到专辑模型实体中
+                String albumName;
+                String folderPath;
+                if (albumNameCol > 0) {
+                    albumName = cursor.getString(albumNameCol);
+                    folderPath = albumName;
+                } else {
+                    File parentFile = new File(path).getParentFile();
+                    if (null == parentFile) {
+                        continue;
+                    }
+                    folderPath = parentFile.getAbsolutePath();
+                    albumName = StringUtils.getLastPathSegment(folderPath);
+                }
+
+                album.addAlbumItem(albumName, folderPath, path, uri);
+                album.getAlbumItem(albumName).addImageItem(imageItem);
+            } while (cursor.moveToNext() && canRun);
+            cursor.close();
+        }
+//        Log.d(TAG, "initAlbum: " + (System.currentTimeMillis() - now));
+    }
+
+    /**
+     * 获取全部专辑名
+     *
+     * @return 专辑名
+     */
+    public String getAllAlbumName(Context context) {
+        String albumItem_all_name =
+                context.getString(R.string.selector_folder_all_video_photo_easy_photos);
+        if (Setting.isOnlyVideo()) {
+            albumItem_all_name = context.getString(R.string.selector_folder_video_easy_photos);
+        } else if (!Setting.showVideo) {
+            //不显示视频
+            albumItem_all_name = context.getString(R.string.selector_folder_all_easy_photos);
+        }
+        return albumItem_all_name;
+    }
+
+    /**
+     * 获取当前专辑项目的图片集
+     *
+     * @return 当前专辑项目的图片集
+     */
+    public ArrayList<Photo> getCurrAlbumItemPhotos(int currAlbumItemIndex) {
+        return album.getAlbumItem(currAlbumItemIndex).photos;
+    }
+
+    /**
+     * 获取专辑项目集
+     *
+     * @return 专辑项目集
+     */
+    public ArrayList<AlbumItem> getAlbumItems() {
+        return album.albumItems;
+    }
+
+    public interface CallBack {
+        void onAlbumWorkedCallBack();
+    }
+
+
+    /**
+     * 获取projections
+     */
+    public String[] getProjections() {
+        if (null == projections || projections.length == 0) {
+            if (Setting.useWidth) {
+                projections = new String[]{MediaStore.Files.FileColumns._ID,
+                        MediaStore.MediaColumns.DATA
+                        , MediaStore.MediaColumns.DISPLAY_NAME,
+                        MediaStore.MediaColumns.DATE_MODIFIED,
+                        MediaStore.MediaColumns.MIME_TYPE, MediaStore.MediaColumns.SIZE,
+                        MediaStore.MediaColumns.BUCKET_DISPLAY_NAME,
+                        MediaStore.MediaColumns.WIDTH, MediaStore.MediaColumns.HEIGHT,
+                        MediaStore.MediaColumns.ORIENTATION};
+            } else {
+                projections = new String[]{MediaStore.Files.FileColumns._ID,
+                        MediaStore.MediaColumns.DATA
+                        , MediaStore.MediaColumns.DISPLAY_NAME,
+                        MediaStore.MediaColumns.DATE_MODIFIED,
+                        MediaStore.MediaColumns.MIME_TYPE, MediaStore.MediaColumns.SIZE,
+                        MediaStore.MediaColumns.BUCKET_DISPLAY_NAME};
+            }
+        }
+        return projections;
+    }
+
+}

+ 49 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/album/entity/Album.java

@@ -0,0 +1,49 @@
+package com.huantansheng.easyphotos.models.album.entity;
+
+import android.net.Uri;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+
+/**
+ * 专辑模型实体类
+ * Created by huan on 2017/10/20.
+ */
+
+public class Album {
+    public ArrayList<AlbumItem> albumItems;
+    private LinkedHashMap<String, AlbumItem> hasAlbumItems;//用于记录专辑项目
+
+    public Album() {
+        albumItems = new ArrayList<>();
+        hasAlbumItems = new LinkedHashMap<>();
+    }
+
+    private void addAlbumItem(AlbumItem albumItem) {
+        this.hasAlbumItems.put(albumItem.name, albumItem);
+        this.albumItems.add(albumItem);
+    }
+
+    public void addAlbumItem(String name, String folderPath, String coverImagePath, Uri coverImageUri) {
+        if (null == hasAlbumItems.get(name)) {
+            addAlbumItem(new AlbumItem(name, folderPath, coverImagePath,coverImageUri));
+        }
+    }
+
+    public AlbumItem getAlbumItem(String name) {
+        return hasAlbumItems.get(name);
+    }
+
+    public AlbumItem getAlbumItem(int currIndex) {
+        return albumItems.get(currIndex);
+    }
+
+    public boolean isEmpty() {
+        return albumItems.isEmpty();
+    }
+
+    public void clear() {
+        albumItems.clear();
+        hasAlbumItems.clear();
+    }
+}

+ 34 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/album/entity/AlbumItem.java

@@ -0,0 +1,34 @@
+package com.huantansheng.easyphotos.models.album.entity;
+
+import android.net.Uri;
+
+import java.util.ArrayList;
+
+/**
+ * 专辑项目实体类
+ * Created by huan on 2017/10/20.
+ */
+
+public class AlbumItem {
+    public String name;
+    public String folderPath;
+    public String coverImagePath;
+    public Uri coverImageUri;
+    public ArrayList<Photo> photos;
+
+    AlbumItem(String name, String folderPath, String coverImagePath, Uri coverImageUri) {
+        this.name = name;
+        this.folderPath = folderPath;
+        this.coverImagePath = coverImagePath;
+        this.coverImageUri = coverImageUri;
+        this.photos = new ArrayList<>();
+    }
+
+    public void addImageItem(Photo imageItem) {
+        this.photos.add(imageItem);
+    }
+
+    public void addImageItem(int index, Photo imageItem) {
+        this.photos.add(index, imageItem);
+    }
+}

+ 114 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/album/entity/Photo.java

@@ -0,0 +1,114 @@
+package com.huantansheng.easyphotos.models.album.entity;
+
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * 图片item实体类
+ * Created by huan on 2017/10/20.
+ */
+
+public class Photo implements Parcelable {
+    private static final String TAG = "Photo";
+    public Uri uri;//图片Uri
+    public String name;//图片名称
+    public String path;//图片全路径
+    public String type;//图片类型
+    public int width;//图片宽度
+    public int height;//图片高度
+    public int orientation;//图片旋转角度
+    public long size;//图片文件大小,单位:Bytes
+    public long duration;//视频时长,单位:毫秒
+    public long time;//图片拍摄的时间戳,单位:毫秒
+    public boolean selected;//是否被选中,内部使用,无需关心
+    public boolean selectedOriginal;//用户选择时是否选择了原图选项
+
+    public Photo(String name, Uri uri, String path, long time, int width, int height,int orientation, long size, long duration, String type) {
+        this.name = name;
+        this.uri = uri;
+        this.path = path;
+        this.time = time;
+        this.width = width;
+        this.height = height;
+        this.orientation = orientation;
+        this.type = type;
+        this.size = size;
+        this.duration = duration;
+        this.selected = false;
+        this.selectedOriginal = false;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        try {
+            Photo other = (Photo) o;
+            return this.path.equalsIgnoreCase(other.path);
+        } catch (ClassCastException e) {
+            Log.e(TAG, "equals: " + Log.getStackTraceString(e));
+        }
+        return super.equals(o);
+    }
+
+    @Override
+    public String toString() {
+        return "Photo{" +
+                "name='" + name + '\'' +
+                ", uri='" + uri.toString() + '\'' +
+                ", path='" + path + '\'' +
+                ", time=" + time + '\'' +
+                ", minWidth=" + width + '\'' +
+                ", minHeight=" + height +
+                ", orientation=" + orientation +
+                '}';
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(this.uri, flags);
+        dest.writeString(this.name);
+        dest.writeString(this.path);
+        dest.writeString(this.type);
+        dest.writeInt(this.width);
+        dest.writeInt(this.height);
+        dest.writeInt(this.orientation);
+        dest.writeLong(this.size);
+        dest.writeLong(this.duration);
+        dest.writeLong(this.time);
+        dest.writeByte(this.selected ? (byte) 1 : (byte) 0);
+        dest.writeByte(this.selectedOriginal ? (byte) 1 : (byte) 0);
+    }
+
+    protected Photo(Parcel in) {
+        this.uri = in.readParcelable(Uri.class.getClassLoader());
+        this.name = in.readString();
+        this.path = in.readString();
+        this.type = in.readString();
+        this.width = in.readInt();
+        this.height = in.readInt();
+        this.orientation = in.readInt();
+        this.size = in.readLong();
+        this.duration = in.readLong();
+        this.time = in.readLong();
+        this.selected = in.readByte() != 0;
+        this.selectedOriginal = in.readByte() != 0;
+    }
+
+    public static final Creator<Photo> CREATOR = new Creator<Photo>() {
+        @Override
+        public Photo createFromParcel(Parcel source) {
+            return new Photo(source);
+        }
+
+        @Override
+        public Photo[] newArray(int size) {
+            return new Photo[size];
+        }
+    };
+}

+ 65 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/Area.java

@@ -0,0 +1,65 @@
+package com.huantansheng.easyphotos.models.puzzle;
+
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.graphics.RectF;
+
+import java.util.List;
+
+/**
+ * @author wupanjie
+ */
+public interface Area {
+  float left();
+
+  float top();
+
+  float right();
+
+  float bottom();
+
+  float centerX();
+
+  float centerY();
+
+  float width();
+
+  float height();
+
+  PointF getCenterPoint();
+
+  boolean contains(PointF point);
+
+  boolean contains(float x, float y);
+
+  boolean contains(Line line);
+
+  Path getAreaPath();
+
+  RectF getAreaRect();
+
+  List<Line> getLines();
+
+  PointF[] getHandleBarPoints(Line line);
+
+  float radian();
+
+  void setRadian(float radian);
+
+  float getPaddingLeft();
+
+  float getPaddingTop();
+
+  float getPaddingRight();
+
+  float getPaddingBottom();
+
+  void setPadding(float padding);
+
+  void setPadding(float paddingLeft, float paddingTop, float paddingRight, float paddingBottom);
+}
+
+
+
+
+

+ 353 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/DegreeSeekBar.java

@@ -0,0 +1,353 @@
+package com.huantansheng.easyphotos.models.puzzle;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.os.Build;
+import androidx.core.content.ContextCompat;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.huantansheng.easyphotos.R;
+
+/**
+ * @author wupanjie
+ */
+public class DegreeSeekBar extends View {
+    private static final String TAG = "DegreeSeekBar";
+    private Paint mTextPaint;
+    private Paint mCirclePaint;
+    private Paint.FontMetricsInt mFontMetrics;
+    private int mBaseLine;
+    private float[] mTextWidths;
+
+    private final Rect mCanvasClipBounds = new Rect();
+
+    private ScrollingListener mScrollingListener;
+    private float mLastTouchedPosition;
+
+    private Paint mPointPaint;
+    private float mPointMargin;
+
+    private boolean mScrollStarted;
+    private int mTotalScrollDistance;
+
+    private Path mIndicatorPath = new Path();
+
+    private int mCurrentDegrees = 0;
+    private int mPointCount = 51;
+
+    private int mPointColor;
+    private int mTextColor;
+    private int mCenterTextColor;
+
+    //阻尼系数的倒数
+    private float mDragFactor = 2.1f;
+
+    private int mMinReachableDegrees = -45;
+    private int mMaxReachableDegrees = 45;
+
+    private String suffix = "";
+
+    public DegreeSeekBar(Context context) {
+        this(context, null);
+    }
+
+    public DegreeSeekBar(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public DegreeSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init();
+    }
+
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    public DegreeSeekBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        init();
+    }
+
+    private void init() {
+        mPointColor = ContextCompat.getColor(getContext(), R.color.easy_photos_fg_primary);
+        mTextColor = ContextCompat.getColor(getContext(), R.color.easy_photos_fg_primary);
+        mCenterTextColor = ContextCompat.getColor(getContext(), R.color.easy_photos_fg_accent);
+
+        mPointPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        mPointPaint.setStyle(Paint.Style.STROKE);
+        mPointPaint.setColor(mPointColor);
+        mPointPaint.setStrokeWidth(2);
+
+        mTextPaint = new Paint();
+        mTextPaint.setColor(mTextColor);
+        mTextPaint.setStyle(Paint.Style.STROKE);
+        mTextPaint.setAntiAlias(true);
+        mTextPaint.setTextSize(24f);
+        mTextPaint.setTextAlign(Paint.Align.LEFT);
+        mTextPaint.setAlpha(100);
+
+        mFontMetrics = mTextPaint.getFontMetricsInt();
+
+        mTextWidths = new float[1];
+        mTextPaint.getTextWidths("0", mTextWidths);
+
+        mCirclePaint = new Paint();
+        mCirclePaint.setStyle(Paint.Style.FILL);
+        mCirclePaint.setAlpha(255);
+        mCirclePaint.setAntiAlias(true);
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+        mPointMargin = (float) w / mPointCount;
+
+        mBaseLine = (h - mFontMetrics.bottom + mFontMetrics.top) / 2 - mFontMetrics.top;
+
+        mIndicatorPath.moveTo(w / 2, h / 2 + mFontMetrics.top / 2 - 18);
+        mIndicatorPath.rLineTo(-8, -8);
+        mIndicatorPath.rLineTo(16, 0);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                mLastTouchedPosition = event.getX();
+                if (!mScrollStarted) {
+                    mScrollStarted = true;
+                    if (mScrollingListener != null) {
+                        mScrollingListener.onScrollStart();
+                    }
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+                mScrollStarted = false;
+                if (mScrollingListener != null) {
+                    mScrollingListener.onScrollEnd();
+                }
+                invalidate();
+                break;
+            case MotionEvent.ACTION_MOVE:
+                float distance = event.getX() - mLastTouchedPosition;
+                if (mCurrentDegrees >= mMaxReachableDegrees && distance < 0) {
+                    mCurrentDegrees = mMaxReachableDegrees;
+                    invalidate();
+                    break;
+                }
+                if (mCurrentDegrees <= mMinReachableDegrees && distance > 0) {
+                    mCurrentDegrees = mMinReachableDegrees;
+                    invalidate();
+                    break;
+                }
+                if (distance != 0) {
+                    onScrollEvent(event, distance);
+                }
+                break;
+        }
+        return true;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        canvas.getClipBounds(mCanvasClipBounds);
+
+        int zeroIndex = mPointCount / 2 + (0 - mCurrentDegrees) / 2;
+        mPointPaint.setColor(mPointColor);
+        for (int i = 0; i < mPointCount; i++) {
+
+            if (i > zeroIndex - Math.abs(mMinReachableDegrees) / 2
+                    && i < zeroIndex + Math.abs(mMaxReachableDegrees) / 2
+                    && mScrollStarted) {
+                mPointPaint.setAlpha(255);
+            } else {
+                mPointPaint.setAlpha(100);
+            }
+
+            if (i > mPointCount / 2 - 8
+                    && i < mPointCount / 2 + 8
+                    && i > zeroIndex - Math.abs(mMinReachableDegrees) / 2
+                    && i < zeroIndex + Math.abs(mMaxReachableDegrees) / 2) {
+                if (mScrollStarted) {
+                    mPointPaint.setAlpha(Math.abs(mPointCount / 2 - i) * 255 / 8);
+                } else {
+                    mPointPaint.setAlpha(Math.abs(mPointCount / 2 - i) * 100 / 8);
+                }
+            }
+
+            canvas.drawPoint(mCanvasClipBounds.centerX() + (i - mPointCount / 2) * mPointMargin,
+                    mCanvasClipBounds.centerY(), mPointPaint);
+
+            if (mCurrentDegrees != 0 && i == zeroIndex) {
+                if (mScrollStarted) {
+                    mTextPaint.setAlpha(255);
+                } else {
+                    mTextPaint.setAlpha(192);
+                }
+                mPointPaint.setStrokeWidth(4);
+                canvas.drawPoint((mCanvasClipBounds.centerX() + (i - mPointCount / 2) * mPointMargin),
+                        mCanvasClipBounds.centerY(), mPointPaint);
+                mPointPaint.setStrokeWidth(2);
+                mTextPaint.setAlpha(100);
+            }
+        }
+
+        for (int i = -180; i <= 180; i += 15) {
+            if (i >= mMinReachableDegrees && i <= mMaxReachableDegrees) {
+                drawDegreeText(i, canvas, true);
+            } else {
+                drawDegreeText(i, canvas, false);
+            }
+        }
+
+        mTextPaint.setTextSize(28f);
+        mTextPaint.setAlpha(255);
+        mTextPaint.setColor(mCenterTextColor);
+
+        if (mCurrentDegrees >= 10) {
+            canvas.drawText(mCurrentDegrees + suffix, getWidth() / 2 - mTextWidths[0], mBaseLine,
+                    mTextPaint);
+        } else if (mCurrentDegrees <= -10) {
+            canvas.drawText(mCurrentDegrees + suffix, getWidth() / 2 - mTextWidths[0] / 2 * 3, mBaseLine,
+                    mTextPaint);
+        } else if (mCurrentDegrees < 0) {
+            canvas.drawText(mCurrentDegrees + suffix, getWidth() / 2 - mTextWidths[0], mBaseLine,
+                    mTextPaint);
+        } else {
+            canvas.drawText(mCurrentDegrees + suffix, getWidth() / 2 - mTextWidths[0] / 2, mBaseLine,
+                    mTextPaint);
+        }
+
+        mTextPaint.setAlpha(100);
+        mTextPaint.setTextSize(24f);
+        mTextPaint.setColor(mTextColor);
+        //画中心三角
+        mCirclePaint.setColor(mCenterTextColor);
+        canvas.drawPath(mIndicatorPath, mCirclePaint);
+        mCirclePaint.setColor(mCenterTextColor);
+    }
+
+    private void drawDegreeText(int degrees, Canvas canvas, boolean canReach) {
+        if (canReach) {
+            if (mScrollStarted) {
+                mTextPaint.setAlpha(Math.min(255, Math.abs(degrees - mCurrentDegrees) * 255 / 15));
+                if (Math.abs(degrees - mCurrentDegrees) <= 7) {
+                    mTextPaint.setAlpha(0);
+                }
+            } else {
+                mTextPaint.setAlpha(100);
+                if (Math.abs(degrees - mCurrentDegrees) <= 7) {
+                    mTextPaint.setAlpha(0);
+                }
+            }
+        } else {
+            mTextPaint.setAlpha(100);
+        }
+        if (degrees == 0) {
+            if (Math.abs(mCurrentDegrees) >= 15 && !mScrollStarted) {
+                mTextPaint.setAlpha(180);
+            }
+            canvas.drawText("0°",
+                    getWidth() / 2 - mTextWidths[0] / 2 - mCurrentDegrees / 2 * mPointMargin,
+                    getHeight() / 2 - 10, mTextPaint);
+        } else {
+            canvas.drawText(degrees + suffix, getWidth() / 2 + mPointMargin * degrees / 2
+                    - mTextWidths[0] / 2 * 3
+                    - mCurrentDegrees / 2 * mPointMargin, getHeight() / 2 - 10, mTextPaint);
+        }
+    }
+
+    private void onScrollEvent(MotionEvent event, float distance) {
+        mTotalScrollDistance -= distance;
+        postInvalidate();
+        mLastTouchedPosition = event.getX();
+        mCurrentDegrees = (int) ((mTotalScrollDistance * mDragFactor) / mPointMargin);
+        if (mScrollingListener != null) {
+            mScrollingListener.onScroll(mCurrentDegrees);
+        }
+    }
+
+    public void setDegreeRange(int min, int max) {
+        if (min > max) {
+            Log.e(TAG, "setDegreeRange: error, max must greater than min");
+        } else {
+            mMinReachableDegrees = min;
+            mMaxReachableDegrees = max;
+
+            if (mCurrentDegrees > mMaxReachableDegrees || mCurrentDegrees < mMinReachableDegrees) {
+                mCurrentDegrees = (mMinReachableDegrees + mMaxReachableDegrees) / 2;
+            }
+            mTotalScrollDistance = (int) (mCurrentDegrees * mPointMargin / mDragFactor);
+            invalidate();
+        }
+    }
+
+    public void setCurrentDegrees(int degrees) {
+        if (degrees <= mMaxReachableDegrees && degrees >= mMinReachableDegrees) {
+            mCurrentDegrees = degrees;
+            mTotalScrollDistance = (int) (degrees * mPointMargin / mDragFactor);
+            invalidate();
+        }
+    }
+
+    public void setScrollingListener(ScrollingListener scrollingListener) {
+        mScrollingListener = scrollingListener;
+    }
+
+    public int getPointColor() {
+        return mPointColor;
+    }
+
+    public void setPointColor(int pointColor) {
+        mPointColor = pointColor;
+        mPointPaint.setColor(mPointColor);
+        postInvalidate();
+    }
+
+    public int getTextColor() {
+        return mTextColor;
+    }
+
+    public void setTextColor(int textColor) {
+        mTextColor = textColor;
+        mTextPaint.setColor(textColor);
+        postInvalidate();
+    }
+
+    public int getCenterTextColor() {
+        return mCenterTextColor;
+    }
+
+    public void setCenterTextColor(int centerTextColor) {
+        mCenterTextColor = centerTextColor;
+        postInvalidate();
+    }
+
+    public float getDragFactor() {
+        return mDragFactor;
+    }
+
+    public void setDragFactor(float dragFactor) {
+        mDragFactor = dragFactor;
+    }
+
+    public void setSuffix(String suffix) {
+        this.suffix = suffix;
+    }
+
+    public interface ScrollingListener {
+
+        void onScrollStart();
+
+        void onScroll(int currentDegrees);
+
+        void onScrollEnd();
+    }
+}
+

+ 52 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/Line.java

@@ -0,0 +1,52 @@
+package com.huantansheng.easyphotos.models.puzzle;
+
+import android.graphics.PointF;
+
+/**
+ * @author wupanjie
+ */
+public interface Line {
+  enum Direction {
+    HORIZONTAL, VERTICAL
+  }
+
+  float length();
+
+  PointF startPoint();
+
+  PointF endPoint();
+
+  Line lowerLine();
+
+  Line upperLine();
+
+  Line attachStartLine();
+
+  Line attachEndLine();
+
+  void setLowerLine(Line lowerLine);
+
+  void setUpperLine(Line upperLine);
+
+  Direction direction();
+
+  float slope();
+
+  boolean contains(float x, float y, float extra);
+
+  void prepareMove();
+
+  boolean move(float offset, float extra);
+
+  void update(float layoutWidth, float layoutHeight);
+
+  float minX();
+
+  float maxX();
+
+  float minY();
+
+  float maxY();
+
+  void offset(float x, float y);
+}

+ 167 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/MatrixUtils.java

@@ -0,0 +1,167 @@
+package com.huantansheng.easyphotos.models.puzzle;
+
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+
+import java.util.Arrays;
+
+import static java.lang.Math.round;
+
+/**
+ * some useful matrix operation methods
+ *
+ * @author wupanjie
+ */
+public class MatrixUtils {
+  private MatrixUtils() {
+    //no instance
+  }
+
+  private static final float[] sMatrixValues = new float[9];
+  private static final Matrix sTempMatrix = new Matrix();
+
+  /**
+   * This method calculates scale value for given Matrix object.
+   */
+  public static float getMatrixScale(Matrix matrix) {
+    return (float) Math.sqrt(Math.pow(getMatrixValue(matrix, Matrix.MSCALE_X), 2) + Math.pow(
+        getMatrixValue(matrix, Matrix.MSKEW_Y), 2));
+  }
+
+  /**
+   * This method calculates rotation angle for given Matrix object.
+   */
+  public static float getMatrixAngle(Matrix matrix) {
+    return (float) -(Math.atan2(getMatrixValue(matrix, Matrix.MSKEW_X),
+        getMatrixValue(matrix, Matrix.MSCALE_X)) * (180 / Math.PI));
+  }
+
+  public static float getMatrixValue(Matrix matrix, int valueIndex) {
+    matrix.getValues(sMatrixValues);
+    return sMatrixValues[valueIndex];
+  }
+
+  public static float getMinMatrixScale(PuzzlePiece piece) {
+    if (piece != null) {
+
+      sTempMatrix.reset();
+      sTempMatrix.setRotate(-piece.getMatrixAngle());
+
+      float[] unrotatedCropBoundsCorners = getCornersFromRect(piece.getArea().getAreaRect());
+
+      sTempMatrix.mapPoints(unrotatedCropBoundsCorners);
+
+      RectF unrotatedCropRect = trapToRect(unrotatedCropBoundsCorners);
+
+      return Math.max(unrotatedCropRect.width() / piece.getWidth(),
+          unrotatedCropRect.height() / piece.getHeight());
+    }
+
+    return 1f;
+  }
+
+  //判断剪裁框是否在图片内
+  static boolean judgeIsImageContainsBorder(PuzzlePiece piece, float rotateDegrees) {
+    sTempMatrix.reset();
+    sTempMatrix.setRotate(-rotateDegrees);
+    float[] unrotatedWrapperCorner = new float[8];
+    float[] unrotateBorderCorner = new float[8];
+    sTempMatrix.mapPoints(unrotatedWrapperCorner, piece.getCurrentDrawablePoints());
+    sTempMatrix.mapPoints(unrotateBorderCorner, getCornersFromRect(piece.getArea().getAreaRect()));
+
+    return trapToRect(unrotatedWrapperCorner).contains(trapToRect(unrotateBorderCorner));
+  }
+
+  static float[] calculateImageIndents(PuzzlePiece piece) {
+    if (piece == null) return new float[] { 0, 0, 0, 0, 0, 0, 0, 0 };
+
+    sTempMatrix.reset();
+    sTempMatrix.setRotate(-piece.getMatrixAngle());
+
+    final float[] currentImageCorners = piece.getCurrentDrawablePoints();
+    final float[] unrotatedImageCorners =
+        Arrays.copyOf(currentImageCorners, currentImageCorners.length);
+    final float[] unrotatedCropBoundsCorners = getCornersFromRect(piece.getArea().getAreaRect());
+
+    sTempMatrix.mapPoints(unrotatedImageCorners);
+    sTempMatrix.mapPoints(unrotatedCropBoundsCorners);
+
+    RectF unrotatedImageRect = trapToRect(unrotatedImageCorners);
+    RectF unrotatedCropRect = trapToRect(unrotatedCropBoundsCorners);
+
+    float deltaLeft = unrotatedImageRect.left - unrotatedCropRect.left;
+    float deltaTop = unrotatedImageRect.top - unrotatedCropRect.top;
+    float deltaRight = unrotatedImageRect.right - unrotatedCropRect.right;
+    float deltaBottom = unrotatedImageRect.bottom - unrotatedCropRect.bottom;
+
+    float indents[] = new float[4];
+
+    indents[0] = (deltaLeft > 0) ? deltaLeft : 0;
+    indents[1] = (deltaTop > 0) ? deltaTop : 0;
+    indents[2] = (deltaRight < 0) ? deltaRight : 0;
+    indents[3] = (deltaBottom < 0) ? deltaBottom : 0;
+
+    sTempMatrix.reset();
+    sTempMatrix.setRotate(piece.getMatrixAngle());
+    sTempMatrix.mapPoints(indents);
+
+    return indents;
+  }
+
+  //计算包含给出点的最小矩形
+  public static RectF trapToRect(float[] array) {
+    RectF r = new RectF(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY,
+        Float.NEGATIVE_INFINITY);
+    int length = array.length;
+    for (int i = 1; i < length; i += 2) {
+      float x = round(array[i - 1] * 10) / 10.f;
+      float y = round(array[i] * 10) / 10.f;
+      r.left = (x < r.left) ? x : r.left;
+      r.top = (y < r.top) ? y : r.top;
+      r.right = (x > r.right) ? x : r.right;
+      r.bottom = (y > r.bottom) ? y : r.bottom;
+    }
+    r.sort();
+    return r;
+  }
+
+  public static float[] getCornersFromRect(RectF r) {
+    return new float[] {
+        r.left, r.top, r.right, r.top, r.right, r.bottom, r.left, r.bottom
+    };
+  }
+
+  public static Matrix generateMatrix(PuzzlePiece piece, float extra) {
+    return generateMatrix(piece.getArea(), piece.getDrawable(), extra);
+  }
+
+  public static Matrix generateMatrix(Area area, Drawable drawable, float extraSize) {
+    return generateCenterCropMatrix(area, drawable.getIntrinsicWidth(),
+        drawable.getIntrinsicHeight(), extraSize);
+  }
+
+  private static Matrix generateCenterCropMatrix(Area area, int width, int height,
+                                                 float extraSize) {
+    final RectF rectF = area.getAreaRect();
+
+    Matrix matrix = new Matrix();
+
+    float offsetX = rectF.centerX() - width / 2;
+    float offsetY = rectF.centerY() - height / 2;
+
+    matrix.postTranslate(offsetX, offsetY);
+
+    float scale;
+
+    if (width * rectF.height() > rectF.width() * height) {
+      scale = (rectF.height() + extraSize) / height;
+    } else {
+      scale = (rectF.width() + extraSize) / width;
+    }
+
+    matrix.postScale(scale, scale, rectF.centerX(), rectF.centerY());
+
+    return matrix;
+  }
+}

+ 88 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzleLayout.java

@@ -0,0 +1,88 @@
+package com.huantansheng.easyphotos.models.puzzle;
+
+import android.graphics.RectF;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author wupanjie
+ */
+public interface PuzzleLayout {
+  void setOuterBounds(RectF bounds);
+
+  void layout();
+
+  int getAreaCount();
+
+  List<Line> getOuterLines();
+
+  List<Line> getLines();
+
+  Area getOuterArea();
+
+  void update();
+
+  void reset();
+
+  Area getArea(int position);
+
+  float width();
+
+  float height();
+
+  void setPadding(float padding);
+
+  float getPadding();
+
+  float getRadian();
+
+  void setRadian(float radian);
+
+  Info generateInfo();
+
+  void setColor(int color);
+
+  int getColor();
+
+  class Info {
+    public static final int TYPE_STRAIGHT = 0;
+    public static final int TYPE_SLANT = 1;
+
+    public int type;
+    public ArrayList<Step> steps;
+    public ArrayList<LineInfo> lineInfos;
+    public float padding;
+    public float radian;
+    public int color;
+  }
+
+  class Step {
+    public static final int ADD_LINE = 0;
+    public static final int ADD_CROSS = 1;
+    public static final int CUT_EQUAL_PART_ONE = 2;
+    public static final int CUT_EQUAL_PART_TWO = 3;
+    public static final int CUT_SPIRAL = 4;
+
+    public int type;
+    public int direction;
+    public int position;
+    public int part;
+    public int hSize;
+    public int vSize;
+  }
+
+  class LineInfo {
+    public float startX;
+    public float startY;
+    public float endX;
+    public float endY;
+
+    public LineInfo(Line line){
+      startX = line.startPoint().x;
+      startY = line.startPoint().y;
+      endX = line.endPoint().x;
+      endY = line.endPoint().y;
+    }
+  }
+}

+ 432 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzlePiece.java

@@ -0,0 +1,432 @@
+package com.huantansheng.easyphotos.models.puzzle;
+
+import android.animation.ValueAnimator;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PointF;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Xfermode;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+
+import static com.huantansheng.easyphotos.models.puzzle.MatrixUtils.calculateImageIndents;
+import static com.huantansheng.easyphotos.models.puzzle.MatrixUtils.getMinMatrixScale;
+import static com.huantansheng.easyphotos.models.puzzle.MatrixUtils.judgeIsImageContainsBorder;
+
+
+/**
+ * @author wupanjie
+ */
+@SuppressWarnings("WeakerAccess") public class PuzzlePiece {
+  private static Xfermode SRC_IN = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
+
+  private Drawable drawable;
+  private Matrix matrix;
+  private Matrix previousMatrix;
+  private Area area;
+  private Rect drawableBounds;
+  private float[] drawablePoints;
+  private float[] mappedDrawablePoints;
+
+  private float previousMoveX;
+  private float previousMoveY;
+
+  private final RectF mappedBounds;
+  private final PointF centerPoint;
+  private final PointF mappedCenterPoint;
+
+  private ValueAnimator animator;
+  private int duration = 300;
+  private Matrix tempMatrix;
+
+  PuzzlePiece(Drawable drawable, Area area, Matrix matrix) {
+    this.drawable = drawable;
+    this.area = area;
+    this.matrix = matrix;
+    this.previousMatrix = new Matrix();
+    this.drawableBounds = new Rect(0, 0, getWidth(), getHeight());
+    this.drawablePoints = new float[] {
+        0f, 0f, getWidth(), 0f, getWidth(), getHeight(), 0f, getHeight()
+    };
+    this.mappedDrawablePoints = new float[8];
+
+    this.mappedBounds = new RectF();
+    this.centerPoint = new PointF(area.centerX(), area.centerY());
+    this.mappedCenterPoint = new PointF();
+
+    this.animator = ValueAnimator.ofFloat(0f, 1f);
+    this.animator.setInterpolator(new DecelerateInterpolator());
+
+    this.tempMatrix = new Matrix();
+  }
+
+  void draw(Canvas canvas) {
+    draw(canvas, 255, true);
+  }
+
+  void draw(Canvas canvas, int alpha) {
+    draw(canvas, alpha, false);
+  }
+
+  private void draw(Canvas canvas, int alpha, boolean needClip) {
+    if (drawable instanceof BitmapDrawable){
+      int saved = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);
+
+      Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
+      Paint paint= ((BitmapDrawable) drawable).getPaint();
+
+      paint.setColor(Color.WHITE);
+      paint.setAlpha(alpha);
+      if (needClip) {
+        canvas.drawPath(area.getAreaPath(), paint);
+        paint.setXfermode(SRC_IN);
+      }
+      canvas.drawBitmap(bitmap,matrix,paint);
+      paint.setXfermode(null);
+
+      canvas.restoreToCount(saved);
+    }else {
+      canvas.save();
+      if (needClip) {
+        canvas.clipPath(area.getAreaPath());
+      }
+      canvas.concat(matrix);
+      drawable.setBounds(drawableBounds);
+      drawable.setAlpha(alpha);
+      drawable.draw(canvas);
+
+      canvas.restore();
+    }
+  }
+
+  public Area getArea() {
+    return area;
+  }
+
+  public void setDrawable(Drawable drawable) {
+    this.drawable = drawable;
+    this.drawableBounds = new Rect(0, 0, getWidth(), getHeight());
+    this.drawablePoints = new float[] {
+        0f, 0f, getWidth(), 0f, getWidth(), getHeight(), 0f, getHeight()
+    };
+  }
+
+  public Drawable getDrawable() {
+    return drawable;
+  }
+
+  public int getWidth() {
+    return drawable.getIntrinsicWidth();
+  }
+
+  public int getHeight() {
+    return drawable.getIntrinsicHeight();
+  }
+
+  public boolean contains(float x, float y) {
+    return area.contains(x, y);
+  }
+
+  public boolean contains(Line line) {
+    return area.contains(line);
+  }
+
+  public Rect getDrawableBounds() {
+    return drawableBounds;
+  }
+
+  void setPreviousMoveX(float previousMoveX) {
+    this.previousMoveX = previousMoveX;
+  }
+
+  void setPreviousMoveY(float previousMoveY) {
+    this.previousMoveY = previousMoveY;
+  }
+
+  private RectF getCurrentDrawableBounds() {
+    matrix.mapRect(mappedBounds, new RectF(drawableBounds));
+    return mappedBounds;
+  }
+
+  private PointF getCurrentDrawableCenterPoint() {
+    getCurrentDrawableBounds();
+    mappedCenterPoint.x = mappedBounds.centerX();
+    mappedCenterPoint.y = mappedBounds.centerY();
+    return mappedCenterPoint;
+  }
+
+  public PointF getAreaCenterPoint() {
+    centerPoint.x = area.centerX();
+    centerPoint.y = area.centerY();
+    return centerPoint;
+  }
+
+  private float getMatrixScale() {
+    return MatrixUtils.getMatrixScale(matrix);
+  }
+
+  float getMatrixAngle() {
+    return MatrixUtils.getMatrixAngle(matrix);
+  }
+
+  float[] getCurrentDrawablePoints() {
+    matrix.mapPoints(mappedDrawablePoints, drawablePoints);
+    return mappedDrawablePoints;
+  }
+
+  boolean isFilledArea() {
+    RectF bounds = getCurrentDrawableBounds();
+    return !(bounds.left > area.left()
+        || bounds.top > area.top()
+        || bounds.right < area.right()
+        || bounds.bottom < area.bottom());
+  }
+
+  boolean canFilledArea() {
+    float scale = MatrixUtils.getMatrixScale(matrix);
+    float minScale = getMinMatrixScale(this);
+    return scale >= minScale;
+  }
+
+  void record() {
+    previousMatrix.set(matrix);
+  }
+
+  void translate(float offsetX, float offsetY) {
+    matrix.set(previousMatrix);
+    postTranslate(offsetX, offsetY);
+  }
+
+  private void zoom(float scaleX, float scaleY, PointF midPoint) {
+    matrix.set(previousMatrix);
+    postScale(scaleX, scaleY, midPoint);
+  }
+
+  void zoomAndTranslate(float scaleX, float scaleY, PointF midPoint, float offsetX, float offsetY) {
+    matrix.set(previousMatrix);
+    postTranslate(offsetX, offsetY);
+    postScale(scaleX, scaleY, midPoint);
+  }
+
+  void set(Matrix matrix) {
+    this.matrix.set(matrix);
+    moveToFillArea(null);
+  }
+
+  void postTranslate(float x, float y) {
+    this.matrix.postTranslate(x, y);
+  }
+
+  void postScale(float scaleX, float scaleY, PointF midPoint) {
+    this.matrix.postScale(scaleX, scaleY, midPoint.x, midPoint.y);
+  }
+
+  void postFlipVertically() {
+    this.matrix.postScale(1, -1, area.centerX(), area.centerY());
+  }
+
+  void postFlipHorizontally() {
+    this.matrix.postScale(-1, 1, area.centerX(), area.centerY());
+  }
+
+  void postRotate(float degree) {
+    this.matrix.postRotate(degree, area.centerX(), area.centerY());
+
+    float minScale = getMinMatrixScale(this);
+    if (getMatrixScale() < minScale) {
+      final PointF midPoint = new PointF();
+      midPoint.set(getCurrentDrawableCenterPoint());
+
+      postScale(minScale / getMatrixScale(), minScale / getMatrixScale(), midPoint);
+    }
+
+    if (!judgeIsImageContainsBorder(this, getMatrixAngle())) {
+      final float[] imageIndents = calculateImageIndents(this);
+      float deltaX = -(imageIndents[0] + imageIndents[2]);
+      float deltaY = -(imageIndents[1] + imageIndents[3]);
+
+      postTranslate(deltaX, deltaY);
+    }
+  }
+
+  private void animateTranslate(final View view, final float translateX, final float translateY) {
+    animator.end();
+    animator.removeAllUpdateListeners();
+    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+      @Override
+      public void onAnimationUpdate(ValueAnimator animation) {
+        float x = translateX * (float) animation.getAnimatedValue();
+        float y = translateY * (float) animation.getAnimatedValue();
+
+        translate(x, y);
+        view.invalidate();
+      }
+    });
+    animator.setDuration(duration);
+    animator.start();
+  }
+
+  void moveToFillArea(final View view) {
+    if (isFilledArea()) return;
+    record();
+
+    RectF rectF = getCurrentDrawableBounds();
+    float offsetX = 0f;
+    float offsetY = 0f;
+
+    if (rectF.left > area.left()) {
+      offsetX = area.left() - rectF.left;
+    }
+
+    if (rectF.top > area.top()) {
+      offsetY = area.top() - rectF.top;
+    }
+
+    if (rectF.right < area.right()) {
+      offsetX = area.right() - rectF.right;
+    }
+
+    if (rectF.bottom < area.bottom()) {
+      offsetY = area.bottom() - rectF.bottom;
+    }
+
+    if (view == null) {
+      postTranslate(offsetX, offsetY);
+    } else {
+      animateTranslate(view, offsetX, offsetY);
+    }
+  }
+
+  void fillArea(final View view, boolean quick) {
+    if (isFilledArea()) return;
+    record();
+
+    final float startScale = getMatrixScale();
+    final float endScale = getMinMatrixScale(this);
+
+    final PointF midPoint = new PointF();
+    midPoint.set(getCurrentDrawableCenterPoint());
+
+    tempMatrix.set(matrix);
+    tempMatrix.postScale(endScale / startScale, endScale / startScale, midPoint.x, midPoint.y);
+
+    RectF rectF = new RectF(drawableBounds);
+    tempMatrix.mapRect(rectF);
+
+    float offsetX = 0f;
+    float offsetY = 0f;
+
+    if (rectF.left > area.left()) {
+      offsetX = area.left() - rectF.left;
+    }
+
+    if (rectF.top > area.top()) {
+      offsetY = area.top() - rectF.top;
+    }
+
+    if (rectF.right < area.right()) {
+      offsetX = area.right() - rectF.right;
+    }
+
+    if (rectF.bottom < area.bottom()) {
+      offsetY = area.bottom() - rectF.bottom;
+    }
+
+    final float translateX = offsetX;
+    final float translateY = offsetY;
+
+    animator.end();
+    animator.removeAllUpdateListeners();
+    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+      @Override
+      public void onAnimationUpdate(ValueAnimator animation) {
+        float value = (float) animation.getAnimatedValue();
+        float scale = (startScale + (endScale - startScale) * value) / startScale;
+        float x = translateX * value;
+        float y = translateY * value;
+
+        zoom(scale, scale, midPoint);
+        postTranslate(x, y);
+        view.invalidate();
+      }
+    });
+
+    if (quick) {
+      animator.setDuration(0);
+    } else {
+      animator.setDuration(duration);
+    }
+    animator.start();
+  }
+
+  void updateWith(final MotionEvent event, final Line line) {
+    float offsetX = (event.getX() - previousMoveX) / 2;
+    float offsetY = (event.getY() - previousMoveY) / 2;
+
+    if (!canFilledArea()) {
+      final Area area = getArea();
+      float deltaScale = getMinMatrixScale(this) / getMatrixScale();
+      postScale(deltaScale, deltaScale, area.getCenterPoint());
+      record();
+
+      previousMoveX = event.getX();
+      previousMoveY = event.getY();
+    }
+
+    if (line.direction() == Line.Direction.HORIZONTAL) {
+      translate(0, offsetY);
+    } else if (line.direction() == Line.Direction.VERTICAL) {
+      translate(offsetX, 0);
+    }
+
+    final RectF rectF = getCurrentDrawableBounds();
+    final Area area = getArea();
+    float moveY = 0f;
+
+    if (rectF.top > area.top()) {
+      moveY = area.top() - rectF.top;
+    }
+
+    if (rectF.bottom < area.bottom()) {
+      moveY = area.bottom() - rectF.bottom;
+    }
+
+    float moveX = 0f;
+
+    if (rectF.left > area.left()) {
+      moveX = area.left() - rectF.left;
+    }
+
+    if (rectF.right < area.right()) {
+      moveX = area.right() - rectF.right;
+    }
+
+    if (moveX != 0 || moveY != 0) {
+      previousMoveX = event.getX();
+      previousMoveY = event.getY();
+      postTranslate(moveX, moveY);
+      record();
+    }
+  }
+
+  public void setArea(Area area) {
+    this.area = area;
+  }
+
+  boolean isAnimateRunning() {
+    return animator.isRunning();
+  }
+
+  void setAnimateDuration(int duration) {
+    this.duration = duration;
+  }
+}

+ 94 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzleUtils.java

@@ -0,0 +1,94 @@
+package com.huantansheng.easyphotos.models.puzzle;
+
+
+import com.huantansheng.easyphotos.models.puzzle.template.slant.OneSlantLayout;
+import com.huantansheng.easyphotos.models.puzzle.template.slant.SlantLayoutHelper;
+import com.huantansheng.easyphotos.models.puzzle.template.slant.ThreeSlantLayout;
+import com.huantansheng.easyphotos.models.puzzle.template.slant.TwoSlantLayout;
+import com.huantansheng.easyphotos.models.puzzle.template.straight.EightStraightLayout;
+import com.huantansheng.easyphotos.models.puzzle.template.straight.FiveStraightLayout;
+import com.huantansheng.easyphotos.models.puzzle.template.straight.FourStraightLayout;
+import com.huantansheng.easyphotos.models.puzzle.template.straight.NineStraightLayout;
+import com.huantansheng.easyphotos.models.puzzle.template.straight.OneStraightLayout;
+import com.huantansheng.easyphotos.models.puzzle.template.straight.SevenStraightLayout;
+import com.huantansheng.easyphotos.models.puzzle.template.straight.SixStraightLayout;
+import com.huantansheng.easyphotos.models.puzzle.template.straight.StraightLayoutHelper;
+import com.huantansheng.easyphotos.models.puzzle.template.straight.ThreeStraightLayout;
+import com.huantansheng.easyphotos.models.puzzle.template.straight.TwoStraightLayout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author wupanjie
+ */
+public class PuzzleUtils {
+  private static final String TAG = "PuzzleUtils";
+
+  private PuzzleUtils() {
+    //no instance
+  }
+
+  public static PuzzleLayout getPuzzleLayout(int type, int borderSize, int themeId) {
+    if (type == 0) {
+      switch (borderSize) {
+        case 1:
+          return new OneSlantLayout(themeId);
+        case 2:
+          return new TwoSlantLayout(themeId);
+        case 3:
+          return new ThreeSlantLayout(themeId);
+        default:
+          return new OneSlantLayout(themeId);
+      }
+    } else {
+      switch (borderSize) {
+        case 1:
+          return new OneStraightLayout(themeId);
+        case 2:
+          return new TwoStraightLayout(themeId);
+        case 3:
+          return new ThreeStraightLayout(themeId);
+        case 4:
+          return new FourStraightLayout(themeId);
+        case 5:
+          return new FiveStraightLayout(themeId);
+        case 6:
+          return new SixStraightLayout(themeId);
+        case 7:
+          return new SevenStraightLayout(themeId);
+        case 8:
+          return new EightStraightLayout(themeId);
+        case 9:
+          return new NineStraightLayout(themeId);
+        default:
+          return new OneStraightLayout(themeId);
+      }
+    }
+  }
+
+  public static List<PuzzleLayout> getAllPuzzleLayouts() {
+    List<PuzzleLayout> puzzleLayouts = new ArrayList<>();
+    //slant layout
+    puzzleLayouts.addAll(SlantLayoutHelper.getAllThemeLayout(2));
+    puzzleLayouts.addAll(SlantLayoutHelper.getAllThemeLayout(3));
+
+    // straight layout
+    puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(2));
+    puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(3));
+    puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(4));
+    puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(5));
+    puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(6));
+    puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(7));
+    puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(8));
+    puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(9));
+    return puzzleLayouts;
+  }
+
+  public static List<PuzzleLayout> getPuzzleLayouts(int pieceCount) {
+    List<PuzzleLayout> puzzleLayouts = new ArrayList<>();
+    puzzleLayouts.addAll(SlantLayoutHelper.getAllThemeLayout(pieceCount));
+    puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(pieceCount));
+    return puzzleLayouts;
+  }
+}

+ 791 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzleView.java

@@ -0,0 +1,791 @@
+package com.huantansheng.easyphotos.models.puzzle;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+
+import androidx.core.content.ContextCompat;
+
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.huantansheng.easyphotos.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author wupanjie
+ */
+public class PuzzleView extends View {
+    private static final String TAG = "SlantPuzzleView";
+
+    private enum ActionMode {
+        NONE,
+        DRAG,
+        ZOOM,
+        MOVE,
+        SWAP
+    }
+
+    private ActionMode currentMode = ActionMode.NONE;
+
+    private List<PuzzlePiece> puzzlePieces = new ArrayList<>();
+
+    private List<PuzzlePiece> needChangePieces = new ArrayList<>();
+    private PuzzleLayout puzzleLayout;
+
+    private RectF bounds;
+    private int lineSize;
+
+    private int duration;
+    private Line handlingLine;
+
+    private PuzzlePiece handlingPiece;
+    private PuzzlePiece replacePiece;
+    private PuzzlePiece previousHandlingPiece;
+
+    private Paint linePaint;
+    private Paint selectedAreaPaint;
+    private Paint handleBarPaint;
+
+    private float downX;
+    private float downY;
+    private float previousDistance;
+    private PointF midPoint;
+    private boolean needDrawLine;
+
+    private boolean needDrawOuterLine;
+    private boolean touchEnable = true;
+    private int lineColor;
+
+    private int selectedLineColor;
+    private int handleBarColor;
+    private float piecePadding;
+    private float pieceRadian;
+
+    private boolean needResetPieceMatrix = true;
+
+    private OnPieceSelectedListener onPieceSelectedListener;
+
+    private Runnable switchToSwapAction = new Runnable() {
+        @Override
+        public void run() {
+            currentMode = ActionMode.SWAP;
+            invalidate();
+        }
+    };
+
+    public PuzzleView(Context context) {
+        this(context, null);
+    }
+
+    public PuzzleView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public PuzzleView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init(context, attrs);
+    }
+
+    private void init(Context context, AttributeSet attrs) {
+        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PuzzleView);
+        lineSize = ta.getInt(R.styleable.PuzzleView_line_size, 4);
+        lineColor = ta.getColor(R.styleable.PuzzleView_line_color,
+                ContextCompat.getColor(getContext(), R.color.easy_photos_fg_primary));
+        selectedLineColor =
+                ta.getColor(R.styleable.PuzzleView_selected_line_color,
+                        ContextCompat.getColor(getContext(), R.color.easy_photos_fg_accent));
+        handleBarColor =
+                ta.getColor(R.styleable.PuzzleView_handle_bar_color,
+                        ContextCompat.getColor(getContext(), R.color.easy_photos_fg_accent));
+        piecePadding = ta.getDimensionPixelSize(R.styleable.PuzzleView_piece_padding, 0);
+        needDrawLine = ta.getBoolean(R.styleable.PuzzleView_need_draw_line, false);
+        needDrawOuterLine = ta.getBoolean(R.styleable.PuzzleView_need_draw_outer_line, false);
+        duration = ta.getInt(R.styleable.PuzzleView_animation_duration, 300);
+        pieceRadian = ta.getFloat(R.styleable.PuzzleView_radian, 0f);
+        ta.recycle();
+
+        bounds = new RectF();
+
+        // init some paint
+        linePaint = new Paint();
+        linePaint.setAntiAlias(true);
+        linePaint.setColor(lineColor);
+        linePaint.setStrokeWidth(lineSize);
+        linePaint.setStyle(Paint.Style.STROKE);
+        linePaint.setStrokeJoin(Paint.Join.ROUND);
+        linePaint.setStrokeCap(Paint.Cap.SQUARE);
+
+        selectedAreaPaint = new Paint();
+        selectedAreaPaint.setAntiAlias(true);
+        selectedAreaPaint.setStyle(Paint.Style.STROKE);
+        selectedAreaPaint.setStrokeJoin(Paint.Join.ROUND);
+        selectedAreaPaint.setStrokeCap(Paint.Cap.ROUND);
+        selectedAreaPaint.setColor(selectedLineColor);
+        selectedAreaPaint.setStrokeWidth(lineSize);
+
+        handleBarPaint = new Paint();
+        handleBarPaint.setAntiAlias(true);
+        handleBarPaint.setStyle(Paint.Style.FILL);
+        handleBarPaint.setColor(handleBarColor);
+        handleBarPaint.setStrokeWidth(lineSize * 3);
+
+        midPoint = new PointF();
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+        resetPuzzleBounds();
+
+        if (puzzlePieces.size() != 0) {
+            int size = puzzlePieces.size();
+            for (int i = 0; i < size; i++) {
+                PuzzlePiece piece = puzzlePieces.get(i);
+                piece.setArea(puzzleLayout.getArea(i));
+                if (needResetPieceMatrix) {
+                    piece.set(MatrixUtils.generateMatrix(piece, 0f));
+                } else {
+                    piece.fillArea(this, true);
+                }
+            }
+        }
+        invalidate();
+    }
+
+    private void resetPuzzleBounds() {
+        bounds.left = getPaddingLeft();
+        bounds.top = getPaddingTop();
+        bounds.right = getWidth() - getPaddingRight();
+        bounds.bottom = getHeight() - getPaddingBottom();
+
+        if (puzzleLayout != null) {
+            puzzleLayout.reset();
+            puzzleLayout.setOuterBounds(bounds);
+            puzzleLayout.layout();
+            puzzleLayout.setPadding(piecePadding);
+            puzzleLayout.setRadian(pieceRadian);
+        }
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        if (puzzleLayout == null) {
+            return;
+        }
+
+        linePaint.setStrokeWidth(lineSize);
+        selectedAreaPaint.setStrokeWidth(lineSize);
+        handleBarPaint.setStrokeWidth(lineSize * 3);
+
+        // draw pieces
+        int count = puzzleLayout.getAreaCount();
+        for (int i = 0; i < count; i++) {
+            if (i >= puzzlePieces.size()) {
+                break;
+            }
+
+            PuzzlePiece piece = puzzlePieces.get(i);
+
+            if (piece == handlingPiece && currentMode == ActionMode.SWAP) {
+                continue;
+            }
+
+            if (puzzlePieces.size() > i) {
+                piece.draw(canvas);
+            }
+        }
+
+        // draw outer bounds
+        if (needDrawOuterLine) {
+            for (Line outerLine : puzzleLayout.getOuterLines()) {
+                drawLine(canvas, outerLine);
+            }
+        }
+
+        // draw slant lines
+        if (needDrawLine) {
+            for (Line line : puzzleLayout.getLines()) {
+                drawLine(canvas, line);
+            }
+        }
+
+        // draw selected area
+        if (handlingPiece != null && currentMode != ActionMode.SWAP) {
+            drawSelectedArea(canvas, handlingPiece);
+        }
+
+        // draw swap piece
+        if (handlingPiece != null && currentMode == ActionMode.SWAP) {
+            handlingPiece.draw(canvas, 128);
+            if (replacePiece != null) {
+                drawSelectedArea(canvas, replacePiece);
+            }
+        }
+    }
+
+    private void drawSelectedArea(Canvas canvas, PuzzlePiece piece) {
+        final Area area = piece.getArea();
+        // draw select area
+        canvas.drawPath(area.getAreaPath(), selectedAreaPaint);
+
+        // draw handle bar
+        for (Line line : area.getLines()) {
+            if (puzzleLayout.getLines().contains(line)) {
+                PointF[] handleBarPoints = area.getHandleBarPoints(line);
+                canvas.drawLine(handleBarPoints[0].x, handleBarPoints[0].y, handleBarPoints[1].x,
+                        handleBarPoints[1].y, handleBarPaint);
+                canvas.drawCircle(handleBarPoints[0].x, handleBarPoints[0].y, lineSize * 3 / 2,
+                        handleBarPaint);
+                canvas.drawCircle(handleBarPoints[1].x, handleBarPoints[1].y, lineSize * 3 / 2,
+                        handleBarPaint);
+            }
+        }
+    }
+
+    private void drawLine(Canvas canvas, Line line) {
+        canvas.drawLine(line.startPoint().x, line.startPoint().y, line.endPoint().x,
+                line.endPoint().y,
+                linePaint);
+    }
+
+    public void setPuzzleLayout(PuzzleLayout puzzleLayout) {
+        clearPieces();
+
+        this.puzzleLayout = puzzleLayout;
+
+        this.puzzleLayout.setOuterBounds(bounds);
+        this.puzzleLayout.layout();
+
+        invalidate();
+    }
+
+    public PuzzleLayout getPuzzleLayout() {
+        return this.puzzleLayout;
+    }
+
+    @SuppressLint("ClickableViewAccessibility")
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (!touchEnable) {
+            return super.onTouchEvent(event);
+        }
+        switch (event.getAction() & MotionEvent.ACTION_MASK) {
+            case MotionEvent.ACTION_DOWN:
+                downX = event.getX();
+                downY = event.getY();
+
+                decideActionMode(event);
+                prepareAction(event);
+                break;
+
+            case MotionEvent.ACTION_POINTER_DOWN:
+                previousDistance = calculateDistance(event);
+                calculateMidPoint(event, midPoint);
+
+                decideActionMode(event);
+                break;
+
+            case MotionEvent.ACTION_MOVE:
+                performAction(event);
+
+                if ((Math.abs(event.getX() - downX) > 10 || Math.abs(event.getY() - downY) > 10)
+                        && currentMode != ActionMode.SWAP) {
+                    removeCallbacks(switchToSwapAction);
+                }
+
+                break;
+
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                finishAction(event);
+                currentMode = ActionMode.NONE;
+                removeCallbacks(switchToSwapAction);
+                break;
+        }
+
+        invalidate();
+        return true;
+    }
+
+    // 决定应该执行什么Action
+    private void decideActionMode(MotionEvent event) {
+        for (PuzzlePiece piece : puzzlePieces) {
+            if (piece.isAnimateRunning()) {
+                currentMode = ActionMode.NONE;
+                return;
+            }
+        }
+
+        if (event.getPointerCount() == 1) {
+            handlingLine = findHandlingLine();
+            if (handlingLine != null) {
+                currentMode = ActionMode.MOVE;
+            } else {
+                handlingPiece = findHandlingPiece();
+
+                if (handlingPiece != null) {
+                    currentMode = ActionMode.DRAG;
+
+                    postDelayed(switchToSwapAction, 500);
+                }
+            }
+        } else if (event.getPointerCount() > 1) {
+            if (handlingPiece != null
+                    && handlingPiece.contains(event.getX(1), event.getY(1))
+                    && currentMode == ActionMode.DRAG) {
+                currentMode = ActionMode.ZOOM;
+            }
+        }
+    }
+
+    // 执行Action前的准备工作
+    @SuppressWarnings("unused")
+    private void prepareAction(MotionEvent event) {
+        switch (currentMode) {
+            case NONE:
+                break;
+            case DRAG:
+                handlingPiece.record();
+                break;
+            case ZOOM:
+                handlingPiece.record();
+                break;
+            case MOVE:
+                handlingLine.prepareMove();
+                needChangePieces.clear();
+                needChangePieces.addAll(findNeedChangedPieces());
+                for (PuzzlePiece piece : needChangePieces) {
+                    piece.record();
+                    piece.setPreviousMoveX(downX);
+                    piece.setPreviousMoveY(downY);
+                }
+                break;
+        }
+    }
+
+    // 执行Action
+    private void performAction(MotionEvent event) {
+        switch (currentMode) {
+            case NONE:
+                break;
+            case DRAG:
+                dragPiece(handlingPiece, event);
+                break;
+            case ZOOM:
+                zoomPiece(handlingPiece, event);
+                break;
+            case SWAP:
+                dragPiece(handlingPiece, event);
+                replacePiece = findReplacePiece(event);
+                break;
+            case MOVE:
+                moveLine(handlingLine, event);
+                break;
+        }
+    }
+
+    // 结束Action
+    private void finishAction(MotionEvent event) {
+        switch (currentMode) {
+            case NONE:
+                break;
+            case DRAG:
+                if (handlingPiece != null && !handlingPiece.isFilledArea()) {
+                    handlingPiece.moveToFillArea(this);
+                }
+
+                if (previousHandlingPiece == handlingPiece
+                        && Math.abs(downX - event.getX()) < 3
+                        && Math.abs(downY - event.getY()) < 3) {
+
+                    handlingPiece = null;
+                }
+
+                // trigger listener
+                if (onPieceSelectedListener != null) {
+                    onPieceSelectedListener.onPieceSelected(handlingPiece,
+                            puzzlePieces.indexOf(handlingPiece));
+                }
+
+                previousHandlingPiece = handlingPiece;
+                break;
+            case ZOOM:
+                if (handlingPiece != null && !handlingPiece.isFilledArea()) {
+                    if (handlingPiece.canFilledArea()) {
+                        handlingPiece.moveToFillArea(this);
+                    } else {
+                        handlingPiece.fillArea(this, false);
+                    }
+                }
+                previousHandlingPiece = handlingPiece;
+                break;
+            case MOVE:
+                break;
+            case SWAP:
+                if (handlingPiece != null && replacePiece != null) {
+                    Drawable temp = handlingPiece.getDrawable();
+
+                    handlingPiece.setDrawable(replacePiece.getDrawable());
+                    replacePiece.setDrawable(temp);
+
+                    handlingPiece.fillArea(this, true);
+                    replacePiece.fillArea(this, true);
+
+                    handlingPiece = null;
+                    replacePiece = null;
+                    previousHandlingPiece = null;
+                    // trigger listener
+                    if (onPieceSelectedListener != null) {
+                        onPieceSelectedListener.onPieceSelected(null,
+                                0);
+                    }
+                }
+                break;
+        }
+
+        handlingLine = null;
+        needChangePieces.clear();
+    }
+
+    private void moveLine(Line line, MotionEvent event) {
+        if (line == null || event == null) return;
+
+        boolean needUpdate;
+        if (line.direction() == Line.Direction.HORIZONTAL) {
+            needUpdate = line.move(event.getY() - downY, 80);
+        } else {
+            needUpdate = line.move(event.getX() - downX, 80);
+        }
+
+        if (needUpdate) {
+            puzzleLayout.update();
+            updatePiecesInArea(line, event);
+        }
+    }
+
+    private void updatePiecesInArea(Line line, MotionEvent event) {
+        int size = needChangePieces.size();
+        for (int i = 0; i < size; i++) {
+            needChangePieces.get(i).updateWith(event, line);
+        }
+    }
+
+    private void zoomPiece(PuzzlePiece piece, MotionEvent event) {
+        if (piece == null || event == null || event.getPointerCount() < 2) return;
+        float scale = calculateDistance(event) / previousDistance;
+        piece.zoomAndTranslate(scale, scale, midPoint, event.getX() - downX, event.getY() - downY);
+    }
+
+    private void dragPiece(PuzzlePiece piece, MotionEvent event) {
+        if (piece == null || event == null) return;
+        piece.translate(event.getX() - downX, event.getY() - downY);
+    }
+
+    public void replace(Bitmap bitmap) {
+        replace(new BitmapDrawable(getResources(), bitmap));
+    }
+
+    public void replace(final Drawable bitmapDrawable) {
+        post(new Runnable() {
+            @Override
+            public void run() {
+                if (handlingPiece == null) {
+                    return;
+                }
+
+                handlingPiece.setDrawable(bitmapDrawable);
+                handlingPiece.set(MatrixUtils.generateMatrix(handlingPiece, 0f));
+
+                postInvalidate();
+            }
+        });
+    }
+
+    public void flipVertically() {
+        if (handlingPiece == null) {
+            return;
+        }
+
+        handlingPiece.postFlipVertically();
+        handlingPiece.record();
+
+        invalidate();
+    }
+
+    public void flipHorizontally() {
+        if (handlingPiece == null) {
+            return;
+        }
+
+        handlingPiece.postFlipHorizontally();
+        handlingPiece.record();
+
+        invalidate();
+    }
+
+    public void rotate(float degree) {
+        if (handlingPiece == null) {
+            return;
+        }
+
+        handlingPiece.postRotate(degree);
+        handlingPiece.record();
+
+        invalidate();
+    }
+
+    private PuzzlePiece findHandlingPiece() {
+        for (PuzzlePiece piece : puzzlePieces) {
+            if (piece.contains(downX, downY)) {
+                return piece;
+            }
+        }
+        return null;
+    }
+
+    private Line findHandlingLine() {
+        for (Line line : puzzleLayout.getLines()) {
+            if (line.contains(downX, downY, 40)) {
+                return line;
+            }
+        }
+        return null;
+    }
+
+    private PuzzlePiece findReplacePiece(MotionEvent event) {
+        for (PuzzlePiece piece : puzzlePieces) {
+            if (piece.contains(event.getX(), event.getY())) {
+                return piece;
+            }
+        }
+        return null;
+    }
+
+    private List<PuzzlePiece> findNeedChangedPieces() {
+        if (handlingLine == null) return new ArrayList<>();
+
+        List<PuzzlePiece> needChanged = new ArrayList<>();
+
+        for (PuzzlePiece piece : puzzlePieces) {
+            if (piece.contains(handlingLine)) {
+                needChanged.add(piece);
+            }
+        }
+
+        return needChanged;
+    }
+
+    private float calculateDistance(MotionEvent event) {
+        float x = event.getX(0) - event.getX(1);
+        float y = event.getY(0) - event.getY(1);
+
+        return (float) Math.sqrt(x * x + y * y);
+    }
+
+    private void calculateMidPoint(MotionEvent event, PointF point) {
+        point.x = (event.getX(0) + event.getX(1)) / 2;
+        point.y = (event.getY(0) + event.getY(1)) / 2;
+    }
+
+    public void reset() {
+        clearPieces();
+        if (puzzleLayout != null) {
+            puzzleLayout.reset();
+        }
+    }
+
+    public void clearPieces() {
+        handlingLine = null;
+        handlingPiece = null;
+        replacePiece = null;
+        needChangePieces.clear();
+        puzzlePieces.clear();
+    }
+
+    public void addPieces(List<Bitmap> bitmaps) {
+        for (Bitmap bitmap : bitmaps) {
+            addPiece(bitmap);
+        }
+
+        postInvalidate();
+    }
+
+    public void addPiece(Bitmap bitmap) {
+        BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), bitmap);
+        bitmapDrawable.setAntiAlias(true);
+        bitmapDrawable.setFilterBitmap(true);
+
+        addPiece(bitmapDrawable);
+    }
+
+    public void addPiece(Drawable drawable) {
+        int position = puzzlePieces.size();
+
+        if (position >= puzzleLayout.getAreaCount()) {
+            Log.e(TAG, "addPiece: can not add more. the current puzzle layout can contains "
+                    + puzzleLayout.getAreaCount()
+                    + " puzzle piece.");
+            return;
+        }
+
+        final Area area = puzzleLayout.getArea(position);
+        area.setPadding(piecePadding);
+
+        PuzzlePiece piece = new PuzzlePiece(drawable, area, new Matrix());
+
+        final Matrix matrix = MatrixUtils.generateMatrix(area, drawable, 0f);
+        piece.set(matrix);
+
+        piece.setAnimateDuration(duration);
+
+        puzzlePieces.add(piece);
+
+        setPiecePadding(piecePadding);
+        setPieceRadian(pieceRadian);
+
+        invalidate();
+    }
+
+    public void setAnimateDuration(int duration) {
+        this.duration = duration;
+        for (PuzzlePiece piece : puzzlePieces) {
+            piece.setAnimateDuration(duration);
+        }
+    }
+
+    public boolean isNeedDrawLine() {
+        return needDrawLine;
+    }
+
+    public void setNeedDrawLine(boolean needDrawLine) {
+        this.needDrawLine = needDrawLine;
+        handlingPiece = null;
+        previousHandlingPiece = null;
+        invalidate();
+    }
+
+    public boolean isNeedDrawOuterLine() {
+        return needDrawOuterLine;
+    }
+
+    public void setNeedDrawOuterLine(boolean needDrawOuterLine) {
+        this.needDrawOuterLine = needDrawOuterLine;
+        invalidate();
+    }
+
+    public int getLineColor() {
+        return lineColor;
+    }
+
+    public void setLineColor(int lineColor) {
+        this.lineColor = lineColor;
+        this.linePaint.setColor(lineColor);
+        invalidate();
+    }
+
+    public int getLineSize() {
+        return lineSize;
+    }
+
+    public void setLineSize(int lineSize) {
+        this.lineSize = lineSize;
+        invalidate();
+    }
+
+    public int getSelectedLineColor() {
+        return selectedLineColor;
+    }
+
+    public void setSelectedLineColor(int selectedLineColor) {
+        this.selectedLineColor = selectedLineColor;
+        this.selectedAreaPaint.setColor(selectedLineColor);
+        invalidate();
+    }
+
+    public int getHandleBarColor() {
+        return handleBarColor;
+    }
+
+    public void setHandleBarColor(int handleBarColor) {
+        this.handleBarColor = handleBarColor;
+        this.handleBarPaint.setColor(handleBarColor);
+        invalidate();
+    }
+
+    public boolean isTouchEnable() {
+        return touchEnable;
+    }
+
+    public void setTouchEnable(boolean touchEnable) {
+        this.touchEnable = touchEnable;
+    }
+
+    public void clearHandling() {
+        handlingPiece = null;
+        handlingLine = null;
+        replacePiece = null;
+        previousHandlingPiece = null;
+        needChangePieces.clear();
+    }
+
+    public void setPiecePadding(float padding) {
+        this.piecePadding = padding;
+        if (puzzleLayout != null) {
+            puzzleLayout.setPadding(padding);
+        }
+
+        invalidate();
+    }
+
+    public void setPieceRadian(float radian) {
+        this.pieceRadian = radian;
+        if (puzzleLayout != null) {
+            puzzleLayout.setRadian(radian);
+        }
+
+        invalidate();
+    }
+
+    @Override
+    public void setBackgroundColor(int color) {
+        super.setBackgroundColor(color);
+        if (puzzleLayout != null) {
+            puzzleLayout.setColor(color);
+        }
+    }
+
+    public void setNeedResetPieceMatrix(boolean needResetPieceMatrix) {
+        this.needResetPieceMatrix = needResetPieceMatrix;
+    }
+
+    public float getPiecePadding() {
+        return piecePadding;
+    }
+
+    public float getPieceRadian() {
+        return pieceRadian;
+    }
+
+    public void setOnPieceSelectedListener(OnPieceSelectedListener onPieceSelectedListener) {
+        this.onPieceSelectedListener = onPieceSelectedListener;
+    }
+
+    public interface OnPieceSelectedListener {
+        void onPieceSelected(PuzzlePiece piece, int position);
+    }
+}

+ 32 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/SquarePuzzleView.java

@@ -0,0 +1,32 @@
+package com.huantansheng.easyphotos.models.puzzle;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+/**
+ * @author wupanjie
+ */
+public class SquarePuzzleView extends PuzzleView {
+  public SquarePuzzleView(Context context) {
+    super(context);
+  }
+
+  public SquarePuzzleView(Context context, AttributeSet attrs) {
+    super(context, attrs);
+  }
+
+  public SquarePuzzleView(Context context, AttributeSet attrs, int defStyleAttr) {
+    super(context, attrs, defStyleAttr);
+  }
+
+  @Override
+  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+    int width = getMeasuredWidth();
+    int height = getMeasuredHeight();
+    int length = width > height ? height : width;
+
+    setMeasuredDimension(length, length);
+  }
+}

+ 34 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/CrossoverPointF.java

@@ -0,0 +1,34 @@
+package com.huantansheng.easyphotos.models.puzzle.slant;
+
+import android.graphics.PointF;
+
+/**
+ * 两条线的交点
+ *
+ * @author wupanjie
+ */
+class CrossoverPointF extends PointF {
+  SlantLine horizontal;
+  SlantLine vertical;
+
+  CrossoverPointF() {
+
+  }
+
+  CrossoverPointF(float x, float y) {
+    this.x = x;
+    this.y = y;
+  }
+
+  CrossoverPointF(SlantLine horizontal, SlantLine vertical) {
+    this.horizontal = horizontal;
+    this.vertical = vertical;
+  }
+
+  void update() {
+    if (horizontal == null || vertical == null){
+      return;
+    }
+    SlantUtils.intersectionOfLines(this, horizontal, vertical);
+  }
+}

+ 294 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantArea.java

@@ -0,0 +1,294 @@
+package com.huantansheng.easyphotos.models.puzzle.slant;
+
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.graphics.RectF;
+
+
+import com.huantansheng.easyphotos.models.puzzle.Area;
+import com.huantansheng.easyphotos.models.puzzle.Line;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+
+import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.distance;
+import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.getPoint;
+import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.intersectionOfLines;
+
+
+/**
+ * @author wupanjie
+ */
+class SlantArea implements Area {
+  SlantLine lineLeft;
+  SlantLine lineTop;
+  SlantLine lineRight;
+  SlantLine lineBottom;
+
+  CrossoverPointF leftTop;
+  CrossoverPointF leftBottom;
+  CrossoverPointF rightTop;
+  CrossoverPointF rightBottom;
+
+  private PointF tempPoint;
+
+  private float paddingLeft;
+  private float paddingTop;
+  private float paddingRight;
+  private float paddingBottom;
+  private float radian;
+
+  private Path areaPath = new Path();
+  private RectF areaRect = new RectF();
+  private PointF[] handleBarPoints = new PointF[2];
+
+  SlantArea() {
+    handleBarPoints[0] = new PointF();
+    handleBarPoints[1] = new PointF();
+
+    leftTop = new CrossoverPointF();
+    leftBottom = new CrossoverPointF();
+    rightTop = new CrossoverPointF();
+    rightBottom = new CrossoverPointF();
+
+    tempPoint = new PointF();
+  }
+
+  SlantArea(SlantArea src) {
+    this();
+    this.lineLeft = src.lineLeft;
+    this.lineTop = src.lineTop;
+    this.lineRight = src.lineRight;
+    this.lineBottom = src.lineBottom;
+
+    this.leftTop = src.leftTop;
+    this.leftBottom = src.leftBottom;
+    this.rightTop = src.rightTop;
+    this.rightBottom = src.rightBottom;
+
+    updateCornerPoints();
+  }
+
+  @Override
+  public float left() {
+    return Math.min(leftTop.x, leftBottom.x) + paddingLeft;
+  }
+
+  @Override
+  public float top() {
+    return Math.min(leftTop.y, rightTop.y) + paddingTop;
+  }
+
+  @Override
+  public float right() {
+    return Math.max(rightTop.x, rightBottom.x) - paddingRight;
+  }
+
+  @Override
+  public float bottom() {
+    return Math.max(leftBottom.y, rightBottom.y) - paddingBottom;
+  }
+
+  @Override
+  public float centerX() {
+    return (left() + right()) / 2;
+  }
+
+  @Override
+  public float centerY() {
+    return (top() + bottom()) / 2;
+  }
+
+  @Override
+  public float width() {
+    return right() - left();
+  }
+
+  @Override
+  public float height() {
+    return bottom() - top();
+  }
+
+  @Override
+  public PointF getCenterPoint() {
+    return new PointF(centerX(), centerY());
+  }
+
+  public Path getAreaPath() {
+    areaPath.reset();
+
+    if (radian > 0) {
+      float tempRatio = radian / distance(leftTop, leftBottom);
+      getPoint(tempPoint, leftTop, leftBottom, Line.Direction.VERTICAL, tempRatio);
+      tempPoint.offset(paddingLeft, paddingTop);
+      areaPath.moveTo(tempPoint.x, tempPoint.y);
+
+      tempRatio = radian / distance(leftTop, rightTop);
+      getPoint(tempPoint, leftTop, rightTop, Line.Direction.HORIZONTAL, tempRatio);
+      tempPoint.offset(paddingLeft, paddingTop);
+      areaPath.quadTo(leftTop.x + paddingLeft, leftTop.y + paddingTop, tempPoint.x, tempPoint.y);
+
+      tempRatio = 1 - tempRatio;
+      getPoint(tempPoint, leftTop, rightTop, Line.Direction.HORIZONTAL, tempRatio);
+      tempPoint.offset(-paddingRight, paddingTop);
+      areaPath.lineTo(tempPoint.x, tempPoint.y);
+
+      tempRatio = radian / distance(rightTop, rightBottom);
+      getPoint(tempPoint, rightTop, rightBottom, Line.Direction.VERTICAL, tempRatio);
+      tempPoint.offset(-paddingRight, paddingTop);
+      areaPath.quadTo(rightTop.x - paddingLeft, rightTop.y + paddingTop, tempPoint.x, tempPoint.y);
+
+      tempRatio = 1 - tempRatio;
+      getPoint(tempPoint, rightTop, rightBottom, Line.Direction.VERTICAL, tempRatio);
+      tempPoint.offset(-paddingRight, -paddingBottom);
+      areaPath.lineTo(tempPoint.x, tempPoint.y);
+
+      tempRatio = 1 - radian / distance(leftBottom, rightBottom);
+      getPoint(tempPoint, leftBottom, rightBottom, Line.Direction.HORIZONTAL, tempRatio);
+      tempPoint.offset(-paddingRight, -paddingBottom);
+      areaPath.quadTo(rightBottom.x - paddingRight, rightBottom.y - paddingTop, tempPoint.x, tempPoint.y);
+
+      tempRatio = 1 - tempRatio;
+      getPoint(tempPoint, leftBottom, rightBottom, Line.Direction.HORIZONTAL, tempRatio);
+      tempPoint.offset(paddingLeft, -paddingBottom);
+      areaPath.lineTo(tempPoint.x, tempPoint.y);
+
+      tempRatio = 1 - radian / distance(leftTop, leftBottom);
+      getPoint(tempPoint, leftTop, leftBottom, Line.Direction.VERTICAL, tempRatio);
+      tempPoint.offset(paddingLeft, -paddingBottom);
+      areaPath.quadTo(leftBottom.x + paddingLeft, leftBottom.y - paddingBottom, tempPoint.x, tempPoint.y);
+
+      tempRatio = 1 - tempRatio;
+      getPoint(tempPoint, leftTop, leftBottom, Line.Direction.VERTICAL, tempRatio);
+      tempPoint.offset(paddingLeft, paddingTop);
+      areaPath.lineTo(tempPoint.x, tempPoint.y);
+    } else {
+      areaPath.moveTo(leftTop.x + paddingLeft, leftTop.y + paddingTop);
+      areaPath.lineTo(rightTop.x - paddingRight, rightTop.y + paddingTop);
+      areaPath.lineTo(rightBottom.x - paddingRight, rightBottom.y - paddingBottom);
+      areaPath.lineTo(leftBottom.x + paddingLeft, leftBottom.y - paddingBottom);
+      areaPath.lineTo(leftTop.x + paddingLeft, leftTop.y + paddingTop);
+    }
+    return areaPath;
+  }
+
+  @Override
+  public RectF getAreaRect() {
+    areaRect.set(left(), top(), right(), bottom());
+    return areaRect;
+  }
+
+  public boolean contains(float x, float y) {
+    return SlantUtils.contains(this, x, y);
+  }
+
+  @Override
+  public boolean contains(Line line) {
+    return lineLeft == line || lineTop == line || lineRight == line || lineBottom == line;
+  }
+
+  @Override
+  public boolean contains(PointF point) {
+    return contains(point.x, point.y);
+  }
+
+  @Override
+  public List<Line> getLines() {
+    return Arrays.asList((Line) lineLeft, lineTop, lineRight, lineBottom);
+  }
+
+  @Override
+  public PointF[] getHandleBarPoints(Line line) {
+    if (line == lineLeft) {
+      getPoint(handleBarPoints[0], leftTop, leftBottom, line.direction(), 0.25f);
+      getPoint(handleBarPoints[1], leftTop, leftBottom, line.direction(), 0.75f);
+      handleBarPoints[0].offset(paddingLeft, 0);
+      handleBarPoints[1].offset(paddingLeft, 0);
+    } else if (line == lineTop) {
+      getPoint(handleBarPoints[0], leftTop, rightTop, line.direction(), 0.25f);
+      getPoint(handleBarPoints[1], leftTop, rightTop, line.direction(), 0.75f);
+      handleBarPoints[0].offset(0, paddingTop);
+      handleBarPoints[1].offset(0, paddingTop);
+    } else if (line == lineRight) {
+      getPoint(handleBarPoints[0], rightTop, rightBottom, line.direction(), 0.25f);
+      getPoint(handleBarPoints[1], rightTop, rightBottom, line.direction(), 0.75f);
+      handleBarPoints[0].offset(-paddingRight, 0);
+      handleBarPoints[1].offset(-paddingRight, 0);
+    } else if (line == lineBottom) {
+      getPoint(handleBarPoints[0], leftBottom, rightBottom, line.direction(), 0.25f);
+      getPoint(handleBarPoints[1], leftBottom, rightBottom, line.direction(), 0.75f);
+      handleBarPoints[0].offset(0, -paddingBottom);
+      handleBarPoints[1].offset(0, -paddingBottom);
+    }
+    return handleBarPoints;
+  }
+
+  @Override
+  public float radian() {
+    return radian;
+  }
+
+  @Override
+  public void setRadian(float radian) {
+    this.radian = radian;
+  }
+
+  @Override
+  public float getPaddingLeft() {
+    return paddingLeft;
+  }
+
+  @Override
+  public float getPaddingTop() {
+    return paddingTop;
+  }
+
+  @Override
+  public float getPaddingRight() {
+    return paddingRight;
+  }
+
+  @Override
+  public float getPaddingBottom() {
+    return paddingBottom;
+  }
+
+  @Override
+  public void setPadding(float padding) {
+    setPadding(padding, padding, padding, padding);
+  }
+
+  @Override
+  public void setPadding(float paddingLeft, float paddingTop, float paddingRight, float paddingBottom) {
+    this.paddingLeft = paddingLeft;
+    this.paddingTop = paddingTop;
+    this.paddingRight = paddingRight;
+    this.paddingBottom = paddingBottom;
+  }
+
+  void updateCornerPoints() {
+    intersectionOfLines(leftTop, lineLeft, lineTop);
+    intersectionOfLines(leftBottom, lineLeft, lineBottom);
+    intersectionOfLines(rightTop, lineRight, lineTop);
+    intersectionOfLines(rightBottom, lineRight, lineBottom);
+  }
+
+  static class AreaComparator implements Comparator<SlantArea> {
+
+    @Override
+    public int compare(SlantArea one, SlantArea two) {
+      if (one.leftTop.y < two.leftTop.y) {
+        return -1;
+      } else if (one.leftTop.y == two.leftTop.y) {
+        if (one.leftTop.x < two.leftTop.x) {
+          return -1;
+        } else {
+          return 1;
+        }
+      } else {
+        return 1;
+      }
+    }
+  }
+}

+ 173 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantLine.java

@@ -0,0 +1,173 @@
+package com.huantansheng.easyphotos.models.puzzle.slant;
+
+import android.graphics.PointF;
+
+import com.huantansheng.easyphotos.models.puzzle.Line;
+
+import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.intersectionOfLines;
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+import static java.lang.Math.pow;
+import static java.lang.Math.sqrt;
+
+/**
+ * 分为两种斜线,横谢线和竖线线
+ * 横斜线-->start为左边的点,end为右边的点
+ * 竖斜线-->start为上面的点,end为下面的点
+ *
+ * @author wupanjie
+ */
+class SlantLine implements Line {
+  CrossoverPointF start;
+  CrossoverPointF end;
+
+  // 移动前的点
+  private PointF previousStart = new PointF();
+  private PointF previousEnd = new PointF();
+
+  public final Line.Direction direction;
+
+  SlantLine attachLineStart;
+  SlantLine attachLineEnd;
+
+  Line upperLine;
+  Line lowerLine;
+
+  SlantLine(Line.Direction direction) {
+    this.direction = direction;
+  }
+
+  SlantLine(CrossoverPointF start, CrossoverPointF end, Line.Direction direction) {
+    this.start = start;
+    this.end = end;
+    this.direction = direction;
+  }
+
+  public float length() {
+    return (float) sqrt(pow(end.x - start.x, 2) + pow(end.y - start.y, 2));
+  }
+
+  @Override
+  public PointF startPoint() {
+    return start;
+  }
+
+  @Override
+  public PointF endPoint() {
+    return end;
+  }
+
+  @Override
+  public Line lowerLine() {
+    return lowerLine;
+  }
+
+  @Override
+  public Line upperLine() {
+    return upperLine;
+  }
+
+  @Override
+  public Line attachStartLine() {
+    return attachLineStart;
+  }
+
+  @Override
+  public Line attachEndLine() {
+    return attachLineEnd;
+  }
+
+  @Override
+  public void setLowerLine(Line lowerLine) {
+    this.lowerLine = lowerLine;
+  }
+
+  @Override
+  public void setUpperLine(Line upperLine) {
+    this.upperLine = upperLine;
+  }
+
+  @Override
+  public Direction direction() {
+    return direction;
+  }
+
+  @Override
+  public float slope() {
+    return SlantUtils.calculateSlope(this);
+  }
+
+  public boolean contains(float x, float y, float extra) {
+    return SlantUtils.contains(this, x, y, extra);
+  }
+
+  @Override
+  public boolean move(float offset, float extra) {
+    if (direction == Line.Direction.HORIZONTAL) {
+      if (previousStart.y + offset < lowerLine.maxY() + extra
+          || previousStart.y + offset > upperLine.minY() - extra
+          || previousEnd.y + offset < lowerLine.maxY() + extra
+          || previousEnd.y + offset > upperLine.minY() - extra) {
+        return false;
+      }
+
+      start.y = previousStart.y + offset;
+      end.y = previousEnd.y + offset;
+    } else {
+      if (previousStart.x + offset < lowerLine.maxX() + extra
+          || previousStart.x + offset > upperLine.minX() - extra
+          || previousEnd.x + offset < lowerLine.maxX() + extra
+          || previousEnd.x + offset > upperLine.minX() - extra) {
+        return false;
+      }
+
+      start.x = previousStart.x + offset;
+      end.x = previousEnd.x + offset;
+    }
+
+    return true;
+  }
+
+  @Override
+  public void prepareMove() {
+    previousStart.set(start);
+    previousEnd.set(end);
+  }
+
+  @Override
+  public void update(float layoutWidth, float layoutHeight) {
+    intersectionOfLines(start, this, attachLineStart);
+    intersectionOfLines(end, this, attachLineEnd);
+  }
+
+  @Override
+  public float minX() {
+    return min(start.x, end.x);
+  }
+
+  @Override
+  public float maxX() {
+    return max(start.x, end.x);
+  }
+
+  @Override
+  public float minY() {
+    return min(start.y, end.y);
+  }
+
+  @Override
+  public float maxY() {
+    return max(start.y, end.y);
+  }
+
+  @Override
+  public void offset(float x, float y) {
+    start.offset(x, y);
+    end.offset(x, y);
+  }
+
+  @Override
+  public String toString() {
+    return "start --> " + start.toString() + ",end --> " + end.toString();
+  }
+}

+ 334 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantPuzzleLayout.java

@@ -0,0 +1,334 @@
+package com.huantansheng.easyphotos.models.puzzle.slant;
+
+import android.graphics.RectF;
+import android.util.Pair;
+
+import com.huantansheng.easyphotos.models.puzzle.Area;
+import com.huantansheng.easyphotos.models.puzzle.Line;
+import com.huantansheng.easyphotos.models.puzzle.PuzzleLayout;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.createLine;
+import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.cutAreaCross;
+import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.cutAreaWith;
+
+
+/**
+ * 斜线布局,外围区域为一矩形
+ *
+ * @author wupanjie
+ */
+public abstract class SlantPuzzleLayout implements PuzzleLayout {
+  private RectF bounds;
+  private SlantArea outerArea;
+
+  private List<Line> outerLines = new ArrayList<>(4);
+  private List<SlantArea> areas = new ArrayList<>();
+  private List<Line> lines = new ArrayList<>();
+
+  private float padding;
+  private float radian;
+  private int color;
+
+  private Comparator<SlantArea> areaComparator = new SlantArea.AreaComparator();
+
+  private ArrayList<Step> steps = new ArrayList<>();
+
+  protected SlantPuzzleLayout() {
+
+  }
+
+  @Override
+  public void setOuterBounds(RectF bounds) {
+    reset();
+
+    this.bounds = bounds;
+
+    CrossoverPointF leftTop = new CrossoverPointF(bounds.left, bounds.top);
+    CrossoverPointF rightTop = new CrossoverPointF(bounds.right, bounds.top);
+    CrossoverPointF leftBottom = new CrossoverPointF(bounds.left, bounds.bottom);
+    CrossoverPointF rightBottom = new CrossoverPointF(bounds.right, bounds.bottom);
+
+    SlantLine lineLeft = new SlantLine(leftTop, leftBottom, Line.Direction.VERTICAL);
+    SlantLine lineTop = new SlantLine(leftTop, rightTop, Line.Direction.HORIZONTAL);
+    SlantLine lineRight = new SlantLine(rightTop, rightBottom, Line.Direction.VERTICAL);
+    SlantLine lineBottom = new SlantLine(leftBottom, rightBottom, Line.Direction.HORIZONTAL);
+
+    outerLines.clear();
+
+    outerLines.add(lineLeft);
+    outerLines.add(lineTop);
+    outerLines.add(lineRight);
+    outerLines.add(lineBottom);
+
+    outerArea = new SlantArea();
+    outerArea.lineLeft = lineLeft;
+    outerArea.lineTop = lineTop;
+    outerArea.lineRight = lineRight;
+    outerArea.lineBottom = lineBottom;
+
+    outerArea.updateCornerPoints();
+
+    areas.clear();
+    areas.add(outerArea);
+  }
+
+  public abstract void layout();
+
+  private void updateLineLimit() {
+    int size = lines.size();
+    for (int i = 0; i < size; i++) {
+      Line line = lines.get(i);
+      updateUpperLine(line);
+      updateLowerLine(line);
+    }
+  }
+
+  private void updateLowerLine(final Line line) {
+    int size = lines.size();
+    for (int i = 0; i < size; i++) {
+      Line l = lines.get(i);
+      if (l.direction() != line.direction()) {
+        continue;
+      }
+
+      if (l.attachStartLine() != line.attachStartLine()
+          || l.attachEndLine() != line.attachEndLine()) {
+        continue;
+      }
+
+      if (l.direction() == Line.Direction.HORIZONTAL) {
+        if (l.minY() > line.lowerLine().maxY() && l.maxY() < line.minY()) {
+          line.setLowerLine(l);
+        }
+      } else {
+        if (l.minX() > line.lowerLine().maxX() && l.maxX() < line.minX()) {
+          line.setLowerLine(l);
+        }
+      }
+    }
+  }
+
+  private void updateUpperLine(final Line line) {
+    int size = lines.size();
+    for (int i = 0; i < size; i++) {
+      Line l = lines.get(i);
+      if (l.direction() != line.direction()) {
+        continue;
+      }
+
+      if (l.attachStartLine() != line.attachStartLine()
+          || l.attachEndLine() != line.attachEndLine()) {
+        continue;
+      }
+
+      if (l.direction() == Line.Direction.HORIZONTAL) {
+        if (l.maxY() < line.upperLine().minY() && l.minY() > line.maxY()) {
+          line.setUpperLine(l);
+        }
+      } else {
+        if (l.maxX() < line.upperLine().minX() && l.minX() > line.maxX()) {
+          line.setUpperLine(l);
+        }
+      }
+    }
+  }
+
+  @Override
+  public int getAreaCount() {
+    return areas.size();
+  }
+
+  @Override
+  public void reset() {
+    lines.clear();
+    areas.clear();
+    areas.add(outerArea);
+    steps.clear();
+  }
+
+  @Override
+  public void update() {
+    int size = lines.size();
+    for (int i = 0; i < size; i++) {
+      lines.get(i).update(width(), height());
+    }
+    int areasSize = areas.size();
+    for (int i = 0; i < areasSize; i++) {
+      areas.get(i).updateCornerPoints();
+    }
+  }
+
+  @Override
+  public float width() {
+    return outerArea == null ? 0 : outerArea.width();
+  }
+
+  @Override
+  public float height() {
+    return outerArea == null ? 0 : outerArea.height();
+  }
+
+  private void sortAreas() {
+    Collections.sort(areas, areaComparator);
+  }
+
+  @Override
+  public List<Line> getOuterLines() {
+    return outerLines;
+  }
+
+  @Override
+  public Area getOuterArea() {
+    return outerArea;
+  }
+
+  public List<SlantArea> getAreas() {
+    return areas;
+  }
+
+  @Override
+  public SlantArea getArea(int position) {
+    return areas.get(position);
+  }
+
+  @Override
+  public List<Line> getLines() {
+    return lines;
+  }
+
+  @Override
+  public void setPadding(float padding) {
+    this.padding = padding;
+    for (Area area : areas) {
+      area.setPadding(padding);
+    }
+
+    outerArea.lineLeft.startPoint().set(bounds.left + padding, bounds.top + padding);
+    outerArea.lineLeft.endPoint().set(bounds.left + padding, bounds.bottom - padding);
+
+    outerArea.lineRight.startPoint().set(bounds.right - padding, bounds.top + padding);
+    outerArea.lineRight.endPoint().set(bounds.right - padding, bounds.bottom - padding);
+
+    outerArea.updateCornerPoints();
+    update();
+  }
+
+  @Override
+  public float getPadding() {
+    return padding;
+  }
+
+  @Override
+  public float getRadian() {
+    return radian;
+  }
+
+  @Override
+  public void setRadian(float radian) {
+    this.radian = radian;
+    for (Area area : areas) {
+      area.setRadian(radian);
+    }
+  }
+
+  @Override
+  public int getColor() {
+    return color;
+  }
+
+  @Override
+  public void setColor(int color) {
+    this.color = color;
+  }
+
+  protected List<SlantArea> addLine(int position, Line.Direction direction, float ratio) {
+    return addLine(position, direction, ratio, ratio);
+  }
+
+  protected List<SlantArea> addLine(int position, Line.Direction direction, float startRatio,
+                                    float endRatio) {
+    SlantArea area = areas.get(position);
+    areas.remove(area);
+    SlantLine line = createLine(area, direction, startRatio, endRatio);
+    lines.add(line);
+
+    List<SlantArea> increasedAreas = cutAreaWith(area, line);
+
+    areas.addAll(increasedAreas);
+
+    updateLineLimit();
+    sortAreas();
+
+    Step step = new Step();
+    step.type = Step.ADD_LINE;
+    step.direction = direction == Line.Direction.HORIZONTAL ? 0 : 1;
+    step.position = position;
+    steps.add(step);
+
+    return increasedAreas;
+  }
+
+  protected void addCross(int position, float startRatio1, float endRatio1,
+      float startRatio2, float endRatio2) {
+    SlantArea area = areas.get(position);
+    areas.remove(area);
+
+    SlantLine horizontal = createLine(area, Line.Direction.HORIZONTAL, startRatio1, endRatio1);
+    SlantLine vertical = createLine(area, Line.Direction.VERTICAL, startRatio2, endRatio2);
+    lines.add(horizontal);
+    lines.add(vertical);
+
+    List<SlantArea> increasedAreas = cutAreaCross(area, horizontal, vertical);
+
+    areas.addAll(increasedAreas);
+    sortAreas();
+
+    Step step = new Step();
+    step.type = Step.ADD_CROSS;
+    step.position = position;
+    steps.add(step);
+  }
+
+  protected void cutArea(int position, int hSize, int vSize) {
+    SlantArea area = areas.get(position);
+    areas.remove(area);
+
+    Pair<List<SlantLine>, List<SlantArea>> spilt =
+        cutAreaWith(area, hSize, vSize);
+
+    lines.addAll(spilt.first);
+    areas.addAll(spilt.second);
+
+    updateLineLimit();
+    sortAreas();
+
+    Step step = new Step();
+    step.type = Step.CUT_EQUAL_PART_ONE;
+    step.position = position;
+    step.hSize = hSize;
+    step.vSize = vSize;
+    steps.add(step);
+  }
+
+  @Override
+  public Info generateInfo() {
+    Info info = new Info();
+    info.type = Info.TYPE_SLANT;
+    info.padding = padding;
+    info.radian = radian;
+    info.color = color;
+    info.steps = steps;
+    ArrayList<LineInfo> lineInfos = new ArrayList<>();
+    for (Line line : lines) {
+      LineInfo lineInfo = new LineInfo(line);
+      lineInfos.add(lineInfo);
+    }
+    info.lineInfos = lineInfos;
+    return info;
+  }
+}

+ 482 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantUtils.java

@@ -0,0 +1,482 @@
+package com.huantansheng.easyphotos.models.puzzle.slant;
+
+import android.graphics.PointF;
+import android.util.Pair;
+
+
+import com.huantansheng.easyphotos.models.puzzle.Line;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author wupanjie
+ */
+class SlantUtils {
+  private static final PointF A = new PointF();
+  private static final PointF B = new PointF();
+  private static final PointF C = new PointF();
+  private static final PointF D = new PointF();
+  private static final PointF AB = new PointF();
+  private static final PointF AM = new PointF();
+  private static final PointF BC = new PointF();
+  private static final PointF BM = new PointF();
+  private static final PointF CD = new PointF();
+  private static final PointF CM = new PointF();
+  private static final PointF DA = new PointF();
+  private static final PointF DM = new PointF();
+
+  private SlantUtils() {
+    //no instance
+  }
+
+  static float distance(PointF one, PointF two) {
+    return (float) Math.sqrt(Math.pow(two.x - one.x, 2) + Math.pow(two.y - one.y, 2));
+  }
+
+  static List<SlantArea> cutAreaWith(SlantArea area, SlantLine line) {
+    List<SlantArea> areas = new ArrayList<>();
+    SlantArea area1 = new SlantArea(area);
+    SlantArea area2 = new SlantArea(area);
+
+    if (line.direction == Line.Direction.HORIZONTAL) {
+      area1.lineBottom = line;
+      area1.leftBottom = line.start;
+      area1.rightBottom = line.end;
+
+      area2.lineTop = line;
+      area2.leftTop = line.start;
+      area2.rightTop = line.end;
+    } else {
+      area1.lineRight = line;
+      area1.rightTop = line.start;
+      area1.rightBottom = line.end;
+
+      area2.lineLeft = line;
+      area2.leftTop = line.start;
+      area2.leftBottom = line.end;
+    }
+
+    areas.add(area1);
+    areas.add(area2);
+
+    return areas;
+  }
+
+  static SlantLine createLine(SlantArea area, Line.Direction direction, float startratio,
+      float endratio) {
+    SlantLine line = new SlantLine(direction);
+
+    if (direction == Line.Direction.HORIZONTAL) {
+      line.start = getPoint(area.leftTop, area.leftBottom, Line.Direction.VERTICAL, startratio);
+      line.end = getPoint(area.rightTop, area.rightBottom, Line.Direction.VERTICAL, endratio);
+
+      line.attachLineStart = area.lineLeft;
+      line.attachLineEnd = area.lineRight;
+
+      line.upperLine = area.lineBottom;
+      line.lowerLine = area.lineTop;
+    } else {
+      line.start = getPoint(area.leftTop, area.rightTop, Line.Direction.HORIZONTAL, startratio);
+      line.end = getPoint(area.leftBottom, area.rightBottom, Line.Direction.HORIZONTAL, endratio);
+
+      line.attachLineStart = area.lineTop;
+      line.attachLineEnd = area.lineBottom;
+
+      line.upperLine = area.lineRight;
+      line.lowerLine = area.lineLeft;
+    }
+
+    return line;
+  }
+
+  static Pair<List<SlantLine>, List<SlantArea>> cutAreaWith(final SlantArea area,
+                                                            final int horizontalSize, final int verticalSize) {
+    List<SlantArea> areaList = new ArrayList<>();
+    List<SlantLine> horizontalLines = new ArrayList<>(horizontalSize);
+
+    SlantArea restArea = new SlantArea(area);
+    for (int i = horizontalSize + 1; i > 1; i--) {
+      SlantLine horizontalLine =
+          createLine(restArea, Line.Direction.HORIZONTAL, (float) (i - 1) / i - 0.025f, (float) (i - 1) / i + 0.025f);
+      horizontalLines.add(horizontalLine);
+      restArea.lineBottom = horizontalLine;
+      restArea.leftBottom = horizontalLine.start;
+      restArea.rightBottom = horizontalLine.end;
+    }
+    List<SlantLine> verticalLines = new ArrayList<>();
+
+    restArea = new SlantArea(area);
+    for (int i = verticalSize + 1; i > 1; i--) {
+      SlantLine verticalLine =
+          createLine(restArea, Line.Direction.VERTICAL, (float) (i - 1) / i + 0.025f, (float) (i - 1) / i - 0.025f);
+      verticalLines.add(verticalLine);
+      SlantArea spiltArea = new SlantArea(restArea);
+      spiltArea.lineLeft = verticalLine;
+      spiltArea.leftTop = verticalLine.start;
+      spiltArea.leftBottom = verticalLine.end;
+
+      int size = horizontalLines.size();
+      for (int j = 0; j <= size; j++) {
+        SlantArea blockArea = new SlantArea(spiltArea);
+        if (j == 0) {
+          blockArea.lineTop = horizontalLines.get(j);
+        } else if (j == size) {
+          blockArea.lineBottom = horizontalLines.get(j - 1);
+
+          CrossoverPointF leftBottom =
+              new CrossoverPointF(blockArea.lineBottom, blockArea.lineLeft);
+          intersectionOfLines(leftBottom, blockArea.lineBottom, blockArea.lineLeft);
+          CrossoverPointF rightBottom =
+              new CrossoverPointF(blockArea.lineBottom, blockArea.lineRight);
+          intersectionOfLines(rightBottom, blockArea.lineBottom, blockArea.lineRight);
+          blockArea.leftBottom = leftBottom;
+          blockArea.rightBottom = rightBottom;
+        } else {
+          blockArea.lineTop = horizontalLines.get(j);
+          blockArea.lineBottom = horizontalLines.get(j - 1);
+        }
+        CrossoverPointF leftTop = new CrossoverPointF(blockArea.lineTop, blockArea.lineLeft);
+        intersectionOfLines(leftTop, blockArea.lineTop, blockArea.lineLeft);
+        CrossoverPointF rightTop = new CrossoverPointF(blockArea.lineTop, blockArea.lineRight);
+        intersectionOfLines(rightTop, blockArea.lineTop, blockArea.lineRight);
+        blockArea.leftTop = leftTop;
+        blockArea.rightTop = rightTop;
+        areaList.add(blockArea);
+      }
+      restArea.lineRight = verticalLine;
+      restArea.rightTop = verticalLine.start;
+      restArea.rightBottom = verticalLine.end;
+    }
+    int size = horizontalLines.size();
+    for (int j = 0; j <= size; j++) {
+      SlantArea blockArea = new SlantArea(restArea);
+      if (j == 0) {
+        blockArea.lineTop = horizontalLines.get(j);
+      } else if (j == size) {
+        blockArea.lineBottom = horizontalLines.get(j - 1);
+        CrossoverPointF leftBottom = new CrossoverPointF(blockArea.lineBottom, blockArea.lineLeft);
+        intersectionOfLines(leftBottom, blockArea.lineBottom, blockArea.lineLeft);
+        CrossoverPointF rightBottom =
+            new CrossoverPointF(blockArea.lineBottom, blockArea.lineRight);
+        intersectionOfLines(rightBottom, blockArea.lineBottom, blockArea.lineRight);
+        blockArea.leftBottom = leftBottom;
+        blockArea.rightBottom = rightBottom;
+      } else {
+        blockArea.lineTop = horizontalLines.get(j);
+        blockArea.lineBottom = horizontalLines.get(j - 1);
+      }
+      CrossoverPointF leftTop = new CrossoverPointF(blockArea.lineTop, blockArea.lineLeft);
+      intersectionOfLines(leftTop, blockArea.lineTop, blockArea.lineLeft);
+      CrossoverPointF rightTop = new CrossoverPointF(blockArea.lineTop, blockArea.lineRight);
+      intersectionOfLines(rightTop, blockArea.lineTop, blockArea.lineRight);
+      blockArea.leftTop = leftTop;
+      blockArea.rightTop = rightTop;
+      areaList.add(blockArea);
+    }
+
+    List<SlantLine> lines = new ArrayList<>();
+    lines.addAll(horizontalLines);
+    lines.addAll(verticalLines);
+    return new Pair<>(lines, areaList);
+  }
+
+  static List<SlantArea> cutAreaCross(final SlantArea area, final SlantLine horizontal,
+                                      final SlantLine vertical) {
+    List<SlantArea> list = new ArrayList<>();
+
+    CrossoverPointF crossoverPoint = new CrossoverPointF(horizontal, vertical);
+    intersectionOfLines(crossoverPoint, horizontal, vertical);
+
+    SlantArea one = new SlantArea(area);
+    one.lineBottom = horizontal;
+    one.lineRight = vertical;
+
+    one.rightTop = vertical.start;
+    one.rightBottom = crossoverPoint;
+    one.leftBottom = horizontal.start;
+    list.add(one);
+
+    SlantArea two = new SlantArea(area);
+    two.lineBottom = horizontal;
+    two.lineLeft = vertical;
+
+    two.leftTop = vertical.start;
+    two.rightBottom = horizontal.end;
+    two.leftBottom = crossoverPoint;
+    list.add(two);
+
+    SlantArea three = new SlantArea(area);
+    three.lineTop = horizontal;
+    three.lineRight = vertical;
+
+    three.leftTop = horizontal.start;
+    three.rightTop = crossoverPoint;
+    three.rightBottom = vertical.end;
+    list.add(three);
+
+    SlantArea four = new SlantArea(area);
+    four.lineTop = horizontal;
+    four.lineLeft = vertical;
+
+    four.leftTop = crossoverPoint;
+    four.rightTop = horizontal.end;
+    four.leftBottom = vertical.end;
+    list.add(four);
+
+    return list;
+  }
+
+  private static CrossoverPointF getPoint(final PointF start, final PointF end,
+                                          final Line.Direction direction, float ratio) {
+    CrossoverPointF point = new CrossoverPointF();
+    getPoint(point, start, end, direction, ratio);
+    return point;
+  }
+
+  static void getPoint(final PointF dst, final PointF start, final PointF end,
+                       final Line.Direction direction, float ratio) {
+    float deltaY = Math.abs(start.y - end.y);
+    float deltaX = Math.abs(start.x - end.x);
+    float maxY = Math.max(start.y, end.y);
+    float minY = Math.min(start.y, end.y);
+    float maxX = Math.max(start.x, end.x);
+    float minX = Math.min(start.x, end.x);
+    if (direction == Line.Direction.HORIZONTAL) {
+      dst.x = minX + deltaX * ratio;
+      if (start.y < end.y) {
+        dst.y = minY + ratio * deltaY;
+      } else {
+        dst.y = maxY - ratio * deltaY;
+      }
+    } else {
+      dst.y = minY + deltaY * ratio;
+      if (start.x < end.x) {
+        dst.x = minX + ratio * deltaX;
+      } else {
+        dst.x = maxX - ratio * deltaX;
+      }
+    }
+  }
+
+  // 叉乘
+  private static float crossProduct(final PointF a, final PointF b) {
+    return a.x * b.y - b.x * a.y;
+  }
+
+  /**
+   * 判断一个斜线区域是否包含(x,y)点
+   *
+   * @param area 斜线区域
+   * @param x x
+   * @param y y
+   * @return 是否包含
+   */
+  static boolean contains(SlantArea area, float x, float y) {
+    AB.x = area.rightTop.x - area.leftTop.x;
+    AB.y = area.rightTop.y - area.leftTop.y;
+
+    AM.x = x - area.leftTop.x;
+    AM.y = y - area.leftTop.y;
+
+    BC.x = area.rightBottom.x - area.rightTop.x;
+    BC.y = area.rightBottom.y - area.rightTop.y;
+
+    BM.x = x - area.rightTop.x;
+    BM.y = y - area.rightTop.y;
+
+    CD.x = area.leftBottom.x - area.rightBottom.x;
+    CD.y = area.leftBottom.y - area.rightBottom.y;
+
+    CM.x = x - area.rightBottom.x;
+    CM.y = y - area.rightBottom.y;
+
+    DA.x = area.leftTop.x - area.leftBottom.x;
+    DA.y = area.leftTop.y - area.leftBottom.y;
+
+    DM.x = x - area.leftBottom.x;
+    DM.y = y - area.leftBottom.y;
+
+    return crossProduct(AB, AM) > 0
+        && crossProduct(BC, BM) > 0
+        && crossProduct(CD, CM) > 0
+        && crossProduct(DA, DM) > 0;
+  }
+
+  static boolean contains(SlantLine line, float x, float y, float extra) {
+    PointF start = line.start;
+    PointF end = line.end;
+    if (line.direction == Line.Direction.VERTICAL) {
+      A.x = start.x - extra;
+      A.y = start.y;
+      B.x = start.x + extra;
+      B.y = start.y;
+      C.x = end.x + extra;
+      C.y = end.y;
+      D.x = end.x - extra;
+      D.y = end.y;
+    } else {
+      A.x = start.x;
+      A.y = start.y - extra;
+      B.x = end.x;
+      B.y = end.y - extra;
+      C.x = end.x;
+      C.y = end.y + extra;
+      D.x = start.x;
+      D.y = start.y + extra;
+    }
+
+    AB.x = B.x - A.x;
+    AB.y = B.y - A.y;
+
+    AM.x = x - A.x;
+    AM.y = y - A.y;
+
+    BC.x = C.x - B.x;
+    BC.y = C.y - B.y;
+
+    BM.x = x - B.x;
+    BM.y = y - B.y;
+
+    CD.x = D.x - C.x;
+    CD.y = D.y - C.y;
+
+    CM.x = x - C.x;
+    CM.y = y - C.y;
+
+    DA.x = A.x - D.x;
+    DA.y = A.y - D.y;
+
+    DM.x = x - D.x;
+    DM.y = y - D.y;
+
+    return crossProduct(AB, AM) > 0
+        && crossProduct(BC, BM) > 0
+        && crossProduct(CD, CM) > 0
+        && crossProduct(DA, DM) > 0;
+  }
+
+  /**
+   * 计算两线的交点
+   *
+   * @param dst 计算出的交点
+   * @param lineOne 线一
+   * @param lineTwo 线二
+   */
+  static void intersectionOfLines(final CrossoverPointF dst, final SlantLine lineOne,
+      final SlantLine lineTwo) {
+    dst.horizontal = lineOne;
+    dst.vertical = lineTwo;
+    if (isParallel(lineOne, lineTwo)) {
+      dst.set(0, 0);
+      return;
+    }
+
+    if (isHorizontalLine(lineOne) && isVerticalLine(lineTwo)) {
+      dst.set(lineTwo.start.x, lineOne.start.y);
+      return;
+    }
+
+    if (isVerticalLine(lineOne) && isHorizontalLine(lineTwo)) {
+      dst.set(lineOne.start.x, lineTwo.start.y);
+      return;
+    }
+
+    if (isHorizontalLine(lineOne) && !isVerticalLine(lineTwo)) {
+      float k = calculateSlope(lineTwo);
+      float b = calculateVerticalIntercept(lineTwo);
+
+      dst.y = lineOne.start.y;
+      dst.x = (dst.y - b) / k;
+      return;
+    }
+
+    if (isVerticalLine(lineOne) && !isHorizontalLine(lineTwo)) {
+      float k = calculateSlope(lineTwo);
+      float b = calculateVerticalIntercept(lineTwo);
+
+      dst.x = lineOne.start.x;
+      dst.y = k * dst.x + b;
+      return;
+    }
+
+    if (isHorizontalLine(lineTwo) && !isVerticalLine(lineOne)) {
+      float k = calculateSlope(lineOne);
+      float b = calculateVerticalIntercept(lineOne);
+
+      dst.y = lineTwo.start.y;
+      dst.x = (dst.y - b) / k;
+      return;
+    }
+
+    if (isVerticalLine(lineTwo) && !isHorizontalLine(lineOne)) {
+      float k = calculateSlope(lineOne);
+      float b = calculateVerticalIntercept(lineOne);
+
+      dst.x = lineTwo.start.x;
+      dst.y = k * dst.x + b;
+      return;
+    }
+
+    final float k1 = calculateSlope(lineOne);
+    final float b1 = calculateVerticalIntercept(lineOne);
+
+    final float k2 = calculateSlope(lineTwo);
+    final float b2 = calculateVerticalIntercept(lineTwo);
+
+    dst.x = (b2 - b1) / (k1 - k2);
+    dst.y = dst.x * k1 + b1;
+  }
+
+  private static boolean isHorizontalLine(SlantLine line) {
+    return line.start.y == line.end.y;
+  }
+
+  private static boolean isVerticalLine(SlantLine line) {
+    return line.start.x == line.end.x;
+  }
+
+  /**
+   * 判断两条线是否平行
+   *
+   * @param lineOne 第一条
+   * @param lineTwo 第二条
+   * @return 是否平行
+   */
+  private static boolean isParallel(final SlantLine lineOne, final SlantLine lineTwo) {
+    return calculateSlope(lineOne) == calculateSlope(lineTwo);
+  }
+
+  /**
+   * 计算线的斜率
+   *
+   * @param line 线
+   * @return 线的斜率
+   */
+  static float calculateSlope(final SlantLine line) {
+    if (isHorizontalLine(line)) {
+      return 0f;
+    } else if (isVerticalLine(line)) {
+      return Float.POSITIVE_INFINITY;
+    } else {
+      return (line.start.y - line.end.y) / (line.start.x - line.end.x);
+    }
+  }
+
+  /**
+   * 计算纵截距
+   *
+   * @param line 线
+   * @return 纵截距
+   */
+  private static float calculateVerticalIntercept(final SlantLine line) {
+    if (isHorizontalLine(line)) {
+      return line.start.y;
+    } else if (isVerticalLine(line)) {
+      return Float.POSITIVE_INFINITY;
+    } else {
+      float k = calculateSlope(line);
+      return line.start.y - k * line.start.x;
+    }
+  }
+}

+ 231 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightArea.java

@@ -0,0 +1,231 @@
+package com.huantansheng.easyphotos.models.puzzle.straight;
+
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.graphics.RectF;
+
+import com.huantansheng.easyphotos.models.puzzle.Area;
+import com.huantansheng.easyphotos.models.puzzle.Line;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+
+
+/**
+ * @author wupanjie
+ */
+class StraightArea implements Area {
+  StraightLine lineLeft;
+  StraightLine lineTop;
+  StraightLine lineRight;
+  StraightLine lineBottom;
+
+  private Path areaPath = new Path();
+  private RectF areaRect = new RectF();
+  private PointF[] handleBarPoints = new PointF[2];
+
+  private float paddingLeft;
+  private float paddingTop;
+  private float paddingRight;
+  private float paddingBottom;
+  private float radian;
+
+  StraightArea(){
+    handleBarPoints[0] = new PointF();
+    handleBarPoints[1] = new PointF();
+  }
+
+  StraightArea(RectF baseRect) {
+    this();
+    setBaseRect(baseRect);
+  }
+
+  private void setBaseRect(RectF baseRect) {
+    PointF one = new PointF(baseRect.left, baseRect.top);
+    PointF two = new PointF(baseRect.right, baseRect.top);
+    PointF three = new PointF(baseRect.left, baseRect.bottom);
+    PointF four = new PointF(baseRect.right, baseRect.bottom);
+
+    lineLeft = new StraightLine(one, three);
+    lineTop = new StraightLine(one, two);
+    lineRight = new StraightLine(two, four);
+    lineBottom = new StraightLine(three, four);
+  }
+
+  StraightArea(StraightArea src) {
+    this.lineLeft = src.lineLeft;
+    this.lineTop = src.lineTop;
+    this.lineRight = src.lineRight;
+    this.lineBottom = src.lineBottom;
+
+    handleBarPoints[0] = new PointF();
+    handleBarPoints[1] = new PointF();
+  }
+
+  @Override
+  public float left() {
+    return lineLeft.minX() + paddingLeft;
+  }
+
+  @Override
+  public float top() {
+    return lineTop.minY() + paddingTop;
+  }
+
+  @Override
+  public float right() {
+    return lineRight.maxX() - paddingRight;
+  }
+
+  @Override
+  public float bottom() {
+    return lineBottom.maxY() - paddingBottom;
+  }
+
+  @Override
+  public float centerX() {
+    return (left() + right()) / 2;
+  }
+
+  @Override
+  public float centerY() {
+    return (top() + bottom()) / 2;
+  }
+
+  @Override
+  public float width() {
+    return right() - left();
+  }
+
+  @Override
+  public float height() {
+    return bottom() - top();
+  }
+
+  @Override
+  public PointF getCenterPoint() {
+    return new PointF(centerX(), centerY());
+  }
+
+  @Override
+  public boolean contains(PointF point) {
+    return contains(point.x, point.y);
+  }
+
+  @Override
+  public boolean contains(float x, float y) {
+    return getAreaRect().contains(x, y);
+  }
+
+  @Override
+  public boolean contains(Line line) {
+    return lineLeft == line || lineTop == line || lineRight == line || lineBottom == line;
+  }
+
+  @Override
+  public Path getAreaPath() {
+    areaPath.reset();
+    areaPath.addRoundRect(getAreaRect(),radian,radian, Path.Direction.CCW);
+    //areaPath.addRect(getAreaRect(), Path.Direction.CCW);
+    return areaPath;
+  }
+
+  @Override
+  public RectF getAreaRect() {
+    areaRect.set(left(), top(), right(), bottom());
+    return areaRect;
+  }
+
+  @Override
+  public List<Line> getLines() {
+    return Arrays.asList((Line) lineLeft, lineTop, lineRight, lineBottom);
+  }
+
+  @Override
+  public PointF[] getHandleBarPoints(Line line) {
+    if (line == lineLeft) {
+      handleBarPoints[0].x = left();
+      handleBarPoints[0].y = top() + height() / 4;
+      handleBarPoints[1].x = left();
+      handleBarPoints[1].y = top() + height() / 4 * 3;
+    } else if (line == lineTop) {
+      handleBarPoints[0].x = left() + width() / 4;
+      handleBarPoints[0].y = top();
+      handleBarPoints[1].x = left() + width() / 4 * 3;
+      handleBarPoints[1].y = top();
+    } else if (line == lineRight) {
+      handleBarPoints[0].x = right();
+      handleBarPoints[0].y = top() + height() / 4;
+      handleBarPoints[1].x = right();
+      handleBarPoints[1].y = top() + height() / 4 * 3;
+    } else if (line == lineBottom) {
+      handleBarPoints[0].x = left() + width() / 4;
+      handleBarPoints[0].y = bottom();
+      handleBarPoints[1].x = left() + width() / 4 * 3;
+      handleBarPoints[1].y = bottom();
+    }
+    return handleBarPoints;
+  }
+
+  @Override
+  public float radian() {
+    return radian;
+  }
+
+  @Override
+  public void setRadian(float radian) {
+    this.radian = radian;
+  }
+
+  @Override
+  public float getPaddingLeft() {
+    return paddingLeft;
+  }
+
+  @Override
+  public float getPaddingTop() {
+    return paddingTop;
+  }
+
+  @Override
+  public float getPaddingRight() {
+    return paddingRight;
+  }
+
+  @Override
+  public float getPaddingBottom() {
+    return paddingBottom;
+  }
+
+  @Override
+  public void setPadding(float padding) {
+    setPadding(padding, padding, padding, padding);
+  }
+
+  @Override
+  public void setPadding(float paddingLeft, float paddingTop, float paddingRight,
+                         float paddingBottom) {
+    this.paddingLeft = paddingLeft;
+    this.paddingTop = paddingTop;
+    this.paddingRight = paddingRight;
+    this.paddingBottom = paddingBottom;
+  }
+
+  static class AreaComparator implements Comparator<StraightArea> {
+    @Override
+    public int compare(StraightArea lhs, StraightArea rhs) {
+      if (lhs.top() < rhs.top()) {
+        return -1;
+      } else if (lhs.top() == rhs.top()) {
+        if (lhs.left() < rhs.left()) {
+          return -1;
+        } else {
+          return 1;
+        }
+      } else {
+        return 1;
+      }
+    }
+  }
+}

+ 215 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightLine.java

@@ -0,0 +1,215 @@
+package com.huantansheng.easyphotos.models.puzzle.straight;
+
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.util.Log;
+
+import com.huantansheng.easyphotos.models.puzzle.Line;
+
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+
+/**
+ * @author wupanjie
+ */
+class StraightLine implements Line {
+  private PointF start;
+  private PointF end;
+
+  private PointF previousStart = new PointF();
+  private PointF previousEnd = new PointF();
+
+  public Line.Direction direction = Direction.HORIZONTAL;
+
+  StraightLine attachLineStart;
+  StraightLine attachLineEnd;
+
+  private Line upperLine;
+  private Line lowerLine;
+
+  private RectF bounds = new RectF();
+
+  StraightLine(PointF start, PointF end) {
+    this.start = start;
+    this.end = end;
+
+    if (start.x == end.x) {
+      direction = Line.Direction.VERTICAL;
+    } else if (start.y == end.y) {
+      direction = Line.Direction.HORIZONTAL;
+    } else {
+      Log.d("StraightLine", "StraightLine: current only support two direction");
+    }
+  }
+
+  @Override
+  public float length() {
+    return (float) Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2));
+  }
+
+  @Override
+  public PointF startPoint() {
+    return start;
+  }
+
+  @Override
+  public PointF endPoint() {
+    return end;
+  }
+
+  @Override
+  public Line lowerLine() {
+    return lowerLine;
+  }
+
+  @Override
+  public Line upperLine() {
+    return upperLine;
+  }
+
+  @Override
+  public Line attachStartLine() {
+    return attachLineStart;
+  }
+
+  @Override
+  public Line attachEndLine() {
+    return attachLineEnd;
+  }
+
+  @Override
+  public void setLowerLine(Line lowerLine) {
+    this.lowerLine = lowerLine;
+  }
+
+  @Override
+  public void setUpperLine(Line upperLine) {
+    this.upperLine = upperLine;
+  }
+
+  void setAttachLineStart(StraightLine attachLineStart) {
+    this.attachLineStart = attachLineStart;
+  }
+
+  void setAttachLineEnd(StraightLine attachLineEnd) {
+    this.attachLineEnd = attachLineEnd;
+  }
+
+  @Override
+  public Direction direction() {
+    return direction;
+  }
+
+  @Override
+  public float slope() {
+    return direction == Direction.HORIZONTAL ? 0 : Float.MAX_VALUE;
+  }
+
+  @Override
+  public boolean contains(float x, float y, float extra) {
+    if (direction == Line.Direction.HORIZONTAL) {
+      bounds.left = start.x;
+      bounds.right = end.x;
+      bounds.top = start.y - extra / 2;
+      bounds.bottom = start.y + extra / 2;
+    } else if (direction == Line.Direction.VERTICAL) {
+      bounds.top = start.y;
+      bounds.bottom = end.y;
+      bounds.left = start.x - extra / 2;
+      bounds.right = start.x + extra / 2;
+    }
+
+    return bounds.contains(x, y);
+  }
+
+  @Override
+  public void prepareMove() {
+    previousStart.set(start);
+    previousEnd.set(end);
+  }
+
+  @Override
+  public boolean move(float offset, float extra) {
+    if (direction == Line.Direction.HORIZONTAL) {
+      if (previousStart.y + offset < lowerLine.maxY() + extra
+          || previousStart.y + offset > upperLine.minY() - extra
+          || previousEnd.y + offset < lowerLine.maxY() + extra
+          || previousEnd.y + offset > upperLine.minY() - extra) {
+        return false;
+      }
+
+      start.y = previousStart.y + offset;
+      end.y = previousEnd.y + offset;
+    } else {
+      if (previousStart.x + offset < lowerLine.maxX() + extra
+          || previousStart.x + offset > upperLine.minX() - extra
+          || previousEnd.x + offset < lowerLine.maxX() + extra
+          || previousEnd.x + offset > upperLine.minX() - extra) {
+        return false;
+      }
+
+      start.x = previousStart.x + offset;
+      end.x = previousEnd.x + offset;
+    }
+
+    return true;
+  }
+
+  @Override
+  public void update(float layoutWidth, float layoutHeight) {
+    if (direction == Line.Direction.HORIZONTAL) {
+      if (attachLineStart != null) {
+        start.x = attachLineStart.getPosition();
+      }
+      if (attachLineEnd != null) {
+        end.x = attachLineEnd.getPosition();
+      }
+    } else if (direction == Line.Direction.VERTICAL) {
+      if (attachLineStart != null) {
+        start.y = attachLineStart.getPosition();
+      }
+      if (attachLineEnd != null) {
+        end.y = attachLineEnd.getPosition();
+      }
+    }
+  }
+
+  public float getPosition() {
+    if (direction == Line.Direction.HORIZONTAL) {
+      return start.y;
+    } else {
+      return start.x;
+    }
+  }
+
+  @Override
+  public float minX() {
+    return min(start.x, end.x);
+  }
+
+  @Override
+  public float maxX() {
+    return max(start.x, end.x);
+  }
+
+  @Override
+  public float minY() {
+    return min(start.y, end.y);
+  }
+
+  @Override
+  public float maxY() {
+    return max(start.y, end.y);
+  }
+
+  @Override
+  public void offset(float x, float y) {
+    start.offset(x, y);
+    end.offset(x, y);
+  }
+
+  @Override
+  public String toString() {
+    return "start --> " + start.toString() + ",end --> " + end.toString();
+  }
+}

+ 359 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightPuzzleLayout.java

@@ -0,0 +1,359 @@
+package com.huantansheng.easyphotos.models.puzzle.straight;
+
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.util.Pair;
+
+import com.huantansheng.easyphotos.models.puzzle.Area;
+import com.huantansheng.easyphotos.models.puzzle.Line;
+import com.huantansheng.easyphotos.models.puzzle.PuzzleLayout;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import static com.huantansheng.easyphotos.models.puzzle.straight.StraightUtils.createLine;
+import static com.huantansheng.easyphotos.models.puzzle.straight.StraightUtils.cutAreaCross;
+import static com.huantansheng.easyphotos.models.puzzle.straight.StraightUtils.cutAreaSpiral;
+
+
+/**
+ * @author wupanjie
+ */
+public abstract class StraightPuzzleLayout implements PuzzleLayout {
+  private RectF bounds;
+  private StraightArea outerArea;
+
+  private List<StraightArea> areas = new ArrayList<>();
+  private List<Line> lines = new ArrayList<>();
+  private List<Line> outerLines = new ArrayList<>(4);
+
+  private float padding;
+  private float radian;
+  private int color;
+
+  private Comparator<StraightArea> areaComparator = new StraightArea.AreaComparator();
+
+  private ArrayList<Step> steps = new ArrayList<>();
+
+  protected StraightPuzzleLayout() {
+
+  }
+
+  @Override
+  public void setOuterBounds(RectF bounds) {
+    reset();
+
+    this.bounds = bounds;
+
+    PointF one = new PointF(bounds.left, bounds.top);
+    PointF two = new PointF(bounds.right, bounds.top);
+    PointF three = new PointF(bounds.left, bounds.bottom);
+    PointF four = new PointF(bounds.right, bounds.bottom);
+
+    StraightLine lineLeft = new StraightLine(one, three);
+    StraightLine lineTop = new StraightLine(one, two);
+    StraightLine lineRight = new StraightLine(two, four);
+    StraightLine lineBottom = new StraightLine(three, four);
+
+    outerLines.clear();
+
+    outerLines.add(lineLeft);
+    outerLines.add(lineTop);
+    outerLines.add(lineRight);
+    outerLines.add(lineBottom);
+
+    outerArea = new StraightArea();
+    outerArea.lineLeft = lineLeft;
+    outerArea.lineTop = lineTop;
+    outerArea.lineRight = lineRight;
+    outerArea.lineBottom = lineBottom;
+
+    areas.clear();
+    areas.add(outerArea);
+  }
+
+  @Override
+  public abstract void layout();
+
+  @Override
+  public int getAreaCount() {
+    return areas.size();
+  }
+
+  @Override
+  public List<Line> getOuterLines() {
+    return outerLines;
+  }
+
+  @Override
+  public List<Line> getLines() {
+    return lines;
+  }
+
+  @Override
+  public void update() {
+    for (Line line : lines) {
+      line.update(width(), height());
+    }
+  }
+
+  @Override
+  public float width() {
+    return outerArea == null ? 0 : outerArea.width();
+  }
+
+  @Override
+  public float height() {
+    return outerArea == null ? 0 : outerArea.height();
+  }
+
+  @Override
+  public void reset() {
+    lines.clear();
+    areas.clear();
+    areas.add(outerArea);
+    steps.clear();
+  }
+
+  @Override
+  public Area getArea(int position) {
+    return areas.get(position);
+  }
+
+  @Override
+
+  public StraightArea getOuterArea() {
+    return outerArea;
+  }
+
+  @Override
+  public void setPadding(float padding) {
+    this.padding = padding;
+
+    for (Area area : areas) {
+      area.setPadding(padding);
+    }
+
+    outerArea.lineLeft.startPoint().set(bounds.left + padding, bounds.top + padding);
+    outerArea.lineLeft.endPoint().set(bounds.left + padding, bounds.bottom - padding);
+
+    outerArea.lineRight.startPoint().set(bounds.right - padding, bounds.top + padding);
+    outerArea.lineRight.endPoint().set(bounds.right - padding, bounds.bottom - padding);
+
+    update();
+  }
+
+  @Override
+  public float getPadding() {
+    return padding;
+  }
+
+  protected void addLine(int position, Line.Direction direction, float ratio) {
+    StraightArea area = areas.get(position);
+    addLine(area, direction, ratio);
+
+    Step step = new Step();
+    step.type = Step.ADD_LINE;
+    step.direction = direction == Line.Direction.HORIZONTAL ? 0 : 1;
+    step.position = position;
+    steps.add(step);
+  }
+
+  private List<StraightArea> addLine(StraightArea area, Line.Direction direction, float ratio) {
+    areas.remove(area);
+    StraightLine line = createLine(area, direction, ratio);
+    lines.add(line);
+
+    List<StraightArea> increasedArea = StraightUtils.cutArea(area, line);
+    areas.addAll(increasedArea);
+
+    updateLineLimit();
+    sortAreas();
+
+    return increasedArea;
+  }
+
+  protected void cutAreaEqualPart(int position, int part, Line.Direction direction) {
+    StraightArea temp = areas.get(position);
+    for (int i = part; i > 1; i--) {
+      temp = addLine(temp, direction, (float) (i - 1) / i).get(0);
+    }
+
+    Step step = new Step();
+    step.type = Step.CUT_EQUAL_PART_TWO;
+    step.part = part;
+    step.position = position;
+    step.direction = direction == Line.Direction.HORIZONTAL ? 0 : 1;
+    steps.add(step);
+  }
+
+  protected void addCross(int position, float ratio) {
+    addCross(position, ratio, ratio);
+  }
+
+  protected void addCross(int position, float horizontalRatio, float verticalRatio) {
+    StraightArea area = areas.get(position);
+    areas.remove(area);
+    StraightLine horizontal = createLine(area, Line.Direction.HORIZONTAL, horizontalRatio);
+    StraightLine vertical = createLine(area, Line.Direction.VERTICAL, verticalRatio);
+    lines.add(horizontal);
+    lines.add(vertical);
+
+    List<StraightArea> newAreas = cutAreaCross(area, horizontal, vertical);
+    areas.addAll(newAreas);
+
+    updateLineLimit();
+    sortAreas();
+
+    Step step = new Step();
+    step.type = Step.ADD_CROSS;
+    step.position = position;
+    steps.add(step);
+  }
+
+  protected void cutAreaEqualPart(int position, int hSize, int vSize) {
+    StraightArea area = areas.get(position);
+    areas.remove(area);
+    Pair<List<StraightLine>, List<StraightArea>> increased =
+        StraightUtils.cutArea(area, hSize, vSize);
+    List<StraightLine> newLines = increased.first;
+    List<StraightArea> newAreas = increased.second;
+
+    lines.addAll(newLines);
+    areas.addAll(newAreas);
+
+    updateLineLimit();
+    sortAreas();
+
+    Step step = new Step();
+    step.type = Step.CUT_EQUAL_PART_ONE;
+    step.position = position;
+    step.hSize = hSize;
+    step.vSize = vSize;
+    steps.add(step);
+  }
+
+  protected void cutSpiral(int position) {
+    StraightArea area = areas.get(position);
+    areas.remove(area);
+    Pair<List<StraightLine>, List<StraightArea>> spilt = cutAreaSpiral(area);
+
+    lines.addAll(spilt.first);
+    areas.addAll(spilt.second);
+
+    updateLineLimit();
+    sortAreas();
+
+    Step step = new Step();
+    step.type = Step.CUT_SPIRAL;
+    step.position = position;
+    steps.add(step);
+  }
+
+  private void sortAreas() {
+    Collections.sort(areas, areaComparator);
+  }
+
+  private void updateLineLimit() {
+    int size = lines.size();
+    for (int i = 0; i < size; i++) {
+      Line line = lines.get(i);
+      updateUpperLine(line);
+      updateLowerLine(line);
+    }
+  }
+
+  private void updateLowerLine(final Line line) {
+    int size = lines.size();
+    for (int i = 0; i < size; i++) {
+      Line l = lines.get(i);
+      if (l == line) {
+        continue;
+      }
+
+      if (l.direction() != line.direction()) {
+        continue;
+      }
+
+      if (l.direction() == Line.Direction.HORIZONTAL) {
+        if (l.maxX() <= line.minX() || line.maxX() <= l.minX()) continue;
+        if (l.minY() > line.lowerLine().maxY() && l.maxY() < line.minY()) {
+          line.setLowerLine(l);
+        }
+      } else {
+        if (l.maxY() <= line.minY() || line.maxY() <= l.minY()) continue;
+        if (l.minX() > line.lowerLine().maxX() && l.maxX() < line.minX()) {
+          line.setLowerLine(l);
+        }
+      }
+    }
+  }
+
+  private void updateUpperLine(final Line line) {
+    int size = lines.size();
+    for (int i = 0; i < size; i++) {
+      Line l = lines.get(i);
+      if (l == line) {
+        continue;
+      }
+
+      if (l.direction() != line.direction()) {
+        continue;
+      }
+
+      if (l.direction() == Line.Direction.HORIZONTAL) {
+        if (l.maxX() <= line.minX() || line.maxX() <= l.minX()) continue;
+        if (l.maxY() < line.upperLine().minY() && l.minY() > line.maxY()) {
+          line.setUpperLine(l);
+        }
+      } else {
+        if (l.maxY() <= line.minY() || line.maxY() <= l.minY()) continue;
+        if (l.maxX() < line.upperLine().minX() && l.minX() > line.maxX()) {
+          line.setUpperLine(l);
+        }
+      }
+    }
+  }
+
+  @Override
+  public float getRadian() {
+    return radian;
+  }
+
+  @Override
+  public void setRadian(float radian) {
+    this.radian = radian;
+    for (Area area : areas) {
+      area.setRadian(radian);
+    }
+  }
+
+  @Override
+  public int getColor() {
+    return color;
+  }
+
+  @Override
+  public void setColor(int color) {
+    this.color = color;
+  }
+
+  @Override
+  public Info generateInfo() {
+    Info info = new Info();
+    info.type = Info.TYPE_STRAIGHT;
+    info.padding = padding;
+    info.radian = radian;
+    info.color = color;
+    info.steps = steps;
+    ArrayList<LineInfo> lineInfos = new ArrayList<>();
+    for (Line line : lines) {
+      LineInfo lineInfo = new LineInfo(line);
+      lineInfos.add(lineInfo);
+    }
+    info.lineInfos = lineInfos;
+    return info;
+  }
+}

+ 236 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightUtils.java

@@ -0,0 +1,236 @@
+package com.huantansheng.easyphotos.models.puzzle.straight;
+
+import android.graphics.PointF;
+import android.util.Pair;
+
+
+import com.huantansheng.easyphotos.models.puzzle.Line;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author wupanjie
+ */
+class StraightUtils {
+    static StraightLine createLine(final StraightArea area, final Line.Direction direction,
+                                   final float ratio) {
+        PointF one = new PointF();
+        PointF two = new PointF();
+        if (direction == Line.Direction.HORIZONTAL) {
+            one.x = area.left();
+            one.y = area.height() * ratio + area.top();
+            two.x = area.right();
+            two.y = area.height() * ratio + area.top();
+        } else if (direction == Line.Direction.VERTICAL) {
+            one.x = area.width() * ratio + area.left();
+            one.y = area.top();
+            two.x = area.width() * ratio + area.left();
+            two.y = area.bottom();
+        }
+
+        StraightLine line = new StraightLine(one, two);
+
+        if (direction == Line.Direction.HORIZONTAL) {
+            line.attachLineStart = area.lineLeft;
+            line.attachLineEnd = area.lineRight;
+
+            line.setUpperLine(area.lineBottom);
+            line.setLowerLine(area.lineTop);
+        } else if (direction == Line.Direction.VERTICAL) {
+            line.attachLineStart = area.lineTop;
+            line.attachLineEnd = area.lineBottom;
+
+            line.setUpperLine(area.lineRight);
+            line.setLowerLine(area.lineLeft);
+        }
+
+        return line;
+    }
+
+    static List<StraightArea> cutArea(final StraightArea area, final StraightLine line) {
+        List<StraightArea> list = new ArrayList<>();
+        if (line.direction() == Line.Direction.HORIZONTAL) {
+            StraightArea one = new StraightArea(area);
+            one.lineBottom = line;
+            list.add(one);
+
+            StraightArea two = new StraightArea(area);
+            two.lineTop = line;
+            list.add(two);
+        } else if (line.direction() == Line.Direction.VERTICAL) {
+            StraightArea one = new StraightArea(area);
+            one.lineRight = line;
+            list.add(one);
+
+            StraightArea two = new StraightArea(area);
+            two.lineLeft = line;
+            list.add(two);
+        }
+
+        return list;
+    }
+
+    static Pair<List<StraightLine>, List<StraightArea>> cutArea(final StraightArea area,
+                                                                final int horizontalSize,
+                                                                final int verticalSize) {
+        List<StraightArea> areaList = new ArrayList<>();
+        List<StraightLine> horizontalLines = new ArrayList<>(horizontalSize);
+
+        StraightArea restArea = new StraightArea(area);
+        for (int i = horizontalSize + 1; i > 1; i--) {
+            StraightLine horizontalLine =
+                    createLine(restArea, Line.Direction.HORIZONTAL, (float) (i - 1) / i);
+            horizontalLines.add(horizontalLine);
+            restArea.lineBottom = horizontalLine;
+        }
+        List<StraightLine> verticalLines = new ArrayList<>();
+
+        restArea = new StraightArea(area);
+        for (int i = verticalSize + 1; i > 1; i--) {
+            StraightLine verticalLine =
+                    createLine(restArea, Line.Direction.VERTICAL, (float) (i - 1) / i);
+            verticalLines.add(verticalLine);
+            StraightArea spiltArea = new StraightArea(restArea);
+            spiltArea.lineLeft = verticalLine;
+            int size = horizontalLines.size();
+            for (int j = 0; j <= size; j++) {
+                StraightArea blockArea = new StraightArea(spiltArea);
+                if (j == 0) {
+                    blockArea.lineTop = horizontalLines.get(j);
+                } else if (j == size) {
+                    blockArea.lineBottom = horizontalLines.get(j - 1);
+                } else {
+                    blockArea.lineTop = horizontalLines.get(j);
+                    blockArea.lineBottom = horizontalLines.get(j - 1);
+                }
+                areaList.add(blockArea);
+            }
+            restArea.lineRight = verticalLine;
+        }
+        int size = horizontalLines.size();
+        for (int j = 0; j <= size; j++) {
+            StraightArea blockArea = new StraightArea(restArea);
+            if (j == 0) {
+                blockArea.lineTop = horizontalLines.get(j);
+            } else if (j == horizontalLines.size()) {
+                blockArea.lineBottom = horizontalLines.get(j - 1);
+            } else {
+                blockArea.lineTop = horizontalLines.get(j);
+                blockArea.lineBottom = horizontalLines.get(j - 1);
+            }
+            areaList.add(blockArea);
+        }
+
+        List<StraightLine> lines = new ArrayList<>();
+        lines.addAll(horizontalLines);
+        lines.addAll(verticalLines);
+        return new Pair<>(lines, areaList);
+    }
+
+    static List<StraightArea> cutAreaCross(final StraightArea area, final StraightLine horizontal,
+                                           final StraightLine vertical) {
+        List<StraightArea> list = new ArrayList<>();
+
+        StraightArea one = new StraightArea(area);
+        one.lineBottom = horizontal;
+        one.lineRight = vertical;
+        list.add(one);
+
+        StraightArea two = new StraightArea(area);
+        two.lineBottom = horizontal;
+        two.lineLeft = vertical;
+        list.add(two);
+
+        StraightArea three = new StraightArea(area);
+        three.lineTop = horizontal;
+        three.lineRight = vertical;
+        list.add(three);
+
+        StraightArea four = new StraightArea(area);
+        four.lineTop = horizontal;
+        four.lineLeft = vertical;
+        list.add(four);
+
+        return list;
+    }
+
+    static Pair<List<StraightLine>, List<StraightArea>> cutAreaSpiral(final StraightArea area) {
+        List<StraightLine> lines = new ArrayList<>();
+        List<StraightArea> areas = new ArrayList<>();
+
+        float width = area.width();
+        float height = area.height();
+
+        float left = area.left();
+        float top = area.top();
+
+        PointF one = new PointF(left, top + height / 3);
+        PointF two = new PointF(left + width / 3 * 2, top);
+        PointF three = new PointF(left + width, top + height / 3 * 2);
+        PointF four = new PointF(left + width / 3, top + height);
+        PointF five = new PointF(left + width / 3, top + height / 3);
+        PointF six = new PointF(left + width / 3 * 2, top + height / 3);
+        PointF seven = new PointF(left + width / 3 * 2, top + height / 3 * 2);
+        PointF eight = new PointF(left + width / 3, top + height / 3 * 2);
+
+        StraightLine l1 = new StraightLine(one, six);
+        StraightLine l2 = new StraightLine(two, seven);
+        StraightLine l3 = new StraightLine(eight, three);
+        StraightLine l4 = new StraightLine(five, four);
+
+        l1.setAttachLineStart(area.lineLeft);
+        l1.setAttachLineEnd(l2);
+        l1.setLowerLine(area.lineTop);
+        l1.setUpperLine(l3);
+
+        l2.setAttachLineStart(area.lineTop);
+        l2.setAttachLineEnd(l3);
+        l2.setLowerLine(l4);
+        l2.setUpperLine(area.lineRight);
+
+        l3.setAttachLineStart(l4);
+        l3.setAttachLineEnd(area.lineRight);
+        l3.setLowerLine(l1);
+        l3.setUpperLine(area.lineBottom);
+
+        l4.setAttachLineStart(l1);
+        l4.setAttachLineEnd(area.lineBottom);
+        l4.setLowerLine(area.lineLeft);
+        l4.setUpperLine(l2);
+
+        lines.add(l1);
+        lines.add(l2);
+        lines.add(l3);
+        lines.add(l4);
+
+        StraightArea b1 = new StraightArea(area);
+        b1.lineRight = l2;
+        b1.lineBottom = l1;
+        areas.add(b1);
+
+        StraightArea b2 = new StraightArea(area);
+        b2.lineLeft = l2;
+        b2.lineBottom = l3;
+        areas.add(b2);
+
+        StraightArea b3 = new StraightArea(area);
+        b3.lineRight = l4;
+        b3.lineTop = l1;
+        areas.add(b3);
+
+        StraightArea b4 = new StraightArea(area);
+        b4.lineTop = l1;
+        b4.lineRight = l2;
+        b4.lineLeft = l4;
+        b4.lineBottom = l3;
+        areas.add(b4);
+
+        StraightArea b5 = new StraightArea(area);
+        b5.lineLeft = l4;
+        b5.lineTop = l3;
+        areas.add(b5);
+
+        return new Pair<>(lines, areas);
+    }
+}

+ 33 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/NumberSlantLayout.java

@@ -0,0 +1,33 @@
+package com.huantansheng.easyphotos.models.puzzle.template.slant;
+
+import android.util.Log;
+
+import com.huantansheng.easyphotos.models.puzzle.slant.SlantPuzzleLayout;
+
+
+/**
+ * @author wupanjie
+ */
+
+public abstract class NumberSlantLayout extends SlantPuzzleLayout {
+
+  static final String TAG = "NumberSlantLayout";
+  protected int theme;
+
+  public NumberSlantLayout(int theme) {
+    if (theme >= getThemeCount()) {
+      Log.e(TAG, "NumberSlantLayout: the most theme count is "
+          + getThemeCount()
+          + " ,you should let theme from 0 to "
+          + (getThemeCount() - 1)
+          + " .");
+    }
+    this.theme = theme;
+  }
+
+  public abstract int getThemeCount();
+
+  public int getTheme() {
+    return theme;
+  }
+}

+ 36 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/OneSlantLayout.java

@@ -0,0 +1,36 @@
+package com.huantansheng.easyphotos.models.puzzle.template.slant;
+
+import com.huantansheng.easyphotos.models.puzzle.Line;
+
+/**
+ * @author wupanjie
+ */
+
+public class OneSlantLayout extends NumberSlantLayout {
+  public OneSlantLayout(int theme) {
+    super(theme);
+  }
+
+  @Override
+  public int getThemeCount() {
+    return 4;
+  }
+
+  @Override
+  public void layout() {
+    switch (theme) {
+      case 0:
+        addLine(0, Line.Direction.HORIZONTAL, 0.56f, 0.44f);
+        break;
+      case 1:
+        addLine(0, Line.Direction.VERTICAL, 0.56f, 0.44f);
+        break;
+      case 2:
+        addCross(0, 0.56f, 0.44f, 0.56f, 0.44f);
+        break;
+      case 3:
+        cutArea(0, 1, 2);
+        break;
+    }
+  }
+}

+ 70 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/SlantLayoutHelper.java

@@ -0,0 +1,70 @@
+package com.huantansheng.easyphotos.models.puzzle.template.slant;
+
+
+
+import com.huantansheng.easyphotos.models.puzzle.PuzzleLayout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author wupanjie
+ */
+public class SlantLayoutHelper {
+  private SlantLayoutHelper() {
+
+  }
+
+  public static List<PuzzleLayout> getAllThemeLayout(int pieceCount) {
+    List<PuzzleLayout> puzzleLayouts = new ArrayList<>();
+    switch (pieceCount) {
+      case 1:
+        for (int i = 0; i < 4; i++) {
+          puzzleLayouts.add(new OneSlantLayout(i));
+        }
+        break;
+      case 2:
+        for (int i = 0; i < 2; i++) {
+          puzzleLayouts.add(new TwoSlantLayout(i));
+        }
+        break;
+      case 3:
+        for (int i = 0; i < 6; i++) {
+          puzzleLayouts.add(new ThreeSlantLayout(i));
+        }
+        break;
+      //case 4:
+      //  for (int i = 0; i < 8; i++) {
+      //    puzzleLayouts.add(new FourStraightLayout(i));
+      //  }
+      //  break;
+      //case 5:
+      //  for (int i = 0; i < 17; i++) {
+      //    puzzleLayouts.add(new FiveStraightLayout(i));
+      //  }
+      //  break;
+      //case 6:
+      //  for (int i = 0; i < 12; i++) {
+      //    puzzleLayouts.add(new SixStraightLayout(i));
+      //  }
+      //  break;
+      //case 7:
+      //  for (int i = 0; i < 9; i++) {
+      //    puzzleLayouts.add(new SevenStraightLayout(i));
+      //  }
+      //  break;
+      //case 8:
+      //  for (int i = 0; i < 11; i++) {
+      //    puzzleLayouts.add(new EightStraightLayout(i));
+      //  }
+      //  break;
+      //case 9:
+      //  for (int i = 0; i < 8; i++) {
+      //    puzzleLayouts.add(new NineStraightLayout(i));
+      //  }
+      //  break;
+    }
+
+    return puzzleLayouts;
+  }
+}

+ 48 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/ThreeSlantLayout.java

@@ -0,0 +1,48 @@
+package com.huantansheng.easyphotos.models.puzzle.template.slant;
+
+import com.huantansheng.easyphotos.models.puzzle.Line;
+
+/**
+ * @author wupanjie
+ */
+
+public class ThreeSlantLayout extends NumberSlantLayout {
+  public ThreeSlantLayout(int theme) {
+    super(theme);
+  }
+
+  @Override
+  public int getThemeCount() {
+    return 6;
+  }
+
+  @Override
+  public void layout() {
+    switch (theme) {
+      case 0:
+        addLine(0, Line.Direction.HORIZONTAL, 0.5f);
+        addLine(0, Line.Direction.VERTICAL, 0.56f, 0.44f);
+        break;
+      case 1:
+        addLine(0, Line.Direction.HORIZONTAL, 0.5f);
+        addLine(1, Line.Direction.VERTICAL, 0.56f, 0.44f);
+        break;
+      case 2:
+        addLine(0, Line.Direction.VERTICAL, 0.5f);
+        addLine(0, Line.Direction.HORIZONTAL, 0.56f, 0.44f);
+        break;
+      case 3:
+        addLine(0, Line.Direction.VERTICAL, 0.5f);
+        addLine(1, Line.Direction.HORIZONTAL, 0.56f, 0.44f);
+        break;
+      case 4:
+        addLine(0, Line.Direction.HORIZONTAL, 0.44f, 0.56f);
+        addLine(0, Line.Direction.VERTICAL, 0.56f, 0.44f);
+        break;
+      case 5:
+        addLine(0, Line.Direction.VERTICAL, 0.56f, 0.44f);
+        addLine(1, Line.Direction.HORIZONTAL, 0.44f, 0.56f);
+        break;
+    }
+  }
+}

+ 30 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/TwoSlantLayout.java

@@ -0,0 +1,30 @@
+package com.huantansheng.easyphotos.models.puzzle.template.slant;
+
+import com.huantansheng.easyphotos.models.puzzle.Line;
+
+/**
+ * @author wupanjie
+ */
+
+public class TwoSlantLayout extends NumberSlantLayout {
+  public TwoSlantLayout(int theme) {
+    super(theme);
+  }
+
+  @Override
+  public int getThemeCount() {
+    return 2;
+  }
+
+  @Override
+  public void layout() {
+    switch (theme) {
+      case 0:
+        addLine(0, Line.Direction.HORIZONTAL, 0.56f, 0.44f);
+        break;
+      case 1:
+        addLine(0, Line.Direction.VERTICAL, 0.56f, 0.44f);
+        break;
+    }
+  }
+}

+ 89 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/EightStraightLayout.java

@@ -0,0 +1,89 @@
+package com.huantansheng.easyphotos.models.puzzle.template.straight;
+
+import com.huantansheng.easyphotos.models.puzzle.Line;
+
+/**
+ * @author wupanjie
+ */
+public class EightStraightLayout extends NumberStraightLayout {
+  public EightStraightLayout(int theme) {
+    super(theme);
+  }
+
+  @Override
+  public int getThemeCount() {
+    return 11;
+  }
+
+  @Override
+  public void layout() {
+    switch (theme) {
+      case 0:
+        cutAreaEqualPart(0, 3, 1);
+        break;
+      case 1:
+        cutAreaEqualPart(0, 1, 3);
+        break;
+      case 2:
+        cutAreaEqualPart(0, 4, Line.Direction.VERTICAL);
+        addLine(3, Line.Direction.HORIZONTAL, 4f / 5);
+        addLine(2, Line.Direction.HORIZONTAL, 3f / 5);
+        addLine(1, Line.Direction.HORIZONTAL, 2f / 5);
+        addLine(0, Line.Direction.HORIZONTAL, 1f / 5);
+        break;
+      case 3:
+        cutAreaEqualPart(0, 4, Line.Direction.HORIZONTAL);
+        addLine(3, Line.Direction.VERTICAL, 4f / 5);
+        addLine(2, Line.Direction.VERTICAL, 3f / 5);
+        addLine(1, Line.Direction.VERTICAL, 2f / 5);
+        addLine(0, Line.Direction.VERTICAL, 1f / 5);
+        break;
+      case 4:
+        cutAreaEqualPart(0, 4, Line.Direction.VERTICAL);
+        addLine(3, Line.Direction.HORIZONTAL, 1f / 5);
+        addLine(2, Line.Direction.HORIZONTAL, 2f / 5);
+        addLine(1, Line.Direction.HORIZONTAL, 3f / 5);
+        addLine(0, Line.Direction.HORIZONTAL, 4f / 5);
+        break;
+      case 5:
+        cutAreaEqualPart(0, 4, Line.Direction.HORIZONTAL);
+        addLine(3, Line.Direction.VERTICAL, 1f / 5);
+        addLine(2, Line.Direction.VERTICAL, 2f / 5);
+        addLine(1, Line.Direction.VERTICAL, 3f / 5);
+        addLine(0, Line.Direction.VERTICAL, 4f / 5);
+        break;
+      case 6:
+        cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL);
+        cutAreaEqualPart(2, 3, Line.Direction.VERTICAL);
+        cutAreaEqualPart(1, 2, Line.Direction.VERTICAL);
+        cutAreaEqualPart(0, 3, Line.Direction.VERTICAL);
+        break;
+      case 7:
+        cutAreaEqualPart(0, 3, Line.Direction.VERTICAL);
+        cutAreaEqualPart(2, 3, Line.Direction.HORIZONTAL);
+        cutAreaEqualPart(1, 2, Line.Direction.HORIZONTAL);
+        cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL);
+        break;
+      case 8:
+        addLine(0, Line.Direction.HORIZONTAL, 4f / 5);
+        cutAreaEqualPart(1, 5, Line.Direction.VERTICAL);
+        addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
+        addLine(1, Line.Direction.VERTICAL, 1f / 2);
+        break;
+      case 9:
+        cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL);
+        cutAreaEqualPart(2, 2, Line.Direction.VERTICAL);
+        cutAreaEqualPart(1, 3, Line.Direction.VERTICAL);
+        addLine(0, Line.Direction.VERTICAL, 3f / 4);
+        addLine(0, Line.Direction.VERTICAL, 1f / 3);
+        break;
+      case 10:
+        cutAreaEqualPart(0, 2, 1);
+        addLine(5, Line.Direction.VERTICAL, 1f / 2);
+        addLine(4, Line.Direction.VERTICAL, 1f / 2);
+        break;
+      default:
+        break;
+    }
+  }
+}

+ 94 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/FiveStraightLayout.java

@@ -0,0 +1,94 @@
+package com.huantansheng.easyphotos.models.puzzle.template.straight;
+
+import com.huantansheng.easyphotos.models.puzzle.Line;
+
+/**
+ * @author wupanjie
+ */
+public class FiveStraightLayout extends NumberStraightLayout {
+
+  public FiveStraightLayout(int theme) {
+    super(theme);
+  }
+
+  @Override
+  public int getThemeCount() {
+    return 15;
+  }
+
+  @Override
+  public void layout() {
+    switch (theme) {
+      case 0:
+        addLine(0, Line.Direction.HORIZONTAL, 2f / 5);
+        addLine(0, Line.Direction.VERTICAL, 1f / 2);
+        cutAreaEqualPart(2, 3, Line.Direction.VERTICAL);
+        break;
+      case 1:
+        addLine(0, Line.Direction.HORIZONTAL, 3f / 5);
+        cutAreaEqualPart(0, 3, Line.Direction.VERTICAL);
+        addLine(3, Line.Direction.VERTICAL, 1f / 2);
+        break;
+      case 2:
+        addLine(0, Line.Direction.VERTICAL, 2f / 5);
+        cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL);
+        addLine(1, Line.Direction.HORIZONTAL, 1f / 2);
+        break;
+      case 3:
+        addLine(0, Line.Direction.VERTICAL, 2f / 5);
+        cutAreaEqualPart(1, 3, Line.Direction.HORIZONTAL);
+        addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
+        break;
+      case 4:
+        addLine(0, Line.Direction.HORIZONTAL, 3f / 4);
+        cutAreaEqualPart(1, 4, Line.Direction.VERTICAL);
+        break;
+      case 5:
+        addLine(0, Line.Direction.HORIZONTAL, 1f / 4);
+        cutAreaEqualPart(0, 4, Line.Direction.VERTICAL);
+        break;
+      case 6:
+        addLine(0, Line.Direction.VERTICAL, 3f / 4);
+        cutAreaEqualPart(1, 4, Line.Direction.HORIZONTAL);
+        break;
+      case 7:
+        addLine(0, Line.Direction.VERTICAL, 1f / 4);
+        cutAreaEqualPart(0, 4, Line.Direction.HORIZONTAL);
+        break;
+      case 8:
+        addLine(0, Line.Direction.HORIZONTAL, 1f / 4);
+        addLine(1, Line.Direction.HORIZONTAL, 2f / 3);
+        addLine(0, Line.Direction.VERTICAL, 1f / 2);
+        addLine(3, Line.Direction.VERTICAL, 1f / 2);
+        break;
+      case 9:
+        addLine(0, Line.Direction.VERTICAL, 1f / 4);
+        addLine(1, Line.Direction.VERTICAL, 2f / 3);
+        addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
+        addLine(2, Line.Direction.HORIZONTAL, 1f / 2);
+        break;
+      case 10:
+        addCross(0, 1f / 3);
+        addLine(2, Line.Direction.HORIZONTAL, 1f / 2);
+        break;
+      case 11:
+        addCross(0, 2f / 3);
+        addLine(1, Line.Direction.HORIZONTAL, 1f / 2);
+        break;
+      case 12:
+        addCross(0, 1f / 3, 2f / 3);
+        addLine(3, Line.Direction.HORIZONTAL, 1f / 2);
+        break;
+      case 13:
+        addCross(0, 2f / 3, 1f / 3);
+        addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
+        break;
+      case 14:
+        cutSpiral(0);
+        break;
+      default:
+        cutAreaEqualPart(0, 5, Line.Direction.HORIZONTAL);
+        break;
+    }
+  }
+}

+ 52 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/FourStraightLayout.java

@@ -0,0 +1,52 @@
+package com.huantansheng.easyphotos.models.puzzle.template.straight;
+
+import com.huantansheng.easyphotos.models.puzzle.Line;
+
+/**
+ * @author wupanjie
+ */
+public class FourStraightLayout extends NumberStraightLayout {
+  private static final String TAG = "FourStraightLayout";
+
+  public FourStraightLayout(int theme) {
+    super(theme);
+  }
+
+  @Override
+  public int getThemeCount() {
+    return 6;
+  }
+
+  @Override
+  public void layout() {
+    switch (theme) {
+      case 0:
+        addCross(0, 1f / 2);
+        break;
+      case 1:
+        addLine(0, Line.Direction.HORIZONTAL, 1f / 3);
+        cutAreaEqualPart(0, 3, Line.Direction.VERTICAL);
+        break;
+      case 2:
+        addLine(0, Line.Direction.HORIZONTAL, 2f / 3);
+        cutAreaEqualPart(1, 3, Line.Direction.VERTICAL);
+        break;
+      case 3:
+        addLine(0, Line.Direction.VERTICAL, 1f / 3);
+        cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL);
+        break;
+      case 4:
+        addLine(0, Line.Direction.VERTICAL, 2f / 3);
+        cutAreaEqualPart(1, 3, Line.Direction.HORIZONTAL);
+        break;
+      case 5:
+        addLine(0, Line.Direction.VERTICAL, 1f / 2);
+        addLine(1, Line.Direction.HORIZONTAL, 2f / 3);
+        addLine(1, Line.Direction.HORIZONTAL, 1f / 3);
+        break;
+      default:
+        cutAreaEqualPart(0, 4, Line.Direction.HORIZONTAL);
+        break;
+    }
+  }
+}

+ 76 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/NineStraightLayout.java

@@ -0,0 +1,76 @@
+package com.huantansheng.easyphotos.models.puzzle.template.straight;
+
+import com.huantansheng.easyphotos.models.puzzle.Line;
+
+/**
+ * @author wupanjie
+ */
+public class NineStraightLayout extends NumberStraightLayout {
+  public NineStraightLayout(int theme) {
+    super(theme);
+  }
+
+  @Override
+  public int getThemeCount() {
+    return 8;
+  }
+
+  @Override
+  public void layout() {
+    switch (theme) {
+      case 0:
+        cutAreaEqualPart(0, 2, 2);
+        break;
+      case 1:
+        addLine(0, Line.Direction.VERTICAL, 3f / 4);
+        addLine(0, Line.Direction.VERTICAL, 1f / 3);
+        cutAreaEqualPart(2, 4, Line.Direction.HORIZONTAL);
+        cutAreaEqualPart(0, 4, Line.Direction.HORIZONTAL);
+        break;
+      case 2:
+        addLine(0, Line.Direction.HORIZONTAL, 3f / 4);
+        addLine(0, Line.Direction.HORIZONTAL, 1f / 3);
+        cutAreaEqualPart(2, 4, Line.Direction.VERTICAL);
+        cutAreaEqualPart(0, 4, Line.Direction.VERTICAL);
+        break;
+      case 3:
+        addLine(0, Line.Direction.HORIZONTAL, 3f / 4);
+        addLine(0, Line.Direction.HORIZONTAL, 1f / 3);
+        cutAreaEqualPart(2, 3, Line.Direction.VERTICAL);
+        addLine(1, Line.Direction.VERTICAL, 3f / 4);
+        addLine(1, Line.Direction.VERTICAL, 1f / 3);
+        cutAreaEqualPart(0, 3, Line.Direction.VERTICAL);
+        break;
+      case 4:
+        addLine(0, Line.Direction.VERTICAL, 3f / 4);
+        addLine(0, Line.Direction.VERTICAL, 1f / 3);
+        cutAreaEqualPart(2, 3, Line.Direction.HORIZONTAL);
+        addLine(1, Line.Direction.HORIZONTAL, 3f / 4);
+        addLine(1, Line.Direction.HORIZONTAL, 1f / 3);
+        cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL);
+        break;
+      case 5:
+        cutAreaEqualPart(0, 3, Line.Direction.VERTICAL);
+        addLine(2, Line.Direction.HORIZONTAL, 3f / 4);
+        addLine(2, Line.Direction.HORIZONTAL, 1f / 3);
+        cutAreaEqualPart(1, 3, Line.Direction.HORIZONTAL);
+        addLine(0, Line.Direction.HORIZONTAL, 3f / 4);
+        addLine(0, Line.Direction.HORIZONTAL, 1f / 3);
+        break;
+      case 6:
+        cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL);
+        addLine(2, Line.Direction.VERTICAL, 3f / 4);
+        addLine(2, Line.Direction.VERTICAL, 1f / 3);
+        cutAreaEqualPart(1, 3, Line.Direction.VERTICAL);
+        addLine(0, Line.Direction.VERTICAL, 3f / 4);
+        addLine(0, Line.Direction.VERTICAL, 1f / 3);
+        break;
+      case 7:
+        addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
+        cutAreaEqualPart(1, 1, 3);
+        break;
+      default:
+        break;
+    }
+  }
+}

+ 31 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/NumberStraightLayout.java

@@ -0,0 +1,31 @@
+package com.huantansheng.easyphotos.models.puzzle.template.straight;
+
+import android.util.Log;
+
+import com.huantansheng.easyphotos.models.puzzle.straight.StraightPuzzleLayout;
+
+
+/**
+ * @author wupanjie
+ */
+public abstract class NumberStraightLayout extends StraightPuzzleLayout {
+  static final String TAG = "NumberStraightLayout";
+  protected int theme;
+
+  public NumberStraightLayout(int theme) {
+    if (theme >= getThemeCount()) {
+      Log.e(TAG, "NumberStraightLayout: the most theme count is "
+          + getThemeCount()
+          + " ,you should let theme from 0 to "
+          + (getThemeCount() - 1)
+          + " .");
+    }
+    this.theme = theme;
+  }
+
+  public abstract int getThemeCount();
+
+  public int getTheme() {
+    return theme;
+  }
+}

+ 45 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/OneStraightLayout.java

@@ -0,0 +1,45 @@
+package com.huantansheng.easyphotos.models.puzzle.template.straight;
+
+import com.huantansheng.easyphotos.models.puzzle.Line;
+
+/**
+ * @author wupanjie
+ */
+public class OneStraightLayout extends NumberStraightLayout {
+
+  public OneStraightLayout(int theme) {
+    super(theme);
+  }
+
+  @Override
+  public int getThemeCount() {
+    return 6;
+  }
+
+  @Override
+  public void layout() {
+    switch (theme) {
+      case 0:
+        addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
+        break;
+      case 1:
+        addLine(0, Line.Direction.VERTICAL, 1f / 2);
+        break;
+      case 2:
+        addCross(0, 1f / 2);
+        break;
+      case 3:
+        cutAreaEqualPart(0, 2, 1);
+        break;
+      case 4:
+        cutAreaEqualPart(0, 1, 2);
+        break;
+      case 5:
+        cutAreaEqualPart(0, 2, 2);
+        break;
+      default:
+        addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
+        break;
+    }
+  }
+}

+ 77 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/SevenStraightLayout.java

@@ -0,0 +1,77 @@
+package com.huantansheng.easyphotos.models.puzzle.template.straight;
+
+import com.huantansheng.easyphotos.models.puzzle.Line;
+
+/**
+ * @author wupanjie
+ */
+public class SevenStraightLayout extends NumberStraightLayout {
+  public SevenStraightLayout(int theme) {
+    super(theme);
+  }
+
+  @Override
+  public int getThemeCount() {
+    return 9;
+  }
+
+  @Override
+  public void layout() {
+    switch (theme) {
+      case 0:
+        addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
+        cutAreaEqualPart(1, 4, Line.Direction.VERTICAL);
+        cutAreaEqualPart(0, 3, Line.Direction.VERTICAL);
+        break;
+      case 1:
+        addLine(0, Line.Direction.VERTICAL, 1f / 2);
+        cutAreaEqualPart(1, 4, Line.Direction.HORIZONTAL);
+        cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL);
+        break;
+      case 2:
+        addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
+        cutAreaEqualPart(1, 1, 2);
+        break;
+      case 3:
+        addLine(0, Line.Direction.HORIZONTAL, 2f / 3);
+        cutAreaEqualPart(1, 3, Line.Direction.VERTICAL);
+        addCross(0, 1f / 2);
+        break;
+      case 4:
+        cutAreaEqualPart(0, 3, Line.Direction.VERTICAL);
+        cutAreaEqualPart(2, 3, Line.Direction.HORIZONTAL);
+        cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL);
+        break;
+      case 5:
+        addLine(0, Line.Direction.HORIZONTAL, 2f / 3);
+        addLine(1, Line.Direction.VERTICAL, 3f / 4);
+        addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
+        addLine(1, Line.Direction.VERTICAL, 2f / 5);
+        cutAreaEqualPart(0, 3, Line.Direction.VERTICAL);
+        break;
+      case 6:
+        addLine(0, Line.Direction.VERTICAL, 2f / 3);
+        addLine(1, Line.Direction.HORIZONTAL, 3f / 4);
+        addLine(0, Line.Direction.VERTICAL, 1f / 2);
+        addLine(1, Line.Direction.HORIZONTAL, 2f / 5);
+        cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL);
+        break;
+      case 7:
+        addLine(0, Line.Direction.VERTICAL, 1f / 4);
+        addLine(1, Line.Direction.VERTICAL, 2f / 3);
+        addLine(2, Line.Direction.HORIZONTAL, 1f / 2);
+        addLine(1, Line.Direction.HORIZONTAL, 3f / 4);
+        addLine(1, Line.Direction.HORIZONTAL, 1f / 3);
+        addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
+        break;
+      case 8:
+        addLine(0, Line.Direction.HORIZONTAL, 1f / 4);
+        addLine(1, Line.Direction.HORIZONTAL, 2f / 3);
+        cutAreaEqualPart(2, 3, Line.Direction.VERTICAL);
+        cutAreaEqualPart(0, 3, Line.Direction.VERTICAL);
+        break;
+      default:
+        break;
+    }
+  }
+}

+ 93 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/SixStraightLayout.java

@@ -0,0 +1,93 @@
+package com.huantansheng.easyphotos.models.puzzle.template.straight;
+
+
+import com.huantansheng.easyphotos.models.puzzle.Line;
+
+/**
+ * @author wupanjie
+ */
+public class SixStraightLayout extends NumberStraightLayout {
+
+  public SixStraightLayout(int theme) {
+    super(theme);
+  }
+
+  @Override
+  public int getThemeCount() {
+    return 12;
+  }
+
+  @Override
+  public void layout() {
+    switch (theme) {
+      case 0:
+        cutAreaEqualPart(0, 2, 1);
+        break;
+
+      case 1:
+        cutAreaEqualPart(0, 1, 2);
+        break;
+      case 2:
+        addCross(0, 2f / 3, 1f / 2);
+        addLine(3, Line.Direction.VERTICAL, 1f / 2);
+        addLine(2, Line.Direction.VERTICAL, 1f / 2);
+        break;
+      case 3:
+        addCross(0, 1f / 2, 2f / 3);
+        addLine(3, Line.Direction.HORIZONTAL, 1f / 2);
+        addLine(1, Line.Direction.HORIZONTAL, 1f / 2);
+        break;
+      case 4:
+        addCross(0, 1f / 2, 1f / 3);
+        addLine(2, Line.Direction.HORIZONTAL, 1f / 2);
+        addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
+        break;
+      case 5:
+        addCross(0, 1f / 3, 1f / 2);
+        addLine(1, Line.Direction.VERTICAL, 1f / 2);
+        addLine(0, Line.Direction.VERTICAL, 1f / 2);
+        break;
+      case 6:
+        addLine(0, Line.Direction.HORIZONTAL, 4f / 5);
+        cutAreaEqualPart(1, 5, Line.Direction.VERTICAL);
+        break;
+      case 7:
+        addLine(0, Line.Direction.HORIZONTAL, 1f / 4);
+        addLine(1, Line.Direction.HORIZONTAL, 2f / 3);
+        addLine(1, Line.Direction.VERTICAL, 1f / 4);
+        addLine(2, Line.Direction.VERTICAL, 2f / 3);
+        addLine(4, Line.Direction.VERTICAL, 1f / 2);
+        break;
+      case 8:
+        addCross(0, 1f / 3);
+        addLine(1, Line.Direction.VERTICAL, 1f / 2);
+        addLine(4, Line.Direction.HORIZONTAL, 1f / 2);
+        break;
+      case 9:
+        addCross(0, 2f / 3, 1f / 3);
+        addLine(3, Line.Direction.VERTICAL, 1f / 2);
+        addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
+        break;
+      case 10:
+        addCross(0, 2f / 3);
+        addLine(2, Line.Direction.VERTICAL, 1f / 2);
+        addLine(1, Line.Direction.HORIZONTAL, 1f / 2);
+        break;
+      case 11:
+        addCross(0, 1f / 3, 2f / 3);
+        addLine(3, Line.Direction.HORIZONTAL, 1f / 2);
+        addLine(0, Line.Direction.VERTICAL, 1f / 2);
+        break;
+      case 12:
+        addCross(0, 1f / 3);
+        addLine(2, Line.Direction.HORIZONTAL, 1f / 2);
+        addLine(1, Line.Direction.VERTICAL, 1f / 2);
+        break;
+      default:
+        addCross(0, 2f / 3, 1f / 2);
+        addLine(3, Line.Direction.VERTICAL, 1f / 2);
+        addLine(2, Line.Direction.VERTICAL, 1f / 2);
+        break;
+    }
+  }
+}

+ 69 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/StraightLayoutHelper.java

@@ -0,0 +1,69 @@
+package com.huantansheng.easyphotos.models.puzzle.template.straight;
+
+
+import com.huantansheng.easyphotos.models.puzzle.PuzzleLayout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author wupanjie
+ */
+public class StraightLayoutHelper {
+  private StraightLayoutHelper() {
+
+  }
+
+  public static List<PuzzleLayout> getAllThemeLayout(int pieceCount) {
+    List<PuzzleLayout> puzzleLayouts = new ArrayList<>();
+    switch (pieceCount) {
+      case 1:
+        for (int i = 0; i < 6; i++) {
+          puzzleLayouts.add(new OneStraightLayout(i));
+        }
+        break;
+      case 2:
+        for (int i = 0; i < 6; i++) {
+          puzzleLayouts.add(new TwoStraightLayout(i));
+        }
+        break;
+      case 3:
+        for (int i = 0; i < 6; i++) {
+          puzzleLayouts.add(new ThreeStraightLayout(i));
+        }
+        break;
+      case 4:
+        for (int i = 0; i < 8; i++) {
+          puzzleLayouts.add(new FourStraightLayout(i));
+        }
+        break;
+      case 5:
+        for (int i = 0; i < 17; i++) {
+          puzzleLayouts.add(new FiveStraightLayout(i));
+        }
+        break;
+      case 6:
+        for (int i = 0; i < 12; i++) {
+          puzzleLayouts.add(new SixStraightLayout(i));
+        }
+        break;
+      case 7:
+        for (int i = 0; i < 9; i++) {
+          puzzleLayouts.add(new SevenStraightLayout(i));
+        }
+        break;
+      case 8:
+        for (int i = 0; i < 11; i++) {
+          puzzleLayouts.add(new EightStraightLayout(i));
+        }
+        break;
+      case 9:
+        for (int i = 0; i < 8; i++) {
+          puzzleLayouts.add(new NineStraightLayout(i));
+        }
+        break;
+    }
+
+    return puzzleLayouts;
+  }
+}

+ 50 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/ThreeStraightLayout.java

@@ -0,0 +1,50 @@
+package com.huantansheng.easyphotos.models.puzzle.template.straight;
+
+
+import com.huantansheng.easyphotos.models.puzzle.Line;
+
+/**
+ * @author wupanjie
+ */
+public class ThreeStraightLayout extends NumberStraightLayout {
+
+  public ThreeStraightLayout(int theme) {
+    super(theme);
+  }
+
+  @Override
+  public int getThemeCount() {
+    return 6;
+  }
+
+  @Override
+  public void layout() {
+    switch (theme) {
+      case 0:
+        cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL);
+        break;
+      case 1:
+        cutAreaEqualPart(0, 3, Line.Direction.VERTICAL);
+        break;
+      case 2:
+        addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
+        addLine(0, Line.Direction.VERTICAL, 1f / 2);
+        break;
+      case 3:
+        addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
+        addLine(1, Line.Direction.VERTICAL, 1f / 2);
+        break;
+      case 4:
+        addLine(0, Line.Direction.VERTICAL, 1f / 2);
+        addLine(0, Line.Direction.HORIZONTAL, 1f / 2);
+        break;
+      case 5:
+        addLine(0, Line.Direction.VERTICAL, 1f / 2);
+        addLine(1, Line.Direction.HORIZONTAL, 1f / 2);
+        break;
+      default:
+        cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL);
+        break;
+    }
+  }
+}

+ 58 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/TwoStraightLayout.java

@@ -0,0 +1,58 @@
+package com.huantansheng.easyphotos.models.puzzle.template.straight;
+
+import android.util.Log;
+
+import com.huantansheng.easyphotos.models.puzzle.Line;
+
+
+/**
+ * @author wupanjie
+ */
+public class TwoStraightLayout extends NumberStraightLayout {
+  private float mRadio = 1f / 2;
+
+  public TwoStraightLayout(int theme) {
+    super(theme);
+  }
+
+  public TwoStraightLayout(float radio, int theme) {
+    super(theme);
+    if (mRadio > 1) {
+      Log.e(TAG, "CrossLayout: the radio can not greater than 1f");
+      mRadio = 1f;
+    }
+    mRadio = radio;
+  }
+
+  @Override
+  public int getThemeCount() {
+    return 6;
+  }
+
+  @Override
+  public void layout() {
+    switch (theme) {
+      case 0:
+        addLine(0, Line.Direction.HORIZONTAL, mRadio);
+        break;
+      case 1:
+        addLine(0, Line.Direction.VERTICAL, mRadio);
+        break;
+      case 2:
+        addLine(0, Line.Direction.HORIZONTAL, 1f / 3);
+        break;
+      case 3:
+        addLine(0, Line.Direction.HORIZONTAL, 2f / 3);
+        break;
+      case 4:
+        addLine(0, Line.Direction.VERTICAL, 1f / 3);
+        break;
+      case 5:
+        addLine(0, Line.Direction.VERTICAL, 2f / 3);
+        break;
+      default:
+        addLine(0, Line.Direction.HORIZONTAL, mRadio);
+        break;
+    }
+  }
+}

+ 220 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/sticker/StickerModel.java

@@ -0,0 +1,220 @@
+package com.huantansheng.easyphotos.models.sticker;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import androidx.fragment.app.FragmentManager;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.huantansheng.easyphotos.EasyPhotos;
+import com.huantansheng.easyphotos.models.sticker.cache.StickerCache;
+import com.huantansheng.easyphotos.models.sticker.entity.TextStickerData;
+import com.huantansheng.easyphotos.models.sticker.view.BitmapSticker;
+import com.huantansheng.easyphotos.models.sticker.view.TextSticker;
+import com.huantansheng.easyphotos.models.sticker.listener.OnStickerClickListener;
+import com.huantansheng.easyphotos.models.sticker.view.EditFragment;
+import com.huantansheng.easyphotos.utils.bitmap.BitmapUtils;
+import com.huantansheng.easyphotos.utils.bitmap.SaveBitmapCallBack;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 贴图view的管理器,用于module与外部解耦
+ * Created by huan on 2017/7/24.
+ */
+
+public class StickerModel {
+    public static final ArrayList<TextStickerData> textDataList = new ArrayList<>();
+
+    public List<BitmapSticker> bitmapStickers;
+    public List<TextSticker> textStickers;
+    public BitmapSticker currBitmapSticker;
+    public TextSticker currTextSticker;
+
+
+    public StickerModel() {
+        super();
+        this.bitmapStickers = new ArrayList<>();
+        this.textStickers = new ArrayList<>();
+    }
+
+    public void addBitmapSticker(Context cxt, String imagePath, int imageResourceId, ViewGroup rootgroup) {
+
+        if (bitmapStickers.size() > 0 && !bitmapStickers.get(bitmapStickers.size() - 1).isChecked) {
+            bitmapStickers.get(bitmapStickers.size() - 1).delete();
+        }
+        final BitmapSticker sticker = new BitmapSticker(cxt, imagePath, imageResourceId, rootgroup.getWidth() / 2, rootgroup.getHeight() / 2);
+        sticker.setOnStickerClickListener(new OnStickerClickListener() {
+            @Override
+            public void onDelete() {
+                bitmapStickers.remove(sticker);
+            }
+
+            @Override
+            public void onEditor() {
+
+            }
+
+            @Override
+            public void onTop() {
+                bitmapStickers.remove(sticker);
+                bitmapStickers.add(sticker);
+            }
+
+            @Override
+            public void onUsing() {
+                if (currBitmapSticker != null && currBitmapSticker != sticker) {
+                    currBitmapSticker.setUsing(false);
+                    currBitmapSticker = sticker;
+                }
+            }
+        });
+        if (currBitmapSticker != null) {
+            currBitmapSticker.setUsing(false);
+        }
+        rootgroup.addView(sticker);
+        currBitmapSticker = sticker;
+        bitmapStickers.add(sticker);
+    }
+
+
+    public void addTextSticker(final Context cxt, final FragmentManager fragmentManager, String text, ViewGroup rootgroup) {
+
+        if (textStickers.size() > 0 && !textStickers.get(textStickers.size() - 1).isChecked) {
+            textStickers.get(textStickers.size() - 1).delete();
+        }
+        final TextSticker sticker = new TextSticker(cxt, text, rootgroup.getWidth() / 2, rootgroup.getHeight() / 2);
+        sticker.setOnStickerClickListener(new OnStickerClickListener() {
+            @Override
+            public void onDelete() {
+                textStickers.remove(sticker);
+            }
+
+            @Override
+            public void onEditor() {
+                EditFragment.show(fragmentManager, sticker);
+            }
+
+            @Override
+            public void onTop() {
+                textStickers.remove(sticker);
+                textStickers.add(sticker);
+            }
+
+            @Override
+            public void onUsing() {
+                if (currTextSticker != null && currTextSticker != sticker) {
+                    currTextSticker.setUsing(false);
+                    currTextSticker = sticker;
+                }
+            }
+        });
+        if (currBitmapSticker != null) {
+            currBitmapSticker.setUsing(false);
+        }
+        rootgroup.addView(sticker);
+        currTextSticker = sticker;
+        textStickers.add(sticker);
+    }
+
+    public void save(Activity act, ViewGroup stickerGroup, View imageGroup, int imageWidth, int imageHeight, final String dirPath, final String namePrefix, final boolean notifyMedia, final SaveBitmapCallBack callBack) {
+
+        if (null != this.currBitmapSticker && this.currBitmapSticker.isUsing()) {
+            this.currBitmapSticker.setUsing(false);
+        }
+        if (null != this.currTextSticker && this.currTextSticker.isUsing()) {
+            this.currTextSticker.setUsing(false);
+        }
+
+        for (BitmapSticker bs : bitmapStickers) {
+            if (bs.isUsing()) {
+                bs.setUsing(false);
+            }
+        }
+
+        for (TextSticker ts : textStickers) {
+            if (ts.isUsing()) {
+                ts.setUsing(false);
+            }
+        }
+
+        Bitmap srcBitmap = Bitmap.createBitmap(stickerGroup.getWidth(), stickerGroup.getHeight(), Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(srcBitmap);
+        stickerGroup.draw(canvas);
+
+        Bitmap cropBitmap = Bitmap.createBitmap(srcBitmap, imageGroup.getLeft(), imageGroup.getTop(), imageGroup.getWidth(), imageGroup.getHeight());
+        BitmapUtils.recycle(srcBitmap);
+        Bitmap saveBitmap = null;
+        if (imageGroup.getWidth() > imageWidth || imageGroup.getHeight() > imageHeight) {
+            saveBitmap = Bitmap.createScaledBitmap(cropBitmap, imageWidth, imageHeight, true);
+            BitmapUtils.recycle(cropBitmap);
+        } else {
+            saveBitmap = cropBitmap;
+        }
+
+        EasyPhotos.saveBitmapToDir(act, dirPath, namePrefix, saveBitmap, notifyMedia, callBack);
+
+    }
+
+    public void setCanvasSize(final Bitmap b, final ViewGroup imageGroup) {
+        if (imageGroup.getMeasuredWidth() == 0) {
+            imageGroup.post(new Runnable() {
+                @Override
+                public void run() {
+                    setSize(b, imageGroup);
+                }
+            });
+        } else {
+            setSize(b, imageGroup);
+        }
+    }
+
+    private void setSize(Bitmap b, ViewGroup v) {
+        int bW = b.getWidth();
+        int bH = b.getHeight();
+
+        int vW = v.getMeasuredWidth();
+        int vH = v.getMeasuredHeight();
+
+        float scalW = (float) vW / (float) bW;
+        float scalH = (float) vH / (float) bH;
+
+        ViewGroup.LayoutParams params = v.getLayoutParams();
+        //如果图片小于viewGroup的宽高则把viewgroup设置为图片宽高
+//        if (bW < vW && bH < vH) {
+//            params.width = bW;
+//            params.height = bH;
+//            v.setLayoutParams(params);
+//            return;
+//        }
+        if (bW >= bH) {
+            params.width = vW;
+            params.height = (int) (scalW * bH);
+        } else {
+            params.width = (int) (scalH * bW);
+            params.height = vH;
+        }
+        if (params.width > vW) {
+            float tempScaleW = (float) vW / (float) params.width;
+            params.width = vW;
+            params.height = (int) (params.height * tempScaleW);
+        }
+        if (params.height > vH) {
+            float tempScaleH = (float) vH / (float) params.height;
+            params.height = vH;
+            params.width = (int) (params.width * tempScaleH);
+        }
+        v.setLayoutParams(params);
+    }
+
+    /**
+     * 释放资源
+     */
+    public void release() {
+        StickerCache.get().clear();
+    }
+
+}

+ 114 - 0
easyPhotos/src/main/java/com/huantansheng/easyphotos/models/sticker/cache/StickerCache.java

@@ -0,0 +1,114 @@
+package com.huantansheng.easyphotos.models.sticker.cache;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import androidx.annotation.IdRes;
+
+import com.huantansheng.easyphotos.EasyPhotos;
+
+import java.util.LinkedHashMap;
+
+/**
+ * 贴纸的图片缓存器
+ * Created by huan on 2017/12/8.
+ */
+
+public class StickerCache {
+    private static StickerCache instance = null;
+
+    public static StickerCache get() {
+        if (null == instance) {
+            synchronized (StickerCache.class) {
+                if (null == instance) {
+                    instance = new StickerCache();
+                }
+            }
+        }
+        return instance;
+    }
+
+    private LinkedHashMap<String, Bitmap> srcBitmapCache = null;
+    private LinkedHashMap<String, Bitmap> mirrorBitmapCache = null;
+    private LinkedHashMap<String, Integer> bitmapUsedCount = null;
+
+    private StickerCache() {
+        srcBitmapCache = new LinkedHashMap<>();
+        mirrorBitmapCache = new LinkedHashMap<>();
+        bitmapUsedCount = new LinkedHashMap<>();
+    }
+
+    public Bitmap getSrcBitmap(String path) {
+        Bitmap bitmap = srcBitmapCache.get(path);
+        if (null == bitmap) {
+            bitmap = BitmapFactory.decodeFile(path);
+            srcBitmapCache.put(path, bitmap);
+            bitmapUsedCount.put(path, 0);
+            convertMirror(path, bitmap);
+        }
+
+        int count = bitmapUsedCount.get(path);
+        bitmapUsedCount.put(path, ++count);
+        return bitmap;
+    }
+
+    public Bitmap getSrcBitmap(Resources resources, @IdRes int resId) {
+        String path = String.valueOf(resId);
+        Bitmap bitmap = srcBitmapCache.get(path);
+        if (null == bitmap) {
+            bitmap = BitmapFactory.decodeResource(resources, resId);
+            srcBitmapCache.put(path, bitmap);
+            bitmapUsedCount.put(path, 0);
+            convertMirror(path, bitmap);
+        }
+
+        int count = bitmapUsedCount.get(path);
+        bitmapUsedCount.put(path, ++count);
+        return bitmap;
+    }
+
+    public Bitmap getMirrorBitmap(String key) {
+        return mirrorBitmapCache.get(key);
+    }
+
+
+    public void clear() {
+        for (String key : srcBitmapCache.keySet()) {
+            recycle(key);
+        }
+    }
+
+    public void recycle(String key) {
+        if (!srcBitmapCache.containsKey(key)) {
+            return;
+        }
+
+        int count = bitmapUsedCount.get(key);
+        if (count > 1) {
+            count--;
+            bitmapUsedCount.put(key, count);
+            return;
+        }
+
+        EasyPhotos.recycle(srcBitmapCache.get(key), mirrorBitmapCache.get(key));
+        removeKey(key);
+    }
+
+    private void convertMirror(String key, Bitmap a) {
+        int w = a.getWidth();
+        int h = a.getHeight();
+
+        Matrix m = new Matrix();
+        m.postScale(-1, 1);   //镜像水平翻转
+        Bitmap mirrorBitmap = Bitmap.createBitmap(a, 0, 0, w, h, m, true);
+        mirrorBitmapCache.put(key, mirrorBitmap);
+    }
+
+
+    private void removeKey(String key) {
+        srcBitmapCache.remove(key);
+        mirrorBitmapCache.remove(key);
+        bitmapUsedCount.remove(key);
+    }
+}

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません