├── app ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── values │ │ │ ├── strings.xml │ │ │ ├── colors.xml │ │ │ └── styles.xml │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── img_showcase.png │ │ └── layout │ │ │ ├── showcase_content.xml │ │ │ ├── activity_main.xml │ │ │ └── activity_second.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── cw │ │ └── showcaseview │ │ ├── MainActivity.java │ │ └── SecondActivity.java ├── build.gradle └── proguard-rules.pro ├── showcase ├── .gitignore ├── src │ └── main │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── cw │ │ └── showcase │ │ └── showcaseview │ │ ├── shape │ │ ├── IShape.java │ │ ├── CircleShape.java │ │ ├── RectangleShape.java │ │ └── OvalShape.java │ │ ├── animation │ │ ├── IAnimationFactory.java │ │ └── AlphaAnimationFactory.java │ │ ├── queue │ │ └── ShowcaseQueue.java │ │ ├── target │ │ └── ViewTarget.java │ │ └── ShowcaseView.java ├── build.gradle └── proguard-rules.pro ├── settings.gradle ├── image ├── image1.jpg ├── image2.jpg ├── image3.jpg ├── image4.jpg └── image5.jpg ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── gradle.properties ├── gradlew.bat ├── README.md └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /showcase/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':showcase' 2 | -------------------------------------------------------------------------------- /image/image1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdzawdh/ShowcaseView/HEAD/image/image1.jpg -------------------------------------------------------------------------------- /image/image2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdzawdh/ShowcaseView/HEAD/image/image2.jpg -------------------------------------------------------------------------------- /image/image3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdzawdh/ShowcaseView/HEAD/image/image3.jpg -------------------------------------------------------------------------------- /image/image4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdzawdh/ShowcaseView/HEAD/image/image4.jpg -------------------------------------------------------------------------------- /image/image5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdzawdh/ShowcaseView/HEAD/image/image5.jpg -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ShowcaseView 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdzawdh/ShowcaseView/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /showcase/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | showcase 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdzawdh/ShowcaseView/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/img_showcase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdzawdh/ShowcaseView/HEAD/app/src/main/res/mipmap-xhdpi/img_showcase.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Oct 22 23:52:10 CST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/layout/showcase_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /showcase/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /showcase/src/main/java/com/cw/showcase/showcaseview/shape/IShape.java: -------------------------------------------------------------------------------- 1 | package com.cw.showcase.showcaseview.shape; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Paint; 5 | 6 | import com.cw.showcase.showcaseview.target.ViewTarget; 7 | 8 | 9 | public interface IShape { 10 | 11 | void draw(Canvas canvas, Paint paint, ViewTarget target, int padding); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /showcase/src/main/java/com/cw/showcase/showcaseview/animation/IAnimationFactory.java: -------------------------------------------------------------------------------- 1 | package com.cw.showcase.showcaseview.animation; 2 | 3 | import android.view.View; 4 | 5 | 6 | public interface IAnimationFactory { 7 | 8 | void fadeInView(View target, long duration, AnimationStartListener listener); 9 | 10 | void fadeOutView(View target, long duration, AnimationEndListener listener); 11 | 12 | interface AnimationStartListener { 13 | void onAnimationStart(); 14 | } 15 | 16 | interface AnimationEndListener { 17 | void onAnimationEnd(); 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | 4 | android { 5 | compileSdkVersion 28 6 | defaultConfig { 7 | minSdkVersion 23 8 | targetSdkVersion 28 9 | versionCode 1 10 | versionName "1.0" 11 | } 12 | buildTypes { 13 | release { 14 | minifyEnabled false 15 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 16 | } 17 | } 18 | } 19 | 20 | dependencies { 21 | implementation fileTree(dir: 'libs', include: ['*.jar']) 22 | implementation "androidx.appcompat:appcompat:1.1.0" 23 | implementation project(path: ':showcase') 24 | } 25 | -------------------------------------------------------------------------------- /showcase/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.github.dcendents.android-maven' 3 | 4 | group='com.github.wdzawdh' 5 | 6 | android { 7 | compileSdkVersion 28 8 | defaultConfig { 9 | minSdkVersion 23 10 | targetSdkVersion 28 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | implementation fileTree(dir: 'libs', include: ['*.jar']) 25 | implementation "androidx.appcompat:appcompat:1.1.0" 26 | } 27 | -------------------------------------------------------------------------------- /showcase/src/main/java/com/cw/showcase/showcaseview/shape/CircleShape.java: -------------------------------------------------------------------------------- 1 | package com.cw.showcase.showcaseview.shape; 2 | 3 | 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | 7 | import com.cw.showcase.showcaseview.target.ViewTarget; 8 | 9 | 10 | public class CircleShape implements IShape { 11 | 12 | @Override 13 | public void draw(Canvas canvas, Paint paint, ViewTarget target, int padding) { 14 | if (canvas != null && paint != null && target != null) { 15 | int radius = Math.max(target.getBounds().width(), target.getBounds().height()) / 2; 16 | canvas.drawCircle(target.getPoint().x, target.getPoint().y, radius + padding, paint); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/Cw/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /showcase/src/main/java/com/cw/showcase/showcaseview/shape/RectangleShape.java: -------------------------------------------------------------------------------- 1 | package com.cw.showcase.showcaseview.shape; 2 | 3 | 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | 7 | import com.cw.showcase.showcaseview.target.ViewTarget; 8 | 9 | 10 | public class RectangleShape implements IShape { 11 | 12 | @Override 13 | public void draw(Canvas canvas, Paint paint, ViewTarget target, int padding) { 14 | canvas.drawRect( 15 | target.getBounds().left - padding, 16 | target.getBounds().top - padding, 17 | target.getBounds().right + padding, 18 | target.getBounds().bottom + padding, 19 | paint 20 | ); 21 | } 22 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /showcase/src/main/java/com/cw/showcase/showcaseview/shape/OvalShape.java: -------------------------------------------------------------------------------- 1 | /** 2 | *   @function:$ 3 | *   @description: $ 4 | *   @param:$ 5 | *   @return:$ 6 | *   @history: 7 | * 1.date:$ $ 8 | *           author:$ 9 | *           modification: 10 | */ 11 | 12 | package com.cw.showcase.showcaseview.shape; 13 | 14 | import android.graphics.Canvas; 15 | import android.graphics.Paint; 16 | import android.graphics.RectF; 17 | 18 | import com.cw.showcase.showcaseview.target.ViewTarget; 19 | 20 | 21 | /** 22 | * @author Cw 23 | * @date 16/7/15 24 | */ 25 | public class OvalShape implements IShape { 26 | 27 | @Override 28 | public void draw(Canvas canvas, Paint paint, ViewTarget target, int padding) { 29 | RectF rectF = new RectF(target.getBounds().left - padding 30 | , target.getBounds().top - padding 31 | , target.getBounds().right + padding 32 | , target.getBounds().bottom + padding); 33 | canvas.drawOval(rectF, paint); 34 | } 35 | } -------------------------------------------------------------------------------- /showcase/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/cw/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_second.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 22 | 23 | 29 | 30 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/cw/showcaseview/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.cw.showcaseview; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | 7 | import com.cw.showcase.showcaseview.ShowcaseView; 8 | 9 | import androidx.appcompat.app.AppCompatActivity; 10 | 11 | 12 | public class MainActivity extends AppCompatActivity { 13 | 14 | private View mTextView; 15 | 16 | @Override 17 | protected void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | setContentView(R.layout.activity_main); 20 | 21 | mTextView = findViewById(R.id.tv_text); 22 | show(); 23 | 24 | } 25 | 26 | public void show() { 27 | new ShowcaseView.Builder(this) 28 | .addTarget(mTextView) 29 | .addImage(R.mipmap.img_showcase, 5.0f, 5.0f, 1.0f, true) 30 | .addShowcaseListener(new ShowcaseView.ShowcaseListener() { 31 | @Override 32 | public void onDisplay(ShowcaseView showcaseView) { 33 | } 34 | 35 | @Override 36 | public void onDismiss(ShowcaseView showcaseView) { 37 | Intent intent = new Intent(MainActivity.this, SecondActivity.class); 38 | startActivity(intent); 39 | } 40 | }) 41 | .build().show(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /showcase/src/main/java/com/cw/showcase/showcaseview/queue/ShowcaseQueue.java: -------------------------------------------------------------------------------- 1 | package com.cw.showcase.showcaseview.queue; 2 | 3 | 4 | import com.cw.showcase.showcaseview.ShowcaseView; 5 | 6 | import java.util.LinkedList; 7 | 8 | /** 9 | * @author Cw 10 | * @date 2017/7/26 11 | */ 12 | public class ShowcaseQueue { 13 | 14 | private static ShowcaseQueue sQueue; 15 | 16 | private LinkedList mList = new LinkedList<>(); 17 | 18 | public static ShowcaseQueue getInstance() { 19 | if (sQueue == null) { 20 | synchronized (ShowcaseQueue.class) { 21 | if (sQueue == null) { 22 | sQueue = new ShowcaseQueue(); 23 | } 24 | } 25 | } 26 | return sQueue; 27 | } 28 | 29 | public boolean add(ShowcaseView showcaseView) { 30 | if (showcaseView == null) { 31 | throw new IllegalArgumentException("showcaseView == null"); 32 | } 33 | return mList.add(showcaseView); 34 | } 35 | 36 | public int getSize() { 37 | return mList.size(); 38 | } 39 | 40 | public synchronized void showQueue() { 41 | if (mList.size() == 0) { 42 | return; 43 | } 44 | ShowcaseView showcaseView = mList.getFirst(); 45 | mList.remove(showcaseView); 46 | showcaseView.addQueueListener(new ShowcaseView.QueueListener() { 47 | @Override 48 | public void onDismiss() { 49 | showQueue(); 50 | } 51 | }); 52 | showcaseView.show(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /showcase/src/main/java/com/cw/showcase/showcaseview/target/ViewTarget.java: -------------------------------------------------------------------------------- 1 | package com.cw.showcase.showcaseview.target; 2 | 3 | import android.graphics.Point; 4 | import android.graphics.Rect; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | 9 | public class ViewTarget { 10 | 11 | private View mView; 12 | private ViewGroup.LayoutParams mOriginalLayoutParams; 13 | private int mOriginalIndex = -1; 14 | 15 | public ViewTarget(View view) { 16 | this(view, -1); 17 | } 18 | 19 | public ViewTarget(View view, int index) { 20 | mView = view; 21 | mOriginalLayoutParams = view.getLayoutParams(); 22 | mOriginalIndex = index; 23 | } 24 | 25 | public View getView() { 26 | return mView; 27 | } 28 | 29 | public ViewGroup.LayoutParams getOriginalLayoutParams() { 30 | return mOriginalLayoutParams; 31 | } 32 | 33 | public int getOriginalIndex() { 34 | return mOriginalIndex; 35 | } 36 | 37 | public Point getPoint() { 38 | int[] location = new int[2]; 39 | mView.getLocationInWindow(location); 40 | int x = location[0] + mView.getWidth() / 2; 41 | int y = location[1] + mView.getHeight() / 2; 42 | return new Point(x, y); 43 | } 44 | 45 | public Rect getBounds() { 46 | int[] location = new int[2]; 47 | mView.getLocationInWindow(location); 48 | return new Rect( 49 | location[0], 50 | location[1], 51 | location[0] + mView.getMeasuredWidth(), 52 | location[1] + mView.getMeasuredHeight() 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /showcase/src/main/java/com/cw/showcase/showcaseview/animation/AlphaAnimationFactory.java: -------------------------------------------------------------------------------- 1 | package com.cw.showcase.showcaseview.animation; 2 | 3 | import android.animation.Animator; 4 | import android.animation.ObjectAnimator; 5 | import android.view.View; 6 | 7 | 8 | public class AlphaAnimationFactory implements IAnimationFactory { 9 | 10 | private static final String ALPHA = "alpha"; 11 | private static final float INVISIBLE = 0f; 12 | private static final float VISIBLE = 1f; 13 | 14 | @Override 15 | public void fadeInView(View target, long duration, final AnimationStartListener listener) { 16 | if (duration == -1) { 17 | listener.onAnimationStart(); 18 | return; 19 | } 20 | ObjectAnimator oa = ObjectAnimator.ofFloat(target, ALPHA, INVISIBLE, VISIBLE); 21 | oa.setDuration(duration).addListener(new Animator.AnimatorListener() { 22 | @Override 23 | public void onAnimationStart(Animator animator) { 24 | listener.onAnimationStart(); 25 | } 26 | 27 | @Override 28 | public void onAnimationEnd(Animator animator) { 29 | } 30 | 31 | @Override 32 | public void onAnimationCancel(Animator animator) { 33 | } 34 | 35 | @Override 36 | public void onAnimationRepeat(Animator animator) { 37 | } 38 | }); 39 | oa.start(); 40 | } 41 | 42 | @Override 43 | public void fadeOutView(View target, long duration, final AnimationEndListener listener) { 44 | if (duration == -1) { 45 | listener.onAnimationEnd(); 46 | return; 47 | } 48 | ObjectAnimator oa = ObjectAnimator.ofFloat(target, ALPHA, INVISIBLE); 49 | oa.setDuration(duration).addListener(new Animator.AnimatorListener() { 50 | @Override 51 | public void onAnimationStart(Animator animator) { 52 | } 53 | 54 | @Override 55 | public void onAnimationEnd(Animator animator) { 56 | listener.onAnimationEnd(); 57 | } 58 | 59 | @Override 60 | public void onAnimationCancel(Animator animator) { 61 | } 62 | 63 | @Override 64 | public void onAnimationRepeat(Animator animator) { 65 | } 66 | }); 67 | oa.start(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /app/src/main/java/com/cw/showcaseview/SecondActivity.java: -------------------------------------------------------------------------------- 1 | package com.cw.showcaseview; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | import android.widget.Toast; 6 | 7 | import com.cw.showcase.showcaseview.ShowcaseView; 8 | 9 | import androidx.annotation.Nullable; 10 | import androidx.appcompat.app.AppCompatActivity; 11 | 12 | 13 | /** 14 | * @author Cw 15 | * @date 2017/7/26 16 | */ 17 | public class SecondActivity extends AppCompatActivity { 18 | 19 | private View mTextView; 20 | private View mTextView2; 21 | private View mTextView3; 22 | private View mImageView; 23 | 24 | @Override 25 | protected void onCreate(@Nullable Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | setContentView(R.layout.activity_second); 28 | mTextView = findViewById(R.id.tv_text); 29 | mTextView2 = findViewById(R.id.tv_text2); 30 | mTextView3 = findViewById(R.id.tv_text3); 31 | mImageView = findViewById(R.id.iv_img1); 32 | show(); 33 | } 34 | 35 | public void show() { 36 | new ShowcaseView.Builder(this) 37 | .setMaskColor("#88EECC33") 38 | .setDismissOnTouch(true) 39 | .setDuration(300L, 300L) 40 | .setTargetPadding(20) 41 | .addTarget(mTextView, ShowcaseView.CIRCLE_SHAPE) 42 | .addShowcaseListener(new ShowcaseView.ShowcaseListener() { 43 | @Override 44 | public void onDisplay(ShowcaseView showcaseView) { 45 | Toast.makeText(getApplication(), "第一个展示啦", Toast.LENGTH_SHORT).show(); 46 | } 47 | 48 | @Override 49 | public void onDismiss(ShowcaseView showcaseView) { 50 | Toast.makeText(getApplication(), "第一个消失啦", Toast.LENGTH_SHORT).show(); 51 | } 52 | }) 53 | .addShowcaseQueue() 54 | .setMaskColor("#66FFB6C1") 55 | .addTarget(mTextView2, ShowcaseView.OVAL_SHAPE) 56 | .addShowcaseListener(new ShowcaseView.ShowcaseListener() { 57 | @Override 58 | public void onDisplay(ShowcaseView showcaseView) { 59 | Toast.makeText(getApplication(), "第二个展示啦", Toast.LENGTH_SHORT).show(); 60 | } 61 | 62 | @Override 63 | public void onDismiss(ShowcaseView showcaseView) { 64 | Toast.makeText(getApplication(), "第二个消失啦", Toast.LENGTH_SHORT).show(); 65 | } 66 | }) 67 | .addShowcaseQueue() 68 | .setMaskColor("#D8BFD8") 69 | .addTarget(mTextView3, ShowcaseView.RECTANGLE_SHAPE) 70 | .addImage(R.mipmap.img_showcase, 5.0f, 8.0f, 1.0f, true) 71 | .addShowcaseListener(new ShowcaseView.ShowcaseListener() { 72 | @Override 73 | public void onDisplay(ShowcaseView showcaseView) { 74 | Toast.makeText(getApplication(), "最后一个展示啦", Toast.LENGTH_SHORT).show(); 75 | } 76 | 77 | @Override 78 | public void onDismiss(ShowcaseView showcaseView) { 79 | Toast.makeText(getApplication(), "最后一个消失啦", Toast.LENGTH_SHORT).show(); 80 | } 81 | }) 82 | .addShowcaseQueue() 83 | //浮动下层View 84 | .addFloatView(mImageView) 85 | .addShowcaseQueue() 86 | .build().showQueue(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##  **Android蒙板控件(用户引导)** 2 | 3 | ### **效果图** 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | ### **添加依赖** 12 | root build.gradle 13 | ``` 14 | allprojects { 15 | repositories { 16 | ... 17 | maven { url 'https://jitpack.io' } 18 | } 19 | } 20 | ``` 21 | app build.gradle 22 | ``` 23 | dependencies { 24 | compile 'com.github.wdzawdh:ShowcaseView:v1.1' 25 | } 26 | ``` 27 | 28 | ### **使用方法** 29 | ### **栗子一** 30 | > 最简单的使用方式 `(图1)`
31 | 32 | >方法解释:(set系列的方法都会有默认值) 33 | 1.setOnlyOneTag 用来标识只显示一次 34 | 2.setMaskColor 遮罩的颜色 35 | 3.setDismissOnTouch 是否触摸任意地方消失 36 | 4.setDuration 显示和消失的事件 37 | 5.setTargetPadding 能控制透明块的大小 38 | 6.addTarget 透明块要在哪个view上展示 39 | 7.addImage 添加图片,五个参数分别是图片资源、x轴的位置(0-10.0f)、y轴的位置(0-10.0f)、图片的缩放比例(当为1.0f时图片宽为屏幕的一半,高度会等比例缩放)、点击图片蒙版是否消失。 40 | 8.addShowcaseListener 监听显示和关闭的事件 41 | ``` 42 | new ShowcaseView.Builder(this) 43 | .setOnlyOneTag(MainActivity.class.getSimpleName()) 44 | .setMaskColor("#88EECC33") 45 | .setDismissOnTouch(true) 46 | .setDuration(1000L, 1000L) 47 | .setTargetPadding(20) 48 | .addTarget(mTextView) 49 | .addImage(R.mipmap.img_showcase, 5.0f, 5.0f, 1.5f, true) 50 | .addShowcaseListener(new ShowcaseView.ShowcaseListener() { 51 | @Override 52 | public void onDisplay(ShowcaseView showcaseView) { 53 | } 54 | 55 | @Override 56 | public void onDismiss(ShowcaseView showcaseView) { 57 | Intent intent = new Intent( 58 | MainActivity.this, SecondActivity.class); 59 | startActivity(intent); 60 | } 61 | }) 62 | .build().show(); 63 | ``` 64 | ### **栗子二** 65 | > `图2 图3 图4` 是添加到队列再显示的方式(showQueue),也就是说当第一个蒙版消失后会接着显示第二个...直到把队列中的蒙版都显示完。addShowcaseQueue方法用来添加到队列,最后通过showQueue方法来依次显示。 66 | 67 | > 特别注意:set系列的属性会在第一个addShowcaseQueue调用后保留下来,直到调用build为止,比如遮罩颜色等。 68 | ``` 69 | new ShowcaseView.Builder(this) 70 | .setOnlyOneTag(MainActivity.class.getSimpleName()) 71 | .setMaskColor("#88EECC33") 72 | .setDismissOnTouch(true) 73 | .setDuration(1000L, 1000L) 74 | .setTargetPadding(20) 75 | .addTarget(mTextView, ShowcaseView.CIRCLE_SHAPE) 76 | .addShowcaseQueue() 77 | .setMaskColor("#66FFB6C1") 78 | .addTarget(mTextView2, ShowcaseView.OVAL_SHAPE) 79 | .addShowcaseQueue() 80 | .setMaskColor("#D8BFD8") 81 | .setDismissOnTouch(false) 82 | .addTarget(mTextView3, ShowcaseView.RECTANGLE_SHAPE) 83 | .addImage(R.mipmap.img_showcase, 5.0f, 8.0f, 1.0f, true) 84 | .addShowcaseListener(new ShowcaseView.ShowcaseListener() { 85 | @Override 86 | public void onDisplay(ShowcaseView showcaseView) { 87 | Toast.makeText(getApplication(), 88 | "最后一个展示啦",Toast.LENGTH_SHORT).show(); 89 | } 90 | 91 | @Override 92 | public void onDismiss(ShowcaseView showcaseView) { 93 | Toast.makeText(getApplication(), 94 | "最后一个消失啦",Toast.LENGTH_SHORT).show(); 95 | } 96 | }) 97 | .addShowcaseQueue() 98 | .build().showQueue(); 99 | ``` 100 | 101 | ### **PS: 关于屏幕适配** 102 | > 为了在不同分辨率的屏幕上显示图片的效果相似,在addImage的时候会默认缩放将图片宽缩放为为屏幕的一半,高度跟随宽等比例缩放,使用时通过第四个参数调整大小,通过第二三个参数调整位置。图片x轴和y轴的位置是按权重调整的,范围是0-10.0f,已图片中心为坐标,(0,0)在最左上方,(10,10)在最右下方。建议先调整大小再调整位置。 103 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /showcase/src/main/java/com/cw/showcase/showcaseview/ShowcaseView.java: -------------------------------------------------------------------------------- 1 | package com.cw.showcase.showcaseview; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.SharedPreferences; 6 | import android.graphics.Bitmap; 7 | import android.graphics.BitmapFactory; 8 | import android.graphics.Canvas; 9 | import android.graphics.Color; 10 | import android.graphics.Paint; 11 | import android.graphics.Point; 12 | import android.graphics.PorterDuff; 13 | import android.graphics.PorterDuffXfermode; 14 | import android.graphics.Rect; 15 | import android.util.AttributeSet; 16 | import android.view.MotionEvent; 17 | import android.view.View; 18 | import android.view.ViewGroup; 19 | import android.view.WindowManager; 20 | import android.view.animation.Animation; 21 | import android.widget.AbsoluteLayout; 22 | import android.widget.FrameLayout; 23 | import android.widget.ImageView; 24 | 25 | import com.cw.showcase.showcaseview.animation.AlphaAnimationFactory; 26 | import com.cw.showcase.showcaseview.animation.IAnimationFactory; 27 | import com.cw.showcase.showcaseview.queue.ShowcaseQueue; 28 | import com.cw.showcase.showcaseview.shape.CircleShape; 29 | import com.cw.showcase.showcaseview.shape.IShape; 30 | import com.cw.showcase.showcaseview.shape.OvalShape; 31 | import com.cw.showcase.showcaseview.shape.RectangleShape; 32 | import com.cw.showcase.showcaseview.target.ViewTarget; 33 | 34 | import java.util.HashMap; 35 | import java.util.Map; 36 | 37 | import androidx.annotation.AttrRes; 38 | import androidx.annotation.NonNull; 39 | import androidx.annotation.Nullable; 40 | 41 | 42 | /** 43 | * @author Cw 44 | * @date 2017/7/24 45 | */ 46 | public class ShowcaseView extends FrameLayout implements View.OnClickListener { 47 | 48 | public static final String PREFERENCE_NAME = ShowcaseView.class.getSimpleName(); 49 | 50 | public static final int CIRCLE_SHAPE = 0; 51 | public static final int RECTANGLE_SHAPE = 1; 52 | public static final int OVAL_SHAPE = 2; 53 | 54 | public static int sShowIndex = 0;//展示的个数 55 | 56 | private String mMaskColor = "#BB000000";//蒙版的背景颜色 57 | private boolean mDismissOnTouch;//是否触摸任意地方消失 58 | private int mTargetPadding;//透明块的内边距 59 | private long mShowDuration = -1;//show的渐显时间 60 | private long mMissDuration = -1;//miss的渐隐时间 61 | private long mDismissDuration;//设置自动消失时间 62 | private String mOnlyOneTag;//只展示一次的标示 63 | 64 | private Activity mActivity; 65 | private Bitmap mBitmap; 66 | private Paint mPaint; 67 | private Canvas mCanvas; 68 | private ViewGroup mDecorView; 69 | private AbsoluteLayout mContentView; 70 | private Map mTargets = new HashMap<>(); 71 | private Map mOriginals = new HashMap<>(); 72 | private AlphaAnimationFactory mAnimationFactory = new AlphaAnimationFactory(); 73 | 74 | public ShowcaseView(@NonNull Activity act) { 75 | this(act, null); 76 | } 77 | 78 | public ShowcaseView(@NonNull Activity act, @Nullable AttributeSet attrs) { 79 | this(act, attrs, 0); 80 | } 81 | 82 | public ShowcaseView(@NonNull Activity act, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { 83 | super(act, attrs, defStyleAttr); 84 | init(act); 85 | } 86 | 87 | private void init(Activity act) { 88 | this.mActivity = act; 89 | //ViewGroup重写onDraw,需要调用setWillNotDraw(false) 90 | setWillNotDraw(false); 91 | setOnClickListener(this); 92 | mContentView = new AbsoluteLayout(act); 93 | this.addView(mContentView); 94 | mDecorView = (ViewGroup) mActivity.getWindow().getDecorView(); 95 | } 96 | 97 | @Override 98 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 99 | super.onSizeChanged(w, h, oldw, oldh); 100 | if (mBitmap == null) { 101 | mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); 102 | } 103 | if (mCanvas == null) { 104 | mCanvas = new Canvas(mBitmap); 105 | } 106 | if (mPaint == null) { 107 | mPaint = new Paint(); 108 | mPaint.setColor(Color.TRANSPARENT); 109 | //将canvas置为透明的重要方法 110 | mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); 111 | mPaint.setFlags(Paint.ANTI_ALIAS_FLAG); 112 | } 113 | } 114 | 115 | @Override 116 | protected void onDraw(Canvas canvas) { 117 | super.onDraw(canvas); 118 | mCanvas.setBitmap(mBitmap); 119 | mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); 120 | mCanvas.drawColor(Color.parseColor(mMaskColor)); 121 | 122 | for (Map.Entry entry : mTargets.entrySet()) { 123 | ViewTarget target = entry.getKey(); 124 | IShape shape = entry.getValue(); 125 | shape.draw(mCanvas, mPaint, target, mTargetPadding); 126 | } 127 | canvas.drawBitmap(mBitmap, 0, 0, null); 128 | } 129 | 130 | @Override 131 | public void onClick(View v) { 132 | if (mDismissOnTouch) { 133 | removeFromWindow(); 134 | } 135 | } 136 | 137 | 138 | //---------------------------------------Builder------------------------------------------------ 139 | 140 | 141 | /** 142 | * Builder创建类,对showcaseView创建并进行一些配置 143 | */ 144 | public static class Builder { 145 | 146 | private ShowcaseView showcaseView; 147 | 148 | public Builder(Activity activity) { 149 | showcaseView = new ShowcaseView(activity); 150 | } 151 | 152 | /** 153 | * 设置蒙版颜色 154 | */ 155 | public Builder setMaskColor(String color) { 156 | showcaseView.setMaskColor(color); 157 | return this; 158 | } 159 | 160 | /** 161 | * 设置渐显时间 162 | * 163 | * @param showDur show的渐显时间 (-1 没有动画) 164 | * @param missDur miss的渐隐时间 165 | */ 166 | public Builder setDuration(long showDur, long missDur) { 167 | showcaseView.setDuration(showDur, missDur); 168 | return this; 169 | } 170 | 171 | /** 172 | * 设置自动消失时间 173 | * 174 | * @param missDur 自动消失时间时间 175 | */ 176 | public Builder setDismissDuration(long missDur) { 177 | showcaseView.setDismissDuration(missDur); 178 | return this; 179 | } 180 | 181 | /** 182 | * 触摸任意地方消失 183 | */ 184 | public Builder setDismissOnTouch(boolean dismiss) { 185 | showcaseView.setDismissOnTouch(dismiss); 186 | return this; 187 | } 188 | 189 | /** 190 | * 设置透明块的内边距 dp 191 | */ 192 | public Builder setTargetPadding(int padding) { 193 | showcaseView.setTargetPadding(padding); 194 | return this; 195 | } 196 | 197 | /** 198 | * 设置点击蒙版消失的View 199 | */ 200 | public Builder setDismissView(View view) { 201 | showcaseView.setDismissView(view); 202 | return this; 203 | } 204 | 205 | /** 206 | * 设置只展示一次的标示 207 | */ 208 | public Builder setOnlyOneTag(String tag) { 209 | showcaseView.setOnlyOneTag(tag); 210 | return this; 211 | } 212 | 213 | /** 214 | * 设置透明块的样式 215 | *

216 | * 默认 CIRCLE_SHAPE 圆形 217 | */ 218 | public Builder addTarget(View view) { 219 | showcaseView.addTarget(view); 220 | return this; 221 | } 222 | 223 | /** 224 | * 设置透明块的样式 225 | * 226 | * @param view view 227 | * @param shapeMode CIRCLE_SHAPE 圆形 RECTANGLE_SHAPE 矩形 OVAL_SHAPE 椭圆 228 | */ 229 | public Builder addTarget(View view, int shapeMode) { 230 | showcaseView.addTarget(view, shapeMode); 231 | return this; 232 | } 233 | 234 | /** 235 | * 增加展示的图片 236 | * 237 | * @param resId resId 238 | * @param xWeight x坐标-权重(总共10.0f,例如5.0f就在屏幕中间) 239 | * @param yWeight y坐标-权重(总共10.0f) 240 | * @param scale 缩放比例 241 | * @param miss 是否点击蒙版消失 242 | */ 243 | public Builder addImage(int resId, float xWeight, float yWeight, float scale, boolean miss) { 244 | showcaseView.addImage(resId, xWeight, yWeight, scale, miss); 245 | return this; 246 | } 247 | 248 | /** 249 | * 增加展示的图片 250 | * 251 | * @param resId resId 252 | * @param xWeight x坐标-权重(总共10.0f,例如5.0f就在屏幕中间) 253 | * @param yWeight y坐标-权重(总共10.0f) 254 | * @param scale 缩放比例 255 | * @param miss 是否点击蒙版消失 256 | * @param animation 动画 257 | */ 258 | public Builder addImage(int resId, float xWeight, float yWeight, float scale, boolean miss, Animation animation) { 259 | showcaseView.addImage(resId, xWeight, yWeight, scale, miss, animation); 260 | return this; 261 | } 262 | 263 | /** 264 | * 增加展示的View 265 | * 266 | * @param view view 267 | * @param width view width 268 | * @param height view height 269 | * @param xWeight x坐标-权重(总共10.0f) 270 | * @param yWeight y坐标-权重(总共10.0f) 271 | */ 272 | public Builder addShowView(View view, int width, int height, float xWeight, float yWeight) { 273 | showcaseView.addShowView(view, width, height, xWeight, yWeight); 274 | return this; 275 | } 276 | 277 | /** 278 | * 将下层View浮动到蒙层上来 279 | * 280 | * @param view view 281 | */ 282 | public Builder addFloatView(View view) { 283 | showcaseView.addFloatView(view); 284 | return this; 285 | } 286 | 287 | /** 288 | * 监听show和dismiss的事件 289 | */ 290 | public Builder addShowcaseListener(ShowcaseListener listener) { 291 | showcaseView.addShowcaseListener(listener); 292 | return this; 293 | } 294 | 295 | /** 296 | * 添加到展示队列 297 | */ 298 | public Builder addShowcaseQueue() { 299 | if (!showcaseView.hasTag()) { 300 | showcaseView.addShowQueue(); 301 | } 302 | String maskColor = showcaseView.mMaskColor; 303 | boolean dismissOnTouch = showcaseView.mDismissOnTouch; 304 | int targetPadding = showcaseView.mTargetPadding; 305 | long showDuration = showcaseView.mShowDuration; 306 | long missDuration = showcaseView.mMissDuration; 307 | long dismissDuration = showcaseView.mDismissDuration; 308 | String onlyOneTag = showcaseView.mOnlyOneTag; 309 | //重建ShowcaseView,保留set系列的属性 310 | showcaseView = new ShowcaseView(showcaseView.mActivity); 311 | showcaseView.mMaskColor = maskColor; 312 | showcaseView.mDismissOnTouch = dismissOnTouch; 313 | showcaseView.mTargetPadding = targetPadding; 314 | showcaseView.mShowDuration = showDuration; 315 | showcaseView.mMissDuration = missDuration; 316 | showcaseView.mDismissDuration = dismissDuration; 317 | showcaseView.mOnlyOneTag = onlyOneTag; 318 | return this; 319 | } 320 | 321 | public ShowcaseView build() { 322 | return showcaseView; 323 | } 324 | } 325 | 326 | 327 | //---------------------------------------Method------------------------------------------------- 328 | 329 | 330 | public static boolean hasShow() { 331 | return sShowIndex != 0; 332 | } 333 | 334 | public static boolean hasTag(Context context, String tag) { 335 | SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); 336 | return settings.getBoolean(tag, false); 337 | } 338 | 339 | /** 340 | * 显示ShowcaseView 341 | */ 342 | public void show() { 343 | if (hasTag()) { 344 | return; 345 | } 346 | mAnimationFactory.fadeInView(this, mShowDuration, new IAnimationFactory.AnimationStartListener() { 347 | @Override 348 | public void onAnimationStart() { 349 | mDecorView.post(new Runnable() { 350 | @Override 351 | public void run() { 352 | sShowIndex++; 353 | //浮动View 354 | for (Map.Entry entry : mOriginals.entrySet()) { 355 | ViewGroup parent = entry.getKey(); 356 | ViewTarget viewTarget = entry.getValue(); 357 | Rect bounds = viewTarget.getBounds(); 358 | Point point = viewTarget.getPoint(); 359 | parent.removeView(viewTarget.getView()); 360 | addShowView(viewTarget.getView(), bounds.width(), bounds.height(), point.x, point.y); 361 | } 362 | mDecorView.addView(ShowcaseView.this); 363 | if (mListener != null) { 364 | mListener.onDisplay(ShowcaseView.this); 365 | } 366 | //自动消失 367 | if (mDismissDuration > 0) { 368 | postDelayed(new Runnable() { 369 | @Override 370 | public void run() { 371 | removeFromWindow(); 372 | } 373 | }, mDismissDuration); 374 | } 375 | } 376 | }); 377 | } 378 | }); 379 | } 380 | 381 | /** 382 | * 将ShowcaseView从Window移除 383 | */ 384 | public void removeFromWindow() { 385 | mAnimationFactory.fadeOutView(this, mMissDuration, new IAnimationFactory.AnimationEndListener() { 386 | 387 | @Override 388 | public void onAnimationEnd() { 389 | sShowIndex--; 390 | //队列为空时putTag(要在QueueListener前调用) 391 | if (ShowcaseQueue.getInstance().getSize() == 0) { 392 | putTag(); 393 | } 394 | 395 | mDecorView.post(new Runnable() { 396 | @Override 397 | public void run() { 398 | mContentView.removeAllViews(); 399 | //移除ShowcaseView 400 | mDecorView.removeView(ShowcaseView.this); 401 | //将浮动的View还给原始父布局 402 | for (Map.Entry entry : mOriginals.entrySet()) { 403 | ViewTarget viewTarget = entry.getValue(); 404 | entry.getKey().addView(viewTarget.getView(), viewTarget.getOriginalIndex(), viewTarget.getOriginalLayoutParams()); 405 | } 406 | if (mBitmap != null) { 407 | mBitmap.recycle(); 408 | mBitmap = null; 409 | } 410 | mTargets = null; 411 | mCanvas = null; 412 | mPaint = null; 413 | } 414 | }); 415 | 416 | if (mListener != null) { 417 | mListener.onDismiss(ShowcaseView.this); 418 | } 419 | if (mQueueListener != null) { 420 | mQueueListener.onDismiss(); 421 | } 422 | } 423 | }); 424 | } 425 | 426 | /** 427 | * 添加到显示队列 428 | */ 429 | public ShowcaseQueue addShowQueue() { 430 | ShowcaseQueue showcaseQueue = ShowcaseQueue.getInstance(); 431 | showcaseQueue.add(this); 432 | return showcaseQueue; 433 | } 434 | 435 | /** 436 | * 依次展示队列里的showcaseView 437 | */ 438 | public void showQueue() { 439 | ShowcaseQueue.getInstance().showQueue(); 440 | } 441 | 442 | /** 443 | * 设置蒙版颜色 444 | */ 445 | public void setMaskColor(String color) { 446 | mMaskColor = color; 447 | } 448 | 449 | /** 450 | * 设置渐显时间 451 | * 452 | * @param showDur show的渐显时间 (-1 没有动画) 453 | * @param missDur miss的渐隐时间 454 | */ 455 | public void setDuration(long showDur, long missDur) { 456 | mShowDuration = showDur; 457 | mMissDuration = missDur; 458 | } 459 | 460 | /** 461 | * 设置自动消失时间 462 | * 463 | * @param missDur 自动消失时间时间 464 | */ 465 | public void setDismissDuration(long missDur) { 466 | mDismissDuration = missDur; 467 | } 468 | 469 | /** 470 | * 触摸任意地方消失 471 | */ 472 | public void setDismissOnTouch(boolean dismiss) { 473 | mDismissOnTouch = dismiss; 474 | } 475 | 476 | /** 477 | * 设置透明块的内边距 dp 478 | */ 479 | public void setTargetPadding(int padding) { 480 | //dip转换px 481 | float scale = mActivity.getResources().getDisplayMetrics().density; 482 | mTargetPadding = (int) (padding * scale + 0.5f); 483 | } 484 | 485 | /** 486 | * 设置点击蒙版消失的View 487 | */ 488 | public void setDismissView(final View view) { 489 | view.setOnTouchListener(new OnTouchListener() { 490 | @Override 491 | public boolean onTouch(View v, MotionEvent event) { 492 | if (ShowcaseView.this.getVisibility() == View.VISIBLE && event.getAction() == MotionEvent.ACTION_DOWN) { 493 | postDelayed(new Runnable() { 494 | @Override 495 | public void run() { 496 | //延迟500ms保证View的onClick先执行 497 | removeFromWindow(); 498 | view.setOnTouchListener(null); 499 | } 500 | }, 500); 501 | } 502 | return false; 503 | } 504 | }); 505 | } 506 | 507 | /** 508 | * 设置只展示一次的标示 509 | */ 510 | public void setOnlyOneTag(String tag) { 511 | mOnlyOneTag = tag; 512 | } 513 | 514 | /** 515 | * 设置透明块的样式 516 | *

517 | * 默认 CIRCLE_SHAPE 圆形 518 | */ 519 | public void addTarget(View view) { 520 | addTarget(view, CIRCLE_SHAPE); 521 | } 522 | 523 | /** 524 | * 设置透明块的样式 525 | * 526 | * @param view view 527 | * @param shapeMode CIRCLE_SHAPE 圆形 RECTANGLE_SHAPE 矩形 OVAL_SHAPE 椭圆 528 | */ 529 | public void addTarget(View view, int shapeMode) { 530 | if (view == null || hasTag()) { 531 | return; 532 | } 533 | switch (shapeMode) { 534 | case CIRCLE_SHAPE: 535 | mTargets.put(new ViewTarget(view), new CircleShape()); 536 | break; 537 | case RECTANGLE_SHAPE: 538 | mTargets.put(new ViewTarget(view), new RectangleShape()); 539 | break; 540 | case OVAL_SHAPE: 541 | mTargets.put(new ViewTarget(view), new OvalShape()); 542 | break; 543 | default: 544 | mTargets.put(new ViewTarget(view), new CircleShape()); 545 | break; 546 | } 547 | } 548 | 549 | /** 550 | * 增加展示的图片 551 | * 552 | * @param resId resId 553 | * @param xWeight x坐标-权重(总共10.0f,例如5.0f就在屏幕中间) 554 | * @param yWeight y坐标-权重(总共10.0f) 555 | * @param scale 缩放比例 (1.0f时图片宽为屏幕的一半,高度等比例缩放) 556 | * @param miss 是否点击蒙版消失 557 | */ 558 | public void addImage(int resId, float xWeight, float yWeight, float scale, boolean miss) { 559 | addImage(resId, xWeight, yWeight, scale, miss, null); 560 | } 561 | 562 | /** 563 | * 增加展示的图片 564 | * 565 | * @param resId resId 566 | * @param xWeight x坐标-权重(总共10.0f,例如5.0f就在屏幕中间) 567 | * @param yWeight y坐标-权重(总共10.0f) 568 | * @param scale 缩放比例 (1.0f时图片宽为屏幕的一半,高度等比例缩放) 569 | * @param miss 是否点击蒙版消失 570 | * @param animation 动画 571 | */ 572 | public void addImage(int resId, float xWeight, float yWeight, float scale, boolean miss, Animation animation) { 573 | ImageView imageView = new ImageView(mActivity.getApplicationContext()); 574 | imageView.setImageResource(resId); 575 | if (miss) setDismissView(imageView); 576 | BitmapFactory.Options options = new BitmapFactory.Options(); 577 | options.inJustDecodeBounds = true; 578 | BitmapFactory.decodeResource(getResources(), resId, options); 579 | options.inJustDecodeBounds = false; 580 | float proportion = (float) options.outHeight / options.outWidth; 581 | WindowManager wm = mActivity.getWindowManager(); 582 | int windowWidth = wm.getDefaultDisplay().getWidth(); 583 | float width = windowWidth / 2 * scale; 584 | float height = width * proportion * scale; 585 | addShowView(imageView, (int) width, (int) height, xWeight, yWeight); 586 | if (animation != null) imageView.startAnimation(animation); 587 | } 588 | 589 | /** 590 | * 增加展示的View 591 | * 592 | * @param view view 593 | * @param width view width 594 | * @param height view height 595 | * @param xWeight x坐标-权重(总共10.0f) 596 | * @param yWeight y坐标-权重(总共10.0f) 597 | */ 598 | public void addShowView(View view, int width, int height, float xWeight, float yWeight) { 599 | WindowManager wm = mActivity.getWindowManager(); 600 | int windowWidth = wm.getDefaultDisplay().getWidth(); 601 | int windowHeight = wm.getDefaultDisplay().getHeight(); 602 | float x = (float) windowWidth / 10 * xWeight; 603 | float y = (float) windowHeight / 10 * yWeight; 604 | addShowView(view, width, height, (int) x, (int) y); 605 | } 606 | 607 | /** 608 | * 将下层View浮动上来 609 | * 610 | * @param view view 611 | */ 612 | public void addFloatView(final View view) { 613 | if (view == null || view.getParent() == null || hasTag()) { 614 | return; 615 | } 616 | ViewGroup parent = (ViewGroup) view.getParent(); 617 | int indexOfChild = parent.indexOfChild(view); 618 | ViewTarget viewTarget = new ViewTarget(view, indexOfChild); 619 | mOriginals.put(parent, viewTarget); 620 | } 621 | 622 | /** 623 | * 增加展示的View(不建议使用,px不利于屏幕适配) 624 | * 625 | * @param view view 626 | * @param width view width 627 | * @param height view height 628 | * @param x 展示位置的x坐标(px) 629 | * @param y 展示位置的y坐标(px) 630 | */ 631 | @Deprecated 632 | public void addShowView(View view, int width, int height, int x, int y) { 633 | if (view == null || hasTag()) { 634 | return; 635 | } 636 | AbsoluteLayout.LayoutParams layoutParams = new AbsoluteLayout.LayoutParams( 637 | width, height, x - width / 2, y - height / 2); 638 | mContentView.addView(view, layoutParams); 639 | } 640 | 641 | private boolean hasTag() { 642 | if (mOnlyOneTag == null) { 643 | return false; 644 | } 645 | SharedPreferences settings = getContext().getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); 646 | return settings.getBoolean(mOnlyOneTag, false); 647 | } 648 | 649 | private boolean putTag() { 650 | if (mOnlyOneTag == null) { 651 | return false; 652 | } 653 | SharedPreferences settings = getContext().getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); 654 | SharedPreferences.Editor editor = settings.edit(); 655 | editor.putBoolean(mOnlyOneTag, true); 656 | return editor.commit(); 657 | } 658 | 659 | 660 | //---------------------------------------Listener----------------------------------------------- 661 | 662 | 663 | /** 664 | * 监听show和dismiss的事件 665 | */ 666 | public void addShowcaseListener(ShowcaseListener listener) { 667 | mListener = listener; 668 | } 669 | 670 | private ShowcaseListener mListener; 671 | 672 | public interface ShowcaseListener { 673 | void onDisplay(ShowcaseView showcaseView); 674 | 675 | void onDismiss(ShowcaseView showcaseView); 676 | } 677 | 678 | /** 679 | * 不建议外部使用,只用于给ShowcaseQueue内部监听onDismiss。 680 | */ 681 | @Deprecated 682 | public void addQueueListener(QueueListener queueListener) { 683 | mQueueListener = queueListener; 684 | } 685 | 686 | private QueueListener mQueueListener; 687 | 688 | public interface QueueListener { 689 | void onDismiss(); 690 | } 691 | 692 | } 693 | --------------------------------------------------------------------------------