Browse Source

修改作品合成部分流程和样式

Pq 6 months ago
parent
commit
b3d1e042de
81 changed files with 3824 additions and 170 deletions
  1. 3 0
      BaseLibrary/build.gradle
  2. 18 0
      BaseLibrary/src/main/java/com/cooleshow/base/bean/ShareIntentBean.java
  3. 31 0
      BaseLibrary/src/main/java/com/cooleshow/base/common/CommonConfig.java
  4. 2 0
      BaseLibrary/src/main/java/com/cooleshow/base/common/WebConstants.java
  5. 13 0
      BaseLibrary/src/main/java/com/cooleshow/base/ui/activity/BaseActivity.java
  6. 11 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/UiUtils.java
  7. 4 0
      BaseLibrary/src/main/java/com/cooleshow/base/utils/helper/WebStartHelper.java
  8. 14 0
      BaseLibrary/src/main/java/com/cooleshow/base/widgets/DialogUtil.java
  9. 26 0
      BaseLibrary/src/main/java/com/cooleshow/base/widgets/ProgressLoading2.java
  10. 6 0
      BaseLibrary/src/main/java/com/cooleshow/base/widgets/dialog/CommonConfirmDialog2.java
  11. 59 29
      BaseLibrary/src/main/res/layout/progress_dialog.xml
  12. 17 1
      accompanySDK/src/main/java/com/daya/accompanysdk/utils/Utils.java
  13. BIN
      accompanySDK/src/main/res/drawable-xhdpi/icon_no_permission_cancel_bg.png
  14. BIN
      accompanySDK/src/main/res/drawable-xhdpi/icon_no_permission_confirm_bg.png
  15. BIN
      accompanySDK/src/main/res/drawable-xhdpi/icon_no_permission_tip_bg.png
  16. BIN
      accompanySDK/src/main/res/drawable-xxhdpi/icon_no_permission_cancel_bg.png
  17. BIN
      accompanySDK/src/main/res/drawable-xxhdpi/icon_no_permission_confirm_bg.png
  18. BIN
      accompanySDK/src/main/res/drawable-xxhdpi/icon_no_permission_tip_bg.png
  19. 1 1
      accompanySDK/src/main/res/layout/accompany_permissions_popu2.xml
  20. 1 0
      accompanySDK/src/main/res/values/colors.xml
  21. 1 0
      guide/.gitignore
  22. 26 0
      guide/build.gradle
  23. 25 0
      guide/proguard-rules.pro
  24. 4 0
      guide/src/main/AndroidManifest.xml
  25. 56 0
      guide/src/main/java/com/app/hubert/guide/NewbieGuide.java
  26. 143 0
      guide/src/main/java/com/app/hubert/guide/core/Builder.java
  27. 342 0
      guide/src/main/java/com/app/hubert/guide/core/Controller.java
  28. 279 0
      guide/src/main/java/com/app/hubert/guide/core/GuideLayout.java
  29. 11 0
      guide/src/main/java/com/app/hubert/guide/lifecycle/FragmentLifecycle.java
  30. 19 0
      guide/src/main/java/com/app/hubert/guide/lifecycle/FragmentLifecycleAdapter.java
  31. 41 0
      guide/src/main/java/com/app/hubert/guide/lifecycle/ListenerFragment.java
  32. 47 0
      guide/src/main/java/com/app/hubert/guide/lifecycle/V4ListenerFragment.java
  33. 24 0
      guide/src/main/java/com/app/hubert/guide/listener/AnimationListenerAdapter.java
  34. 21 0
      guide/src/main/java/com/app/hubert/guide/listener/OnEventListener.java
  35. 26 0
      guide/src/main/java/com/app/hubert/guide/listener/OnGuideChangedListener.java
  36. 12 0
      guide/src/main/java/com/app/hubert/guide/listener/OnHighlightDrewListener.java
  37. 20 0
      guide/src/main/java/com/app/hubert/guide/listener/OnLayoutInflatedListener.java
  38. 14 0
      guide/src/main/java/com/app/hubert/guide/listener/OnPageChangedListener.java
  39. 289 0
      guide/src/main/java/com/app/hubert/guide/model/GuidePage.java
  40. 43 0
      guide/src/main/java/com/app/hubert/guide/model/HighLight.java
  41. 61 0
      guide/src/main/java/com/app/hubert/guide/model/HighlightOptions.java
  42. 52 0
      guide/src/main/java/com/app/hubert/guide/model/HighlightRectF.java
  43. 94 0
      guide/src/main/java/com/app/hubert/guide/model/HighlightView.java
  44. 227 0
      guide/src/main/java/com/app/hubert/guide/model/RelativeGuide.java
  45. 108 0
      guide/src/main/java/com/app/hubert/guide/util/LogUtil.java
  46. 106 0
      guide/src/main/java/com/app/hubert/guide/util/ScreenUtils.java
  47. 73 0
      guide/src/main/java/com/app/hubert/guide/util/ViewUtils.java
  48. 2 0
      guide/src/main/res/values/strings.xml
  49. 3 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/api/Api.java
  50. 9 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/bean/MusicInfoBean.java
  51. 35 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/bean/MusicMergeConfigBean.java
  52. 13 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/constants/MHWebApi.java
  53. 12 3
      musicMerge/src/main/java/com/cooleshow/musicmerge/constants/MusicMergeConfig.java
  54. 40 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/constants/WorksType.java
  55. 1 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/contract/MusicFileHandleContract.java
  56. 132 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/helper/MusicMergeGuideHelper.java
  57. 15 2
      musicMerge/src/main/java/com/cooleshow/musicmerge/presenter/MusicFileHandlePresenter.java
  58. 1 3
      musicMerge/src/main/java/com/cooleshow/musicmerge/ui/MusicHandleActivity.java
  59. 288 23
      musicMerge/src/main/java/com/cooleshow/musicmerge/ui/MusicHandleActivity_.java
  60. 73 3
      musicMerge/src/main/java/com/cooleshow/musicmerge/ui/MusicHandleSettingFragment.java
  61. 258 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/ui/MusicScoreFragment.java
  62. 52 1
      musicMerge/src/main/java/com/cooleshow/musicmerge/viewmodel/MusicMergeViewModel.java
  63. 31 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/widget/CenterItemDecoration.java
  64. 57 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/widget/CustomScrollView.java
  65. 54 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/widget/MusicScoreJsInterface.java
  66. 13 4
      musicMerge/src/main/java/com/cooleshow/musicmerge/widget/ShareDialog.java
  67. 27 0
      musicMerge/src/main/java/com/cooleshow/musicmerge/widget/UploadCoverTipDialog.java
  68. BIN
      musicMerge/src/main/res/drawable-xhdpi/mh_icon_shadow_cover.png
  69. BIN
      musicMerge/src/main/res/drawable-xxhdpi/icon_upload_video_cover.png
  70. BIN
      musicMerge/src/main/res/drawable-xxhdpi/mh_icon_shadow_cover.png
  71. 7 0
      musicMerge/src/main/res/drawable/shape_809ec8_to_3a65a2_15dp.xml
  72. 4 0
      musicMerge/src/main/res/drawable/shape_mh_music_score_bg.xml
  73. 16 7
      musicMerge/src/main/res/drawable/shape_volume_progreesbar_thumb.xml
  74. 49 2
      musicMerge/src/main/res/layout/ac_music_handle_layout.xml
  75. 16 11
      musicMerge/src/main/res/layout/dialog_share_layout.xml
  76. 77 38
      musicMerge/src/main/res/layout/dialog_upload_cover_tip_layout.xml
  77. 136 42
      musicMerge/src/main/res/layout/fg_music_handle_setting_layout.xml
  78. 10 0
      musicMerge/src/main/res/layout/mh_fg_music_score_fragment.xml
  79. 1 0
      musicMerge/src/main/res/values/strings.xml
  80. 1 0
      settings.gradle
  81. 21 0
      usercenter/src/main/java/com/cooleshow/usercenter/helper/UserHelper.java

+ 3 - 0
BaseLibrary/build.gradle

@@ -216,4 +216,7 @@ dependencies {
     compileOnly files('libs/emoji-ios-release.aar')
     compileOnly files('libs/emoji-release.aar')
     compileOnly files('libs/filedownload-1.0.0.aar')
+
+    //依赖引导页模块
+    api project(':guide')
 }

+ 18 - 0
BaseLibrary/src/main/java/com/cooleshow/base/bean/ShareIntentBean.java

@@ -10,8 +10,18 @@ public class ShareIntentBean {
     private String title;
     private String des;
     private String thumb;
+    private int thumbRes;
+    private String id;
     private SHARE_MEDIA share_media;
 
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
     public SHARE_MEDIA getShare_media() {
         return share_media;
     }
@@ -28,6 +38,14 @@ public class ShareIntentBean {
         this.thumb = thumb;
     }
 
+    public int getThumbRes() {
+        return thumbRes;
+    }
+
+    public void setThumbRes(int thumbRes) {
+        this.thumbRes = thumbRes;
+    }
+
     public String getLinkUrl() {
         return linkUrl;
     }

+ 31 - 0
BaseLibrary/src/main/java/com/cooleshow/base/common/CommonConfig.java

@@ -0,0 +1,31 @@
+package com.cooleshow.base.common;
+
+import android.text.TextUtils;
+
+/**
+ * Author by pq, Date on 2023/2/9.
+ */
+public class CommonConfig {
+    public static String APP_MY_WORKS_GUIDE_TAG = "AppMyWorks";
+    public static boolean isNeedShowMusicMergeGuide = true;
+    public static boolean isNeedRequestMusicMergeGuideInfo = true;
+
+    public static void resetGuide() {
+        isNeedRequestMusicMergeGuideInfo = true;
+        isNeedShowMusicMergeGuide = true;
+    }
+
+
+    public static boolean isNeedRequestGuideInfo(String guideTag) {
+        if (TextUtils.equals(guideTag, APP_MY_WORKS_GUIDE_TAG)) {
+            return isNeedRequestMusicMergeGuideInfo;
+        }
+        return false;
+    }
+
+    public static void updateNoNeedRequest(String guideTag) {
+        if (TextUtils.equals(guideTag, APP_MY_WORKS_GUIDE_TAG)) {
+            isNeedRequestMusicMergeGuideInfo = false;
+        }
+    }
+}

+ 2 - 0
BaseLibrary/src/main/java/com/cooleshow/base/common/WebConstants.java

@@ -183,4 +183,6 @@ public abstract class WebConstants {
     public static String getAccompanyUA() {
         return WebConstants.WEB_UA_PARAMS + WebConstants.getCustomUAParams();
     }
+
+    public static final String WORKS_MUSIC_SCORE = getBaseUrlH5() + "#/simple-detail?id=%s&musicRenderType=%s&part-index=%s";////作品合成单行铺面
 }

+ 13 - 0
BaseLibrary/src/main/java/com/cooleshow/base/ui/activity/BaseActivity.java

@@ -1,5 +1,6 @@
 package com.cooleshow.base.ui.activity;
 
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.view.View;
 import android.widget.ImageView;
@@ -96,6 +97,18 @@ public abstract class BaseActivity<V extends ViewBinding> extends RxAppCompatAct
         mLoading.setCanceledOnTouchOutside(cancelable);
     }
 
+    public void showLoadingAndCancel(String text) {
+        if (mLoading != null && !mLoading.isShowing()) {
+            mLoading.showLoadingAndCancel(text);
+        }
+    }
+
+    public void setLoadingStyle(Drawable drawable, int contentTestSize){
+        if (mLoading != null) {
+            mLoading.setLoadingStyle(drawable,contentTestSize);
+        }
+    }
+
     @Override
     public void hideLoading() {
         if (mLoading != null) {

+ 11 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/UiUtils.java

@@ -440,4 +440,15 @@ public class UiUtils {
         drawableRight = isShow ? selectDrawable : R.drawable.icon_arrow_down;
         textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, drawableRight, 0);
     }
+
+    public static void setTextMarquee(TextView textView) {
+        if (textView != null) {
+            textView.setEllipsize(TextUtils.TruncateAt.MARQUEE);
+            textView.setSingleLine(true);
+            textView.setSelected(true);
+            textView.setFocusable(true);
+            textView.setFocusableInTouchMode(true);
+            textView.setMarqueeRepeatLimit(-1);
+        }
+    }
 }

+ 4 - 0
BaseLibrary/src/main/java/com/cooleshow/base/utils/helper/WebStartHelper.java

@@ -263,4 +263,8 @@ public class WebStartHelper {
                 .withString(WebConstants.WEB_URL, WebConstants.ICP_QUERY)
                 .navigation();
     }
+
+    public static String getWorksMusicScoreUrl(String musicId, String musicRenderType, String partIndex,boolean isVideo) {
+        return String.format(WebConstants.WORKS_MUSIC_SCORE, musicId, musicRenderType,partIndex);
+    }
 }

+ 14 - 0
BaseLibrary/src/main/java/com/cooleshow/base/widgets/DialogUtil.java

@@ -39,6 +39,20 @@ public class DialogUtil {
                 .show(fragmentmanager);
     }
 
+    public static void showInCenterNoCancel(FragmentManager fragmentmanager, int resourcesId, ShowListener showListener) {
+        CommonDialog.init().setLayoutId(resourcesId)
+                .setConvertListener(new ViewConvertListener() {
+                    @Override
+                    public void convertView(ViewHolder holder, BaseDialog dialog) {
+                        showListener.onShow(holder, dialog);
+                    }
+                })
+                .setDimAmount(0.7f)
+                .setGravity(Gravity.CENTER)
+                .setOutCancel(false)
+                .show(fragmentmanager);
+    }
+
     public static void showInCenterWithClose(FragmentManager fragmentmanager, int resourcesId, String title, String content, OnDialogButtonClickListener onDialogButtonClickListener) {
         if (fragmentmanager == null) {
             return;

+ 26 - 0
BaseLibrary/src/main/java/com/cooleshow/base/widgets/ProgressLoading2.java

@@ -3,11 +3,13 @@ package com.cooleshow.base.widgets;
 import android.app.Dialog;
 import android.content.Context;
 import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.view.Gravity;
 import android.view.View;
 import android.view.WindowManager;
 import android.widget.ImageView;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import com.airbnb.lottie.LottieAnimationView;
@@ -24,6 +26,8 @@ public class ProgressLoading2 extends BaseFullDialog {
     private ImageView mImageView;
     private TextView mTvLoadingText;
     private LottieAnimationView mViewLoadingAnim;
+    private TextView mTvCancel;
+    private LinearLayout mLlContent;
 
     public ProgressLoading2(@NonNull Context context) {
         super(context, R.style.LightProgressDialog);
@@ -42,6 +46,8 @@ public class ProgressLoading2 extends BaseFullDialog {
         mImageView = findViewById(R.id.iv_loading);
         mTvLoadingText = findViewById(R.id.tv_loading_text);
         mViewLoadingAnim = findViewById(R.id.view_loading_anim);
+        mTvCancel = findViewById(R.id.tv_cancel_loading);
+        mLlContent = findViewById(R.id.ll_content);
         mViewLoadingAnim.setImageAssetsFolder("lottie/refresh/images/");
         mViewLoadingAnim.setAnimation("lottie/refresh_anim.json");
         mViewLoadingAnim.loop(true);
@@ -69,6 +75,17 @@ public class ProgressLoading2 extends BaseFullDialog {
         }
     }
 
+    public void showLoadingAndCancel(String text) {
+        show();
+        if (mTvCancel != null) {
+            mTvCancel.setVisibility(View.VISIBLE);
+        }
+        if (mTvLoadingText != null) {
+            mTvLoadingText.setText(text);
+            mTvLoadingText.setVisibility(View.VISIBLE);
+        }
+    }
+
     public void updateLoadingText(String text) {
         if (mTvLoadingText != null) {
             mTvLoadingText.setText(text);
@@ -86,4 +103,13 @@ public class ProgressLoading2 extends BaseFullDialog {
             mViewLoadingAnim.cancelAnimation();
         }
     }
+
+    public void setLoadingStyle(Drawable drawable, int contentTestSize){
+        if (mLlContent != null) {
+            mLlContent.setBackground(drawable);
+        }
+        if (mTvLoadingText != null) {
+            mTvLoadingText.setTextSize(contentTestSize);
+        }
+    }
 }

+ 6 - 0
BaseLibrary/src/main/java/com/cooleshow/base/widgets/dialog/CommonConfirmDialog2.java

@@ -164,4 +164,10 @@ public class CommonConfirmDialog2 extends BaseFullDialog {
             mTvCancel.setOnClickListener(listener);
         }
     }
+
+
+    public void setIsCanCel(boolean isCanCel){
+        setCancelable(isCanCel);
+        setCanceledOnTouchOutside(isCanCel);
+    }
 }

+ 59 - 29
BaseLibrary/src/main/res/layout/progress_dialog.xml

@@ -3,41 +3,71 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     style="@style/WrapWrap.Vertical"
-    android:layout_width="100dp"
-    android:minHeight="100dp"
-    android:paddingTop="12dp"
-    android:paddingBottom="12dp"
+    android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:background="@drawable/bg_white_10dp"
-    android:gravity="center">
+    android:layout_gravity="center"
+    android:gravity="center"
+    android:minWidth="80dp">
 
-    <ImageView
-        android:visibility="gone"
-        android:id="@+id/iv_loading"
-        style="@style/WrapWrap"
+    <LinearLayout
+        android:background="@drawable/bg_white_10dp"
+        android:paddingEnd="13dp"
+        android:paddingStart="13dp"
+        android:paddingBottom="5dp"
+        android:gravity="center_horizontal"
+        android:minHeight="98dp"
+        android:id="@+id/ll_content"
+        android:minWidth="98dp"
         android:layout_gravity="center"
-        android:background="@drawable/progress_dialog_anim" />
-
-    <com.airbnb.lottie.LottieAnimationView
-        android:id="@+id/view_loading_anim"
         android:layout_width="wrap_content"
-        android:layout_height="47dp"
-        android:layout_gravity="center"
-        app:lottie_autoPlay="false"
-        app:lottie_imageAssetsFolder="lottie/refresh/images/"
-        app:lottie_loop="true" />
+        android:layout_height="wrap_content">
+
+        <ImageView
+            android:id="@+id/iv_loading"
+            style="@style/WrapWrap"
+            android:layout_gravity="center"
+            android:background="@drawable/progress_dialog_anim"
+            android:visibility="gone" />
+
+        <com.airbnb.lottie.LottieAnimationView
+            android:id="@+id/view_loading_anim"
+            android:layout_width="wrap_content"
+            android:layout_height="47dp"
+            android:layout_gravity="center"
+            app:lottie_autoPlay="false"
+            app:lottie_imageAssetsFolder="lottie/refresh/images/"
+            app:lottie_loop="true" />
 
 
+        <TextView
+            android:id="@+id/tv_loading_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_horizontal|bottom"
+            android:layout_marginTop="5dp"
+            android:gravity="center"
+            android:text="加载中"
+            android:textColor="@color/color_333333"
+            android:textSize="@dimen/sp_12"
+            android:visibility="visible"
+            tools:text="加载中" />
+
+    </LinearLayout>
+
     <TextView
-        android:layout_marginTop="5dp"
-        android:layout_gravity="center_horizontal|bottom"
-        android:id="@+id/tv_loading_text"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="加载中"
-        tools:text="加载中"
+        android:id="@+id/tv_cancel_loading"
+        android:layout_width="90dp"
+        android:layout_height="30dp"
+        android:layout_below="@+id/ll_content"
+        android:layout_centerHorizontal="true"
+        android:layout_gravity="center_horizontal"
+        android:layout_marginTop="12dp"
+        android:background="@drawable/shape_16dp_border_1dp"
         android:gravity="center"
-        android:textColor="@color/color_333333"
-        android:textSize="@dimen/sp_12"
-        android:visibility="visible" />
+        android:paddingStart="14dp"
+        android:paddingEnd="14dp"
+        android:text="@string/cancel"
+        android:textColor="@color/white"
+        android:textSize="@dimen/sp_13"
+        android:visibility="gone" />
 </LinearLayout>

+ 17 - 1
accompanySDK/src/main/java/com/daya/accompanysdk/utils/Utils.java

@@ -5,7 +5,9 @@ import android.text.SpannableString;
 import android.view.View;
 import android.widget.TextView;
 
+import com.cooleshow.base.utils.PermissionUtils;
 import com.cooleshow.base.utils.UiUtils;
+import com.cooleshow.base.widgets.DialogUtil;
 import com.daya.accompanysdk.R;
 
 import androidx.fragment.app.FragmentManager;
@@ -14,7 +16,21 @@ import androidx.fragment.app.FragmentManager;
  * Author by pq, Date on 2024/10/9.
  */
 public class Utils {
+
     public static void showPermissionTipDialog2(FragmentManager fragmentmanager, Context context, String title, String tip) {
-        UiUtils.showPermissionTipDialog(fragmentmanager,context,title,tip);
+        DialogUtil.showInCenterNoCancel(fragmentmanager, R.layout.accompany_permissions_popu2, (holder, dialog) -> {
+            TextView tvContent = holder.getView(R.id.tv_content);
+            View btncancel = holder.getView(R.id.btn_cancel);
+            View btnCommit = holder.getView(R.id.btn_commit);
+            SpannableString string = UiUtils.diffColorString("请", tip, "访问权限", context.getResources().getColor(R.color.color_080808), context.getResources().getColor(R.color.color_f95234));
+            tvContent.setText(string);
+            btncancel.setOnClickListener(view1 -> {
+                dialog.dismiss();
+            });
+            btnCommit.setOnClickListener(view1 -> {
+                PermissionUtils.toSelfSetting(context);
+                dialog.dismiss();
+            });
+        });
     }
 }

BIN
accompanySDK/src/main/res/drawable-xhdpi/icon_no_permission_cancel_bg.png


BIN
accompanySDK/src/main/res/drawable-xhdpi/icon_no_permission_confirm_bg.png


BIN
accompanySDK/src/main/res/drawable-xhdpi/icon_no_permission_tip_bg.png


BIN
accompanySDK/src/main/res/drawable-xxhdpi/icon_no_permission_cancel_bg.png


BIN
accompanySDK/src/main/res/drawable-xxhdpi/icon_no_permission_confirm_bg.png


BIN
accompanySDK/src/main/res/drawable-xxhdpi/icon_no_permission_tip_bg.png


+ 1 - 1
accompanySDK/src/main/res/layout/accompany_permissions_popu2.xml

@@ -41,7 +41,7 @@
         <View
             android:id="@+id/view_help2"
             app:layout_constraintTop_toTopOf="parent"
-            android:layout_marginEnd="39dp"
+            android:layout_marginEnd="48dp"
             app:layout_constraintRight_toRightOf="@+id/iv_top"
             android:layout_width="1px"
             android:layout_height="1px"/>

+ 1 - 0
accompanySDK/src/main/res/values/colors.xml

@@ -1,4 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
     <color name="color_080808">#080808</color>
+    <color name="color_f95234">#F95234</color>
 </resources>

+ 1 - 0
guide/.gitignore

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

+ 26 - 0
guide/build.gradle

@@ -0,0 +1,26 @@
+apply plugin: 'com.android.library'
+
+group = 'com.github.huburt-Hu'
+
+android {
+    compileSdkVersion rootProject.ext.android.compileSdkVersion
+
+    defaultConfig {
+        minSdkVersion 14
+        targetSdkVersion 26
+        versionCode 22
+        versionName "2.2.0"
+
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+    compileOnly "com.android.support:appcompat-v7:$rootProject.ext.android.appcompat"
+}

+ 25 - 0
guide/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 /Users/julie/Library/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

+ 4 - 0
guide/src/main/AndroidManifest.xml

@@ -0,0 +1,4 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.app.hubert.guide">
+
+</manifest>

+ 56 - 0
guide/src/main/java/com/app/hubert/guide/NewbieGuide.java

@@ -0,0 +1,56 @@
+package com.app.hubert.guide;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import com.app.hubert.guide.core.Builder;
+
+import androidx.fragment.app.Fragment;
+
+/**
+ * Created by 
+ * <p>
+ * Created on 2017/7/27.
+ */
+public class NewbieGuide {
+
+    public static final String TAG = "NewbieGuide";
+
+    /**
+     * 成功显示标示
+     */
+    public static final int SUCCESS = 1;
+
+    /**
+     * 显示失败标示,即已经显示过一次
+     */
+    public static final int FAILED = -1;
+
+    /**
+     * 新手指引入口
+     *
+     * @param activity activity
+     * @return builder对象,设置参数
+     */
+    public static Builder with(Activity activity) {
+        return new Builder(activity);
+    }
+
+    public static Builder with(Fragment fragment) {
+        return new Builder(fragment);
+    }
+
+
+    /**
+     * 重置标签的显示次数
+     *
+     * @param context
+     * @param label   标签名
+     */
+    public static void resetLabel(Context context, String label) {
+        SharedPreferences sp = context.getSharedPreferences(NewbieGuide.TAG, Activity.MODE_PRIVATE);
+        sp.edit().putInt(label, 0).apply();
+    }
+}
+

+ 143 - 0
guide/src/main/java/com/app/hubert/guide/core/Builder.java

@@ -0,0 +1,143 @@
+package com.app.hubert.guide.core;
+
+import android.app.Activity;
+import android.text.TextUtils;
+import android.view.View;
+
+import com.app.hubert.guide.listener.OnEventListener;
+import com.app.hubert.guide.listener.OnGuideChangedListener;
+import com.app.hubert.guide.listener.OnPageChangedListener;
+import com.app.hubert.guide.model.GuidePage;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.fragment.app.Fragment;
+
+public class Builder {
+    Activity activity;
+    Fragment fragment;
+//    android.support.v4.app.Fragment v4Fragment;
+    String label;
+    boolean alwaysShow;//总是显示 default false
+    View anchor;//锚点view
+    int showCounts = 1;//显示次数 default once
+    OnGuideChangedListener onGuideChangedListener;
+    OnPageChangedListener onPageChangedListener;
+    OnEventListener eventListener;
+    List<GuidePage> guidePages = new ArrayList<>();
+
+    public Builder(Activity activity) {
+        this.activity = activity;
+    }
+
+    public Builder(Fragment fragment) {
+        this.fragment = fragment;
+        this.activity = fragment.getActivity();
+    }
+
+//    public Builder(android.support.v4.app.Fragment v4Fragment) {
+//        this.v4Fragment = v4Fragment;
+//        this.activity = v4Fragment.getActivity();
+//    }
+
+    /**
+     * 引导层显示的锚点,即根布局,不设置的话默认是decorView
+     *
+     * @param anchor root
+     */
+    public Builder anchor(View anchor) {
+        this.anchor = anchor;
+        return this;
+    }
+
+    /**
+     * 引导层的显示次数,默认是1次。<br>
+     * 这里的次数是通过sp控制的,是指同一个label在不清除缓存的情况下可以显示的总次数。
+     *
+     * @param count 次数
+     */
+    public Builder setShowCounts(int count) {
+        this.showCounts = count;
+        return this;
+    }
+
+    /**
+     * 是否总是显示引导层,即是否无限次的显示。<br>
+     * 默认为false,如果设置了true,{@link Builder#setShowCounts} 将无效。
+     *
+     * @param b
+     */
+    public Builder alwaysShow(boolean b) {
+        this.alwaysShow = b;
+        return this;
+    }
+
+    /**
+     * 添加引导页
+     */
+    public Builder addGuidePage(GuidePage page) {
+        guidePages.add(page);
+        return this;
+    }
+
+    /**
+     * 设置引导层隐藏,显示监听
+     */
+    public Builder setOnGuideChangedListener(OnGuideChangedListener listener) {
+        onGuideChangedListener = listener;
+        return this;
+    }
+
+    /**
+     * 设置引导页切换监听
+     */
+    public Builder setOnPageChangedListener(OnPageChangedListener onPageChangedListener) {
+        this.onPageChangedListener = onPageChangedListener;
+        return this;
+    }
+
+    /**
+     * 设置引导层的辨识名,必须设置项,否则报错
+     */
+    public Builder setLabel(String label) {
+        this.label = label;
+        return this;
+    }
+
+    public Builder setEventListener(OnEventListener eventListener) {
+        this.eventListener = eventListener;
+        return this;
+    }
+
+    /**
+     * 构建引导层controller
+     *
+     * @return controller
+     */
+    public Controller build() {
+        check();
+        return new Controller(this);
+    }
+
+    /**
+     * 构建引导层controller并直接显示引导层
+     *
+     * @return controller
+     */
+    public Controller show() {
+        check();
+        Controller controller = new Controller(this);
+        controller.show();
+        return controller;
+    }
+
+    private void check() {
+        if (TextUtils.isEmpty(label)) {
+            throw new IllegalArgumentException("the param 'label' is missing, please call setLabel()");
+        }
+        if (activity == null && (fragment != null)) {
+            throw new IllegalStateException("activity is null, please make sure that fragment is showing when call NewbieGuide");
+        }
+    }
+}

+ 342 - 0
guide/src/main/java/com/app/hubert/guide/core/Controller.java

@@ -0,0 +1,342 @@
+package com.app.hubert.guide.core;
+
+import android.app.Activity;
+import android.content.SharedPreferences;
+import android.os.Build;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.RelativeLayout;
+
+import com.app.hubert.guide.NewbieGuide;
+import com.app.hubert.guide.lifecycle.FragmentLifecycleAdapter;
+import com.app.hubert.guide.lifecycle.ListenerFragment;
+import com.app.hubert.guide.lifecycle.V4ListenerFragment;
+import com.app.hubert.guide.listener.OnEventListener;
+import com.app.hubert.guide.listener.OnGuideChangedListener;
+import com.app.hubert.guide.listener.OnLayoutInflatedListener;
+import com.app.hubert.guide.listener.OnPageChangedListener;
+import com.app.hubert.guide.model.GuidePage;
+import com.app.hubert.guide.model.RelativeGuide;
+import com.app.hubert.guide.util.LogUtil;
+import com.app.hubert.guide.util.ScreenUtils;
+
+import java.lang.reflect.Field;
+import java.security.InvalidParameterException;
+import java.util.List;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+
+/**
+ * Created by
+ * <p>
+ * Created on 2017/7/27.
+ * <p>
+ * guide的控制器,可以通过该类控制引导层的显示与回退,或者重置label
+ */
+public class Controller {
+
+    private static final String LISTENER_FRAGMENT = "listener_fragment";
+
+    private Activity activity;
+    private Fragment fragment;
+    private OnGuideChangedListener onGuideChangedListener;
+    private OnPageChangedListener onPageChangedListener;
+    private OnEventListener mEventListener;
+    private String label;
+    private boolean alwaysShow;
+    private int showCounts;//显示次数
+    private List<GuidePage> guidePages;
+    private int current;//当前页
+    private GuideLayout currentLayout;
+    private FrameLayout mParentView;
+    private SharedPreferences sp;
+    private int indexOfChild = -1;//使用anchor时记录的在父布局的位置
+    private boolean isShowing;
+
+    public Controller(Builder builder) {
+        this.activity = builder.activity;
+        this.fragment = builder.fragment;
+        this.onGuideChangedListener = builder.onGuideChangedListener;
+        this.onPageChangedListener = builder.onPageChangedListener;
+        this.mEventListener = builder.eventListener;
+        this.label = builder.label;
+        this.alwaysShow = builder.alwaysShow;
+        this.guidePages = builder.guidePages;
+        showCounts = builder.showCounts;
+
+        View anchor = builder.anchor;
+        if (anchor == null) {
+            anchor = activity.findViewById(android.R.id.content);
+        }
+        if (anchor instanceof FrameLayout) {
+            mParentView = (FrameLayout) anchor;
+        } else {
+            FrameLayout frameLayout = new FrameLayout(activity);
+            ViewGroup parent = (ViewGroup) anchor.getParent();
+            indexOfChild = parent.indexOfChild(anchor);
+            parent.removeView(anchor);
+            if (indexOfChild >= 0) {
+                parent.addView(frameLayout, indexOfChild, anchor.getLayoutParams());
+            } else {
+                parent.addView(frameLayout, anchor.getLayoutParams());
+            }
+            ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams
+                    (ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+            frameLayout.addView(anchor, lp);
+            mParentView = frameLayout;
+        }
+        sp = activity.getSharedPreferences(NewbieGuide.TAG, Activity.MODE_PRIVATE);
+
+    }
+
+    /**
+     * 显示指引layout
+     */
+    public void show() {
+        final int showed = sp.getInt(label, 0);
+        if (!alwaysShow) {
+            if (showed >= showCounts) {
+                return;
+            }
+        }
+
+        if (isShowing) {
+            return;
+        }
+        isShowing = true;
+        mParentView.post(new Runnable() {
+            @Override
+            public void run() {
+                if (guidePages == null || guidePages.size() == 0) {
+                    throw new IllegalStateException("there is no guide to show!! Please add at least one Page.");
+                }
+                if (onGuideChangedListener != null) {
+                    boolean canShow = onGuideChangedListener.isCanShow();
+                    if(!canShow){
+                        return;
+                    }
+                }
+                current = 0;
+                showGuidePage();
+                if (onGuideChangedListener != null) {
+                    onGuideChangedListener.onShowed(Controller.this);
+                }
+                addListenerFragment();
+                sp.edit().putInt(label, showed + 1).apply();
+            }
+        });
+    }
+
+    /**
+     * 显示相应position的引导页
+     *
+     * @param position from 0 to (pageSize - 1)
+     */
+    public void showPage(int position) {
+        if (position < 0 || position > guidePages.size() - 1) {
+            throw new InvalidParameterException("The Guide page position is out of range. current:"
+                    + position + ", range: [ 0, " + guidePages.size() + " )");
+        }
+        if (current == position) {
+            return;
+        }
+        current = position;
+        //fix #59 GuideLayout.setOnGuideLayoutDismissListener() on a null object reference
+        if (currentLayout != null) {
+            currentLayout.setOnGuideLayoutDismissListener(new GuideLayout.OnGuideLayoutDismissListener() {
+                @Override
+                public void onGuideLayoutDismiss(GuideLayout guideLayout) {
+                    showGuidePage();
+                }
+            });
+            currentLayout.remove();
+        } else {
+            showGuidePage();
+        }
+    }
+
+    /**
+     * 显示当前引导页的前一页
+     */
+    public void showPreviewPage() {
+        showPage(--current);
+    }
+
+    /**
+     * 显示current引导页
+     */
+    private void showGuidePage() {
+        GuidePage page = guidePages.get(current);
+        GuideLayout guideLayout = new GuideLayout(activity, page, this);
+        guideLayout.setOnGuideLayoutDismissListener(new GuideLayout.OnGuideLayoutDismissListener() {
+            @Override
+            public void onGuideLayoutDismiss(GuideLayout guideLayout) {
+                showNextOrRemove();
+            }
+        });
+
+        mParentView.addView(guideLayout, new FrameLayout.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+        showCloseView(guideLayout);
+        currentLayout = guideLayout;
+        if (onPageChangedListener != null) {
+            onPageChangedListener.onPageChanged(current);
+        }
+        isShowing = true;
+    }
+
+    private void showCloseView(FrameLayout parentView) {
+        if (mEventListener != null) {
+            View closeView = mEventListener.getCloseView(parentView);
+            if (closeView != null) {
+                closeView.setOnClickListener(new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        remove();
+                    }
+                });
+                parentView.addView(closeView);
+            }
+        }
+    }
+
+    private void showNextOrRemove() {
+        if (current < guidePages.size() - 1) {
+            current++;
+            showGuidePage();
+        } else {
+            if (onGuideChangedListener != null) {
+                onGuideChangedListener.onRemoved(Controller.this);
+            }
+            removeListenerFragment();
+            isShowing = false;
+        }
+    }
+
+    /**
+     * 清楚当前Controller的label记录
+     */
+    public void resetLabel() {
+        resetLabel(label);
+    }
+
+    /**
+     * 清除"显示过"的标记
+     *
+     * @param label 引导标示
+     */
+    public void resetLabel(String label) {
+        sp.edit().putInt(label, 0).apply();
+    }
+
+    /**
+     * 中断引导层的显示,后续未显示的page将不再显示
+     */
+    public void remove() {
+        if (currentLayout != null && currentLayout.getParent() != null) {
+            ViewGroup parent = (ViewGroup) currentLayout.getParent();
+            parent.removeView(currentLayout);
+            //移除anchor添加的frameLayout
+            if (!(parent instanceof FrameLayout)) {
+                ViewGroup original = (ViewGroup) parent.getParent();
+                View anchor = parent.getChildAt(0);
+                parent.removeAllViews();
+                if (anchor != null) {
+                    if (indexOfChild > 0) {
+                        original.addView(anchor, indexOfChild, parent.getLayoutParams());
+                    } else {
+                        original.addView(anchor, parent.getLayoutParams());
+                    }
+                }
+            }
+            if (onGuideChangedListener != null) {
+                onGuideChangedListener.onRemoved(this);
+            }
+            currentLayout = null;
+        }
+        isShowing = false;
+    }
+
+    public boolean isShowing() {
+        return isShowing;
+    }
+
+    private void addListenerFragment() {
+        //fragment监听销毁界面关闭引导层
+        if (fragment != null && Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
+            compatibleFragment(fragment);
+            FragmentManager fm = fragment.getChildFragmentManager();
+            ListenerFragment listenerFragment = (ListenerFragment) fm.findFragmentByTag(LISTENER_FRAGMENT);
+            if (listenerFragment == null) {
+                listenerFragment = new ListenerFragment();
+                fm.beginTransaction().add(listenerFragment, LISTENER_FRAGMENT).commitAllowingStateLoss();
+            }
+            listenerFragment.setFragmentLifecycle(new FragmentLifecycleAdapter() {
+                @Override
+                public void onDestroyView() {
+                    LogUtil.i("ListenerFragment.onDestroyView");
+                    remove();
+                }
+            });
+        }
+
+//        if (v4Fragment != null) {
+//            android.support.v4.app.FragmentManager v4Fm = v4Fragment.getChildFragmentManager();
+//            V4ListenerFragment v4ListenerFragment = (V4ListenerFragment) v4Fm.findFragmentByTag(LISTENER_FRAGMENT);
+//            if (v4ListenerFragment == null) {
+//                v4ListenerFragment = new V4ListenerFragment();
+//                v4Fm.beginTransaction().add(v4ListenerFragment, LISTENER_FRAGMENT).commitAllowingStateLoss();
+//            }
+//            v4ListenerFragment.setFragmentLifecycle(new FragmentLifecycleAdapter() {
+//                @Override
+//                public void onDestroyView() {
+//                    LogUtil.i("v4ListenerFragment.onDestroyView");
+//                    remove();
+//                }
+//            });
+//        }
+    }
+
+    private void removeListenerFragment() {
+        //隐藏引导层时移除监听fragment
+        if (fragment != null && Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
+            FragmentManager fm = fragment.getChildFragmentManager();
+            ListenerFragment listenerFragment = (ListenerFragment) fm.findFragmentByTag(LISTENER_FRAGMENT);
+            if (listenerFragment != null) {
+                fm.beginTransaction().remove(listenerFragment).commitAllowingStateLoss();
+            }
+        }
+//        if (v4Fragment != null) {
+//            android.support.v4.app.FragmentManager v4Fm = v4Fragment.getChildFragmentManager();
+//            V4ListenerFragment v4ListenerFragment = (V4ListenerFragment) v4Fm.findFragmentByTag(LISTENER_FRAGMENT);
+//            if (v4ListenerFragment != null) {
+//                v4Fm.beginTransaction().remove(v4ListenerFragment).commitAllowingStateLoss();
+//            }
+//        }
+    }
+
+    /**
+     * For bug of Fragment in Android
+     * https://issuetracker.google.com/issues/36963722
+     *
+     * @param fragment
+     */
+    private void compatibleFragment(Fragment fragment) {
+        try {
+            Field childFragmentManager = Fragment.class.getDeclaredField("mChildFragmentManager");
+            childFragmentManager.setAccessible(true);
+            childFragmentManager.set(fragment, null);
+        } catch (NoSuchFieldException e) {
+            throw new RuntimeException(e);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}

+ 279 - 0
guide/src/main/java/com/app/hubert/guide/core/GuideLayout.java

@@ -0,0 +1,279 @@
+package com.app.hubert.guide.core;
+
+import android.content.Context;
+import android.graphics.BlurMaskFilter;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.widget.FrameLayout;
+import android.widget.RelativeLayout;
+
+import com.app.hubert.guide.NewbieGuide;
+import com.app.hubert.guide.listener.AnimationListenerAdapter;
+import com.app.hubert.guide.listener.OnHighlightDrewListener;
+import com.app.hubert.guide.listener.OnLayoutInflatedListener;
+import com.app.hubert.guide.model.GuidePage;
+import com.app.hubert.guide.model.HighLight;
+import com.app.hubert.guide.model.HighlightOptions;
+import com.app.hubert.guide.model.RelativeGuide;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by
+ * <p>
+ * Created on 2017/7/27.
+ */
+public class GuideLayout extends FrameLayout {
+
+    public static final int DEFAULT_BACKGROUND_COLOR = 0xb2000000;
+
+    private Controller controller;
+    private Paint mPaint;
+    public GuidePage guidePage;
+    private OnGuideLayoutDismissListener listener;
+    private float downX;
+    private float downY;
+    private int touchSlop;
+
+    public GuideLayout(Context context, GuidePage page, Controller controller) {
+        super(context);
+        init();
+        setGuidePage(page);
+        this.controller = controller;
+    }
+
+    private GuideLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    private GuideLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    private void init() {
+        mPaint = new Paint();
+        mPaint.setAntiAlias(true);
+        PorterDuffXfermode xfermode = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
+        mPaint.setXfermode(xfermode);
+
+        //设置画笔遮罩滤镜,可以传入BlurMaskFilter或EmbossMaskFilter,前者为模糊遮罩滤镜而后者为浮雕遮罩滤镜
+        //这个方法已经被标注为过时的方法了,如果你的应用启用了硬件加速,你是看不到任何阴影效果的
+//        mPaint.setMaskFilter(new BlurMaskFilter(10, BlurMaskFilter.Blur.INNER));
+        //关闭当前view的硬件加速
+        setLayerType(LAYER_TYPE_SOFTWARE, null);
+
+        //ViewGroup默认设定为true,会使onDraw方法不执行,如果复写了onDraw(Canvas)方法,需要清除此标记
+        setWillNotDraw(false);
+
+        touchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+    }
+
+    private void setGuidePage(GuidePage page) {
+        this.guidePage = page;
+        setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (guidePage.isEverywhereCancelable()) {
+                    remove();
+                }
+            }
+        });
+    }
+
+    @Override
+    public boolean performClick() {
+        return super.performClick();
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        int action = event.getAction();
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                downX = event.getX();
+                downY = event.getY();
+                break;
+            case MotionEvent.ACTION_MOVE:
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                float upX = event.getX();
+                float upY = event.getY();
+                if (Math.abs(upX - downX) < touchSlop && Math.abs(upY - downY) < touchSlop) {
+                    List<HighLight> highLights = guidePage.getHighLights();
+                    for (HighLight highLight : highLights) {
+                        RectF rectF = highLight.getRectF((ViewGroup) getParent());
+                        if (rectF.contains(upX, upY)) {
+                            if (guidePage.isEverywhereCancelable()) {
+                                remove();
+                                return true;
+                            } else {
+                                notifyClickListener(highLight);
+                                return true;
+                            }
+                        }
+                    }
+                    performClick();
+                }
+                break;
+
+        }
+        return super.onTouchEvent(event);
+    }
+
+    private void notifyClickListener(HighLight highLight) {
+        HighlightOptions options = highLight.getOptions();
+        if (options != null) {
+            if (options.onClickListener != null) {
+                options.onClickListener.onClick(this);
+            }
+        }
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        int backgroundColor = guidePage.getBackgroundColor();
+        canvas.drawColor(backgroundColor == 0 ? DEFAULT_BACKGROUND_COLOR : backgroundColor);
+        drawHighlights(canvas);
+    }
+
+    private void drawHighlights(Canvas canvas) {
+        List<HighLight> highLights = guidePage.getHighLights();
+        if (highLights != null) {
+            for (HighLight highLight : highLights) {
+                RectF rectF = highLight.getRectF((ViewGroup) getParent());
+                switch (highLight.getShape()) {
+                    case CIRCLE:
+                        canvas.drawCircle(rectF.centerX(), rectF.centerY(), highLight.getRadius(), mPaint);
+                        break;
+                    case OVAL:
+                        canvas.drawOval(rectF, mPaint);
+                        break;
+                    case ROUND_RECTANGLE:
+                        canvas.drawRoundRect(rectF, highLight.getRound(), highLight.getRound(), mPaint);
+                        break;
+                    case RECTANGLE:
+                    default:
+                        canvas.drawRect(rectF, mPaint);
+                        break;
+                }
+                notifyDrewListener(canvas, highLight, rectF);
+            }
+        }
+    }
+
+    private void notifyDrewListener(Canvas canvas, HighLight highLight, RectF rectF) {
+        HighlightOptions options = highLight.getOptions();
+        if (options != null) {
+            if (options.onHighlightDrewListener != null) {
+                options.onHighlightDrewListener.onHighlightDrew(canvas, rectF);
+            }
+        }
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        try{
+            addCustomToLayout(guidePage);
+            Animation enterAnimation = guidePage.getEnterAnimation();
+            if (enterAnimation != null) {
+                startAnimation(enterAnimation);
+            }
+        }catch (Exception e){
+            e.printStackTrace();
+            dismiss();
+        }
+    }
+
+    /**
+     * 将自定义布局填充到guideLayout中
+     */
+    private void addCustomToLayout(GuidePage guidePage) {
+        removeAllViews();
+        int layoutResId = guidePage.getLayoutResId();
+        if (layoutResId != 0) {
+            View view = LayoutInflater.from(getContext()).inflate(layoutResId, this, false);
+            RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+            int[] viewIds = guidePage.getClickToDismissIds();
+            if (viewIds != null && viewIds.length > 0) {
+                for (int viewId : viewIds) {
+                    View click = view.findViewById(viewId);
+                    if (click != null) {
+                        click.setOnClickListener(new View.OnClickListener() {
+                            @Override
+                            public void onClick(View v) {
+                                remove();
+                            }
+                        });
+                    } else {
+                        Log.w(NewbieGuide.TAG, "can't find the view by id : " + viewId + " which used to remove guide page");
+                    }
+                }
+            }
+            OnLayoutInflatedListener inflatedListener = guidePage.getOnLayoutInflatedListener();
+            if (inflatedListener != null) {
+                inflatedListener.onLayoutInflated(view, controller);
+            }
+            addView(view, params);
+        }
+        List<RelativeGuide> relativeGuides = guidePage.getRelativeGuides();
+        if (relativeGuides.size() > 0) {
+            for (RelativeGuide relativeGuide : relativeGuides) {
+                View guideLayout1 = relativeGuide.getGuideLayout((ViewGroup) getParent(), controller);
+                addView(guideLayout1);
+                OnLayoutInflatedListener inflatedListener = guidePage.getOnLayoutInflatedListener();
+                if (inflatedListener != null) {
+                    inflatedListener.onLayoutInflated(guideLayout1, controller);
+                }
+            }
+        }
+    }
+
+    public void setOnGuideLayoutDismissListener(OnGuideLayoutDismissListener listener) {
+        this.listener = listener;
+    }
+
+    public void remove() {
+        Animation exitAnimation = guidePage.getExitAnimation();
+        if (exitAnimation != null) {
+            exitAnimation.setAnimationListener(new AnimationListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animation animation) {
+                    dismiss();
+                }
+            });
+            startAnimation(exitAnimation);
+        } else {
+            dismiss();
+        }
+    }
+
+    private void dismiss() {
+        if (getParent() != null) {
+            ((ViewGroup) getParent()).removeView(this);
+            if (listener != null) {
+                listener.onGuideLayoutDismiss(this);
+            }
+        }
+    }
+
+    public interface OnGuideLayoutDismissListener {
+        void onGuideLayoutDismiss(GuideLayout guideLayout);
+    }
+
+}

+ 11 - 0
guide/src/main/java/com/app/hubert/guide/lifecycle/FragmentLifecycle.java

@@ -0,0 +1,11 @@
+package com.app.hubert.guide.lifecycle;
+
+public interface FragmentLifecycle {
+    void onStart();
+
+    void onStop();
+
+    void onDestroyView();
+
+    void onDestroy();
+}

+ 19 - 0
guide/src/main/java/com/app/hubert/guide/lifecycle/FragmentLifecycleAdapter.java

@@ -0,0 +1,19 @@
+package com.app.hubert.guide.lifecycle;
+
+public abstract class FragmentLifecycleAdapter implements FragmentLifecycle {
+    @Override
+    public void onStart() {
+    }
+
+    @Override
+    public void onStop() {
+    }
+
+    @Override
+    public void onDestroyView() {
+    }
+
+    @Override
+    public void onDestroy() {
+    }
+}

+ 41 - 0
guide/src/main/java/com/app/hubert/guide/lifecycle/ListenerFragment.java

@@ -0,0 +1,41 @@
+package com.app.hubert.guide.lifecycle;
+
+
+import com.app.hubert.guide.util.LogUtil;
+
+import androidx.fragment.app.Fragment;
+
+public class ListenerFragment extends Fragment {
+
+    FragmentLifecycle mFragmentLifecycle;
+
+    public void setFragmentLifecycle(FragmentLifecycle lifecycle) {
+        mFragmentLifecycle = lifecycle;
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        LogUtil.d("onStart: ");
+        if (mFragmentLifecycle != null) mFragmentLifecycle.onStart();
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        if (mFragmentLifecycle != null) mFragmentLifecycle.onStop();
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        if (mFragmentLifecycle != null) mFragmentLifecycle.onDestroyView();
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        LogUtil.d("onDestroy: ");
+        if (mFragmentLifecycle != null) mFragmentLifecycle.onDestroy();
+    }
+}

+ 47 - 0
guide/src/main/java/com/app/hubert/guide/lifecycle/V4ListenerFragment.java

@@ -0,0 +1,47 @@
+package com.app.hubert.guide.lifecycle;
+
+
+import com.app.hubert.guide.util.LogUtil;
+
+import androidx.fragment.app.Fragment;
+
+/**
+ * Created by 
+ * <p>
+ * Created on 2017/9/13.
+ */
+
+public class V4ListenerFragment extends Fragment {
+
+    FragmentLifecycle mFragmentLifecycle;
+
+    public void setFragmentLifecycle(FragmentLifecycle lifecycle) {
+        mFragmentLifecycle = lifecycle;
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        LogUtil.d("onStart: ");
+        if (mFragmentLifecycle != null) mFragmentLifecycle.onStart();
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        if (mFragmentLifecycle != null) mFragmentLifecycle.onStop();
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        if (mFragmentLifecycle != null) mFragmentLifecycle.onDestroyView();
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        LogUtil.d("onDestroy: ");
+        if (mFragmentLifecycle != null) mFragmentLifecycle.onDestroy();
+    }
+}

+ 24 - 0
guide/src/main/java/com/app/hubert/guide/listener/AnimationListenerAdapter.java

@@ -0,0 +1,24 @@
+package com.app.hubert.guide.listener;
+
+import android.view.animation.Animation;
+
+/**
+ * Created by  on 2018/2/12.
+ */
+
+public abstract class AnimationListenerAdapter implements Animation.AnimationListener {
+    @Override
+    public void onAnimationStart(Animation animation) {
+
+    }
+
+    @Override
+    public void onAnimationEnd(Animation animation) {
+
+    }
+
+    @Override
+    public void onAnimationRepeat(Animation animation) {
+
+    }
+}

+ 21 - 0
guide/src/main/java/com/app/hubert/guide/listener/OnEventListener.java

@@ -0,0 +1,21 @@
+package com.app.hubert.guide.listener;
+
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.app.hubert.guide.core.Controller;
+
+/**
+ * Created by  on 2018/2/12.
+ * <p>
+ * 用于引导层布局初始化
+ */
+
+public interface OnEventListener {
+
+    /**
+     * @param        {@link com.app.hubert.guide.model.GuidePage#setLayoutRes(int, int...)}方法传入的layoutRes填充后的view
+     * @param  {@link Controller}
+     */
+    View getCloseView(FrameLayout parent);
+}

+ 26 - 0
guide/src/main/java/com/app/hubert/guide/listener/OnGuideChangedListener.java

@@ -0,0 +1,26 @@
+package com.app.hubert.guide.listener;
+
+import com.app.hubert.guide.core.Controller;
+
+/**
+ * Created by   on 2017/7/27.
+ * <p>
+ * 引导层显示和消失的监听
+ */
+public interface OnGuideChangedListener {
+    /**
+     * 当引导层显示时回调
+     *
+     * @param controller
+     */
+    void onShowed(Controller controller);
+
+    /**
+     * 当引导层消失时回调
+     *
+     * @param controller
+     */
+    void onRemoved(Controller controller);
+
+    boolean isCanShow();
+}

+ 12 - 0
guide/src/main/java/com/app/hubert/guide/listener/OnHighlightDrewListener.java

@@ -0,0 +1,12 @@
+package com.app.hubert.guide.listener;
+
+import android.graphics.Canvas;
+import android.graphics.RectF;
+
+/**
+ * Created by  on 2018/7/9.
+ */
+public interface OnHighlightDrewListener {
+
+    void onHighlightDrew(Canvas canvas, RectF rectF);
+}

+ 20 - 0
guide/src/main/java/com/app/hubert/guide/listener/OnLayoutInflatedListener.java

@@ -0,0 +1,20 @@
+package com.app.hubert.guide.listener;
+
+import android.view.View;
+
+import com.app.hubert.guide.core.Controller;
+
+/**
+ * Created by  on 2018/2/12.
+ * <p>
+ * 用于引导层布局初始化
+ */
+
+public interface OnLayoutInflatedListener {
+
+    /**
+     * @param view       {@link com.app.hubert.guide.model.GuidePage#setLayoutRes(int, int...)}方法传入的layoutRes填充后的view
+     * @param controller {@link Controller}
+     */
+    void onLayoutInflated(View view, Controller controller);
+}

+ 14 - 0
guide/src/main/java/com/app/hubert/guide/listener/OnPageChangedListener.java

@@ -0,0 +1,14 @@
+package com.app.hubert.guide.listener;
+
+/**
+ * Created by  on 2017/11/16.
+ * <p>
+ * 引导页改变的监听
+ */
+
+public interface OnPageChangedListener {
+    /**
+     * @param page 当前引导页的position,第一页为0
+     */
+    void onPageChanged(int page);
+}

+ 289 - 0
guide/src/main/java/com/app/hubert/guide/model/GuidePage.java

@@ -0,0 +1,289 @@
+package com.app.hubert.guide.model;
+
+import android.graphics.RectF;
+import android.view.View;
+import android.view.animation.Animation;
+
+import com.app.hubert.guide.listener.OnHighlightDrewListener;
+import com.app.hubert.guide.listener.OnLayoutInflatedListener;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.LayoutRes;
+import androidx.annotation.Nullable;
+
+/**
+ * Created by 
+ * <p>
+ * Created on 2017/11/16.
+ */
+
+public class GuidePage {
+
+    private List<HighLight> highLights = new ArrayList<>();
+    private boolean everywhereCancelable = true;
+    private int backgroundColor;
+
+    private int layoutResId;
+    private int[] clickToDismissIds;
+    private OnLayoutInflatedListener onLayoutInflatedListener;
+    private OnHighlightDrewListener onHighlightDrewListener;
+    private Animation enterAnimation, exitAnimation;
+
+    public static GuidePage newInstance() {
+        return new GuidePage();
+    }
+
+    public GuidePage addHighLight(View view) {
+        return addHighLight(view, HighLight.Shape.RECTANGLE, 0, 0, null);
+    }
+
+    public GuidePage addHighLight(View view, RelativeGuide relativeGuide) {
+        return addHighLight(view, HighLight.Shape.RECTANGLE, 0, 0, relativeGuide);
+    }
+
+    public GuidePage addHighLight(View view, RelativeGuide relativeGuide,int round) {
+        return addHighLight(view, HighLight.Shape.ROUND_RECTANGLE, round, 0, relativeGuide);
+    }
+
+    public GuidePage addHighLight(View view, RelativeGuide relativeGuide,int round,int extraHeight) {
+        return addHighLight(view, HighLight.Shape.ROUND_RECTANGLE, round, 0, extraHeight,relativeGuide);
+    }
+
+    public GuidePage addHighLight(View view, HighLight.Shape shape) {
+        return addHighLight(view, shape, 0, 0, null);
+    }
+
+    public GuidePage addHighLight(View view, HighLight.Shape shape, RelativeGuide relativeGuide) {
+        return addHighLight(view, shape, 0, 0, relativeGuide);
+    }
+
+    public GuidePage addHighLight(View view, HighLight.Shape shape, int padding) {
+        return addHighLight(view, shape, 0, padding, null);
+    }
+
+    public GuidePage addHighLight(View view, HighLight.Shape shape, int padding, RelativeGuide relativeGuide) {
+        return addHighLight(view, shape, 0, padding, relativeGuide);
+    }
+
+    /**
+     * 添加需要高亮的view
+     *
+     * @param view          需要高亮的view
+     * @param shape         高亮形状{@link com.app.hubert.guide.model.HighLight.Shape}
+     * @param round         圆角尺寸,单位dp,仅{@link com.app.hubert.guide.model.HighLight.Shape#ROUND_RECTANGLE}有效
+     * @param padding       高亮相对view的padding,单位px
+     * @param relativeGuide 相对于高亮的引导布局
+     */
+    public GuidePage addHighLight(View view, HighLight.Shape shape, int round, int padding,
+                                  @Nullable RelativeGuide relativeGuide) {
+        HighlightView highlight = new HighlightView(view, shape, round, padding);
+        if (relativeGuide != null) {
+            relativeGuide.highLight = highlight;
+            highlight.setOptions(new HighlightOptions.Builder().setRelativeGuide(relativeGuide).build());
+        }
+        highLights.add(highlight);
+        return this;
+    }
+
+    /**
+     * 添加需要高亮的view
+     *
+     * @param view          需要高亮的view
+     * @param shape         高亮形状{@link com.app.hubert.guide.model.HighLight.Shape}
+     * @param round         圆角尺寸,单位dp,仅{@link com.app.hubert.guide.model.HighLight.Shape#ROUND_RECTANGLE}有效
+     * @param padding       高亮相对view的padding,单位px
+     * @param relativeGuide 相对于高亮的引导布局
+     */
+    public GuidePage addHighLight(View view, HighLight.Shape shape, int round, int padding,int extraHeight,
+                                  @Nullable RelativeGuide relativeGuide) {
+        HighlightView highlight = new HighlightView(view, shape, round, padding,extraHeight);
+        if (relativeGuide != null) {
+            relativeGuide.highLight = highlight;
+            highlight.setOptions(new HighlightOptions.Builder().setRelativeGuide(relativeGuide).build());
+        }
+        highLights.add(highlight);
+        return this;
+    }
+
+    public GuidePage addHighLight(RectF rectF) {
+        return addHighLight(rectF, HighLight.Shape.RECTANGLE, 0, null);
+    }
+
+    public GuidePage addHighLight(RectF rectF, RelativeGuide relativeGuide) {
+        return addHighLight(rectF, HighLight.Shape.RECTANGLE, 0, relativeGuide);
+    }
+
+    public GuidePage addHighLight(RectF rectF, HighLight.Shape shape) {
+        return addHighLight(rectF, shape, 0, null);
+    }
+
+    public GuidePage addHighLight(RectF rectF, HighLight.Shape shape, RelativeGuide relativeGuide) {
+        return addHighLight(rectF, shape, 0, relativeGuide);
+    }
+
+    public GuidePage addHighLight(RectF rectF, HighLight.Shape shape, int round) {
+        return addHighLight(rectF, shape, round, null);
+    }
+
+    /**
+     * 添加高亮区域
+     *
+     * @param rectF         高亮区域,相对与anchor view(默认是decorView)
+     * @param shape         高亮形状{@link com.app.hubert.guide.model.HighLight.Shape}
+     * @param round         圆角尺寸,单位dp,仅{@link com.app.hubert.guide.model.HighLight.Shape#ROUND_RECTANGLE}有效
+     * @param relativeGuide 相对于高亮的引导布局
+     */
+    public GuidePage addHighLight(RectF rectF, HighLight.Shape shape, int round, RelativeGuide relativeGuide) {
+        HighlightRectF highlight = new HighlightRectF(rectF, shape, round);
+        if (relativeGuide != null) {
+            relativeGuide.highLight = highlight;
+            highlight.setOptions(new HighlightOptions.Builder().setRelativeGuide(relativeGuide).build());
+        }
+        highLights.add(highlight);
+        return this;
+    }
+
+    public GuidePage addHighLightWithOptions(View view, HighlightOptions options) {
+        return addHighLightWithOptions(view, HighLight.Shape.RECTANGLE, 0, 0, options);
+    }
+
+    public GuidePage addHighLightWithOptions(View view, HighLight.Shape shape, HighlightOptions options) {
+        return addHighLightWithOptions(view, shape, 0, 0, options);
+    }
+
+    public GuidePage addHighLightWithOptions(View view, HighLight.Shape shape, int round, int padding, HighlightOptions options) {
+        HighlightView highlight = new HighlightView(view, shape, round, padding);
+        if (options != null) {
+            if (options.relativeGuide != null) {
+                options.relativeGuide.highLight = highlight;
+            }
+        }
+        highlight.setOptions(options);
+        highLights.add(highlight);
+        return this;
+    }
+
+    public GuidePage addHighLightWithOptions(RectF rectF, HighlightOptions options) {
+        return addHighLightWithOptions(rectF, HighLight.Shape.RECTANGLE, 0, options);
+    }
+
+    public GuidePage addHighLightWithOptions(RectF rectF, HighLight.Shape shape, HighlightOptions options) {
+        return addHighLightWithOptions(rectF, shape, 0, options);
+    }
+
+    public GuidePage addHighLightWithOptions(RectF rectF, HighLight.Shape shape, int round, HighlightOptions options) {
+        HighlightRectF highlight = new HighlightRectF(rectF, shape, round);
+        if (options != null) {
+            if (options.relativeGuide != null) {
+                options.relativeGuide.highLight = highlight;
+            }
+        }
+        highlight.setOptions(options);
+        highLights.add(highlight);
+        return this;
+    }
+
+    /**
+     * 添加引导层布局
+     *
+     * @param resId 布局id
+     * @param id    布局中点击消失引导页的控件id
+     */
+    public GuidePage setLayoutRes(@LayoutRes int resId, int... id) {
+        this.layoutResId = resId;
+        clickToDismissIds = id;
+        return this;
+    }
+
+    public GuidePage setEverywhereCancelable(boolean everywhereCancelable) {
+        this.everywhereCancelable = everywhereCancelable;
+        return this;
+    }
+
+    /**
+     * 设置背景色
+     */
+    public GuidePage setBackgroundColor(@ColorInt int backgroundColor) {
+        this.backgroundColor = backgroundColor;
+        return this;
+    }
+
+    /**
+     * 设置自定义layout填充监听,用于自定义layout初始化
+     *
+     * @param onLayoutInflatedListener listener
+     */
+    public GuidePage setOnLayoutInflatedListener(OnLayoutInflatedListener onLayoutInflatedListener) {
+        this.onLayoutInflatedListener = onLayoutInflatedListener;
+        return this;
+    }
+
+    /**
+     * 设置进入动画
+     */
+    public GuidePage setEnterAnimation(Animation enterAnimation) {
+        this.enterAnimation = enterAnimation;
+        return this;
+    }
+
+    /**
+     * 设置退出动画
+     */
+    public GuidePage setExitAnimation(Animation exitAnimation) {
+        this.exitAnimation = exitAnimation;
+        return this;
+    }
+
+    public boolean isEverywhereCancelable() {
+        return everywhereCancelable;
+    }
+
+    public boolean isEmpty() {
+        return layoutResId == 0 && highLights.size() == 0;
+    }
+
+    public List<HighLight> getHighLights() {
+        return highLights;
+    }
+
+    public int getBackgroundColor() {
+        return backgroundColor;
+    }
+
+    public int getLayoutResId() {
+        return layoutResId;
+    }
+
+    public int[] getClickToDismissIds() {
+        return clickToDismissIds;
+    }
+
+    public OnLayoutInflatedListener getOnLayoutInflatedListener() {
+        return onLayoutInflatedListener;
+    }
+
+    public Animation getEnterAnimation() {
+        return enterAnimation;
+    }
+
+    public Animation getExitAnimation() {
+        return exitAnimation;
+    }
+
+    public List<RelativeGuide> getRelativeGuides() {
+        List<RelativeGuide> relativeGuides = new ArrayList<>();
+        for (HighLight highLight : highLights) {
+            HighlightOptions options = highLight.getOptions();
+            if (options != null) {
+                if (options.relativeGuide != null) {
+                    relativeGuides.add(options.relativeGuide);
+                }
+            }
+        }
+        return relativeGuides;
+    }
+}

+ 43 - 0
guide/src/main/java/com/app/hubert/guide/model/HighLight.java

@@ -0,0 +1,43 @@
+package com.app.hubert.guide.model;
+
+import android.graphics.RectF;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Created by  on 2018/6/6.
+ */
+public interface HighLight {
+
+    Shape getShape();
+
+    /**
+     * @param view anchor view
+     * @return highlight's rectF
+     */
+    RectF getRectF(View view);
+
+    /**
+     * 当shape为CIRCLE时调用此方法获取半径
+     */
+    float getRadius();
+
+    /**
+     * 获取圆角,仅当shape = Shape.ROUND_RECTANGLE才调用次方法
+     */
+    int getRound();
+
+    /**
+     * 额外参数
+     */
+    @Nullable
+    HighlightOptions getOptions();
+
+    public enum Shape {
+        CIRCLE,//圆形
+        RECTANGLE, //矩形
+        OVAL,//椭圆
+        ROUND_RECTANGLE;//圆角矩形
+    }
+}

+ 61 - 0
guide/src/main/java/com/app/hubert/guide/model/HighlightOptions.java

@@ -0,0 +1,61 @@
+package com.app.hubert.guide.model;
+
+import android.view.View;
+
+import com.app.hubert.guide.listener.OnHighlightDrewListener;
+
+/**
+ * Created by  on 2018/7/9.
+ */
+public class HighlightOptions {
+    public View.OnClickListener onClickListener;
+    public RelativeGuide relativeGuide;
+    public OnHighlightDrewListener onHighlightDrewListener;
+    public boolean fetchLocationEveryTime;
+
+    public static class Builder {
+
+        private HighlightOptions options;
+
+        public Builder() {
+            options = new HighlightOptions();
+        }
+
+        /**
+         * 高亮点击事件
+         */
+        public Builder setOnClickListener(View.OnClickListener listener) {
+            options.onClickListener = listener;
+            return this;
+        }
+
+        /**
+         * @param relativeGuide 高亮相对位置引导布局
+         */
+        public Builder setRelativeGuide(RelativeGuide relativeGuide) {
+            options.relativeGuide = relativeGuide;
+            return this;
+        }
+
+        /**
+         * @param listener 高亮绘制后回调该监听,用于绘制额外内容
+         */
+        public Builder setOnHighlightDrewListener(OnHighlightDrewListener listener) {
+            options.onHighlightDrewListener = listener;
+            return this;
+        }
+
+        /**
+         * 是否每次显示引导层都重新获取高亮位置
+         */
+        public Builder isFetchLocationEveryTime(boolean b) {
+            options.fetchLocationEveryTime = b;
+            return this;
+        }
+
+        public HighlightOptions build() {
+            return options;
+        }
+    }
+
+}

+ 52 - 0
guide/src/main/java/com/app/hubert/guide/model/HighlightRectF.java

@@ -0,0 +1,52 @@
+package com.app.hubert.guide.model;
+
+import android.graphics.RectF;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Created by  on 2018/6/6.
+ */
+public class HighlightRectF implements HighLight {
+
+    private RectF rectF;
+    private Shape shape;
+    private int round;
+    private HighlightOptions options;
+
+    public HighlightRectF(@NonNull RectF rectF, @NonNull Shape shape, int round) {
+        this.rectF = rectF;
+        this.shape = shape;
+        this.round = round;
+    }
+
+    public void setOptions(HighlightOptions options) {
+        this.options = options;
+    }
+
+    @Override
+    public Shape getShape() {
+        return shape;
+    }
+
+    @Override
+    public RectF getRectF(View view) {
+        return rectF;
+    }
+
+    @Override
+    public float getRadius() {
+        return Math.min(rectF.width() / 2, rectF.height() / 2);
+    }
+
+    @Override
+    public int getRound() {
+        return round;
+    }
+
+    @Override
+    public HighlightOptions getOptions() {
+        return options;
+    }
+}

+ 94 - 0
guide/src/main/java/com/app/hubert/guide/model/HighlightView.java

@@ -0,0 +1,94 @@
+package com.app.hubert.guide.model;
+
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.View;
+
+import com.app.hubert.guide.util.LogUtil;
+import com.app.hubert.guide.util.ViewUtils;
+
+/**
+ * Created by
+ * <p>
+ * Created on 2017/7/27.
+ */
+public class HighlightView implements HighLight {
+
+    private View mHole;
+    private Shape shape;
+    private int round;
+    private int extraHeight;
+    /**
+     * 高亮相对view的padding
+     */
+    private int padding;
+    private HighlightOptions options;
+    private RectF rectF;
+
+    public HighlightView(View mHole, Shape shape, int round, int padding) {
+        this.mHole = mHole;
+        this.shape = shape;
+        this.round = round;
+        this.padding = padding;
+    }
+
+    public HighlightView(View mHole, Shape shape, int round, int padding, int extraHeight) {
+        this.mHole = mHole;
+        this.shape = shape;
+        this.round = round;
+        this.padding = padding;
+        this.extraHeight = extraHeight;
+    }
+
+    public void setOptions(HighlightOptions options) {
+        this.options = options;
+    }
+
+    @Override
+    public Shape getShape() {
+        return shape;
+    }
+
+    @Override
+    public int getRound() {
+        return round;
+    }
+
+    @Override
+    public HighlightOptions getOptions() {
+        return options;
+    }
+
+    @Override
+    public float getRadius() {
+        if (mHole == null) {
+            throw new IllegalArgumentException("the highlight view is null!");
+        }
+        return Math.max(mHole.getWidth() / 2, mHole.getHeight() / 2) + padding;
+    }
+
+    @Override
+    public RectF getRectF(View target) {
+        if (mHole == null) {
+            throw new IllegalArgumentException("the highlight view is null!");
+        }
+        if (rectF == null) {
+            rectF = fetchLocation(target);
+        } else if (options != null && options.fetchLocationEveryTime) {
+            rectF = fetchLocation(target);
+        }
+        LogUtil.i(mHole.getClass().getSimpleName() + "'s location:" + rectF);
+        return rectF;
+    }
+
+    private RectF fetchLocation(View target) {
+        RectF location = new RectF();
+        Rect locationInView = ViewUtils.getLocationInView(target, mHole);
+        location.left = locationInView.left-extraHeight - padding;
+        location.top = locationInView.top- extraHeight - padding;
+        location.right = locationInView.right+ extraHeight + padding;
+        location.bottom = locationInView.bottom + extraHeight + padding;
+        return location;
+    }
+
+}

+ 227 - 0
guide/src/main/java/com/app/hubert/guide/model/RelativeGuide.java

@@ -0,0 +1,227 @@
+package com.app.hubert.guide.model;
+
+import android.graphics.RectF;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import com.app.hubert.guide.core.Controller;
+import com.app.hubert.guide.util.LogUtil;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.LayoutRes;
+
+/**
+ * Created by  on 2018/6/28.
+ */
+public class RelativeGuide {
+
+    @IntDef({android.view.Gravity.LEFT, android.view.Gravity.TOP,
+            android.view.Gravity.RIGHT, android.view.Gravity.BOTTOM, LimitGravity.BOTTOM_RIGHT, LimitGravity.TOP_CENTER,
+            LimitGravity.BOTTOM_CENTER, LimitGravity.BOTTOM_LEFT, LimitGravity.TOP_RIGHT, LimitGravity.TOP_LEFT_TO_OVER,
+            LimitGravity.TOP_RIGHT_TO_OVER, LimitGravity.BOTTOM_TO_FULL, LimitGravity.TOP_RIGHT_TO_OVER2, LimitGravity.TOP_CENTER2,
+            LimitGravity.BOTTOM_LEFT2})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface LimitGravity {
+        int BOTTOM_RIGHT = 101;
+        int TOP_CENTER = 102;
+        int BOTTOM_CENTER = 103;
+        int TOP_RIGHT = 104;
+        int BOTTOM_LEFT = 105;
+        int TOP_LEFT_TO_OVER = 106;
+        int TOP_RIGHT_TO_OVER = 107;
+        int BOTTOM_TO_FULL = 108;
+        int TOP_RIGHT_TO_OVER2 = 109;
+        int TOP_CENTER2 = 110;
+        int BOTTOM_LEFT2 = 111;
+    }
+
+    public static class MarginInfo {
+        public int leftMargin;
+        public int topMargin;
+        public int rightMargin;
+        public int bottomMargin;
+        public int gravity;
+
+        @Override
+        public String toString() {
+            return "MarginInfo{" +
+                    "leftMargin=" + leftMargin +
+                    ", topMargin=" + topMargin +
+                    ", rightMargin=" + rightMargin +
+                    ", bottomMargin=" + bottomMargin +
+                    ", gravity=" + gravity +
+                    '}';
+        }
+    }
+
+    public HighLight highLight;
+    @LayoutRes
+    public int layout;
+    public int padding;
+    public int gravity;
+
+    public RelativeGuide(@LayoutRes int layout, @LimitGravity int gravity) {
+        this.layout = layout;
+        this.gravity = gravity;
+    }
+
+    /**
+     * @param layout  相对位置引导布局
+     * @param gravity 仅限left top right bottom
+     * @param padding 与高亮view的padding,单位px
+     */
+    public RelativeGuide(@LayoutRes int layout, @LimitGravity int gravity, int padding) {
+        this.layout = layout;
+        this.gravity = gravity;
+        this.padding = padding;
+    }
+
+    public final View getGuideLayout(ViewGroup viewGroup, Controller controller) {
+        View view = LayoutInflater.from(viewGroup.getContext()).inflate(layout, viewGroup, false);
+        onLayoutInflated(view);
+        onLayoutInflated(view, controller);
+        FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) view.getLayoutParams();
+        MarginInfo marginInfo = getMarginInfo(gravity, viewGroup, view);
+        LogUtil.e(marginInfo.toString());
+        offsetMargin(marginInfo, viewGroup, view);
+        layoutParams.gravity = marginInfo.gravity;
+        layoutParams.leftMargin += marginInfo.leftMargin;
+        layoutParams.topMargin += marginInfo.topMargin;
+        layoutParams.rightMargin += marginInfo.rightMargin;
+        layoutParams.bottomMargin += marginInfo.bottomMargin;
+        view.setLayoutParams(layoutParams);
+        return view;
+    }
+
+    private MarginInfo getMarginInfo(@LimitGravity int gravity, ViewGroup viewGroup, View view) {
+        MarginInfo marginInfo = new MarginInfo();
+        RectF rectF = highLight.getRectF(viewGroup);
+        switch (gravity) {
+            case Gravity.LEFT:
+                marginInfo.gravity = Gravity.RIGHT;
+                marginInfo.rightMargin = (int) (viewGroup.getWidth() - rectF.left + padding);
+                marginInfo.topMargin = (int) rectF.top;
+                break;
+            case Gravity.TOP:
+                marginInfo.gravity = Gravity.BOTTOM;
+                marginInfo.bottomMargin = (int) (viewGroup.getHeight() - rectF.top + padding);
+                marginInfo.leftMargin = (int) rectF.left;
+                break;
+            case Gravity.RIGHT:
+                marginInfo.leftMargin = (int) (rectF.right + padding);
+                marginInfo.topMargin = (int) rectF.top;
+                break;
+            case Gravity.BOTTOM:
+                marginInfo.topMargin = (int) (rectF.bottom + padding);
+                marginInfo.leftMargin = (int) rectF.left;
+                break;
+            case LimitGravity.TOP_RIGHT:
+                marginInfo.gravity = Gravity.BOTTOM;
+                marginInfo.bottomMargin = (int) (viewGroup.getHeight() - rectF.top + padding);
+                view.measure(0, 0);
+                int viewWidth = view.getMeasuredWidth();
+                int rectFWidth = (int) (rectF.right - rectF.left);
+                int ii = (rectFWidth - viewWidth);
+                marginInfo.leftMargin = (int) rectF.left + ii;
+                break;
+            case LimitGravity.BOTTOM_RIGHT:
+                marginInfo.topMargin = (int) (rectF.bottom + padding);
+                view.measure(0, 0);
+                int measureWidth = view.getMeasuredWidth();
+                marginInfo.leftMargin = (int) (rectF.right - measureWidth);
+                break;
+            case LimitGravity.TOP_CENTER:
+//                marginInfo.gravity = Gravity.BOTTOM | Gravity.CENTER;
+//                marginInfo.bottomMargin = (int) (viewGroup.getHeight() - rectF.top + padding);
+                marginInfo.gravity = Gravity.BOTTOM;
+                view.measure(0, 0);
+                marginInfo.bottomMargin = (int) (viewGroup.getHeight() - rectF.top + padding);
+                int width2 = view.getMeasuredWidth();
+                int targetWidth2 = (int) (rectF.right - rectF.left);
+                int x = (targetWidth2 - width2) / 2;//宽度差累加到leftMargin达到居中
+                marginInfo.leftMargin = (int) rectF.left + x;
+                break;
+            case LimitGravity.TOP_CENTER2:
+                marginInfo.gravity = Gravity.BOTTOM;
+                view.measure(0, 0);
+                marginInfo.bottomMargin = (int) (viewGroup.getHeight() - rectF.top + padding);
+                int width3 = viewGroup.getWidth();
+                int targetWidth3 = (int) (rectF.right - rectF.left);
+                int x3 = (targetWidth3 - width3) / 2;//宽度差累加到leftMargin达到居中
+                marginInfo.leftMargin = (int) rectF.left + x3;
+                break;
+            case LimitGravity.BOTTOM_CENTER:
+                marginInfo.topMargin = (int) (rectF.bottom + padding);
+                view.measure(0, 0);
+                int width = viewGroup.getWidth();
+                int targetWidth = (int) (rectF.right - rectF.left);
+                int i = (targetWidth - width) / 2;//宽度差累加到leftMargin达到居中
+                marginInfo.leftMargin = (int) rectF.left + i;
+                break;
+            case LimitGravity.BOTTOM_LEFT:
+                marginInfo.topMargin = (int) (rectF.bottom + padding);
+                view.measure(0, 0);
+                marginInfo.leftMargin = (int) rectF.left;
+                break;
+            case LimitGravity.BOTTOM_LEFT2:
+                marginInfo.gravity = Gravity.RIGHT|Gravity.BOTTOM;
+                marginInfo.bottomMargin = (int) (viewGroup.getHeight() - rectF.bottom);
+                marginInfo.rightMargin = (int) (viewGroup.getWidth() - rectF.left - padding);
+                break;
+            case LimitGravity.TOP_LEFT_TO_OVER:
+                marginInfo.gravity = Gravity.BOTTOM;
+                view.measure(0, 0);
+                marginInfo.bottomMargin = (int) (viewGroup.getHeight() - rectF.bottom);
+                marginInfo.leftMargin = (int) rectF.left + padding;
+                break;
+            case LimitGravity.TOP_RIGHT_TO_OVER:
+                marginInfo.gravity = Gravity.BOTTOM;
+                view.measure(0, 0);
+                marginInfo.bottomMargin = (int) (viewGroup.getHeight() - rectF.bottom);
+                marginInfo.leftMargin = (int) (rectF.right - view.getMeasuredWidth() + padding);
+                break;
+            case LimitGravity.BOTTOM_TO_FULL:
+                marginInfo.topMargin = (int) (rectF.bottom + padding);
+                marginInfo.leftMargin = 0;
+                break;
+            case LimitGravity.TOP_RIGHT_TO_OVER2:
+                marginInfo.gravity = Gravity.BOTTOM;
+                view.measure(0, 0);
+                marginInfo.bottomMargin = (int) (viewGroup.getHeight() - rectF.bottom - padding);
+                marginInfo.leftMargin = (int) (rectF.right - view.getMeasuredWidth() + padding);
+                break;
+        }
+        return marginInfo;
+    }
+
+    protected void offsetMargin(MarginInfo marginInfo, ViewGroup viewGroup, View view) {
+        //do nothing
+    }
+
+    /**
+     * 复写初始化布局
+     *
+     * @param view inflated from layout
+     * @see RelativeGuide#onLayoutInflated(View view, Controller controller)
+     */
+    @Deprecated
+    protected void onLayoutInflated(View view) {
+        //do nothing
+    }
+
+    /**
+     * 复写初始化布局
+     *
+     * @param view       inflated from layout
+     * @param controller controller
+     */
+    protected void onLayoutInflated(View view, Controller controller) {
+        //do nothing
+    }
+}

+ 108 - 0
guide/src/main/java/com/app/hubert/guide/util/LogUtil.java

@@ -0,0 +1,108 @@
+package com.app.hubert.guide.util;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.app.hubert.guide.NewbieGuide;
+
+import java.util.Locale;
+
+/**
+ * 简易控制日志输出的util
+ */
+public class LogUtil {
+
+    private static final int NONE = 8;
+    private static final String tagPrefix = NewbieGuide.TAG;
+
+
+    /**
+     * 修改打印级别
+     */
+    public static final int level = NONE;
+//    public static final int level = Log.VERBOSE;
+
+    /**
+     * 得到tag(所在类.方法(L:行))
+     */
+    private static String generateTag() {
+        StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[4];
+        String callerClazzName = stackTraceElement.getClassName();
+        callerClazzName = callerClazzName.substring(callerClazzName.lastIndexOf(".") + 1);
+        String tag = "%s.%s(L:%d)";
+        tag = String.format(Locale.CHINA, tag, callerClazzName, stackTraceElement.getMethodName(), stackTraceElement.getLineNumber());
+        //给tag设置前缀
+        tag = TextUtils.isEmpty(tagPrefix) ? tag : tagPrefix + ":" + tag;
+        return tag;
+    }
+
+    public static void v(String msg) {
+        if (level <= Log.VERBOSE) {
+            String tag = generateTag();
+            Log.v(tag, msg);
+        }
+    }
+
+    public static void v(String msg, Throwable tr) {
+        if (level <= Log.VERBOSE) {
+            String tag = generateTag();
+            Log.v(tag, msg, tr);
+        }
+    }
+
+    public static void d(String msg) {
+        if (level <= Log.DEBUG) {
+            String tag = generateTag();
+            Log.d(tag, msg);
+        }
+    }
+
+    public static void d(String msg, Throwable tr) {
+        if (level <= Log.DEBUG) {
+            String tag = generateTag();
+            Log.d(tag, msg, tr);
+        }
+    }
+
+    public static void i(String msg) {
+        if (level <= Log.INFO) {
+            String tag = generateTag();
+            Log.i(tag, msg);
+        }
+    }
+
+    public static void i(String msg, Throwable tr) {
+        if (level <= Log.INFO) {
+            String tag = generateTag();
+            Log.i(tag, msg, tr);
+        }
+    }
+
+    public static void w(String msg) {
+        if (level <= Log.WARN) {
+            String tag = generateTag();
+            Log.w(tag, msg);
+        }
+    }
+
+    public static void w(String msg, Throwable tr) {
+        if (level <= Log.WARN) {
+            String tag = generateTag();
+            Log.w(tag, msg, tr);
+        }
+    }
+
+    public static void e(String msg) {
+        if (level <= Log.ERROR) {
+            String tag = generateTag();
+            Log.e(tag, msg);
+        }
+    }
+
+    public static void e(String msg, Throwable tr) {
+        if (level <= Log.ERROR) {
+            String tag = generateTag();
+            Log.e(tag, msg, tr);
+        }
+    }
+}  

+ 106 - 0
guide/src/main/java/com/app/hubert/guide/util/ScreenUtils.java

@@ -0,0 +1,106 @@
+package com.app.hubert.guide.util;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.os.Build;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.ViewConfiguration;
+
+/**
+ * Created by 
+ * <p>
+ * Created on 2017/7/27.
+ */
+public class ScreenUtils {
+
+    private ScreenUtils() {
+        throw new AssertionError();
+    }
+
+    /**
+     * dp单位转成px
+     *
+     * @param context context
+     * @param dp      dp值
+     * @return px值
+     */
+    public static int dp2px(Context context, int dp) {
+        return (int) (dp * context.getResources().getDisplayMetrics().density);
+    }
+
+    /**
+     * 获取屏幕宽度
+     */
+    public static int getScreenWidth(Context context) {
+        DisplayMetrics dm = context.getResources().getDisplayMetrics();
+        return dm.widthPixels;
+    }
+
+    /**
+     * 获取屏幕高度
+     */
+    public static int getScreenHeight(Context context) {
+        DisplayMetrics dm = context.getResources().getDisplayMetrics();
+        return dm.heightPixels;
+    }
+
+    /**
+     * 获取状态栏高度
+     */
+    public static int getStatusBarHeight(Context context) {
+        // 一般是25dp
+        int height = dp2px(context, 20);
+        LogUtil.i("common statusBar height:" + height);
+        //获取status_bar_height资源的ID
+        int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
+        if (resourceId > 0) {
+            height = context.getResources().getDimensionPixelSize(resourceId);
+            LogUtil.i("real statusBar height:" + height);
+        }
+        LogUtil.i("finally statusBar height:" + height);
+        return height;
+    }
+
+    /**
+     * 虚拟操作拦(home等)是否显示
+     */
+    public static boolean isNavigationBarShow(Activity activity) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            Display display = activity.getWindowManager().getDefaultDisplay();
+            Point size = new Point();
+            Point realSize = new Point();
+            display.getSize(size);
+            display.getRealSize(realSize);
+            return realSize.y != size.y;
+        } else {
+            boolean menu = ViewConfiguration.get(activity).hasPermanentMenuKey();
+            boolean back = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
+            if (menu || back) {
+                return false;
+            } else {
+                return true;
+            }
+        }
+    }
+
+    /**
+     * 获取虚拟操作拦(home等)高度
+     */
+    public static int getNavigationBarHeight(Activity activity) {
+        if (!isNavigationBarShow(activity))
+            return 0;
+        int height = 0;
+        Resources resources = activity.getResources();
+        //获取NavigationBar的高度
+        int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
+        if (resourceId > 0)
+            height = resources.getDimensionPixelSize(resourceId);
+        LogUtil.i("NavigationBar的高度:" + height);
+        return height;
+    }
+}

+ 73 - 0
guide/src/main/java/com/app/hubert/guide/util/ViewUtils.java

@@ -0,0 +1,73 @@
+package com.app.hubert.guide.util;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.View;
+import android.widget.HorizontalScrollView;
+import android.widget.ScrollView;
+
+import androidx.viewpager.widget.ViewPager;
+
+/**
+ * Created by zhy on 15/10/8.
+ */
+public class ViewUtils {
+    private static final String FRAGMENT_CON = "NoSaveStateFrameLayout";
+
+    public static Rect getLocationInView(View parent, View child) {
+        if (child == null || parent == null) {
+            throw new IllegalArgumentException("parent and child can not be null .");
+        }
+
+        View decorView = null;
+        Context context = child.getContext();
+        if (context instanceof Activity) {
+            decorView = ((Activity) context).getWindow().getDecorView();
+        }
+
+        Rect result = new Rect();
+        Rect tmpRect = new Rect();
+
+        View tmp = child;
+
+        if (child == parent) {
+            child.getHitRect(result);
+            return result;
+        }
+        while (tmp != decorView && tmp != parent) {
+            LogUtil.i("tmp class:" + tmp.getClass().getSimpleName());
+            tmp.getHitRect(tmpRect);
+            LogUtil.i("tmp hit Rect:" + tmpRect);
+            if (!tmp.getClass().equals(FRAGMENT_CON)) {
+                result.left += tmpRect.left;
+                result.top += tmpRect.top;
+            }
+            tmp = (View) tmp.getParent();
+            if (tmp == null) {
+                throw new IllegalArgumentException("the view is not showing in the window!");
+            }
+            //fix ScrollView中无法获取正确的位置
+            if (tmp.getParent() instanceof ScrollView) {
+                ScrollView scrollView = (ScrollView) tmp.getParent();
+                int scrollY = scrollView.getScrollY();
+                LogUtil.i("scrollY:" + scrollY);
+                result.top -= scrollY;
+            }
+            if (tmp.getParent() instanceof HorizontalScrollView) {
+                HorizontalScrollView horizontalScrollView = (HorizontalScrollView) tmp.getParent();
+                int scrollX = horizontalScrollView.getScrollX();
+                LogUtil.i("scrollX:" + scrollX);
+                result.left -= scrollX;
+            }
+
+            //added by isanwenyu@163.com fix bug #21 the wrong rect user will received in ViewPager
+            if (tmp.getParent() != null && (tmp.getParent() instanceof ViewPager)) {
+                tmp = (View) tmp.getParent();
+            }
+        }
+        result.right = result.left + child.getMeasuredWidth();
+        result.bottom = result.top + child.getMeasuredHeight();
+        return result;
+    }
+}

+ 2 - 0
guide/src/main/res/values/strings.xml

@@ -0,0 +1,2 @@
+<resources>
+</resources>

+ 3 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/api/Api.java

@@ -36,4 +36,7 @@ public interface Api {
     @FormUrlEncoded
     @POST("{group_name}" + "/userMusic/remove")
     Observable<BaseResponse<Object>> delDraftWorks(@Path("group_name") String group_name,@Field("id") String ids);
+
+    @GET("{group_name}" + "/userMusic/musicPracticeRecord/{id}")
+    Observable<BaseResponse<MusicDataBean>> getDetailByRecordId(@Path("group_name") String group_name, @Path("id") String id);
 }

+ 9 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/bean/MusicInfoBean.java

@@ -9,6 +9,15 @@ public class MusicInfoBean {
     private String preCover;
     private String musicTitle;
     private String des;
+    private boolean selectFlag =true;//是否能被看到评级、评分字段
+
+    public boolean isSelectFlag() {
+        return selectFlag;
+    }
+
+    public void setSelectFlag(boolean selectFlag) {
+        this.selectFlag = selectFlag;
+    }
 
     public String getVideoCover() {
         return videoCover;

+ 35 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/bean/MusicMergeConfigBean.java

@@ -1,5 +1,9 @@
 package com.cooleshow.musicmerge.bean;
 
+import com.cooleshow.musicmerge.constants.MergeConfig;
+import com.cooleshow.musicmerge.constants.MusicMergeConfig;
+import com.google.gson.annotations.SerializedName;
+
 /**
  * Author by pq, Date on 2023/11/24.
  */
@@ -9,7 +13,26 @@ public class MusicMergeConfigBean {
     private int accompanyVolume;
     private int defaultDelay;
     private int evaluateDelay;
+    @SerializedName("part-index")
+    private int partIndex;
     private float speedRate;
+    private String musicRenderType;
+
+    public String getMusicRenderType() {
+        return musicRenderType;
+    }
+
+    public void setMusicRenderType(String musicRenderType) {
+        this.musicRenderType = musicRenderType;
+    }
+
+    public int getPartIndex() {
+        return partIndex;
+    }
+
+    public void setPartIndex(int partIndex) {
+        this.partIndex = partIndex;
+    }
 
     public float getSpeedRate() {
         if (speedRate != 0) {
@@ -61,4 +84,16 @@ public class MusicMergeConfigBean {
     public void setDefaultDelay(int defaultDelay) {
         this.defaultDelay = defaultDelay;
     }
+
+    public static MusicMergeConfigBean createDefault() {
+        MusicMergeConfigBean musicMergeConfigBean = new MusicMergeConfigBean();
+        musicMergeConfigBean.setOffset(0);
+        musicMergeConfigBean.setAccompanyVolume(100);
+        musicMergeConfigBean.setOriginalVolume(100);
+        musicMergeConfigBean.setDefaultDelay(0);
+        musicMergeConfigBean.setEvaluateDelay(0);
+        musicMergeConfigBean.setSpeedRate(MergeConfig.DEFAULT_PLAY_SPEED);
+        musicMergeConfigBean.setMusicRenderType(MusicMergeConfig.STAFF);
+        return musicMergeConfigBean;
+    }
 }

+ 13 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/constants/MHWebApi.java

@@ -0,0 +1,13 @@
+package com.cooleshow.musicmerge.constants;
+
+/**
+ * Author by pq, Date on 2024/7/25.
+ */
+public class MHWebApi {
+    public static final String API_PLAY ="api_play";//开始播放
+    public static final String API_PAUSED ="api_paused";//播放暂停
+    public static final String API_PLAYPROGRESS ="api_playProgress";//播放进度
+    public static final String API_MUSICPAGE ="api_musicPage";//页面渲染完成
+    public static final String API_UPDATEPROGRESS ="api_updateProgress";//快进更新进度
+    public static final String CONTENT_KEY ="content";
+}

+ 12 - 3
musicMerge/src/main/java/com/cooleshow/musicmerge/constants/MusicMergeConfig.java

@@ -7,15 +7,24 @@ import com.cooleshow.base.utils.ScreenUtils;
  * Author by pq, Date on 2023/11/1.
  */
 public class MusicMergeConfig {
-    public static final String OFFSET_KEY="offset";
-    public static final String ORIGINALVOLUME_KEY="originalVolume";
-    public static final String ACCOMPANYVOLUME_KEY="accompanyVolume";
+    public static final String OFFSET_KEY = "offset";
+    public static final String ORIGINALVOLUME_KEY = "originalVolume";
+    public static final String ACCOMPANYVOLUME_KEY = "accompanyVolume";
     public static final String DEFAULTDELAY_KEY = "defaultDelay";
     public static final String EVALUATEDELAY_KEY = "evaluateDelay";
     public static final String SPEEDRATE_KEY = "speedRate";
+    public static final String PART_INDEX_KEY = "part-index";
+    public static final String MUSICRENDERTYPE_KEY = "musicRenderType";
+    public static final String STAFF = "staff";//五线谱
+    public static final String FIRSTTONE = "firstTone";//简谱
+    public static final String FIXEDTONE = "fixedTone";//固定音高
+
+    public final static int WORKS_PUBLISH_SUCCESS = 1;//作品发布成功
+    public final static int RETRY_EVALUATION = 2;//重新评测录制
 
     public static final String DELAY_FOR_CURRENT_CACHE_KEY = "delayForCurrent";
     public static final int MAX_THUMBNAIL_COUNT = 10;
+    public static final int MAX_MUST_HANDLE_STEP = 3;//合成页面必要的步骤,全部完成后再消失loading
 
     public static int getMaxWidthForThumbWidth() {
         int result = ScreenUtils.getScreenWidth() / MAX_THUMBNAIL_COUNT;

+ 40 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/constants/WorksType.java

@@ -0,0 +1,40 @@
+package com.cooleshow.musicmerge.constants;
+
+
+import com.daya.common_stu_tea.interfaces.IFilterViewData;
+
+/**
+ * Author by pq, Date on 2024/5/14.
+ */
+public enum WorksType implements IFilterViewData {
+    ALL("", "全部"),
+    VIDEO("VIDEO", "视频"),
+    AUDIO("AUDIO", "音频");
+
+    private final String value;
+    private final String id;
+
+    public String getValue() {
+        return value;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    WorksType(String id, String s) {
+        this.id = id;
+        this.value = s;
+    }
+
+
+    @Override
+    public String getDataId() {
+        return getId();
+    }
+
+    @Override
+    public String getShowName() {
+        return getValue();
+    }
+}

+ 1 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/contract/MusicFileHandleContract.java

@@ -15,4 +15,5 @@ public interface MusicFileHandleContract extends BaseView {
 
     void upLoadImageFailure();
 
+    void getDetailByRecordSuccess(MusicDataBean data);
 }

+ 132 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/helper/MusicMergeGuideHelper.java

@@ -0,0 +1,132 @@
+package com.cooleshow.musicmerge.helper;
+
+import android.app.Activity;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.app.hubert.guide.NewbieGuide;
+import com.app.hubert.guide.core.Builder;
+import com.app.hubert.guide.core.Controller;
+import com.app.hubert.guide.listener.OnEventListener;
+import com.app.hubert.guide.listener.OnGuideChangedListener;
+import com.app.hubert.guide.listener.OnLayoutInflatedListener;
+import com.app.hubert.guide.model.GuidePage;
+import com.app.hubert.guide.model.RelativeGuide;
+import com.cooleshow.base.utils.SizeUtils;
+import com.cooleshow.musicmerge.R;
+
+/**
+ * Author by pq, Date on 2024/7/26.
+ */
+public class MusicMergeGuideHelper {
+
+    public static void showGuide(Activity activity, View[] views, OnGuideChangedListener guideChangedListener) {
+//        for (int i = 0; i < views.length; i++) {
+//            views[i].setVisibility(View.VISIBLE);
+//        }
+//        Builder builder = NewbieGuide.with(activity)
+//                .setLabel("music_merge_guide")
+//                .alwaysShow(true)
+//                .addGuidePage(GuidePage.newInstance()
+//                        .setEverywhereCancelable(true)
+//                        .setBackgroundColor(activity.getResources().getColor(com.cooleshow.base.R.color.color_ad000000))
+//                        .setOnLayoutInflatedListener(new OnLayoutInflatedListener() {
+//                            @Override
+//                            public void onLayoutInflated(View view, Controller controller) {
+//                                setStepText(view, 1, views.length);
+//                            }
+//                        })
+//                        .addHighLight(views[0], new RelativeGuide(R.layout.mh_guide_step1, Gravity.LEFT, 0), 20))
+//                .addGuidePage(GuidePage.newInstance()
+//                        .setEverywhereCancelable(true)
+//                        .setBackgroundColor(activity.getResources().getColor(com.cooleshow.base.R.color.color_ad000000))
+//                        .setOnLayoutInflatedListener(new OnLayoutInflatedListener() {
+//                            @Override
+//                            public void onLayoutInflated(View view, Controller controller) {
+//                                setStepText(view, 2, views.length);
+//                            }
+//                        })
+//                        .addHighLight(views[1], new RelativeGuide(R.layout.mh_guide_step2, RelativeGuide.LimitGravity.BOTTOM_LEFT2, SizeUtils.dp2px(0)), 20))
+//                .addGuidePage(GuidePage.newInstance()
+//                        .setEverywhereCancelable(true)
+//                        .setBackgroundColor(activity.getResources().getColor(com.cooleshow.base.R.color.color_ad000000))
+//                        .setOnLayoutInflatedListener(new OnLayoutInflatedListener() {
+//                            @Override
+//                            public void onLayoutInflated(View view, Controller controller) {
+//                                setStepText(view, 3, views.length);
+//                            }
+//                        })
+//                        .addHighLight(views[2], new RelativeGuide(R.layout.mh_guide_step3, RelativeGuide.LimitGravity.BOTTOM_LEFT2, SizeUtils.dp2px(0)), 20))
+//                .addGuidePage(GuidePage.newInstance()
+//                        .setEverywhereCancelable(true)
+//                        .setBackgroundColor(activity.getResources().getColor(com.cooleshow.base.R.color.color_ad000000))
+//                        .setOnLayoutInflatedListener(new OnLayoutInflatedListener() {
+//                            @Override
+//                            public void onLayoutInflated(View view, Controller controller) {
+//                                setStepText(view, 4, views.length);
+//                            }
+//                        })
+//                        .addHighLight(views[3], new RelativeGuide(R.layout.mh_guide_step4, RelativeGuide.LimitGravity.BOTTOM_LEFT2, SizeUtils.dp2px(0)), 20));
+//
+//        if (views.length > 6) {
+//            builder.addGuidePage(GuidePage.newInstance()
+//                    .setEverywhereCancelable(true)
+//                    .setBackgroundColor(activity.getResources().getColor(com.cooleshow.base.R.color.color_ad000000))
+//                    .setOnLayoutInflatedListener(new OnLayoutInflatedListener() {
+//                        @Override
+//                        public void onLayoutInflated(View view, Controller controller) {
+//                            setStepText(view, 5, views.length);
+//                        }
+//                    })
+//                    .addHighLight(views[4], new RelativeGuide(R.layout.mh_guide_step5, RelativeGuide.LimitGravity.TOP_RIGHT, SizeUtils.dp2px(0)), 20));
+//        }
+//        builder.addGuidePage(GuidePage.newInstance()
+//                        .setEverywhereCancelable(true)
+//                        .setBackgroundColor(activity.getResources().getColor(com.cooleshow.base.R.color.color_ad000000))
+//                        .setOnLayoutInflatedListener(new OnLayoutInflatedListener() {
+//                            @Override
+//                            public void onLayoutInflated(View view, Controller controller) {
+//                                setStepText(view, views.length - 1, views.length);
+//                            }
+//                        })
+//                        .addHighLight(views[views.length-2], new RelativeGuide(R.layout.mh_guide_step6, RelativeGuide.LimitGravity.TOP_RIGHT, SizeUtils.dp2px(0)), 20))
+//                .addGuidePage(GuidePage.newInstance()
+//                        .setEverywhereCancelable(true)
+//                        .setBackgroundColor(activity.getResources().getColor(com.cooleshow.base.R.color.color_ad000000))
+//                        .setOnLayoutInflatedListener(new OnLayoutInflatedListener() {
+//                            @Override
+//                            public void onLayoutInflated(View view, Controller controller) {
+//                                View tv_relook = view.findViewById(R.id.tv_relook);
+//                                if (tv_relook != null) {
+//                                    tv_relook.setOnClickListener(new View.OnClickListener() {
+//                                        @Override
+//                                        public void onClick(View v) {
+//                                            controller.showPage(0);
+//                                        }
+//                                    });
+//                                }
+//                            }
+//                        })
+//                        .addHighLight(views[views.length-1], new RelativeGuide(R.layout.mh_guide_step7, RelativeGuide.LimitGravity.TOP_RIGHT, SizeUtils.dp2px(0)), 20))
+//                .setOnGuideChangedListener(guideChangedListener)
+//                .setEventListener(new OnEventListener() {
+//                    @Override
+//                    public View getCloseView(FrameLayout parent) {
+//                        View view = LayoutInflater.from(activity).inflate(R.layout.mh_item_close_guide_layout, parent, false);
+//                        return view;
+//                    }
+//                });
+//        builder.show();
+    }
+
+
+    private static void setStepText(View view, int step, int max) {
+//        TextView tv_next_tip = view.findViewById(R.id.tv_next_tip);
+//        if (tv_next_tip != null) {
+//            tv_next_tip.setText(String.format("下一步(%d/%d)", step, max));
+//        }
+    }
+}

+ 15 - 2
musicMerge/src/main/java/com/cooleshow/musicmerge/presenter/MusicFileHandlePresenter.java

@@ -88,12 +88,10 @@ public class MusicFileHandlePresenter extends BasePresenter<MusicFileHandleContr
     }
 
     public void getDetail(String recordId) {
-        getView().showLoading();
         addSubscribe(create(Api.class).getDetail(BaseConstant.CLIENT_API_GROUP_NAME, recordId), new BaseObserver<MusicDataBean>(getView()) {
             @Override
             protected void onSuccess(MusicDataBean data) {
                 if (getView() != null) {
-                    getView().hideLoading();
                     getView().getDetailSuccess(data);
                 }
             }
@@ -104,6 +102,21 @@ public class MusicFileHandlePresenter extends BasePresenter<MusicFileHandleContr
         });
     }
 
+    public void getDetailByRecordId(String recordId){
+        addSubscribe(create(Api.class).getDetailByRecordId(BaseConstant.CLIENT_API_GROUP_NAME, recordId), new BaseObserver<MusicDataBean>() {
+            @Override
+            protected void onSuccess(MusicDataBean data) {
+                if (getView() != null) {
+                    getView().getDetailByRecordSuccess(data);
+                }
+            }
+
+            @Override
+            public void onComplete() {
+            }
+        });
+    }
+
     public void upLoadImage(Activity activity, String filePath,boolean isVideoCover) {
         Observable.create(new ObservableOnSubscribe<String>() {
                     @Override

+ 1 - 3
musicMerge/src/main/java/com/cooleshow/musicmerge/ui/MusicHandleActivity.java

@@ -23,7 +23,6 @@ import android.view.animation.LinearInterpolator;
 import android.widget.FrameLayout;
 import android.widget.SeekBar;
 
-import com.alibaba.android.arouter.facade.annotation.Route;
 import com.alibaba.android.arouter.launcher.ARouter;
 import com.cooleshow.base.bean.ShareIntentBean;
 import com.cooleshow.base.common.WebConstants;
@@ -48,9 +47,8 @@ import com.cooleshow.base.utils.UrlUtils;
 import com.cooleshow.base.utils.helper.CommonShareHelper;
 import com.cooleshow.base.utils.helper.QMUIStatusBarHelper;
 import com.cooleshow.base.utils.helper.upload.UploadHelper;
-import com.cooleshow.base.widgets.dialog.CommonConfirmDialog;
 import com.cooleshow.base.widgets.dialog.CommonConfirmDialog2;
-import com.cooleshow.base.widgets.dialog.ShareDialog;
+import com.cooleshow.musicmerge.widget.ShareDialog;
 import com.cooleshow.musicmerge.R;
 import com.cooleshow.musicmerge.bean.MusicDataBean;
 import com.cooleshow.musicmerge.bean.MusicInfoBean;

+ 288 - 23
musicMerge/src/main/java/com/cooleshow/musicmerge/ui/MusicHandleActivity_.java

@@ -48,14 +48,16 @@ import com.cooleshow.base.utils.UiUtils;
 import com.cooleshow.base.utils.UrlUtils;
 import com.cooleshow.base.utils.helper.CommonShareHelper;
 import com.cooleshow.base.utils.helper.QMUIStatusBarHelper;
+import com.cooleshow.base.utils.helper.WebStartHelper;
 import com.cooleshow.base.utils.helper.upload.UploadHelper;
 import com.cooleshow.base.widgets.dialog.CommonConfirmDialog2;
-import com.cooleshow.base.widgets.dialog.ShareDialog;
+import com.cooleshow.musicmerge.widget.ShareDialog;
 import com.cooleshow.musicmerge.R;
 import com.cooleshow.musicmerge.bean.MusicDataBean;
 import com.cooleshow.musicmerge.bean.MusicInfoBean;
 import com.cooleshow.musicmerge.bean.MusicMergeConfigBean;
 import com.cooleshow.musicmerge.callback.ResultCallback;
+import com.cooleshow.musicmerge.constants.MHWebApi;
 import com.cooleshow.musicmerge.constants.MergeConfig;
 import com.cooleshow.musicmerge.constants.MusicMergeConfig;
 import com.cooleshow.musicmerge.contract.MusicFileHandleContract;
@@ -66,6 +68,7 @@ import com.cooleshow.musicmerge.player.MergeTrackManager;
 import com.cooleshow.musicmerge.presenter.MusicFileHandlePresenter;
 import com.cooleshow.musicmerge.viewmodel.MusicMergeViewModel;
 import com.cooleshow.musicmerge.widget.MergeLoadingTipDialog;
+import com.cooleshow.usercenter.helper.UserHelper;
 import com.luck.picture.lib.PictureSelector;
 import com.luck.picture.lib.entity.LocalMedia;
 import com.umeng.socialize.UMShareAPI;
@@ -77,8 +80,12 @@ import java.util.List;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.lifecycle.Observer;
 import androidx.lifecycle.ViewModelProvider;
 
+import static com.cooleshow.base.common.WebConstants.WEB_URL;
+
 /**
  * Author by pq, Date on 2023/8/28.
  */
@@ -127,6 +134,11 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
 
     private float accompanyPlaySpeed = MergeConfig.DEFAULT_PLAY_SPEED;
     private MergeTrackManager audioPlayer;
+    private MusicScoreFragment mMusicScoreFragment;
+
+    private String musicRenderType = MusicMergeConfig.STAFF;
+    private boolean hasWorks = false;//是否已经生成了作品
+    private int partIndex;
 
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -138,6 +150,10 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
     protected void initView() {
         mTitle = getIntent().getStringExtra("title");
         viewBinding.tvTitle.setText(mTitle);
+        UiUtils.setTextMarquee(viewBinding.tvTitle);
+        String userName = UserHelper.getUserName();
+        viewBinding.tvTitle2.setVisibility(TextUtils.isEmpty(userName) ? View.GONE : View.VISIBLE);
+        viewBinding.tvTitle2.setText("演奏: " + userName);
         int c_orientation = getIntent().getIntExtra("c_orientation", ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
         isNeedResetScreenOrientation = c_orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 
@@ -151,12 +167,46 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
         bundle.putInt("defaultDelay", defaultDelay);
         mSettingFragment.setArguments(bundle);
         getSupportFragmentManager().beginTransaction().replace(R.id.fl_setting, mSettingFragment).commitAllowingStateLoss();
+
+        String musicSheetId = getIntent().getStringExtra("musicSheetId");
+        String musicRenderType = getIntent().getStringExtra("musicRenderType");
+        if (TextUtils.isEmpty(musicRenderType)) {
+            musicRenderType = MusicMergeConfig.STAFF;
+        }
+        this.musicRenderType = musicRenderType;
+        if (!TextUtils.isEmpty(musicSheetId)) {
+            boolean isVideo = MyFileUtils.isVideo(recordFilePath);
+            initMusicScoreFragment(musicSheetId, this.musicRenderType, isVideo);
+        }
+    }
+
+    private void initMusicScoreFragment(String musicId, String musicRenderType, boolean isVideo) {
+        if (mMusicScoreFragment == null) {
+            mMusicScoreFragment = new MusicScoreFragment();
+            Bundle bundle = new Bundle();
+            if(isVideo){
+                viewBinding.flMusicScoreContainer.setBackgroundResource(R.drawable.shape_mh_music_score_bg);
+            }else{
+                viewBinding.flMusicScoreContainer.setBackgroundColor(Color.TRANSPARENT);
+            }
+            bundle.putString(WEB_URL, createUrl(musicId, musicRenderType, isVideo));
+            mMusicScoreFragment.setArguments(bundle);
+            getSupportFragmentManager().beginTransaction().replace(R.id.fl_music_score_container, mMusicScoreFragment).commitAllowingStateLoss();
+        }
+    }
+
+    private String createUrl(String musicId, String musicRenderType, boolean isVideo) {
+        //五线谱:staff、简谱:firstTone、固定音高:fixedTone
+//        return "https://dev.kt.colexiu.com/instrument/#/simple-detail?id=1001728&musicRenderType=staff";
+//        return "https://www.baidu.com";
+        return WebStartHelper.getWorksMusicScoreUrl(musicId, musicRenderType,String.valueOf(partIndex), isVideo);
     }
 
     private void initSurfaceView() {
         initVideoUIStyle();
         mViewModel.getVideoFilePath().setValue(recordFilePath);
         viewBinding.groupAudioView.setVisibility(View.GONE);
+        viewBinding.musicFrequencyView.setVisibility(View.GONE);
         viewBinding.flSurface.setVisibility(View.VISIBLE);
         viewBinding.viewVideoTopBg.setVisibility(View.VISIBLE);
         viewBinding.viewVideoBottomBg.setVisibility(View.VISIBLE);
@@ -170,6 +220,7 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
 
     private void initVideoUIStyle() {
         viewBinding.tvTitle.setTextColor(Color.WHITE);
+        viewBinding.tvTitle2.setTextColor(Color.WHITE);
         viewBinding.ivBack.setImageResource(com.cooleshow.base.R.drawable.ic_back_white);
         viewBinding.ivPlay.setImageResource(R.drawable.icon_music_merge_play_white);
         viewBinding.tvCurrentProgress.setTextColor(Color.WHITE);
@@ -198,6 +249,8 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
         defaultDelay = getIntent().getIntExtra("defaultDelay", 0);
         evaluateDelay = Math.abs(getIntent().getIntExtra("evaluateDelay", 0));
         accompanyPlaySpeed = getIntent().getFloatExtra(MusicMergeConfig.SPEEDRATE_KEY, MergeConfig.DEFAULT_PLAY_SPEED);
+        partIndex = getIntent().getIntExtra(MusicMergeConfig.PART_INDEX_KEY, 0);
+
         loadCover();
         if (TextUtils.isEmpty(mRecordId)) {
             ToastUtil.getInstance().showShort("作品生成失败");
@@ -213,18 +266,30 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
         initPlayer();
         LOG.i("recordFilePath:" + recordFilePath);
         LOG.i("accompanyUrl:" + accompanyUrl);
+        showAssetsLoading();
         if (!checkRecordFile()) {
             if (!TextUtils.isEmpty(worksId)) {
+                hasWorks = true;
                 presenter.getDetail(worksId);
+                mViewModel.getIsCanRetryRecord().setValue(false);
             } else {
                 ToastUtil.getInstance().showShort("作品生成失败");
                 finish();
             }
         } else {
             preLoad();
+            presenter.getDetailByRecordId(mRecordId);
+            mViewModel.getIsCanRetryRecord().setValue(true);
         }
     }
 
+    private void showAssetsLoading() {
+        showLoadingAndCancel(getString(R.string.mh_loading_str));
+        setLoadingCancelable(false);
+//        setLoadingStyle(getResources().getDrawable(R.drawable.mh_shape_loading_bg), 16);
+    }
+
+
     private void initViewModel() {
         ViewModelProvider.AndroidViewModelFactory instance =
                 ViewModelProvider.AndroidViewModelFactory
@@ -233,6 +298,81 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
                 .get(MusicMergeViewModel.class);
         mViewModel.getWorksId().setValue(worksId);
         refreshMusicInfo(imgCover);
+
+        mViewModel.getMusicScoreHeightInfo().observe(this, new Observer<Integer>() {
+            @Override
+            public void onChanged(Integer height) {
+                if (viewBinding == null) {
+                    return;
+                }
+                if (height != null && height > 0) {
+                    viewBinding.flMusicScoreContainer.setVisibility(View.VISIBLE);
+                    ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) viewBinding.flMusicScoreContainer.getLayoutParams();
+                    layoutParams.height = height;
+                    viewBinding.flMusicScoreContainer.setLayoutParams(layoutParams);
+                    toCheckIsPlay();
+                    if (mViewModel != null) {
+                        LOG.i("曲谱加载完成");
+                        mViewModel.addHandleStep();
+                    }
+                } else {
+                    viewBinding.flMusicScoreContainer.setVisibility(View.GONE);
+                }
+            }
+        });
+
+        mViewModel.getStepInfo().observe(this, new Observer<Integer>() {
+            @Override
+            public void onChanged(Integer value) {
+                LOG.i("getStepInfo onChanged:" + value);
+                if (value != null) {
+                    if (mViewModel.isCompletedAllStep()) {
+                        hideLoading();
+                        toPlay(getAccompanyPath());
+                    } else {
+                        if (value == MusicMergeConfig.MAX_MUST_HANDLE_STEP - 1) {
+                            //其他步骤已经完成,判断是否显示引导页
+                            checkGuide();
+                        }
+                    }
+                }
+            }
+        });
+    }
+
+    private void toCheckIsPlay() {
+        if (isPlaying()) {
+            syncPlayStatusToH5(MHWebApi.API_PLAY);
+        }
+    }
+
+    private void syncPlayStatusToH5(String api) {
+        if (mMusicScoreFragment != null) {
+            if (TextUtils.equals(api, MHWebApi.API_PAUSED)) {
+                long playProgress = getAudioPlayer().getPlayProgress();
+                long result = (long) (playProgress * accompanyPlaySpeed);
+                double second = (double) result / 1000;
+                mMusicScoreFragment.sendApiEvent(api, second);
+            } else {
+                mMusicScoreFragment.sendApiEvent(api);
+            }
+        }
+    }
+
+    private void syncPlayProgressToH5(long progress) {
+        if (mMusicScoreFragment != null) {
+            long result = (long) (progress * accompanyPlaySpeed);
+            mMusicScoreFragment.sendProgressEvent(result);
+        }
+    }
+
+    private void syncSeekResultToH5(int seekResult) {
+        if (isPlaying()) {
+            return;
+        }
+        if (mMusicScoreFragment != null) {
+            mMusicScoreFragment.sendUpdateProgressEvent(seekResult);
+        }
     }
 
     private void preLoad() {
@@ -261,7 +401,7 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
 
     private void toCreateWavFile() {
         setLoadingCancelable(false);
-        showLoading("草稿处理中");
+        showAssetsLoading();
         String wavFileSavePath = MixHelper.getInstance().getWavFileSavePath(recordFilePath, MyFileUtils.WAV_FILE_SUFFIX);
         MixHelper.getInstance().getWavFromMp4(recordFilePath, wavFileSavePath, new ResultCallback<String>() {
             @Override
@@ -272,7 +412,6 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
                 runOnUiThread(new Runnable() {
                     @Override
                     public void run() {
-                        hideLoading();
                         if (!TextUtils.isEmpty(s)) {
                             recordWavFilePath = s;
                             preLoad();
@@ -368,6 +507,12 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
             public void pausePlay() {
                 MusicHandleActivity_.this.pausePlay();
             }
+
+
+            @Override
+            public void retryRecord() {
+                showRetryRecordTip();
+            }
         });
         viewBinding.seekPlay.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
             @Override
@@ -509,6 +654,7 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
                 });
             }
 
+
             @Override
             public void onUploadProgress(double v) {
                 toUpdateLoadingText(getCurrentProgress((int) v), getString(R.string.upload_works_tip));
@@ -520,7 +666,10 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
     }
 
     private void toNotify(String url) {
-        String configJson = mSettingFragment.getConfigJson(defaultDelay, evaluateDelay);
+        if (!checkActivityExist()) {
+            return;
+        }
+        String configJson = mSettingFragment.getConfigJson(defaultDelay, evaluateDelay, musicRenderType, partIndex);
         MusicInfoBean value = mViewModel.getMusicInfoLiveData().getValue();
         String videoCover = "";
         String cover = imgCover;
@@ -539,8 +688,11 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
     }
 
     private void toNotifyDraft(String url) {
+        if (!checkActivityExist()) {
+            return;
+        }
         if (mSettingFragment != null) {
-            String configJson = mSettingFragment.getConfigJson(defaultDelay, evaluateDelay);
+            String configJson = mSettingFragment.getConfigJson(defaultDelay, evaluateDelay, musicRenderType, partIndex);
             presenter.saveDraft(mRecordId, url, accompanyUrl, imgCover, configJson);
         }
     }
@@ -683,6 +835,7 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
         int seekResult = (int) (getAudioTotalDuration() * percent);
         LOG.i("pq", "seekResult:" + seekResult);
         getAudioPlayer().seekPercent(percent);
+        syncSeekResultToH5(seekResult);
 
         if (isVideo) {
             int seekResult2 = countAccompanyPosition(seekResult);
@@ -816,6 +969,7 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
             //拖拽的时候不更新
             viewBinding.seekPlay.setProgress(percent);
         }
+        syncPlayProgressToH5(progress);
     }
 
     private void setDurationText() {
@@ -865,6 +1019,12 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
     }
 
     private void toRealPlayVideo() {
+        //步骤没加载完成,不播放视频
+        if (!mViewModel.isCompletedAllStep()) {
+            //预加载显示画面
+            player2.seekTo(0);
+            return;
+        }
         if (audioPlayDuration != 0 && videoPlayDuration != 0) {
             player2.resetToPrepare();
             player2.start();
@@ -942,14 +1102,31 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
     }
 
     private void toPlay(String accompanyPath) {
-
+        LOG.i("toPlay:" + accompanyPath);
+        LOG.i("toPlay:" + Thread.currentThread().getName());
+        LOG.i("toPlay:" + recordFilePath);
+        prepareVideoIfNeed();
+        if (!mViewModel.isCompletedAllStep()) {
+            //主要流程文件下载处理完毕
+            LOG.i("音视频文件加载完成");
+            mViewModel.addHandleStep();
+            return;
+        }
         String[] files = new String[]{accompanyPath, recordWavFilePath};
         getAudioPlayer().play2(files);
-        if (isVideo) {
+    }
+
+    private void prepareVideoIfNeed() {
+        if (isVideo && !videoIsPrepare()) {
             player2.play(recordFilePath);
+            player2.setSurface(new Surface(mSurfaceTexture));
         }
     }
 
+    private boolean videoIsPrepare() {
+        return videoPlayDuration != 0;
+    }
+
     private boolean checkRecordFile(String recordUrl) {
         String fileEndSuffix = MyFileUtils.getWAVOrMp4FileSuffix(recordUrl);
         String wavFileSavePath = MixHelper.getInstance().getWavFileSavePath(recordUrl, MyFileUtils.WAV_FILE_SUFFIX);
@@ -969,7 +1146,7 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
         }
         LOG.i("pq", "下载草稿");
         setLoadingCancelable(false);
-        showLoading("草稿下载中");
+        showAssetsLoading();
         MixHelper.getInstance().downloadOriginalFile(recordUrl, fileEndSuffix, new ResultCallback<String>() {
             @Override
             public void onSuccess(String s) {
@@ -985,7 +1162,6 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
                 runOnUiThread(new Runnable() {
                     @Override
                     public void run() {
-                        hideLoading();
                         preLoad();
                     }
                 });
@@ -999,7 +1175,8 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
                 runOnUiThread(new Runnable() {
                     @Override
                     public void run() {
-                        updateLoadingText("草稿下载中" + progressPercent + "%");
+//                        updateLoadingText("草稿下载中" + progressPercent + "%");
+                        updateLoadingText(getString(R.string.mh_loading_str));
                     }
                 });
             }
@@ -1037,7 +1214,7 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
             return false;
         }
         setLoadingCancelable(false);
-        showLoading("伴奏下载中");
+        showAssetsLoading();
         MixHelper.getInstance().downloadAccompany(accompanyUrl, accompanyPlaySpeed, MyFileUtils.MP3_FILE_SUFFIX, new ResultCallback<String>() {
             @Override
             public void onSuccess(String s) {
@@ -1048,7 +1225,6 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
                     @Override
                     public void run() {
                         preparePlay();
-                        hideLoading();
                     }
                 });
             }
@@ -1061,7 +1237,8 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
                 runOnUiThread(new Runnable() {
                     @Override
                     public void run() {
-                        updateLoadingText("伴奏下载中" + progressPercent + "%");
+//                        updateLoadingText("伴奏下载中" + progressPercent + "%");
+                        updateLoadingText(getString(R.string.mh_loading_str));
                     }
                 });
             }
@@ -1136,6 +1313,13 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
             checkTipToFinish();
             return;
         }
+
+        if (id == com.cooleshow.base.R.id.tv_cancel_loading) {
+            //取消loading,关闭页面
+            hideLoading();
+            finish();
+            return;
+        }
     }
 
     private void resumePlay() {
@@ -1178,7 +1362,7 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
 
     private void checkTipToFinish() {
         boolean hasUpdate = mViewModel.isHasUpdate();
-        if (hasUpdate) {
+        if (hasUpdate || !hasWorks) {
             showSaveTipDialog();
         } else {
             finish();
@@ -1190,6 +1374,7 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
         CommonConfirmDialog2 commonConfirmDialog = new CommonConfirmDialog2(this);
         commonConfirmDialog.setWidth(SizeUtils.dp2px(387));
         commonConfirmDialog.show();
+        commonConfirmDialog.setIsCanCel(false);
         commonConfirmDialog.setTitle("提示");
         commonConfirmDialog.setContent("是否将本次录制的作品保存为草稿?");
         commonConfirmDialog.setOnConfirmClickListener(new View.OnClickListener() {
@@ -1209,6 +1394,30 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
         });
     }
 
+    private void showRetryRecordTip() {
+        pausePlay();
+        CommonConfirmDialog2 retryTipDialog = new CommonConfirmDialog2(this);
+        retryTipDialog.setWidth(SizeUtils.dp2px(350));
+        retryTipDialog.show();
+        retryTipDialog.setIsCanCel(false);
+        retryTipDialog.setTitle("温馨提示");
+        retryTipDialog.setContent("是否重新录制作品?");
+        retryTipDialog.setOnConfirmClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                retryTipDialog.dismiss();
+                goRetryRecordToFinish();
+            }
+        });
+        retryTipDialog.setOnCancelClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                retryTipDialog.dismiss();
+            }
+        });
+    }
+
+
     /**
      * 监听返回键
      *
@@ -1231,9 +1440,11 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
         if (visibility == View.VISIBLE) {
             viewBinding.groupSetting.setVisibility(View.GONE);
             viewBinding.ivUnfoldSentting.setVisibility(View.VISIBLE);
+            viewBinding.viewBgRightPoint.setVisibility(View.GONE);
         } else {
             viewBinding.groupSetting.setVisibility(View.VISIBLE);
             viewBinding.ivUnfoldSentting.setVisibility(View.GONE);
+            viewBinding.viewBgRightPoint.setVisibility(View.VISIBLE);
         }
 
         mHandler.postDelayed(new Runnable() {
@@ -1249,10 +1460,12 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
             viewBinding.ivPlayPointer.setRotation(0);
             handleAnim(true);
             viewBinding.ivPlay.setImageResource(isVideo ? R.drawable.icon_music_merge_pause_white : R.drawable.icon_music_merge_pause);
+            syncPlayStatusToH5(MHWebApi.API_PLAY);
         } else {
             handleAnim(false);
             viewBinding.ivPlayPointer.setRotation(92);
             viewBinding.ivPlay.setImageResource(isVideo ? R.drawable.icon_music_merge_play_white : R.drawable.icon_music_merge_play);
+            syncPlayStatusToH5(MHWebApi.API_PAUSED);
         }
     }
 
@@ -1261,6 +1474,7 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
         if (isVideo) {
             return;
         }
+        viewBinding.musicFrequencyView.setVisibility(isPlay ? View.VISIBLE : View.GONE);
         if (isPlay) {
             viewBinding.musicFrequencyView.setMediaPlayer(getAudioPlayer().getAudioSessionId());
         }
@@ -1329,6 +1543,7 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
         bean.setTitle(getString(R.string.share_works_title));
         bean.setDes(shareDes);
         bean.setThumb(imgCover);
+        bean.setId(worksId);
         return bean;
     }
 
@@ -1350,6 +1565,15 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
             public void toShare(ShareType shareType) {
                 goShare(intentBean, shareType);
             }
+
+            @Override
+            public void goLookDetail() {
+                //适配从云教练里面跳转的页面顺序,暂时这样吧
+                int pos = 4;
+                JumpUtils.jumpMain(pos);
+                goMyWorks(0);
+                WebStartHelper.startMyWorks(intentBean.getId());
+            }
         });
         shareDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
             @Override
@@ -1385,6 +1609,13 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
         finish();
     }
 
+    private void goRetryRecordToFinish() {
+        Intent intent = new Intent();
+        intent.putExtra("saveWorksStatus", MusicMergeConfig.RETRY_EVALUATION);
+        setResult(RESULT_OK, intent);
+        finish();
+    }
+
 
     @Override
     public void getDetailSuccess(MusicDataBean data) {
@@ -1395,15 +1626,15 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
             this.originalFileUrl = data.getVideoUrl();
             String jsonConfig = data.getJsonConfig();
             accompanyUrl = data.getAccompanyUrl();
+            boolean isVideo = MyFileUtils.isVideoFromUrl(data.getVideoUrl());
+            MusicMergeConfigBean musicMergeConfigBean = toApplyConfig(jsonConfig);
+            initMusicScoreFragment(data.getMusicSheetId(), musicMergeConfigBean.getMusicRenderType(), isVideo);
             if (mSettingFragment != null) {
-                if (!TextUtils.isEmpty(jsonConfig)) {
-                    toApplyConfig(jsonConfig);
-                }
+                mSettingFragment.applyConfig(musicMergeConfigBean);
                 mSettingFragment.setAccompanyUrl(accompanyUrl, accompanyPlaySpeed);
             }
             //这里为了兼容IOS录制的wav音频文件格式不正确 导致合成失败的问题Failed to read frame size: Could not seek to 1026.
             //取服务端存储的文件
-            boolean isVideo = MyFileUtils.isVideoFromUrl(data.getVideoUrl());
             String fileUrl = isVideo ? data.getVideoUrl() : data.getRecordFilePath();
             boolean b = checkRecordFile(fileUrl);
             if (b) {
@@ -1413,16 +1644,20 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
         }
     }
 
-    private void toApplyConfig(String jsonConfig) {
+    private MusicMergeConfigBean toApplyConfig(String jsonConfig) {
         try {
             MusicMergeConfigBean musicMergeConfigBean = GsonUtils.fromJson(jsonConfig, MusicMergeConfigBean.class);
             defaultDelay = Math.abs(musicMergeConfigBean.getDefaultDelay());
             evaluateDelay = Math.abs(musicMergeConfigBean.getEvaluateDelay());
             mSettingFragment.applyConfig(musicMergeConfigBean);
             accompanyPlaySpeed = musicMergeConfigBean.getSpeedRate();
+            this.musicRenderType = musicMergeConfigBean.getMusicRenderType();
+            this.partIndex=musicMergeConfigBean.getPartIndex();
+            return musicMergeConfigBean;
         } catch (Exception e) {
             e.printStackTrace();
         }
+        return MusicMergeConfigBean.createDefault();
     }
 
     @Override
@@ -1430,6 +1665,7 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
         if (!checkActivityExist()) {
             return;
         }
+        hasWorks = true;
         currentStep = 1;
         mViewModel.getUpdateEvent().setValue(false);
         if (isNeedFinishPage) {
@@ -1457,8 +1693,9 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
         CommonConfirmDialog2 commonConfirmDialog = new CommonConfirmDialog2(this);
         commonConfirmDialog.setWidth(SizeUtils.dp2px(387));
         commonConfirmDialog.show();
+        commonConfirmDialog.setIsCanCel(false);
         commonConfirmDialog.setTitle("提示");
-        commonConfirmDialog.setContent("已成功保存到草稿,草稿7天未发布\n将自动删除。");
+        commonConfirmDialog.setContent("已成功保存到草稿,草稿7天未发布将自动删除。");
         commonConfirmDialog.setCancelText("确认");
         commonConfirmDialog.setConfirmText("查看草稿");
         commonConfirmDialog.setOnCancelClickListener(new View.OnClickListener() {
@@ -1473,15 +1710,15 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
                 commonConfirmDialog.dismiss();
                 //从首页个人中心跳转我的作品
                 JumpUtils.jumpMain(4);
-                goMyWorks();
+                goMyWorks(1);
             }
         });
 
     }
 
-    private void goMyWorks() {
+    private void goMyWorks(int pos) {
         ARouter.getInstance().build(RouterPath.Homework.MY_WORK)
-                .withInt(Constants.MAIN_PAGE_SELECT_POTION_KEY, 1)
+                .withInt(Constants.MAIN_PAGE_SELECT_POTION_KEY, pos)
                 .navigation();
         finish();
     }
@@ -1544,6 +1781,34 @@ public class MusicHandleActivity_ extends BaseMVPActivity<AcMusicHandleLayoutBin
     }
 
     @Override
+    public void getDetailByRecordSuccess(MusicDataBean data) {
+        //是否有作品记录
+        if (data != null) {
+            hasWorks = true;
+        } else {
+            hasWorks = false;
+        }
+    }
+
+    private void checkGuide() {
+        if (mSettingFragment != null) {
+            boolean canShowGuide = mSettingFragment.isCanShowGuide();
+            if (canShowGuide) {
+                hideLoading();
+                pausePlay();
+                if (viewBinding.flSetting.getVisibility() == View.GONE) {
+                    handleSettingVisibility();
+                }
+                mSettingFragment.showGuide();
+            } else {
+                //不需要显示引导页
+                LOG.i("引导页无需显示,完成");
+                mViewModel.addHandleStep();
+            }
+        }
+    }
+
+    @Override
     protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
         UMShareAPI.get(MusicHandleActivity_.this).onActivityResult(requestCode, resultCode, data);

+ 73 - 3
musicMerge/src/main/java/com/cooleshow/musicmerge/ui/MusicHandleSettingFragment.java

@@ -11,6 +11,9 @@ import android.view.View;
 import android.widget.SeekBar;
 
 import com.alibaba.android.arouter.launcher.ARouter;
+import com.app.hubert.guide.core.Controller;
+import com.app.hubert.guide.listener.OnGuideChangedListener;
+import com.cooleshow.base.common.CommonConfig;
 import com.cooleshow.base.router.RouterPath;
 import com.cooleshow.base.ui.fragment.BaseFragment;
 import com.cooleshow.base.utils.FileUtils;
@@ -27,8 +30,10 @@ import com.cooleshow.musicmerge.constants.MergeConfig;
 import com.cooleshow.musicmerge.constants.MusicMergeConfig;
 import com.cooleshow.musicmerge.databinding.FgMusicHandleSettingLayoutBinding;
 import com.cooleshow.musicmerge.helper.MixHelper;
+import com.cooleshow.musicmerge.helper.MusicMergeGuideHelper;
 import com.cooleshow.musicmerge.viewmodel.MusicMergeViewModel;
 import com.cooleshow.musicmerge.widget.UploadCoverTipDialog;
+import com.cooleshow.usercenter.helper.UserHelper;
 import com.luck.picture.lib.PictureSelector;
 import com.luck.picture.lib.config.PictureConfig;
 import com.luck.picture.lib.config.PictureMimeType;
@@ -59,6 +64,7 @@ public class MusicHandleSettingFragment extends BaseFragment<FgMusicHandleSettin
 
     private int defaultDelay = 0;
     private int evaluateDelay = 0;
+    private Controller guideController;
 
     private Runnable mRunnable = new Runnable() {
         @Override
@@ -229,8 +235,8 @@ public class MusicHandleSettingFragment extends BaseFragment<FgMusicHandleSettin
         }
 
         if (id == R.id.tv_record) {
-            if (getActivity() != null) {
-                getActivity().onKeyDown(KeyEvent.KEYCODE_BACK,null);
+            if (mEventListener != null) {
+                mEventListener.retryRecord();
             }
             return;
         }
@@ -440,7 +446,7 @@ public class MusicHandleSettingFragment extends BaseFragment<FgMusicHandleSettin
         return value;
     }
 
-    public String getConfigJson(int defaultDelay, int evaluateDelay) {
+    public String getConfigJson(int defaultDelay, int evaluateDelay, String musicRenderType, int partIndex) {
         if (isDetached()) {
             return "";
         }
@@ -458,6 +464,8 @@ public class MusicHandleSettingFragment extends BaseFragment<FgMusicHandleSettin
             jsonObject.put(MusicMergeConfig.DEFAULTDELAY_KEY, defaultDelay);
             jsonObject.put(MusicMergeConfig.EVALUATEDELAY_KEY, evaluateDelay);
             jsonObject.put(MusicMergeConfig.SPEEDRATE_KEY, accompanySpeed);
+            jsonObject.put(MusicMergeConfig.MUSICRENDERTYPE_KEY, musicRenderType);
+            jsonObject.put(MusicMergeConfig.PART_INDEX_KEY, partIndex);
             return jsonObject.toString();
         } catch (JSONException e) {
             e.printStackTrace();
@@ -493,6 +501,63 @@ public class MusicHandleSettingFragment extends BaseFragment<FgMusicHandleSettin
         this.accompanySpeed = accompanySpeed;
     }
 
+    public void showGuide() {
+        if (!isCanShowGuide()) {
+            return;
+        }
+        View[] views;
+        if (mViewModel.isCanRetryRecord()) {
+            views = new View[]{mViewBinding.viewGuide1, mViewBinding.viewGuide2, mViewBinding.viewGuide3, mViewBinding.viewGuide4, mViewBinding.viewGuide5,
+                    mViewBinding.viewGuide6, mViewBinding.viewGuide7};
+        } else {
+            views = new View[]{mViewBinding.viewGuide1, mViewBinding.viewGuide2, mViewBinding.viewGuide3, mViewBinding.viewGuide4,
+                    mViewBinding.viewGuide6, mViewBinding.viewGuide7};
+        }
+        MusicMergeGuideHelper.showGuide(getActivity(), views, new OnGuideChangedListener() {
+            @Override
+            public void onShowed(Controller controller) {
+                MusicHandleSettingFragment.this.guideController = controller;
+                //控制已经显示过引导页
+                CommonConfig.isNeedShowMusicMergeGuide = false;
+                notifyCompletedGuide();
+            }
+
+            @Override
+            public void onRemoved(Controller controller) {
+                LOG.i("引导页完成");
+                if (mViewModel != null) {
+                    mViewModel.addHandleStep();
+                }
+            }
+
+            @Override
+            public boolean isCanShow() {
+                return isCanShowGuide();
+            }
+        });
+    }
+
+    private void notifyCompletedGuide() {
+        UserHelper.setHasShowGuideCompleted(CommonConfig.APP_MY_WORKS_GUIDE_TAG);
+    }
+
+    public boolean isCanShowGuide() {
+        if (!MusicHandleSettingFragment.this.isResumed()) {
+            return false;
+        }
+        LOG.i("isNeedShowMusicMergeGuide:" + CommonConfig.isNeedShowMusicMergeGuide);
+        if (!CommonConfig.isNeedShowMusicMergeGuide) {
+            return false;
+        }
+
+        boolean hasShowGuideCompleted = UserHelper.getHasShowGuideCompleted(CommonConfig.APP_MY_WORKS_GUIDE_TAG);
+        if (hasShowGuideCompleted) {
+            CommonConfig.isNeedShowMusicMergeGuide = false;
+            return false;
+        }
+        return true;
+    }
+
     public interface OnEventListener {
         void onVolumeChange(int type, float value);
 
@@ -505,6 +570,8 @@ public class MusicHandleSettingFragment extends BaseFragment<FgMusicHandleSettin
         void saveDraft();
 
         void pausePlay();
+
+        void retryRecord();
     }
 
     @Override
@@ -512,6 +579,9 @@ public class MusicHandleSettingFragment extends BaseFragment<FgMusicHandleSettin
         removeCallBack(mRunnable);
         removeCallBack(mRunnable2);
         super.onDestroy();
+        if (guideController != null) {
+            guideController.remove();
+        }
     }
 
     private void removeCallBack(Runnable runnable) {

+ 258 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/ui/MusicScoreFragment.java

@@ -0,0 +1,258 @@
+package com.cooleshow.musicmerge.ui;
+
+import android.net.Uri;
+import android.os.Build;
+import android.view.View;
+import android.webkit.GeolocationPermissions;
+import android.webkit.ValueCallback;
+import android.webkit.WebChromeClient;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.widget.FrameLayout;
+
+import com.cooleshow.base.BuildConfig;
+import com.cooleshow.base.common.WebConstants;
+import com.cooleshow.base.ui.fragment.BaseFragment;
+import com.cooleshow.base.utils.LOG;
+import com.cooleshow.base.utils.SizeUtils;
+import com.cooleshow.base.widgets.LollipopFixedWebView;
+import com.cooleshow.base.widgets.WebClient;
+import com.cooleshow.musicmerge.constants.MHWebApi;
+import com.cooleshow.musicmerge.databinding.MhFgMusicScoreFragmentBinding;
+import com.cooleshow.musicmerge.viewmodel.MusicMergeViewModel;
+import com.cooleshow.musicmerge.widget.MusicScoreJsInterface;
+import com.cooleshow.usercenter.helper.UserHelper;
+
+import org.json.JSONObject;
+
+import androidx.lifecycle.ViewModelProvider;
+
+import static com.cooleshow.base.common.WebConstants.WEB_URL;
+
+
+/**
+ * Author by pq, Date on 2024/7/25.
+ */
+public class MusicScoreFragment extends BaseFragment<MhFgMusicScoreFragmentBinding> implements MusicScoreJsInterface.onGetMethodsListener {
+    private WebView webView;
+    private String webViewUrl;
+    private MusicMergeViewModel mViewModel;
+
+    @Override
+    protected MhFgMusicScoreFragmentBinding getLayoutView() {
+        return MhFgMusicScoreFragmentBinding.inflate(getLayoutInflater());
+    }
+
+    @Override
+    protected void initView(View rootView) {
+        initWebView();
+    }
+
+    private void initWebView() {
+        try {
+            if (Build.VERSION.SDK_INT >= 21 && Build.VERSION.SDK_INT < 23) {
+                webView = new LollipopFixedWebView(getContext());
+            } else {
+                webView = new WebView(getContext());
+            }
+        } catch (Exception e) {
+
+        }
+        if (null == webView) {
+            return;
+        }
+        mViewBinding.flContainer.removeAllViews();
+        mViewBinding.flContainer.addView(webView, new FrameLayout.LayoutParams(
+                FrameLayout.LayoutParams.MATCH_PARENT,
+                FrameLayout.LayoutParams.MATCH_PARENT));
+        initWebViewSetting();
+        MusicScoreJsInterface jsInterface = new MusicScoreJsInterface();
+        jsInterface.setOnItemClickListener(this);
+        webView.addJavascriptInterface(jsInterface, WebConstants.WEB_JS_INTERFACE);
+        webView.setWebViewClient(new WebClient(){
+            @Override
+            public void onPageFinished(WebView view, String url) {
+                super.onPageFinished(view, url);
+                LOG.i("onPageFinished");
+//                onPageLoadCompleted(20);
+            }
+        });
+        webView.setWebChromeClient(new MyWebChromeClient());
+        webView.setBackgroundColor(0);
+        String userToken = UserHelper.getUserToken();
+        if (getArguments() != null) {
+            webViewUrl = getArguments().getString(WEB_URL);
+        }
+        LOG.i("pq", "webViewUrl:" + webViewUrl);
+        webView.loadUrl(webViewUrl);
+
+    }
+
+    private void initWebViewSetting() {
+        //声明WebSettings子类
+        WebSettings webSettings = webView.getSettings();
+        webSettings.setUserAgentString(webSettings.getUserAgentString() + WebConstants.WEB_UA_PARAMS);
+        webSettings.setGeolocationDatabasePath(getContext().getApplicationContext().getFilesDir().getPath());
+        webSettings.setGeolocationEnabled(true);
+        webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
+        //如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript
+        webSettings.setJavaScriptEnabled(true);
+        webSettings.setMediaPlaybackRequiresUserGesture(false);//false允许自动播放音视频
+        //是否启用缓存
+        webSettings.setAppCacheEnabled(true);
+
+        // 开启DOM缓存,默认状态下是不支持LocalStorage的
+        webSettings.setDomStorageEnabled(true);
+        // 开启数据库缓存
+        webSettings.setDatabaseEnabled(true);
+        // 地址跨域导致视频预览图片加载不出来 无法播放:
+        webSettings.setAllowUniversalAccessFromFileURLs(false);
+        webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
+
+        //设置自适应屏幕,两者合用
+        webSettings.setUseWideViewPort(true); //将图片调整到适合webview的大小
+        webSettings.setLoadWithOverviewMode(true); // 缩放至屏幕的大小
+        //缩放操作
+        webSettings.setSupportZoom(false); //支持缩放,默认为true。是下面那个的前提。
+        // 设置允许JS弹窗
+        webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
+        //其他细节操作
+        webSettings.setCacheMode(WebSettings.LOAD_DEFAULT); //webview中缓存
+        webSettings.setAllowFileAccess(true); //设置可以访问文件
+        webSettings.setJavaScriptCanOpenWindowsAutomatically(true); //支持通过JS打开新窗口
+        webSettings.setLoadsImagesAutomatically(true); //支持自动加载图片
+        webSettings.setDefaultTextEncodingName("UTF-8");//设置编码格式
+
+        webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NARROW_COLUMNS);  //富文本适配
+        webSettings.setAppCacheMaxSize(Long.MAX_VALUE);
+        webSettings.setAppCachePath(getContext().getDir("appcache", 0).getPath());
+        webSettings.setDatabasePath(getContext().getDir("databases", 0).getPath());
+        webSettings.setGeolocationDatabasePath(getContext().getDir("geolocation", 0)
+                .getPath());
+        webSettings.setPluginState(WebSettings.PluginState.ON_DEMAND);
+        if (BuildConfig.DEBUG) {
+            webView.setWebContentsDebuggingEnabled(true);
+        }
+        webSettings.setTextZoom(100);//设置字体默认的缩放比例,以避免手机系统的字体修改对页面字体及布局造成影响。
+        webView.setHorizontalScrollBarEnabled(false);
+        webView.setVerticalScrollBarEnabled(false);
+    }
+
+    @Override
+    protected void initData() {
+        initViewModel();
+    }
+
+    private void initViewModel() {
+        ViewModelProvider.AndroidViewModelFactory instance =
+                ViewModelProvider.AndroidViewModelFactory
+                        .getInstance(getActivity().getApplication());
+        mViewModel = new ViewModelProvider(getActivity(), instance)
+                .get(MusicMergeViewModel.class);
+    }
+
+    @Override
+    public void onPageLoadCompleted(double height) {
+        //页面渲染完成
+        if (mViewModel != null) {
+            int result = (int) Math.ceil(height);
+            int i = SizeUtils.dp2px(result);
+            mViewModel.getMusicScoreHeightInfo().setValue(i);
+        }
+    }
+
+
+    private class MyWebChromeClient extends WebChromeClient {
+        public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
+            callback.invoke(origin, true, false);
+        }
+
+        @Override
+        public void onProgressChanged(WebView view, int newProgress) {
+            if (newProgress == 100) {
+            } else {
+            }
+        }
+
+        @Override
+        public void onReceivedTitle(WebView view, String title) {
+            super.onReceivedTitle(view, title);
+        }
+
+        // Android > 5.0.1
+        public boolean onShowFileChooser(
+                WebView webView, ValueCallback<Uri[]> filePathCallback,
+                FileChooserParams fileChooserParams) {
+            return true;
+        }
+
+        //  横屏时,显示视频的view
+
+        // 点击全屏按钮时,调用的方法
+        @Override
+        public void onShowCustomView(View view, CustomViewCallback callback) {
+            super.onShowCustomView(view, callback);
+        }
+
+        // 取消全屏调用的方法
+        @Override
+        public void onHideCustomView() {
+            super.onHideCustomView();
+        }
+    }
+
+    public void sendApiEvent(String apiKey, Object... args) {
+        try {
+            JSONObject jsonObject = new JSONObject();
+            JSONObject contentJson = new JSONObject();
+            jsonObject.put("api", apiKey);
+            if (args != null && args.length > 0) {
+                contentJson.put("currentTime", args[0]);
+            }
+            jsonObject.put("content", contentJson);
+            onSendMessage(jsonObject.toString());
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void sendProgressEvent(long progress) {
+        try {
+            double second = (double) progress / 1000;
+            JSONObject jsonObject = new JSONObject();
+            JSONObject contentJson = new JSONObject();
+            jsonObject.put("api", MHWebApi.API_PLAYPROGRESS);
+            contentJson.put("currentTime", second);
+            jsonObject.put("content", contentJson);
+            onSendMessage(jsonObject.toString());
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void sendUpdateProgressEvent(long progress) {
+        try {
+            double second = (double) progress / 1000;
+            JSONObject jsonObject = new JSONObject();
+            JSONObject contentJson = new JSONObject();
+            jsonObject.put("api", MHWebApi.API_UPDATEPROGRESS);
+            contentJson.put("currentTime", second);
+            jsonObject.put("content", contentJson);
+            onSendMessage(jsonObject.toString());
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void onSendMessage(String message) {
+        if (webView == null) {
+            return;
+        }
+        LOG.i("onSendMessage:" + message);
+        webView.evaluateJavascript("postMessage('" + message + "','*')", new ValueCallback<String>() {
+            @Override
+            public void onReceiveValue(String s) {
+            }
+        });
+    }
+}

+ 52 - 1
musicMerge/src/main/java/com/cooleshow/musicmerge/viewmodel/MusicMergeViewModel.java

@@ -1,6 +1,7 @@
 package com.cooleshow.musicmerge.viewmodel;
 
 import com.cooleshow.musicmerge.bean.MusicInfoBean;
+import com.cooleshow.musicmerge.constants.MusicMergeConfig;
 
 import androidx.lifecycle.MutableLiveData;
 import androidx.lifecycle.ViewModel;
@@ -14,6 +15,9 @@ public class MusicMergeViewModel extends ViewModel {
     private MutableLiveData<Boolean> isVideoFile = new MutableLiveData<>();   //
     private MutableLiveData<String> videoFilePath = new MutableLiveData<>();   //
     private MutableLiveData<Boolean> updateStatus = new MutableLiveData<>();   //
+    private MutableLiveData<Integer> musicScoreHeightInfo = new MutableLiveData<>();//谱面渲染高度   //
+    private MutableLiveData<Boolean> isCanRetryRecord = new MutableLiveData<>();//是否可以重录   //
+    private MutableLiveData<Integer> mustStepHandleInfo = new MutableLiveData<>();//作品合成页面,必要步骤监听页面   //
 
     public MutableLiveData<MusicInfoBean> getMusicInfoLiveData() {
         return musicInfoLiveData;
@@ -35,7 +39,28 @@ public class MusicMergeViewModel extends ViewModel {
         return isVideoFile;
     }
 
-    public void resetMusicInfo(String des, String url,String videoCover) {
+    public MutableLiveData<Integer> getMusicScoreHeightInfo() {
+        return musicScoreHeightInfo;
+    }
+
+    public MutableLiveData<Boolean> getIsCanRetryRecord() {
+        return isCanRetryRecord;
+    }
+
+
+    public MutableLiveData<Integer> getStepInfo() {
+        return mustStepHandleInfo;
+    }
+
+    public boolean isCanRetryRecord() {
+        Boolean value = getIsCanRetryRecord().getValue();
+        if (value != null) {
+            return value;
+        }
+        return true;
+    }
+
+    public void resetMusicInfo(String des, String url, String videoCover) {
         MusicInfoBean value = getMusicInfoLiveData().getValue();
         if (value != null) {
             value.setDes(des);
@@ -77,6 +102,14 @@ public class MusicMergeViewModel extends ViewModel {
         }
     }
 
+    public void refreshMusicWorksSelectFlag(boolean selectFlag) {
+        MusicInfoBean value = getMusicInfoLiveData().getValue();
+        if (value != null) {
+            value.setSelectFlag(selectFlag);
+            getMusicInfoLiveData().setValue(value);
+        }
+    }
+
     public boolean isHasUpdate() {
         Boolean value = getUpdateEvent().getValue();
         if (value != null && value) {
@@ -84,4 +117,22 @@ public class MusicMergeViewModel extends ViewModel {
         }
         return false;
     }
+
+    public int getCurrentStep() {
+        Integer value = getStepInfo().getValue();
+        if (value != null) {
+            return value;
+        }
+        return 0;
+    }
+
+    public void addHandleStep() {
+        int currentStep = getCurrentStep();
+        currentStep++;
+        getStepInfo().setValue(currentStep);
+    }
+
+    public boolean isCompletedAllStep(){
+        return getCurrentStep()>= MusicMergeConfig.MAX_MUST_HANDLE_STEP;
+    }
 }

+ 31 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/widget/CenterItemDecoration.java

@@ -0,0 +1,31 @@
+package com.cooleshow.musicmerge.widget;
+
+import android.graphics.Rect;
+import android.view.View;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+/**
+ * Author by pq, Date on 2023/11/8.
+ */
+public class CenterItemDecoration extends RecyclerView.ItemDecoration{
+    private int width;
+    public CenterItemDecoration(int width) {
+        this.width= width;
+    }
+
+    @Override
+    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
+        int itemPosition = parent.getChildAdapterPosition(view);
+        int offset = (parent.getWidth()) / 2;
+        int itemCount = parent.getAdapter().getItemCount();
+        if (itemPosition == 0) {
+            outRect.left = offset;
+        }else if (itemPosition == itemCount-1){
+            outRect.right = offset;
+        }else{
+            outRect.left = 0;
+            outRect.right = 0;
+        }
+    }
+}

+ 57 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/widget/CustomScrollView.java

@@ -0,0 +1,57 @@
+package com.cooleshow.musicmerge.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ScrollView;
+
+/**
+ * Author by pq, Date on 2024/8/5.
+ */
+public class CustomScrollView extends ScrollView {
+
+    private OnScrollBottomListener _listener;
+    private int _calCount;
+    public CustomScrollView(Context context) {
+        super(context);
+    }
+
+    public CustomScrollView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public CustomScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+        View view = this.getChildAt(0);
+        if (this.getHeight() + this.getScrollY() == view.getHeight()) {
+            _calCount++;
+            if (_calCount == 1) {
+                if (_listener != null) {
+                    _listener.scrollToBottom(true);
+                }
+            }
+        } else {
+            _calCount = 0;
+            if (_listener != null) {
+                _listener.scrollToBottom(false);
+            }
+        }
+    }
+
+
+    public interface OnScrollBottomListener {
+        void scrollToBottom(boolean isBottom);
+    }
+
+    public void registerOnScrollViewScrollToBottom(OnScrollBottomListener l) {
+        _listener = l;
+    }
+
+    public void unRegisterOnScrollViewScrollToBottom() {
+        _listener = null;
+    }
+}

+ 54 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/widget/MusicScoreJsInterface.java

@@ -0,0 +1,54 @@
+package com.cooleshow.musicmerge.widget;
+
+import android.text.TextUtils;
+import android.webkit.JavascriptInterface;
+
+
+import com.cooleshow.base.utils.LOG;
+import com.cooleshow.base.utils.ThreadUtils;
+import com.cooleshow.base.utils.Utils;
+import com.cooleshow.musicmerge.constants.MHWebApi;
+
+import org.json.JSONObject;
+
+/**
+ * Author by pq, Date on 2024/7/25.
+ */
+public class MusicScoreJsInterface extends Object {
+    @JavascriptInterface
+    public void postMessage(String message) {
+        LOG.i("mhH5Msg:" + message.toString());
+        ThreadUtils.getMainHandler().post(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    JSONObject jsonObject = new JSONObject(message);
+                    String api = jsonObject.getString("api");
+                    if (TextUtils.equals(api, MHWebApi.API_MUSICPAGE)) {
+                        //页面渲染完成
+                        JSONObject contentJson = jsonObject.optJSONObject(MHWebApi.CONTENT_KEY);
+                        double height = contentJson.optDouble("height",0);
+                        if (onListener != null) {
+                            onListener.onPageLoadCompleted(height);
+                        }
+                        return;
+                    }
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        });
+    }
+
+    public onGetMethodsListener onListener;
+
+    public void setOnItemClickListener(onGetMethodsListener onListener) {
+        this.onListener = onListener;
+    }
+
+    public interface onGetMethodsListener {
+
+        void onPageLoadCompleted(double height);
+
+    }
+}

+ 13 - 4
BaseLibrary/src/main/java/com/cooleshow/base/widgets/dialog/ShareDialog.java → musicMerge/src/main/java/com/cooleshow/musicmerge/widget/ShareDialog.java

@@ -1,4 +1,4 @@
-package com.cooleshow.base.widgets.dialog;
+package com.cooleshow.musicmerge.widget;
 
 import android.content.Context;
 import android.os.Bundle;
@@ -6,9 +6,10 @@ import android.view.View;
 import android.view.Window;
 import android.view.WindowManager;
 
-import com.cooleshow.base.R;
 import com.cooleshow.base.constanst.ShareType;
 import com.cooleshow.base.utils.UiUtils;
+import com.cooleshow.base.widgets.dialog.BaseFullDialog;
+import com.cooleshow.musicmerge.R;
 
 import androidx.annotation.NonNull;
 
@@ -20,7 +21,7 @@ public class ShareDialog extends BaseFullDialog implements View.OnClickListener
 
 
     public ShareDialog(@NonNull Context context) {
-        super(context, R.style.DialogStyle);
+        super(context, com.cooleshow.base.R.style.DialogStyle);
     }
 
     @Override
@@ -64,7 +65,13 @@ public class ShareDialog extends BaseFullDialog implements View.OnClickListener
             return;
         }
         int id = v.getId();
-        if (id == R.id.tv_cancel || id == R.id.iv_close) {
+        if(id == R.id.tv_cancel){
+            if (mEventListener != null) {
+                mEventListener.goLookDetail();
+            }
+            return;
+        }
+        if (id == R.id.iv_close) {
             dismiss();
             return;
         }
@@ -96,5 +103,7 @@ public class ShareDialog extends BaseFullDialog implements View.OnClickListener
 
     public interface OnEventListener {
         void toShare(ShareType shareType);
+
+        void goLookDetail();
     }
 }

+ 27 - 0
musicMerge/src/main/java/com/cooleshow/musicmerge/widget/UploadCoverTipDialog.java

@@ -5,6 +5,8 @@ import android.text.Editable;
 import android.text.TextUtils;
 import android.text.TextWatcher;
 import android.view.View;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
 import android.widget.EditText;
 import android.widget.ImageView;
 import android.widget.TextView;
@@ -59,6 +61,9 @@ public class UploadCoverTipDialog extends BaseDialog implements View.OnClickList
     private TextView mTvByAlbum;
     private TextView mTvByCrop;
     private ImageView mIvVideoCover;
+    private CheckBox mCbLook;
+    private CustomScrollView mScrollView;
+    private ImageView mIvShadow;
 
     private boolean isVideoFile = false;
 
@@ -80,6 +85,9 @@ public class UploadCoverTipDialog extends BaseDialog implements View.OnClickList
         mTvByAlbum = holder.getView(R.id.tv_by_album);
         mTvByCrop = holder.getView(R.id.tv_by_crop);
         mIvVideoCover = holder.getView(R.id.iv_video_cover);
+        mCbLook = holder.getView(R.id.cb_look);
+        mScrollView = holder.getView(R.id.scrollView);
+        mIvShadow = holder.getView(R.id.iv_shadow);
 
         String userName = UserHelper.getUserName();
         mTvName.setText(userName);
@@ -123,9 +131,28 @@ public class UploadCoverTipDialog extends BaseDialog implements View.OnClickList
         mIvIcon.setOnClickListener(this);
         mTvByAlbum.setOnClickListener(this);
         mTvByCrop.setOnClickListener(this);
+        mCbLook.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                if (mViewModel != null) {
+                    //选中不让看 未选中可以看到
+                    mViewModel.refreshMusicWorksSelectFlag(!isChecked);
+                }
+            }
+        });
 
 
         mEtContent.addTextChangedListener(mTextWatcher);
+        mScrollView.registerOnScrollViewScrollToBottom(new CustomScrollView.OnScrollBottomListener() {
+            @Override
+            public void scrollToBottom(boolean isBottom) {
+                if(isBottom){
+                    mIvShadow.setVisibility(View.GONE);
+                }else{
+                    mIvShadow.setVisibility(View.VISIBLE);
+                }
+            }
+        });
     }
 
     public void setData(String cover, String musicName,String videoCover) {

BIN
musicMerge/src/main/res/drawable-xhdpi/mh_icon_shadow_cover.png


BIN
musicMerge/src/main/res/drawable-xxhdpi/icon_upload_video_cover.png


BIN
musicMerge/src/main/res/drawable-xxhdpi/mh_icon_shadow_cover.png


+ 7 - 0
musicMerge/src/main/res/drawable/shape_809ec8_to_3a65a2_15dp.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <gradient android:startColor="#9680B1C8"
+        android:endColor="#963A98A2"
+        android:angle="270"/>
+    <corners android:radius="15dp"/>
+</shape>

+ 4 - 0
musicMerge/src/main/res/drawable/shape_mh_music_score_bg.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="#b3ffffff"/>
+</shape>

+ 16 - 7
musicMerge/src/main/res/drawable/shape_volume_progreesbar_thumb.xml

@@ -1,8 +1,17 @@
 <?xml version="1.0" encoding="utf-8"?>
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
-    <solid android:color="@color/main_style_color" />
-    <size
-        android:width="18dp"
-        android:height="18dp" />
-</shape>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:bottom="6dp"
+        android:right="6dp"
+        android:top="6dp"
+        android:left="6dp"
+        android:width="12dp"
+        android:height="12dp">
+        <shape android:shape="oval">
+            <solid android:color="@color/main_style_color" />
+            <size
+                android:width="12dp"
+                android:height="12dp" />
+        </shape>
+    </item>
+</layer-list>

+ 49 - 2
musicMerge/src/main/res/layout/ac_music_handle_layout.xml

@@ -3,8 +3,26 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:background="@drawable/bg_musicmerge">
+    android:layout_height="match_parent">
+
+    <ImageView
+        app:layout_constraintLeft_toLeftOf="parent"
+        android:scaleType="centerCrop"
+        android:src="@drawable/bg_musicmerge"
+        android:layout_width="0dp"
+        app:layout_constraintRight_toRightOf="@+id/view_bg_right_point"
+        android:layout_height="match_parent"/>
+
+
+
+    <View
+        android:visibility="visible"
+        android:layout_marginEnd="280dp"
+        android:id="@+id/view_bg_right_point"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        android:layout_width="1px"
+        android:layout_height="1px"/>
 
     <View
         android:id="@+id/view_video_bg"
@@ -190,6 +208,20 @@
         android:layout_width="0dp"
         android:layout_height="80dp"/>
 
+    <FrameLayout
+        tools:visibility="visible"
+        android:id="@+id/fl_music_score_container"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="3dp"
+        android:visibility="gone"
+        android:paddingStart="25dp"
+        android:paddingEnd="25dp"
+        app:layout_constraintBottom_toTopOf="@+id/tv_current_progress"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toLeftOf="@+id/fl_setting" />
+
+
     <ImageView
         android:id="@+id/iv_play"
         android:layout_width="42dp"
@@ -318,6 +350,21 @@
         tools:text="悬崖上的金鱼姬" />
 
     <TextView
+        android:id="@+id/tv_title2"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="4dp"
+        android:ellipsize="end"
+        android:includeFontPadding="false"
+        android:maxWidth="150dp"
+        android:maxLines="1"
+        android:textColor="@color/color_333333"
+        android:textSize="@dimen/sp_12"
+        app:layout_constraintLeft_toLeftOf="@+id/tv_title"
+        app:layout_constraintTop_toBottomOf="@+id/tv_title"
+        tools:text="演奏:张子珊张子珊张子珊张子珊" />
+
+    <TextView
         android:visibility="gone"
         android:id="@+id/tv_toast_view"
         android:paddingEnd="26dp"

+ 16 - 11
BaseLibrary/src/main/res/layout/dialog_share_layout.xml → musicMerge/src/main/res/layout/dialog_share_layout.xml

@@ -4,17 +4,18 @@
     android:layout_width="300dp"
     android:layout_gravity="center"
     android:layout_height="wrap_content"
-    android:background="@drawable/shape_10dp_white">
+    android:paddingBottom="18dp"
+    android:background="@drawable/bg_white_12dp">
 
     <TextView
         android:id="@+id/tv_title"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:paddingStart="22dp"
-        android:paddingTop="15dp"
-        android:text="作品发布成功!快来分享吧!"
-        android:textColor="@color/color_333333"
-        android:textSize="@dimen/sp_16"
+        android:paddingTop="23dp"
+        android:text="作品发布成功,快来分享吧!"
+        android:textColor="@color/color_777777"
+        android:textSize="@dimen/sp_15"
+        app:layout_constraintRight_toRightOf="parent"
         app:layout_constraintLeft_toLeftOf="parent"
         app:layout_constraintTop_toTopOf="parent" />
 
@@ -103,6 +104,7 @@
         app:layout_constraintTop_toBottomOf="@+id/iv_copy_link" />
 
     <View
+        android:visibility="gone"
         android:id="@+id/view_line"
         android:layout_width="match_parent"
         android:layout_height="1dp"
@@ -113,13 +115,16 @@
         app:layout_constraintTop_toBottomOf="@+id/tv_wx_title" />
 
     <TextView
+        android:background="@drawable/shape_2dc7aa_18dp"
         android:id="@+id/tv_cancel"
-        android:layout_width="match_parent"
-        android:layout_height="57dp"
+        android:layout_width="144dp"
+        android:layout_height="36dp"
         android:gravity="center"
-        android:text="@string/cancel"
-        android:textColor="@color/color_aaaaaa"
-        android:textSize="@dimen/sp_16"
+        android:text="查看作品"
+        android:layout_marginTop="18dp"
+        android:textColor="@color/white"
+        android:textSize="@dimen/sp_15"
+        app:layout_constraintRight_toRightOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintLeft_toLeftOf="parent"
         app:layout_constraintTop_toBottomOf="@+id/view_line" />

+ 77 - 38
musicMerge/src/main/res/layout/dialog_upload_cover_tip_layout.xml

@@ -36,10 +36,11 @@
                 android:layout_height="wrap_content"
                 android:padding="10dp"
                 android:src="@drawable/icon_close_dialog"
+                app:layout_constraintBottom_toBottomOf="@+id/tv_title"
                 app:layout_constraintRight_toRightOf="parent"
-                app:layout_constraintTop_toTopOf="parent" />
+                app:layout_constraintTop_toTopOf="@+id/tv_title" />
 
-            <ScrollView
+            <com.cooleshow.musicmerge.widget.CustomScrollView
                 android:fillViewport="true"
                 android:id="@+id/scrollView"
                 app:layout_constraintTop_toBottomOf="@+id/tv_title"
@@ -47,21 +48,24 @@
                 android:layout_height="275dp">
 
                 <androidx.constraintlayout.widget.ConstraintLayout
-                    android:paddingBottom="18dp"
+                    android:paddingBottom="54dp"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content">
 
-                    <ImageView
+                    <com.cooleshow.base.widgets.QMUIRadiusImageView
                         android:visibility="visible"
                         android:layout_marginStart="12dp"
                         android:layout_marginEnd="12dp"
+                        android:layout_marginTop="10dp"
+                        app:layout_constraintRight_toRightOf="parent"
                         app:layout_constraintLeft_toLeftOf="parent"
-                        app:layout_constraintTop_toTopOf="parent"
+                        app:layout_constraintTop_toBottomOf="@+id/view_info_bg"
                         android:id="@+id/iv_video_cover"
-                        android:adjustViewBounds="true"
-                        android:src="@drawable/icon_upload_video_cover_default"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"/>
+                        app:qmui_corner_radius="12dp"
+                        app:layout_constraintDimensionRatio="16:9"
+                        android:src="@drawable/icon_upload_video_cover"
+                        android:layout_width="0dp"
+                        android:layout_height="0dp"/>
 
                     <View
                         android:id="@+id/view_line_helper"
@@ -72,11 +76,11 @@
 
                     <View
                         android:id="@+id/view_select_cover_bg"
+                        android:layout_marginBottom="14dp"
                         app:layout_constraintRight_toRightOf="@+id/iv_video_cover"
                         app:layout_constraintLeft_toLeftOf="@+id/iv_video_cover"
-                        app:layout_constraintBottom_toBottomOf="@+id/view_line_helper"
-                        app:layout_constraintTop_toTopOf="@+id/view_line_helper"
-                        android:background="@drawable/shape_80b1c8_to_3a98a2_15dp"
+                        app:layout_constraintBottom_toBottomOf="@+id/iv_video_cover"
+                        android:background="@drawable/shape_809ec8_to_3a65a2_15dp"
                         android:layout_width="248dp"
                         android:layout_height="30dp"/>
 
@@ -123,23 +127,21 @@
                         android:layout_height="0dp"/>
 
                     <androidx.constraintlayout.widget.Group
-                        android:visibility="gone"
+                        android:visibility="visible"
                         android:id="@+id/group_video_cover"
                         app:constraint_referenced_ids="tv_by_crop,tv_by_album,view_center,view_select_cover_bg,view_line_helper,iv_video_cover"
                         android:layout_width="wrap_content"
                         android:layout_height="wrap_content"/>
 
-
                     <FrameLayout
-                        app:layout_goneMarginTop="0dp"
-                        android:layout_marginTop="26dp"
+                        android:layout_marginTop="10dp"
                         app:layout_constraintTop_toBottomOf="@+id/iv_video_cover"
                         android:id="@+id/fl_input"
                         android:layout_width="match_parent"
-                        android:layout_height="87dp"
+                        android:layout_height="97dp"
                         android:layout_marginStart="12dp"
                         android:layout_marginEnd="12dp"
-                        android:background="@drawable/bg_white_10dp"
+                        android:background="@drawable/shape_10dp_white"
                         app:layout_constraintLeft_toLeftOf="parent"
                         app:layout_constraintRight_toRightOf="parent">
 
@@ -159,7 +161,6 @@
                             android:textColor="@color/black_333"
                             android:textColorHint="@color/color_aaaaaa"
                             android:textSize="@dimen/sp_14"
-                            android:theme="@style/MyEditText"
                             app:layout_constraintLeft_toLeftOf="parent"
                             app:layout_constraintTop_toTopOf="parent" />
 
@@ -180,17 +181,43 @@
                     </FrameLayout>
 
 
+                    <FrameLayout
+                        android:visibility="gone"
+                        android:id="@+id/fl_check"
+                        android:layout_width="0dp"
+                        android:layout_height="48dp"
+                        android:layout_marginTop="12dp"
+                        android:background="@drawable/bg_white_10dp"
+                        app:layout_constraintLeft_toLeftOf="@+id/fl_input"
+                        app:layout_constraintRight_toRightOf="@+id/fl_input"
+                        app:layout_constraintTop_toBottomOf="@+id/fl_input">
+
+                        <CheckBox
+                            android:id="@+id/cb_look"
+                            android:layout_width="match_parent"
+                            android:layout_height="match_parent"
+                            android:layout_marginStart="12dp"
+                            android:layout_marginEnd="12dp"
+                            android:background="@color/transparent"
+                            android:button="@drawable/common_check_selector2"
+                            android:ellipsize="end"
+                            android:maxLines="1"
+                            android:paddingStart="6dp"
+                            android:text="发布时不让别人看到我的评级和分数"
+                            android:textColor="@color/color_777777"
+                            android:textSize="@dimen/sp_14" />
+                    </FrameLayout>
+
                     <View
                         android:id="@+id/view_info_bg"
                         android:layout_width="match_parent"
                         android:layout_height="86dp"
                         android:layout_marginStart="12dp"
-                        android:layout_marginTop="12dp"
                         android:layout_marginEnd="12dp"
-                        android:background="@drawable/bg_white_10dp"
+                        android:background="@drawable/shape_10dp_white"
                         app:layout_constraintLeft_toLeftOf="parent"
                         app:layout_constraintRight_toRightOf="parent"
-                        app:layout_constraintTop_toBottomOf="@+id/fl_input" />
+                        app:layout_constraintTop_toTopOf="parent" />
 
                     <View
                         android:layout_width="61dp"
@@ -239,6 +266,7 @@
                         android:textColor="@color/color_131415"
                         android:textSize="@dimen/sp_16"
                         android:textStyle="bold"
+                        app:layout_constraintVertical_chainStyle="packed"
                         app:layout_constraintBottom_toTopOf="@+id/tv_name"
                         app:layout_constraintLeft_toRightOf="@+id/iv_icon"
                         app:layout_constraintRight_toRightOf="@+id/view_info_bg"
@@ -249,10 +277,10 @@
                         android:id="@+id/tv_name"
                         android:layout_width="0dp"
                         android:layout_height="wrap_content"
-                        android:layout_marginTop="4dp"
                         android:layout_marginEnd="12dp"
                         android:drawablePadding="5dp"
                         android:ellipsize="end"
+                        android:layout_marginTop="4dp"
                         android:includeFontPadding="false"
                         android:maxLines="1"
                         android:textColor="@color/color_777777"
@@ -262,23 +290,34 @@
                         app:layout_constraintRight_toRightOf="@+id/view_info_bg"
                         app:layout_constraintTop_toBottomOf="@+id/tv_music_title"
                         tools:text="张子珊" />
-
-                    <TextView
-                        android:id="@+id/tv_publish"
-                        android:layout_width="220dp"
-                        android:layout_height="38dp"
-                        android:layout_marginTop="18dp"
-                        android:background="@drawable/shape_login_bt_bg"
-                        android:gravity="center"
-                        android:text="发布作品"
-                        android:textColor="@color/white"
-                        android:textStyle="bold"
-                        app:layout_constraintLeft_toLeftOf="parent"
-                        app:layout_constraintRight_toRightOf="parent"
-                        app:layout_constraintTop_toBottomOf="@+id/view_info_bg" />
                 </androidx.constraintlayout.widget.ConstraintLayout>
-            </ScrollView>
 
+            </com.cooleshow.musicmerge.widget.CustomScrollView>
+
+
+            <ImageView
+                android:id="@+id/iv_shadow"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:adjustViewBounds="true"
+                android:src="@drawable/mh_icon_shadow_cover"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintLeft_toLeftOf="parent"
+                app:layout_constraintRight_toRightOf="parent" />
+
+            <TextView
+                android:id="@+id/tv_publish"
+                android:layout_width="144dp"
+                android:layout_height="36dp"
+                android:layout_marginBottom="7dp"
+                android:background="@drawable/shape_2dc7aa_18dp"
+                android:gravity="center"
+                android:text="发布作品"
+                android:textColor="@color/white"
+                android:textStyle="bold"
+                app:layout_constraintLeft_toLeftOf="parent"
+                app:layout_constraintRight_toRightOf="parent"
+                app:layout_constraintBottom_toBottomOf="parent" />
         </androidx.constraintlayout.widget.ConstraintLayout>
     </FrameLayout>
 </LinearLayout>

+ 136 - 42
musicMerge/src/main/res/layout/fg_music_handle_setting_layout.xml

@@ -22,6 +22,7 @@
         app:layout_constraintTop_toTopOf="parent" />
 
     <View
+        android:id="@+id/view_line_top"
         android:background="@color/color_f2f2f2"
         app:layout_constraintLeft_toLeftOf="parent"
         android:layout_marginTop="13dp"
@@ -42,32 +43,32 @@
         app:layout_constraintLeft_toLeftOf="parent"
         app:layout_constraintTop_toBottomOf="@+id/tv_title"/>
 
-        <View
-            android:layout_marginStart="9dp"
-            android:layout_marginEnd="9dp"
-            android:id="@+id/view_line"
-            app:layout_constraintRight_toRightOf="@+id/seek_volume"
-            app:layout_constraintLeft_toLeftOf="@+id/seek_volume"
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintHorizontal_bias="1"
-            android:layout_width="1px"
-            android:layout_height="1px"/>
-
-        <TextView
-            android:visibility="gone"
-            android:id="@+id/tv_record_volume_value"
-            android:textStyle="bold"
-            android:textColor="@color/color_131415"
-            android:gravity="center_horizontal"
-            android:layout_marginBottom="5dp"
-            android:paddingTop="3dp"
-            app:layout_constraintBottom_toTopOf="@+id/seek_volume"
-            android:background="@drawable/icon_seekbar_value_bg"
-            app:layout_constraintRight_toRightOf="@+id/view_line"
-            app:layout_constraintLeft_toLeftOf="@+id/view_line"
-            android:text="100"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"/>
+    <View
+        android:layout_marginStart="9dp"
+        android:layout_marginEnd="9dp"
+        android:id="@+id/view_line"
+        app:layout_constraintRight_toRightOf="@+id/seek_volume"
+        app:layout_constraintLeft_toLeftOf="@+id/seek_volume"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintHorizontal_bias="1"
+        android:layout_width="1px"
+        android:layout_height="1px"/>
+
+    <TextView
+        android:visibility="gone"
+        android:id="@+id/tv_record_volume_value"
+        android:textStyle="bold"
+        android:textColor="@color/color_131415"
+        android:gravity="center_horizontal"
+        android:layout_marginBottom="5dp"
+        android:paddingTop="3dp"
+        app:layout_constraintBottom_toTopOf="@+id/seek_volume"
+        android:background="@drawable/icon_seekbar_value_bg"
+        app:layout_constraintRight_toRightOf="@+id/view_line"
+        app:layout_constraintLeft_toLeftOf="@+id/view_line"
+        android:text="100"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
 
 
     <androidx.appcompat.widget.AppCompatSeekBar
@@ -76,7 +77,7 @@
         android:id="@+id/seek_volume"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
-        android:layout_marginTop="20dp"
+        android:layout_marginTop="8dp"
         android:max="100"
         android:background="@null"
         android:maxHeight="4dp"
@@ -95,7 +96,7 @@
         android:id="@+id/tv_title3"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginTop="23dp"
+        android:layout_marginTop="21dp"
         android:text="伴奏音量"
         android:includeFontPadding="false"
         android:textColor="@color/color_131415"
@@ -111,7 +112,7 @@
         android:id="@+id/seek_volume2"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
-        android:layout_marginTop="20dp"
+        android:layout_marginTop="8dp"
         android:max="100"
         android:background="@null"
         android:maxHeight="4dp"
@@ -156,7 +157,7 @@
         android:id="@+id/tv_title4"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginTop="20dp"
+        android:layout_marginTop="21dp"
         android:text="演奏对齐"
         android:textColor="@color/color_131415"
         android:textSize="@dimen/sp_14"
@@ -231,25 +232,25 @@
 
     <TextView
         android:id="@+id/tv_offset_tip"
-        app:layout_constraintRight_toRightOf="@+id/iv_reduce"
-        app:layout_constraintLeft_toLeftOf="@+id/iv_reduce"
-        app:layout_constraintTop_toTopOf="@+id/tv_offset_result"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="演奏延后"
         android:textColor="@color/color_8f8f8f"
         android:textSize="@dimen/sp_11"
-        android:text="演奏延后"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"/>
+        app:layout_constraintLeft_toLeftOf="@+id/iv_reduce"
+        app:layout_constraintRight_toRightOf="@+id/iv_reduce"
+        app:layout_constraintTop_toTopOf="@+id/tv_offset_result" />
 
     <TextView
         android:id="@+id/tv_offset_tip2"
-        app:layout_constraintRight_toRightOf="@+id/iv_add"
-        app:layout_constraintLeft_toLeftOf="@+id/iv_add"
-        app:layout_constraintTop_toTopOf="@+id/tv_offset_result"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="演奏提前"
         android:textColor="@color/color_8f8f8f"
         android:textSize="@dimen/sp_11"
-        android:text="演奏提前"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"/>
+        app:layout_constraintLeft_toLeftOf="@+id/iv_add"
+        app:layout_constraintRight_toRightOf="@+id/iv_add"
+        app:layout_constraintTop_toTopOf="@+id/tv_offset_result" />
 
     <TextView
         android:layout_marginStart="29dp"
@@ -321,4 +322,97 @@
         android:src="@drawable/icon_shrink_setting_arrow"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"/>
+
+    <View
+        android:id="@+id/view_guide1"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginStart="20dp"
+        android:layout_marginEnd="10dp"
+        android:layout_marginBottom="10dp"
+        android:visibility="gone"
+        app:layout_constraintBottom_toTopOf="@+id/tv_title4"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/view_line_top" />
+
+    <View
+        android:id="@+id/view_guide2"
+        android:layout_width="70dp"
+        android:layout_height="70dp"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="@+id/tv_offset_tip"
+        app:layout_constraintLeft_toLeftOf="@+id/iv_reduce"
+        app:layout_constraintRight_toRightOf="@+id/iv_reduce"
+        app:layout_constraintTop_toTopOf="@+id/iv_reduce" />
+
+
+    <View
+        android:id="@+id/view_guide3"
+        android:layout_width="70dp"
+        android:layout_height="70dp"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="@+id/tv_offset_tip2"
+        app:layout_constraintLeft_toLeftOf="@+id/iv_add"
+        app:layout_constraintRight_toRightOf="@+id/iv_add"
+        app:layout_constraintTop_toTopOf="@+id/iv_add" />
+
+
+    <View
+        android:id="@+id/view_guide4"
+        android:layout_width="0dp"
+        android:layout_height="70dp"
+        android:layout_marginStart="5dp"
+        android:layout_marginEnd="5dp"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="@+id/tv_offset_tip2"
+        app:layout_constraintLeft_toRightOf="@+id/iv_reduce"
+        app:layout_constraintRight_toLeftOf="@+id/iv_add"
+        app:layout_constraintTop_toTopOf="@+id/iv_add" />
+
+    <View
+        android:id="@+id/view_guide5"
+        android:layout_width="50dp"
+        android:layout_height="50dp"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="@+id/tv_record"
+        app:layout_constraintLeft_toLeftOf="@+id/tv_record"
+        app:layout_constraintRight_toRightOf="@+id/tv_record"
+        app:layout_constraintTop_toTopOf="@+id/tv_record" />
+
+    <View
+        android:id="@+id/view_helper_line1"
+        android:layout_width="1px"
+        android:layout_height="1px"
+        android:layout_marginStart="10dp"
+        app:layout_constraintLeft_toRightOf="@+id/tv_save_works"
+        app:layout_constraintTop_toTopOf="@+id/tv_save_works" />
+
+    <View
+        android:id="@+id/view_helper_line2"
+        android:layout_width="1px"
+        android:layout_height="1px"
+        android:layout_marginEnd="10dp"
+        app:layout_constraintRight_toLeftOf="@+id/tv_save_works"
+        app:layout_constraintTop_toTopOf="@+id/tv_save_works" />
+
+    <View
+        android:id="@+id/view_guide6"
+        android:layout_width="0dp"
+        android:layout_height="50dp"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="@+id/tv_save_works"
+        app:layout_constraintLeft_toLeftOf="@+id/view_helper_line2"
+        app:layout_constraintRight_toRightOf="@+id/view_helper_line1"
+        app:layout_constraintTop_toTopOf="@+id/tv_save_works" />
+
+    <View
+        android:id="@+id/view_guide7"
+        android:layout_width="50dp"
+        android:layout_height="50dp"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="@+id/tv_save"
+        app:layout_constraintLeft_toLeftOf="@+id/tv_save"
+        app:layout_constraintRight_toRightOf="@+id/tv_save"
+        app:layout_constraintTop_toTopOf="@+id/tv_save" />
 </androidx.constraintlayout.widget.ConstraintLayout>

+ 10 - 0
musicMerge/src/main/res/layout/mh_fg_music_score_fragment.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <FrameLayout
+        android:id="@+id/fl_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+</FrameLayout>

+ 1 - 0
musicMerge/src/main/res/values/strings.xml

@@ -8,4 +8,5 @@
     <string name="loading_str">加载中</string>
     <string name="share_works_title">我在酷乐秀发布了演奏作品</string>
     <string name="publish_success">发布成功</string>
+    <string name="mh_loading_str">资源加载中,请稍等…</string>
 </resources>

+ 1 - 0
settings.gradle

@@ -86,3 +86,4 @@ include ':institution'
 include ':musicMerge'
 //include ':accompany'
 include ':accompanySDK'
+include ':guide'

+ 21 - 0
usercenter/src/main/java/com/cooleshow/usercenter/helper/UserHelper.java

@@ -4,6 +4,7 @@ import android.text.TextUtils;
 
 import com.alibaba.android.arouter.launcher.ARouter;
 import com.cooleshow.base.common.BaseConstant;
+import com.cooleshow.base.common.CommonConfig;
 import com.cooleshow.base.data.net.CommonParamsHelper;
 import com.cooleshow.base.router.RouterPath;
 import com.cooleshow.base.utils.DateUtil;
@@ -345,4 +346,24 @@ public class UserHelper {
         String tenantId = getTenantId();
         return isTenantAccount(tenantId);
     }
+
+    public static boolean getHasShowGuideCompleted(String key) {
+        return SPUtils.getInstance().getBoolean(key, false);
+    }
+
+    public static void setHasShowGuideCompleted(String key) {
+        SPUtils.getInstance().put(key, true);
+    }
+
+    public static void updateHasShowGuideCompleted(String key, boolean value) {
+        SPUtils.getInstance().put(key, value);
+    }
+
+    /**
+     * 清除引导页缓存
+     */
+    public static void resetGuide() {
+        updateHasShowGuideCompleted(CommonConfig.APP_MY_WORKS_GUIDE_TAG, false);
+        CommonConfig.resetGuide();
+    }
 }