├── .gitignore ├── .idea ├── assetWizardSettings.xml ├── codeStyles │ └── Project.xml ├── dictionaries │ └── justin.xml ├── gradle.xml ├── misc.xml └── runConfigurations.xml ├── JscTestKeyStore.jks ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── jsc │ │ └── exam │ │ └── com │ │ └── guidance │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ │ └── jsc │ │ │ └── exam │ │ │ └── com │ │ │ └── guidance │ │ │ ├── BaseActivity.java │ │ │ ├── BaseAppCompatActivity.java │ │ │ ├── BaseEmptyFragmentActivity.java │ │ │ ├── EmptyFragmentActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── adapter │ │ │ ├── BaseRecyclerViewAdapter.java │ │ │ ├── BlankSpaceItemDecoration.java │ │ │ ├── ClassItemAdapter.java │ │ │ └── ViewAdapter.java │ │ │ ├── bean │ │ │ └── ClassItem.java │ │ │ ├── fragments │ │ │ ├── AboutFragment.java │ │ │ ├── BaseFragment.java │ │ │ ├── GuidanceDialogFragment.java │ │ │ ├── GuidanceLayoutFragment.java │ │ │ ├── GuidancePopupWindowFragment.java │ │ │ └── GuidanceRippleViewFragment.java │ │ │ ├── retrofit │ │ │ ├── ApiService.java │ │ │ ├── CustomHttpClient.java │ │ │ ├── CustomRetrofit.java │ │ │ └── LoadingDialogObserver.java │ │ │ ├── utils │ │ │ ├── CompatResourceUtils.java │ │ │ ├── ConnectivityHelper.java │ │ │ └── WindowUtils.java │ │ │ └── widgets │ │ │ ├── DotView.java │ │ │ ├── JSCItemLayout.java │ │ │ ├── SquareImageView.java │ │ │ └── dialog │ │ │ └── BottomShowDialog.java │ └── res │ │ ├── drawable-v21 │ │ └── ripple_round_corner_white_r4.xml │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable-xhdpi │ │ └── hand_o_up.png │ │ ├── drawable-xxhdpi │ │ ├── btn_background.png │ │ ├── ic_chevron_left_white_24dp.png │ │ ├── kit_ic_assignment_blue_24dp.png │ │ └── kit_ic_chevron_right_gray_24dp.png │ │ ├── drawable │ │ ├── guidance_demo_qr_code.png │ │ ├── ic_launcher_background.xml │ │ └── ripple_round_corner_white_r4.xml │ │ ├── layout │ │ ├── fragment_abount.xml │ │ ├── fragment_guidance_dialog.xml │ │ ├── fragment_guidance_layout.xml │ │ ├── fragment_guidance_popup_window.xml │ │ └── fragment_guidance_ripple_view.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── jsc │ └── exam │ └── com │ └── guidance │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── guidanceLibrary ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── jsc │ │ └── kit │ │ └── guidance │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── jsc │ │ │ └── kit │ │ │ └── guidance │ │ │ ├── GuidanceDialog.java │ │ │ ├── GuidanceLayout.java │ │ │ ├── GuidancePopupWindow.java │ │ │ ├── GuidanceRippleView.java │ │ │ ├── OnTargetClickListener.java │ │ │ ├── PermissionUtils.java │ │ │ └── ViewDrawingCacheUtils.java │ └── res │ │ └── values │ │ ├── guidance_attrs.xml │ │ ├── guidance_dimens.xml │ │ └── guidance_ids.xml │ └── test │ └── java │ └── jsc │ └── kit │ └── guidance │ └── ExampleUnitTest.java ├── output ├── GuidanceDemo.apk ├── output.json └── shots │ ├── guidance_layout.png │ ├── guidance_layout_s.png │ ├── guidance_ripple_view.png │ └── guidance_ripple_view_s.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches/build_file_checksums.ser 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | .DS_Store 9 | /build 10 | /captures 11 | .externalNativeBuild 12 | -------------------------------------------------------------------------------- /.idea/assetWizardSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 56 | 57 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/dictionaries/justin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 36 | 37 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 94 | 106 | 107 | 108 | 109 | 110 | 111 | 113 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /JscTestKeyStore.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustinRoom/GuidanceDemo/a595e8381ddaa8c727bab4714e3aaf0435846b8c/JscTestKeyStore.jks -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Guidance 2 | **LatestVersion** 3 | 4 | [ ![Download](https://api.bintray.com/packages/justinquote/maven/guidance-component/images/download.svg) ](https://bintray.com/justinquote/maven/guidance-component/_latestVersion) 5 | 6 | 7 | 8 | function guidance library and demo 9 | 10 | 11 | Scan QRCode to download demo application below: 12 | 13 | ![](/app/src/main/res/drawable/guidance_demo_qr_code.png) 14 | 15 | ### 1、implementation 16 | + 1.1、Gradle 17 | ``` 18 | implementation 'jsc.kit.guidance:guidance-component:_lastVersion' 19 | ``` 20 | + 1.2、Maven 21 | ``` 22 | 23 | jsc.kit.guidance 24 | guidance-component 25 | _lastVersion 26 | pom 27 | 28 | ``` 29 | 30 | ### 2、attrs 31 | + 2.1、[GuidanceRippleView](/guidanceLibrary/src/main/java/jsc/kit/guidance/GuidanceRippleView.java) 32 | 33 | | 名称 | 类型 | 描述 | 34 | |:---|:---|:---| 35 | |`grvCount`|integer|最大波纹圈数| 36 | |`grvSpace`|dimension|两个波纹圈之间距离间隔| 37 | |`grvSpeed`|integer|每秒波纹动画帧数, 默认是48帧。标准的电影动画帧数是24帧| 38 | |`grvColors`|string|每一个波纹圈的颜色,格式如:`{#008577, #D81B60, #00574B}`| 39 | |`grvAutoRun`|boolean|是否开启自动播放| 40 | 41 | + 2.2、[GuidanceLayout](/guidanceLibrary/src/main/java/jsc/kit/guidance/GuidanceLayout.java) 42 | 43 | | 子View | 类型 | 属性 | 44 | |:---|:---|:---| 45 | |`rippleViewView`|[GuidanceRippleView](/guidanceLibrary/src/main/java/jsc/kit/guidance/GuidanceRippleView.java)|[GuidanceRippleView](/guidanceLibrary/src/main/java/jsc/kit/guidance/GuidanceRippleView.java)所有属性| 46 | 47 | ### 3、usage 48 | + 3.1、[GuidanceRippleView](/guidanceLibrary/src/main/java/jsc/kit/guidance/GuidanceRippleView.java): 49 | ``` 50 | 58 | ``` 59 | 60 | + 3.2、[GuidanceLayout](/guidanceLibrary/src/main/java/jsc/kit/guidance/GuidanceLayout.java): 61 | ``` 62 | 70 | ``` 71 | 72 | + 3.3、[GuidanceDialog](/guidanceLibrary/src/main/java/jsc/kit/guidance/GuidanceDialog.java): 73 | ``` 74 | private void showGuidanceDialog() { 75 | final GuidanceDialog dialog = new GuidanceDialog(getContext()); 76 | dialog.setTargetClickListener(new OnTargetClickListener() { 77 | @Override 78 | public boolean onTargetClick(GuidanceLayout layout) { 79 | Toast.makeText(layout.getContext(), "clicked me", Toast.LENGTH_SHORT).show(); 80 | switch (layout.getCurStepIndex()) { 81 | case 0: 82 | showStep(layout, R.id.item_layout_1); 83 | return true; 84 | case 1: 85 | showStep(layout, R.id.item_layout_2); 86 | return true; 87 | case 2: 88 | showStep(layout, R.id.item_layout_3); 89 | return true; 90 | default: 91 | return false; 92 | } 93 | } 94 | }); 95 | dialog.show(); 96 | GuidanceLayout guidanceLayout = dialog.getGuidanceLayout(); 97 | if (guidanceLayout == null) 98 | return; 99 | showStep(guidanceLayout, R.id.item_layout_0); 100 | } 101 | ``` 102 | + 3.3、[GuidancePopupWindow](/guidanceLibrary/src/main/java/jsc/kit/guidance/GuidancePopupWindow.java): 103 | ``` 104 | private void showContentGuidance() { 105 | final GuidancePopupWindow popupWindow = new GuidancePopupWindow(getActivity()); 106 | popupWindow.setTargetClickListener(new OnTargetClickListener() { 107 | @Override 108 | public boolean onTargetClick(GuidanceLayout layout) { 109 | Toast.makeText(layout.getContext(), "clicked me", Toast.LENGTH_SHORT).show(); 110 | switch (layout.getCurStepIndex()) { 111 | case 0: 112 | layout.removeAllCustomViews(); 113 | showStep(layout, R.id.item_layout_1); 114 | return true; 115 | case 1: 116 | layout.removeAllCustomViews(); 117 | showStep(layout, R.id.item_layout_2); 118 | return true; 119 | case 2: 120 | layout.removeAllCustomViews(); 121 | showStep(layout, R.id.item_layout_3); 122 | return true; 123 | default: 124 | return false; 125 | } 126 | } 127 | }); 128 | popupWindow.show(); 129 | GuidanceLayout guidanceLayout = popupWindow.getGuidanceLayout(); 130 | showStep(guidanceLayout, R.id.item_layout_0); 131 | } 132 | ``` 133 | ``` 134 | private void showStep(GuidanceLayout layout, int targetViewId) { 135 | layout.removeAllCustomViews(); 136 | showStep(layout, getView().findViewById(targetViewId)); 137 | } 138 | 139 | private void showStep(GuidanceLayout guidanceLayout, View target) { 140 | Context context = guidanceLayout.getContext(); 141 | int statusBarHeight = ViewDrawingCacheUtils.getStatusBarHeight(context); 142 | int actionBarHeight = ViewDrawingCacheUtils.getActionBarSize(context); 143 | int[] location = ViewDrawingCacheUtils.getWindowLocation(target); 144 | guidanceLayout.updateTargetViewLocation( 145 | target, location[0], 146 | location[1] - statusBarHeight, 147 | new GuidanceLayout.OnInitRippleViewSizeListener() { 148 | @Override 149 | public int onInitializeRippleViewSize(@NonNull Bitmap bitmap) { 150 | return bitmap.getHeight(); 151 | } 152 | }, 153 | true, 154 | new GuidanceLayout.OnRippleViewLocationUpdatedCallback() { 155 | @Override 156 | public void onRippleViewLocationUpdated(@NonNull GuidanceRippleView rippleView, @NonNull Rect targetRect) { 157 | 158 | } 159 | }); 160 | 161 | ImageView imageView = new ImageView(guidanceLayout.getContext()); 162 | imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 163 | imageView.setImageResource(R.drawable.hand_o_up); 164 | guidanceLayout.addCustomView(imageView, new GuidanceLayout.OnCustomViewAddListener() { 165 | 166 | @Override 167 | public void onViewInit(@NonNull ImageView customView, @NonNull FrameLayout.LayoutParams params, @NonNull Rect targetRect) { 168 | customView.measure(0, 0); 169 | params.topMargin = targetRect.bottom + 12; 170 | params.leftMargin = targetRect.left - (customView.getMeasuredWidth() - targetRect.width()) / 2; 171 | } 172 | 173 | @Override 174 | public void onViewAdded(@NonNull ImageView customView, @NonNull Rect targetRect) { 175 | ObjectAnimator animator = ObjectAnimator.ofFloat(customView, View.TRANSLATION_Y, 0, 32, 0) 176 | .setDuration(1200); 177 | animator.setRepeatCount(-1); 178 | animator.start(); 179 | } 180 | }, null); 181 | } 182 | ``` 183 | 184 | | 组件 | 使用示例 | 185 | |:---|:---| 186 | |[GuidanceRippleView](/guidanceLibrary/src/main/java/jsc/kit/guidance/GuidanceRippleView.java)|[GuidanceRippleViewFragment](/app/src/main/java/jsc/exam/com/guidance/fragments/GuidanceRippleViewFragment.java)| 187 | |[GuidanceLayout](/guidanceLibrary/src/main/java/jsc/kit/guidance/GuidanceLayout.java)|[GuidanceLayoutFragment](/app/src/main/java/jsc/exam/com/guidance/fragments/GuidanceLayoutFragment.java)| 188 | |[GuidanceDialog](/guidanceLibrary/src/main/java/jsc/kit/guidance/GuidanceDialog.java)|[GuidanceDialogFragment](/app/src/main/java/jsc/exam/com/guidance/fragments/GuidanceDialogFragment.java)| 189 | |[GuidancePopupWindow](/guidanceLibrary/src/main/java/jsc/kit/guidance/GuidancePopupWindow.java)|[GuidancePopupWindowFragment](/app/src/main/java/jsc/exam/com/guidance/fragments/GuidancePopupWindowFragment.java)| 190 | 191 | ### 4、Screenshots 192 | + 4.1、[GuidanceRippleView](/guidanceLibrary/src/main/java/jsc/kit/guidance/GuidanceRippleView.java) 193 | 194 | ![GuidanceRippleView](/output/shots/guidance_ripple_view_s.png) 195 | 196 | + 4.2、[GuidanceLayout](/guidanceLibrary/src/main/java/jsc/kit/guidance/GuidanceLayout.java) 197 | 198 | ![GuidanceLayout](/output/shots/guidance_layout_s.png) 199 | 200 | ### 5、release log 201 | 202 | ##### version:0.1.0 203 | + 1、create project 204 | 205 | ### LICENSE 206 | ``` 207 | Copyright 2018 JustinRoom 208 | 209 | Licensed under the Apache License, Version 2.0 (the "License"); 210 | you may not use this file except in compliance with the License. 211 | You may obtain a copy of the License at 212 | 213 | http://www.apache.org/licenses/LICENSE-2.0 214 | 215 | Unless required by applicable law or agreed to in writing, software 216 | distributed under the License is distributed on an "AS IS" BASIS, 217 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 218 | See the License for the specific language governing permissions and 219 | limitations under the License. 220 | ``` 221 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | import java.text.SimpleDateFormat 2 | 3 | apply plugin: 'com.android.application' 4 | def apkName = "${rootProject.name}.apk" 5 | 6 | android { 7 | compileSdkVersion sdk_veriosn 8 | defaultConfig { 9 | applicationId "jsc.exam.com.guidance" 10 | minSdkVersion min_sdk_veriosn 11 | targetSdkVersion sdk_veriosn 12 | versionCode version_code 13 | versionName version_name 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | 16 | buildConfigField("String", "BUILD_TIME", "\"${new SimpleDateFormat("yyyy年MM月dd日 HH:mm").format(new Date())}\"") 17 | buildConfigField("String", "BASE_URL", "\"https://raw.githubusercontent.com/\"") 18 | buildConfigField("String", "VERSION_URL", "\"JustinRoom/${rootProject.name}/master/output/output.json\"") 19 | buildConfigField("String", "DOWNLOAD_URL", "\"JustinRoom/${rootProject.name}/master/output/%s\"") 20 | buildConfigField("String", "GITHUB_URL", "\"https://github.com/JustinRoom/${rootProject.name}\"") 21 | buildConfigField("String", "APK_URL", "\"https://raw.githubusercontent.com/JustinRoom/${rootProject.name}/master/output/${apkName}\"") 22 | } 23 | 24 | signingConfigs { 25 | debug { 26 | keyAlias 'Jsc' 27 | keyPassword '123456' 28 | storeFile file('../JscTestKeyStore.jks') 29 | storePassword '123456' 30 | } 31 | release { 32 | keyAlias 'Jsc' 33 | keyPassword '123456' 34 | storeFile file('../JscTestKeyStore.jks') 35 | storePassword '123456' 36 | } 37 | } 38 | 39 | buildTypes { 40 | debug { 41 | signingConfig signingConfigs.debug 42 | } 43 | release { 44 | minifyEnabled false 45 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 46 | signingConfig signingConfigs.release 47 | } 48 | } 49 | applicationVariants.all { variant -> 50 | variant.outputs.all { 51 | outputFileName = "${apkName}" 52 | } 53 | } 54 | } 55 | 56 | dependencies { 57 | implementation fileTree(include: ['*.jar'], dir: 'libs') 58 | implementation 'com.android.support:appcompat-v7:28.0.0' 59 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 60 | testImplementation 'junit:junit:4.12' 61 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 62 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 63 | implementation project(':guidanceLibrary') 64 | implementation 'com.android.support:recyclerview-v7:28.0.0' 65 | 66 | api 'com.squareup.retrofit2:retrofit:2.4.0' 67 | api 'com.squareup.retrofit2:converter-gson:2.4.0' 68 | api 'com.squareup.retrofit2:converter-scalars:2.4.0' 69 | api 'com.squareup.retrofit2:adapter-rxjava2:2.4.0' 70 | //https://github.com/square/okhttp 71 | api 'com.squareup.okhttp3:logging-interceptor:3.10.0' 72 | 73 | //https://github.com/ReactiveX/RxAndroid 74 | api 'io.reactivex.rxjava2:rxandroid:2.0.2' 75 | api 'io.reactivex.rxjava2:rxjava:2.1.12' 76 | } 77 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/jsc/exam/com/guidance/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package jsc.exam.com.guidance; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("jsc.exam.com.wheelview", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustinRoom/GuidanceDemo/a595e8381ddaa8c727bab4714e3aaf0435846b8c/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/jsc/exam/com/guidance/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package jsc.exam.com.guidance; 2 | 3 | import android.graphics.Color; 4 | import android.support.annotation.StringRes; 5 | import android.support.v7.app.ActionBar; 6 | import android.support.v7.widget.ActionMenuView; 7 | import android.util.TypedValue; 8 | import android.view.Gravity; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.FrameLayout; 12 | import android.widget.ImageView; 13 | import android.widget.TextView; 14 | 15 | import jsc.exam.com.guidance.utils.CompatResourceUtils; 16 | import jsc.exam.com.guidance.utils.WindowUtils; 17 | 18 | public abstract class BaseActivity extends BaseAppCompatActivity { 19 | 20 | private ImageView ivBack; 21 | private TextView tvTitle; 22 | private ActionMenuView actionMenuView; 23 | 24 | @Override 25 | protected void initActionBar(ActionBar actionBar) { 26 | if (actionBar == null) 27 | return; 28 | 29 | int padding = CompatResourceUtils.getDimensionPixelSize(this, R.dimen.space_12); 30 | FrameLayout customView = new FrameLayout(this); 31 | // customView.setPadding(padding, 0, padding, 0); 32 | ActionBar.LayoutParams barParams = new ActionBar.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, WindowUtils.getActionBarSize(this)); 33 | actionBar.setDisplayShowCustomEnabled(true); 34 | actionBar.setCustomView(customView, barParams); 35 | //添加标题 36 | tvTitle = new TextView(this); 37 | tvTitle.setTextColor(Color.WHITE); 38 | tvTitle.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18); 39 | tvTitle.setGravity(Gravity.CENTER); 40 | customView.addView(tvTitle, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); 41 | //添加返回按钮 42 | ivBack = new ImageView(this); 43 | ivBack.setPadding(padding / 2, 0, padding / 2, 0); 44 | ivBack.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 45 | ivBack.setImageResource(R.drawable.ic_chevron_left_white_24dp); 46 | ivBack.setBackground(WindowUtils.getSelectableItemBackgroundBorderless(this)); 47 | customView.addView(ivBack, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)); 48 | ivBack.setOnClickListener(new View.OnClickListener() { 49 | @Override 50 | public void onClick(View v) { 51 | onBackPressed(); 52 | } 53 | }); 54 | //添加menu菜单 55 | actionMenuView = new ActionMenuView(this); 56 | FrameLayout.LayoutParams menuParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT); 57 | menuParams.gravity = Gravity.END | Gravity.CENTER_VERTICAL; 58 | customView.addView(actionMenuView, menuParams); 59 | } 60 | 61 | public ActionMenuView getActionMenuView() { 62 | return actionMenuView; 63 | } 64 | 65 | public final void showTitleBarBackView(boolean show) { 66 | if (ivBack != null) 67 | ivBack.setVisibility(show ? View.VISIBLE : View.GONE); 68 | } 69 | 70 | public final void setTitleBarTitle(CharSequence title) { 71 | if (tvTitle != null) 72 | tvTitle.setText(title); 73 | } 74 | 75 | public final void setTitleBarTitle(@StringRes int resId) { 76 | if (tvTitle != null) 77 | tvTitle.setText(resId); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/jsc/exam/com/guidance/BaseAppCompatActivity.java: -------------------------------------------------------------------------------- 1 | package jsc.exam.com.guidance; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.CallSuper; 5 | import android.support.annotation.Nullable; 6 | import android.support.annotation.StringRes; 7 | import android.support.v7.app.ActionBar; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.view.Window; 10 | import android.view.WindowManager; 11 | import android.widget.Toast; 12 | 13 | /** 14 | *
Email:1006368252@qq.com 15 | *
QQ:1006368252 16 | *
https://github.com/JustinRoom/Guidance 17 | * 18 | * @author jiangshicheng 19 | */ 20 | public abstract class BaseAppCompatActivity extends AppCompatActivity { 21 | 22 | /** 23 | * Show full screen or not. 24 | * 25 | * @return {@code true}, show full screen, else not. 26 | */ 27 | protected boolean fullScreen() { 28 | return false; 29 | } 30 | 31 | protected void initActionBar(ActionBar actionBar) { 32 | 33 | } 34 | 35 | @Override 36 | protected void onCreate(@Nullable Bundle savedInstanceState) { 37 | super.onCreate(savedInstanceState); 38 | initComponent(); 39 | if (fullScreen()) { 40 | //without ActionBar 41 | requestWindowFeature(Window.FEATURE_NO_TITLE); 42 | if (getSupportActionBar() != null) 43 | getSupportActionBar().hide(); 44 | //show full screen 45 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); 46 | } else { 47 | initActionBar(getSupportActionBar()); 48 | } 49 | } 50 | 51 | /** 52 | * Initialize components here. 53 | */ 54 | protected void initComponent() { 55 | 56 | } 57 | 58 | /** 59 | * Destroy components here. 60 | */ 61 | @CallSuper 62 | protected void destroyComponent() { 63 | 64 | } 65 | 66 | @Override 67 | protected void onDestroy() { 68 | destroyComponent(); 69 | super.onDestroy(); 70 | } 71 | 72 | public final void showToast(@StringRes int resId) { 73 | Toast.makeText(this, resId, Toast.LENGTH_SHORT).show(); 74 | } 75 | 76 | public final void showToast(CharSequence txt) { 77 | Toast.makeText(this, txt, Toast.LENGTH_SHORT).show(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/jsc/exam/com/guidance/BaseEmptyFragmentActivity.java: -------------------------------------------------------------------------------- 1 | package jsc.exam.com.guidance; 2 | 3 | import android.content.pm.ActivityInfo; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.app.Fragment; 7 | import android.support.v7.app.ActionBar; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.view.Window; 10 | import android.view.WindowManager; 11 | 12 | /** 13 | * Empty activity for launching any {@link Fragment}. 14 | * 15 | *
Email:1006368252@qq.com 16 | *
QQ:1006368252 17 | *
https://github.com/JustinRoom/Guidance 18 | * 19 | * @author jiangshicheng 20 | */ 21 | public abstract class BaseEmptyFragmentActivity extends AppCompatActivity { 22 | 23 | public final static String EXTRA_FULL_SCREEN = "full_screen"; 24 | public final static String EXTRA_SHOW_ACTION_BAR = "show_action_bar"; 25 | public final static String EXTRA_FRAGMENT_CLASS_NAME = "class_name"; 26 | public final static String EXTRA_TITLE = "title"; 27 | public final static String EXTRA_LANDSCAPE = "landscape"; 28 | 29 | public abstract void initActionBar(ActionBar actionBar); 30 | 31 | @Override 32 | protected void onCreate(@Nullable Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | if (getIntent().getBooleanExtra(EXTRA_FULL_SCREEN, false)) { 35 | //without ActionBar 36 | requestWindowFeature(Window.FEATURE_NO_TITLE); 37 | if (getSupportActionBar() != null) 38 | getSupportActionBar().hide(); 39 | //show full screen 40 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); 41 | } else { 42 | if (getIntent().getBooleanExtra(EXTRA_SHOW_ACTION_BAR, true)) { 43 | initActionBar(getSupportActionBar()); 44 | } else if (getSupportActionBar() != null) { 45 | getSupportActionBar().hide(); 46 | } 47 | } 48 | 49 | String className = getIntent().getStringExtra(EXTRA_FRAGMENT_CLASS_NAME); 50 | if (className != null && className.length() > 0) { 51 | Class clzz = null; 52 | Object obj = null; 53 | try { 54 | clzz = Class.forName(className); 55 | obj = clzz.newInstance(); 56 | } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { 57 | e.printStackTrace(); 58 | } 59 | if (obj instanceof Fragment) { 60 | Fragment fragment = (Fragment) obj; 61 | Bundle arguments = getIntent().getExtras(); 62 | if (arguments != null) { 63 | fragment.setArguments(arguments); 64 | } 65 | getSupportFragmentManager().beginTransaction().replace(android.R.id.content, fragment).commit(); 66 | } 67 | } 68 | } 69 | 70 | @Override 71 | protected void onResume() { 72 | if (getIntent().getBooleanExtra(EXTRA_LANDSCAPE, false) && getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) { 73 | setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); 74 | } 75 | super.onResume(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/jsc/exam/com/guidance/EmptyFragmentActivity.java: -------------------------------------------------------------------------------- 1 | package jsc.exam.com.guidance; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.graphics.Color; 6 | import android.os.Bundle; 7 | import android.support.v7.app.ActionBar; 8 | import android.support.v7.widget.ActionMenuView; 9 | import android.text.TextUtils; 10 | import android.util.TypedValue; 11 | import android.view.Gravity; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.widget.FrameLayout; 15 | import android.widget.ImageView; 16 | import android.widget.TextView; 17 | 18 | import jsc.exam.com.guidance.utils.CompatResourceUtils; 19 | import jsc.exam.com.guidance.utils.WindowUtils; 20 | 21 | /** 22 | *
Email:1006368252@qq.com 23 | *
QQ:1006368252 24 | *
https://github.com/JustinRoom/Guidance 25 | * 26 | * @author jiangshicheng 27 | */ 28 | public class EmptyFragmentActivity extends BaseEmptyFragmentActivity { 29 | 30 | public static void launch(Context context, Bundle extras) { 31 | context.startActivity(createIntent(context, extras)); 32 | } 33 | 34 | private static Intent createIntent(Context context, Bundle extras) { 35 | Intent intent = new Intent(context, EmptyFragmentActivity.class); 36 | if (extras != null) 37 | intent.putExtras(extras); 38 | return intent; 39 | } 40 | 41 | @Override 42 | public void initActionBar(ActionBar actionBar) { 43 | if (actionBar == null) 44 | return; 45 | 46 | //You can initialize custom action bar here. 47 | int padding = CompatResourceUtils.getDimensionPixelSize(this, R.dimen.space_12); 48 | FrameLayout customView = new FrameLayout(this); 49 | // customView.setPadding(padding, 0, padding, 0); 50 | ActionBar.LayoutParams barParams = new ActionBar.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, WindowUtils.getActionBarSize(this)); 51 | actionBar.setDisplayShowCustomEnabled(true); 52 | actionBar.setCustomView(customView, barParams); 53 | //添加标题 54 | TextView tvTitle = new TextView(this); 55 | tvTitle.setTextColor(Color.WHITE); 56 | tvTitle.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18); 57 | tvTitle.setGravity(Gravity.CENTER); 58 | tvTitle.setText(getClass().getSimpleName().replace("Activity", "")); 59 | customView.addView(tvTitle, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); 60 | //添加返回按钮 61 | ImageView ivBack = new ImageView(this); 62 | ivBack.setPadding(padding / 2, 0, padding / 2, 0); 63 | ivBack.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 64 | ivBack.setImageResource(R.drawable.ic_chevron_left_white_24dp); 65 | ivBack.setBackground(WindowUtils.getSelectableItemBackgroundBorderless(this)); 66 | customView.addView(ivBack, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)); 67 | ivBack.setOnClickListener(new View.OnClickListener() { 68 | @Override 69 | public void onClick(View v) { 70 | onBackPressed(); 71 | } 72 | }); 73 | //添加menu菜单 74 | ActionMenuView actionMenuView = new ActionMenuView(this); 75 | FrameLayout.LayoutParams menuParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT); 76 | menuParams.gravity = Gravity.END | Gravity.CENTER_VERTICAL; 77 | customView.addView(actionMenuView, menuParams); 78 | 79 | String title = getIntent().getStringExtra(EXTRA_TITLE); 80 | tvTitle.setText(TextUtils.isEmpty(title) ? getClass().getSimpleName().replace("Activity", "") : title); 81 | } 82 | } -------------------------------------------------------------------------------- /app/src/main/java/jsc/exam/com/guidance/MainActivity.java: -------------------------------------------------------------------------------- 1 | package jsc.exam.com.guidance; 2 | 3 | import android.content.DialogInterface; 4 | import android.content.Intent; 5 | import android.content.SharedPreferences; 6 | import android.content.pm.PackageInfo; 7 | import android.content.pm.PackageManager; 8 | import android.net.Uri; 9 | import android.os.Bundle; 10 | import android.support.v7.app.AlertDialog; 11 | import android.support.v7.widget.LinearLayoutManager; 12 | import android.support.v7.widget.RecyclerView; 13 | import android.util.Log; 14 | import android.util.Pair; 15 | import android.view.View; 16 | import android.widget.FrameLayout; 17 | 18 | import org.json.JSONException; 19 | import org.json.JSONObject; 20 | 21 | import java.util.ArrayList; 22 | import java.util.Date; 23 | import java.util.List; 24 | import java.util.Locale; 25 | 26 | import io.reactivex.android.schedulers.AndroidSchedulers; 27 | import io.reactivex.disposables.Disposable; 28 | import io.reactivex.functions.Action; 29 | import io.reactivex.functions.Consumer; 30 | import io.reactivex.schedulers.Schedulers; 31 | import jsc.exam.com.guidance.adapter.BaseRecyclerViewAdapter; 32 | import jsc.exam.com.guidance.adapter.BlankSpaceItemDecoration; 33 | import jsc.exam.com.guidance.adapter.ClassItemAdapter; 34 | import jsc.exam.com.guidance.bean.ClassItem; 35 | import jsc.exam.com.guidance.fragments.AboutFragment; 36 | import jsc.exam.com.guidance.fragments.GuidanceDialogFragment; 37 | import jsc.exam.com.guidance.fragments.GuidanceLayoutFragment; 38 | import jsc.exam.com.guidance.fragments.GuidancePopupWindowFragment; 39 | import jsc.exam.com.guidance.fragments.GuidanceRippleViewFragment; 40 | import jsc.exam.com.guidance.retrofit.ApiService; 41 | import jsc.exam.com.guidance.retrofit.CustomHttpClient; 42 | import jsc.exam.com.guidance.retrofit.CustomRetrofit; 43 | import jsc.exam.com.guidance.utils.CompatResourceUtils; 44 | import okhttp3.OkHttpClient; 45 | import retrofit2.Retrofit; 46 | 47 | public class MainActivity extends BaseActivity { 48 | 49 | RecyclerView recyclerView; 50 | SharedPreferences sharedPreferences = null; 51 | 52 | @Override 53 | protected void onCreate(Bundle savedInstanceState) { 54 | super.onCreate(savedInstanceState); 55 | recyclerView = new RecyclerView(this); 56 | recyclerView.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); 57 | recyclerView.setLayoutManager(new LinearLayoutManager(this)); 58 | recyclerView.addItemDecoration(new BlankSpaceItemDecoration( 59 | CompatResourceUtils.getDimensionPixelSize(this, R.dimen.space_16), 60 | CompatResourceUtils.getDimensionPixelSize(this, R.dimen.space_2), 61 | CompatResourceUtils.getDimensionPixelSize(this, R.dimen.space_16), 62 | CompatResourceUtils.getDimensionPixelSize(this, R.dimen.space_2) 63 | )); 64 | setContentView(recyclerView); 65 | setTitleBarTitle(getClass().getSimpleName().replace("Activity", "")); 66 | showTitleBarBackView(false); 67 | 68 | ClassItemAdapter adapter = new ClassItemAdapter(); 69 | recyclerView.setAdapter(adapter); 70 | adapter.setOnItemClickListener(new BaseRecyclerViewAdapter.OnItemClickListener() { 71 | @Override 72 | public void onItemClick(View itemView, int position, ClassItem item, int viewType) { 73 | toNewActivity(item); 74 | } 75 | }); 76 | adapter.setItems(getClassItems()); 77 | 78 | //check upgrade if the latest checking time is 2 hours ago 79 | sharedPreferences = getSharedPreferences("share_camera_mask_view", MODE_PRIVATE); 80 | long lastCheckUpdateTimeStamp = sharedPreferences.getLong("lastCheckUpdateTimeStamp", 0); 81 | long curTime = new Date().getTime(); 82 | if (curTime - lastCheckUpdateTimeStamp > 2 * 60 * 60_000) { 83 | checkUpdate(); 84 | } 85 | } 86 | 87 | private void toNewActivity(ClassItem item) { 88 | switch (item.getType()) { 89 | case ClassItem.TYPE_ACTIVITY: 90 | startActivity(new Intent(this, item.getClazz())); 91 | break; 92 | case ClassItem.TYPE_FRAGMENT: 93 | Bundle bundle = new Bundle(); 94 | bundle.putString(EmptyFragmentActivity.EXTRA_TITLE, item.getLabel()); 95 | bundle.putBoolean(EmptyFragmentActivity.EXTRA_FULL_SCREEN, item.isFullScreen()); 96 | bundle.putBoolean(EmptyFragmentActivity.EXTRA_SHOW_ACTION_BAR, true); 97 | bundle.putBoolean(EmptyFragmentActivity.EXTRA_LANDSCAPE, item.isLandscape()); 98 | bundle.putString(EmptyFragmentActivity.EXTRA_FRAGMENT_CLASS_NAME, item.getClazz().getName()); 99 | EmptyFragmentActivity.launch(this, bundle); 100 | break; 101 | } 102 | } 103 | 104 | private List getClassItems() { 105 | List classItems = new ArrayList<>(); 106 | classItems.add(new ClassItem(ClassItem.TYPE_FRAGMENT, "GuidanceRippleView", GuidanceRippleViewFragment.class, true)); 107 | classItems.add(new ClassItem(ClassItem.TYPE_FRAGMENT, "GuidanceLayout", GuidanceLayoutFragment.class, true)); 108 | classItems.add(new ClassItem(ClassItem.TYPE_FRAGMENT, "GuidanceDialog", GuidanceDialogFragment.class, true)); 109 | classItems.add(new ClassItem(ClassItem.TYPE_FRAGMENT, "GuidancePopupWindow", GuidancePopupWindowFragment.class, true)); 110 | classItems.add(new ClassItem(ClassItem.TYPE_FRAGMENT, "About", AboutFragment.class, false)); 111 | return classItems; 112 | } 113 | 114 | private void checkUpdate() { 115 | OkHttpClient client = new CustomHttpClient() 116 | .addHeader(new Pair<>("token", "")) 117 | .setConnectTimeout(5_000) 118 | .setShowLog(true) 119 | .createOkHttpClient(); 120 | Retrofit retrofit = new CustomRetrofit() 121 | //我在app的build.gradle文件的defaultConfig标签里定义了BASE_URL 122 | .setBaseUrl(BuildConfig.BASE_URL) 123 | .setOkHttpClient(client) 124 | .createRetrofit(); 125 | Disposable disposable = retrofit.create(ApiService.class) 126 | .getVersionInfo() 127 | .subscribeOn(Schedulers.io()) 128 | .observeOn(AndroidSchedulers.mainThread()) 129 | .subscribe(new Consumer() { 130 | @Override 131 | public void accept(String s) throws Exception { 132 | explainVersionInfoJson(s); 133 | } 134 | }, new Consumer() { 135 | @Override 136 | public void accept(Throwable throwable) throws Exception { 137 | showToast(throwable.getLocalizedMessage()); 138 | } 139 | }, new Action() { 140 | @Override 141 | public void run() throws Exception { 142 | 143 | } 144 | }); 145 | } 146 | 147 | private void explainVersionInfoJson(String json) { 148 | json = json.substring(1, json.length() - 1); 149 | try { 150 | JSONObject object = new JSONObject(json).getJSONObject("apkInfo"); 151 | int versionCode = object.getInt("versionCode"); 152 | String versionName = object.getString("versionName"); 153 | String fileName = object.getString("outputFile"); 154 | String content = object.getString("content"); 155 | 156 | PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_GIDS); 157 | long curVersionCode = packageInfo.versionCode; 158 | String curVersionName = packageInfo.versionName; 159 | 160 | Log.i("MainActivity", "explainVersionInfoJson: {versionCod" + versionCode + ", curVersionCode:" + curVersionCode); 161 | //a new version 162 | if (versionCode > curVersionCode) { 163 | sharedPreferences.edit().putLong("lastCheckUpdateTimeStamp", new Date().getTime()).apply(); 164 | showNewVersionDialog(String.format( 165 | Locale.CHINA, 166 | "当前版本:\u2000%1s\n" 167 | + "最新版本:\u2000%2s\n\n" 168 | + "更新内容:%3s" 169 | + "\n\n立即更新?", 170 | curVersionName, 171 | versionName, 172 | content 173 | ), fileName); 174 | } 175 | } catch (JSONException | PackageManager.NameNotFoundException e) { 176 | e.printStackTrace(); 177 | } 178 | } 179 | 180 | private void showNewVersionDialog(String content, final String fileName) { 181 | new AlertDialog.Builder(this) 182 | .setTitle("新版本提示") 183 | .setMessage(content) 184 | .setPositiveButton("更新", new DialogInterface.OnClickListener() { 185 | @Override 186 | public void onClick(DialogInterface dialog, int which) { 187 | String url = BuildConfig.BASE_URL + BuildConfig.DOWNLOAD_URL; 188 | Uri uri = Uri.parse(String.format(Locale.CHINA, url, fileName)); 189 | Intent intent = new Intent(Intent.ACTION_VIEW, uri); 190 | startActivity(intent); 191 | 192 | } 193 | }) 194 | .setNegativeButton("知道了", null) 195 | .show(); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /app/src/main/java/jsc/exam/com/guidance/adapter/BaseRecyclerViewAdapter.java: -------------------------------------------------------------------------------- 1 | package jsc.exam.com.guidance.adapter; 2 | 3 | 4 | import android.support.annotation.IdRes; 5 | import android.support.annotation.IntRange; 6 | import android.support.annotation.LayoutRes; 7 | import android.support.annotation.NonNull; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | /** 17 | *
Email:1006368252@qq.com 18 | *
QQ:1006368252 19 | *
https://github.com/JustinRoom/Guidance 20 | * 21 | * @author jiangshicheng 22 | */ 23 | public abstract class BaseRecyclerViewAdapter extends RecyclerView.Adapter implements ViewAdapter { 24 | 25 | private List items = null; 26 | @LayoutRes 27 | protected int layoutId = -1; 28 | private boolean itemClickEnable; 29 | private boolean itemLongClickEnable; 30 | private OnItemClickListener onViewClickListener = null; 31 | private OnItemClickListener onItemClickListener = null; 32 | private OnItemLongClickListener onItemLongClickListener = null; 33 | private View.OnClickListener listener = null; 34 | private View.OnClickListener onClickListener = null; 35 | private View.OnLongClickListener onLongClickListener = null; 36 | 37 | public interface OnItemClickListener { 38 | void onItemClick(View itemView, int position, T item, int viewType); 39 | } 40 | 41 | public interface OnItemLongClickListener { 42 | boolean onItemLongClick(View itemView, int position, T item, int viewType); 43 | } 44 | 45 | public BaseRecyclerViewAdapter() { 46 | this(-1, true, true); 47 | } 48 | 49 | public BaseRecyclerViewAdapter(@LayoutRes int layoutId) { 50 | this(layoutId, true, true); 51 | } 52 | 53 | public BaseRecyclerViewAdapter(@LayoutRes int layoutId, boolean itemClickEnable, boolean itemLongClickEnable) { 54 | this.layoutId = layoutId; 55 | this.itemClickEnable = itemClickEnable; 56 | this.itemLongClickEnable = itemLongClickEnable; 57 | items = new ArrayList<>(); 58 | } 59 | 60 | public List getItems() { 61 | return items; 62 | } 63 | 64 | public T getItemAt(int position) { 65 | return position < getItemCount() ? items.get(position) : null; 66 | } 67 | 68 | public void setItems(List items) { 69 | this.items = items; 70 | ensureListNotNull(); 71 | notifyDataSetChanged(); 72 | } 73 | 74 | public void addItems(List items) { 75 | addItems(getItemCount(), items); 76 | } 77 | 78 | @Override 79 | public void addItems(@IntRange(from = 0) int position, List items) { 80 | if (items == null || items.isEmpty()) 81 | return; 82 | ensureListNotNull(); 83 | this.items.addAll(items); 84 | notifyItemRangeInserted(position, items.size()); 85 | } 86 | 87 | @Override 88 | public void addItem(T item) { 89 | addItem(getItemCount(), item); 90 | } 91 | 92 | @Override 93 | public void addItem(int position, T item) { 94 | if (item == null) 95 | return; 96 | ensureListNotNull(); 97 | this.items.add(position, item); 98 | notifyItemInserted(position); 99 | } 100 | 101 | @Override 102 | public void removeItem(T item) { 103 | if (item == null) 104 | return; 105 | int position = -1; 106 | for (int i = 0; i < getItemCount(); i++) { 107 | if (item == getItemAt(i)){ 108 | position = i; 109 | break; 110 | } 111 | } 112 | removeItem(position); 113 | } 114 | 115 | @Override 116 | public void removeItem(int position) { 117 | if (position < 0) 118 | return; 119 | items.remove(position); 120 | notifyItemRemoved(position); 121 | } 122 | 123 | private void ensureListNotNull(){ 124 | if (this.items == null) 125 | this.items = new ArrayList<>(); 126 | } 127 | 128 | public void setOnItemClickListener(OnItemClickListener onItemClickListener) { 129 | this.onItemClickListener = onItemClickListener; 130 | } 131 | 132 | public void setOnItemLongClickListener(OnItemLongClickListener onItemLongClickListener) { 133 | this.onItemLongClickListener = onItemLongClickListener; 134 | } 135 | 136 | public void setOnViewClickListener(OnItemClickListener onViewClickListener) { 137 | this.onViewClickListener = onViewClickListener; 138 | } 139 | 140 | public View createView(@NonNull ViewGroup parent, int viewType) { 141 | return LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false); 142 | } 143 | 144 | @Override 145 | public final void onBindViewHolder(@NonNull final VH holder, int position) { 146 | holder.itemView.setTag(position); 147 | addOnItemClickListener(holder); 148 | addOnItemLongClickListener(holder); 149 | bindViewHolder(holder, position, getItemAt(position), getItemViewType(position)); 150 | } 151 | 152 | public abstract void bindViewHolder(@NonNull final VH holder, int position, T item, int viewType); 153 | 154 | @Override 155 | public int getItemCount() { 156 | return items.size(); 157 | } 158 | 159 | private void addOnItemClickListener(@NonNull final VH holder){ 160 | if (!itemClickEnable) return; 161 | if (onClickListener == null){ 162 | onClickListener = new View.OnClickListener() { 163 | @Override 164 | public void onClick(View v) { 165 | int position = (int) v.getTag(); 166 | if (onItemClickListener != null) 167 | onItemClickListener.onItemClick(v, position, getItemAt(position), getItemViewType(position)); 168 | } 169 | }; 170 | } 171 | holder.itemView.setOnClickListener(onClickListener); 172 | } 173 | 174 | private void addOnItemLongClickListener(@NonNull final VH holder){ 175 | if (!itemLongClickEnable) return; 176 | if (onLongClickListener == null){ 177 | onLongClickListener = new View.OnLongClickListener() { 178 | @Override 179 | public boolean onLongClick(View v) { 180 | int position = (int) v.getTag(); 181 | if (onItemLongClickListener != null) { 182 | return onItemLongClickListener.onItemLongClick(v, position, getItemAt(position), getItemViewType(position)); 183 | } 184 | return false; 185 | } 186 | }; 187 | } 188 | holder.itemView.setOnLongClickListener(onLongClickListener); 189 | } 190 | 191 | public final void addOnViewClickListener(@NonNull final VH holder, @IdRes int id){ 192 | //listener使用全局模式,避免每次都创建而造成内存d抖动 193 | if (listener == null) 194 | listener = new View.OnClickListener() { 195 | @Override 196 | public void onClick(View v) { 197 | int position = holder.getAdapterPosition(); 198 | if (onViewClickListener != null) 199 | onViewClickListener.onItemClick(v, position, getItemAt(position), getItemViewType(position)); 200 | } 201 | }; 202 | View view = holder.itemView.findViewById(id); 203 | if (view != null) 204 | view.setOnClickListener(listener); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /app/src/main/java/jsc/exam/com/guidance/adapter/BlankSpaceItemDecoration.java: -------------------------------------------------------------------------------- 1 | package jsc.exam.com.guidance.adapter; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Rect; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.View; 7 | 8 | /** 9 | * Black space line {@link android.support.v7.widget.RecyclerView.ItemDecoration} for {@link RecyclerView}. 10 | *

11 | *
Email:1006368252@qq.com 12 | *
QQ:1006368252 13 | *
https://github.com/JustinRoom/Guidance 14 | * 15 | * @author jiangshicheng 16 | */ 17 | public class BlankSpaceItemDecoration extends RecyclerView.ItemDecoration { 18 | 19 | int leftSpace; 20 | int topSpace; 21 | int rightSpace; 22 | int bottomSpace; 23 | boolean showFirstTop; 24 | boolean showLastBottom; 25 | 26 | public BlankSpaceItemDecoration(int leftSpace, int topSpace, int rightSpace, int bottomSpace) { 27 | this.leftSpace = leftSpace; 28 | this.topSpace = topSpace; 29 | this.rightSpace = rightSpace; 30 | this.bottomSpace = bottomSpace; 31 | } 32 | 33 | public void showFFTLB(boolean showFirstTop, boolean showLastBottom){ 34 | this.showFirstTop = showFirstTop; 35 | this.showLastBottom = showLastBottom; 36 | } 37 | 38 | @Override 39 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 40 | super.getItemOffsets(outRect, view, parent, state); 41 | outRect.left = leftSpace; 42 | outRect.top = topSpace; 43 | outRect.right = rightSpace; 44 | outRect.bottom = bottomSpace; 45 | RecyclerView.Adapter adapter = parent.getAdapter(); 46 | if (adapter != null && adapter.getItemCount() > 1) { 47 | if (!showFirstTop && parent.getChildAdapterPosition(view) == 0) 48 | outRect.top = 0; 49 | 50 | if (!showLastBottom && parent.getChildAdapterPosition(view) == adapter.getItemCount() - 1) 51 | outRect.bottom = 0; 52 | } 53 | } 54 | 55 | @Override 56 | public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 57 | super.onDraw(c, parent, state); 58 | } 59 | 60 | @Override 61 | public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { 62 | super.onDrawOver(c, parent, state); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/jsc/exam/com/guidance/adapter/ClassItemAdapter.java: -------------------------------------------------------------------------------- 1 | package jsc.exam.com.guidance.adapter; 2 | 3 | 4 | import android.support.annotation.NonNull; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import jsc.exam.com.guidance.R; 10 | import jsc.exam.com.guidance.bean.ClassItem; 11 | import jsc.exam.com.guidance.utils.CompatResourceUtils; 12 | import jsc.exam.com.guidance.widgets.JSCItemLayout; 13 | 14 | public class ClassItemAdapter extends BaseRecyclerViewAdapter { 15 | 16 | public ClassItemAdapter() { 17 | } 18 | 19 | public ClassItemAdapter(int layoutId) { 20 | super(layoutId); 21 | } 22 | 23 | public ClassItemAdapter(int layoutId, boolean itemClickEnable, boolean itemLongClickEnable) { 24 | super(layoutId, itemClickEnable, itemLongClickEnable); 25 | } 26 | 27 | @Override 28 | public View createView(@NonNull ViewGroup parent, int viewType) { 29 | JSCItemLayout layout = new JSCItemLayout(parent.getContext()); 30 | layout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, CompatResourceUtils.getDimensionPixelSize(parent, R.dimen.item_height))); 31 | layout.setBackgroundResource(R.drawable.ripple_round_corner_white_r4); 32 | layout.setPadding( 33 | CompatResourceUtils.getDimensionPixelSize(parent, R.dimen.space_8), 34 | 0, 35 | CompatResourceUtils.getDimensionPixelSize(parent, R.dimen.space_8), 36 | 0 37 | ); 38 | layout.getLabelView().setPadding( 39 | CompatResourceUtils.getDimensionPixelSize(parent, R.dimen.space_12), 40 | 0, 41 | 0, 42 | 0 43 | ); 44 | return layout; 45 | } 46 | 47 | @NonNull 48 | @Override 49 | public ClassItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 50 | JSCItemLayout v = (JSCItemLayout) createView(parent, viewType); 51 | return new ClassItemViewHolder(v); 52 | } 53 | 54 | @Override 55 | public void bindViewHolder(@NonNull ClassItemViewHolder holder, int position, ClassItem item, int viewType) { 56 | holder.layout.setLabel(item.getLabel()); 57 | holder.layout.showDotView(item.isUpdated()); 58 | } 59 | 60 | static class ClassItemViewHolder extends RecyclerView.ViewHolder { 61 | 62 | JSCItemLayout layout; 63 | 64 | ClassItemViewHolder(JSCItemLayout layout) { 65 | super(layout); 66 | this.layout = layout; 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /app/src/main/java/jsc/exam/com/guidance/adapter/ViewAdapter.java: -------------------------------------------------------------------------------- 1 | package jsc.exam.com.guidance.adapter; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | *
Email:1006368252@qq.com 7 | *
QQ:1006368252 8 | *
https://github.com/JustinRoom/Guidance 9 | * 10 | * @author jiangshicheng 11 | */ 12 | public interface ViewAdapter { 13 | 14 | public List getItems(); 15 | 16 | public T getItemAt(int position); 17 | 18 | public void setItems(List items); 19 | 20 | public void addItems(List items); 21 | 22 | public void addItems(int position, List items); 23 | 24 | public void addItem(T item); 25 | 26 | public void addItem(int position, T item); 27 | 28 | public void removeItem(int position); 29 | 30 | public void removeItem(T item); 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/jsc/exam/com/guidance/bean/ClassItem.java: -------------------------------------------------------------------------------- 1 | package jsc.exam.com.guidance.bean; 2 | 3 | import android.support.annotation.IntDef; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | 8 | public class ClassItem { 9 | public static final int TYPE_ACTIVITY = 0; 10 | public static final int TYPE_FRAGMENT = 1; 11 | @IntDef({TYPE_ACTIVITY, TYPE_FRAGMENT}) 12 | @Retention(RetentionPolicy.SOURCE) 13 | public @interface Type { 14 | } 15 | 16 | private String label; 17 | private Class clazz; 18 | private int type; 19 | private boolean updated; 20 | private boolean isLandscape; 21 | private boolean fullScreen; 22 | 23 | public ClassItem() { 24 | } 25 | 26 | public ClassItem(@Type int type, String label, Class clazz, boolean updated) { 27 | this(type, label, clazz, updated, false, false); 28 | } 29 | public ClassItem(@Type int type, String label, Class clazz, boolean updated, boolean isLandscape, boolean fullScreen) { 30 | this.type = type; 31 | this.label = label; 32 | this.clazz = clazz; 33 | this.updated = updated; 34 | this.isLandscape = isLandscape; 35 | this.fullScreen = fullScreen; 36 | } 37 | 38 | public String getLabel() { 39 | return label; 40 | } 41 | 42 | public void setLabel(String label) { 43 | this.label = label; 44 | } 45 | 46 | public Class getClazz() { 47 | return clazz; 48 | } 49 | 50 | public void setClazz(Class clazz) { 51 | this.clazz = clazz; 52 | } 53 | 54 | public boolean isUpdated() { 55 | return updated; 56 | } 57 | 58 | public void setUpdated(boolean updated) { 59 | this.updated = updated; 60 | } 61 | 62 | public void setType(@Type int type) { 63 | this.type = type; 64 | } 65 | 66 | public int getType() { 67 | return type; 68 | } 69 | 70 | public boolean isLandscape() { 71 | return isLandscape; 72 | } 73 | 74 | public void setLandscape(boolean landscape) { 75 | isLandscape = landscape; 76 | } 77 | 78 | public boolean isFullScreen() { 79 | return fullScreen; 80 | } 81 | 82 | public void setFullScreen(boolean fullScreen) { 83 | this.fullScreen = fullScreen; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/jsc/exam/com/guidance/fragments/AboutFragment.java: -------------------------------------------------------------------------------- 1 | package jsc.exam.com.guidance.fragments; 2 | 3 | import android.content.DialogInterface; 4 | import android.content.Intent; 5 | import android.content.pm.PackageInfo; 6 | import android.content.pm.PackageManager; 7 | import android.net.Uri; 8 | import android.os.Bundle; 9 | import android.support.annotation.NonNull; 10 | import android.support.annotation.Nullable; 11 | import android.support.v4.app.Fragment; 12 | import android.support.v7.app.AlertDialog; 13 | import android.util.Log; 14 | import android.util.Pair; 15 | import android.view.LayoutInflater; 16 | import android.view.View; 17 | import android.view.ViewGroup; 18 | import android.widget.TextView; 19 | import android.widget.Toast; 20 | 21 | import org.json.JSONException; 22 | import org.json.JSONObject; 23 | 24 | import java.util.Locale; 25 | 26 | import io.reactivex.android.schedulers.AndroidSchedulers; 27 | import io.reactivex.disposables.Disposable; 28 | import io.reactivex.functions.Action; 29 | import io.reactivex.functions.Consumer; 30 | import io.reactivex.schedulers.Schedulers; 31 | import jsc.exam.com.guidance.BuildConfig; 32 | import jsc.exam.com.guidance.R; 33 | import jsc.exam.com.guidance.retrofit.ApiService; 34 | import jsc.exam.com.guidance.retrofit.CustomHttpClient; 35 | import jsc.exam.com.guidance.retrofit.CustomRetrofit; 36 | import okhttp3.OkHttpClient; 37 | import retrofit2.Retrofit; 38 | 39 | /** 40 | *
Email:1006368252@qq.com 41 | *
QQ:1006368252 42 | *
https://github.com/JustinRoom/Guidance 43 | * 44 | * @author jiangshicheng 45 | */ 46 | public class AboutFragment extends Fragment { 47 | 48 | @Nullable 49 | @Override 50 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 51 | View root = inflater.inflate(R.layout.fragment_abount, container, false); 52 | TextView tvVersion = root.findViewById(R.id.tv_version); 53 | TextView tvUpdateContent = root.findViewById(R.id.tv_update_content); 54 | TextView tvBuildTime = root.findViewById(R.id.tv_build_time); 55 | try { 56 | PackageInfo packageInfo = inflater.getContext().getPackageManager().getPackageInfo(inflater.getContext().getPackageName(), PackageManager.GET_GIDS); 57 | tvVersion.setText(String.format(Locale.CHINA, "version:%s", packageInfo.versionName)); 58 | } catch (PackageManager.NameNotFoundException e) { 59 | e.printStackTrace(); 60 | } 61 | tvBuildTime.setText(String.format(Locale.CHINA, "build time:%s", BuildConfig.BUILD_TIME)); 62 | 63 | root.findViewById(R.id.btn_check_update).setOnClickListener(new View.OnClickListener() { 64 | @Override 65 | public void onClick(View v) { 66 | v.setEnabled(false); 67 | checkUpdate(); 68 | } 69 | }); 70 | tvUpdateContent.setText("当前版本更新内容:" + getString(R.string.app_update_content)); 71 | return root; 72 | } 73 | 74 | private void checkUpdate() { 75 | OkHttpClient client = new CustomHttpClient() 76 | .addHeader(new Pair<>("token", "")) 77 | .setConnectTimeout(5_000) 78 | .setShowLog(true) 79 | .createOkHttpClient(); 80 | Retrofit retrofit = new CustomRetrofit() 81 | //我在app的build.gradle文件的defaultConfig标签里定义了BASE_URL 82 | .setBaseUrl(BuildConfig.BASE_URL) 83 | .setOkHttpClient(client) 84 | .createRetrofit(); 85 | Disposable disposable = retrofit.create(ApiService.class) 86 | .getVersionInfo() 87 | .subscribeOn(Schedulers.io()) 88 | .observeOn(AndroidSchedulers.mainThread()) 89 | .subscribe(new Consumer() { 90 | @Override 91 | public void accept(String s) throws Exception { 92 | explainVersionInfoJson(s); 93 | } 94 | }, new Consumer() { 95 | @Override 96 | public void accept(Throwable throwable) throws Exception { 97 | 98 | } 99 | }, new Action() { 100 | @Override 101 | public void run() throws Exception { 102 | if (getView() != null) { 103 | getView().findViewById(R.id.btn_check_update).setEnabled(true); 104 | } 105 | } 106 | }); 107 | } 108 | 109 | private void explainVersionInfoJson(String json) { 110 | json = json.substring(1, json.length() - 1); 111 | try { 112 | JSONObject object = new JSONObject(json).getJSONObject("apkInfo"); 113 | int versionCode = object.getInt("versionCode"); 114 | String versionName = object.getString("versionName"); 115 | String fileName = object.getString("outputFile"); 116 | String content = object.getString("content"); 117 | 118 | PackageInfo packageInfo = getActivity().getPackageManager().getPackageInfo(getActivity().getPackageName(), PackageManager.GET_GIDS); 119 | long curVersionCode = packageInfo.versionCode; 120 | String curVersionName = packageInfo.versionName; 121 | 122 | Log.i("MainActivity", "explainVersionInfoJson: {versionCod" + versionCode + ", curVersionCode:" + curVersionCode); 123 | //a new version 124 | if (versionCode > curVersionCode) { 125 | showNewVersionDialog(String.format( 126 | Locale.CHINA, 127 | "当前版本:\u2000%1s\n" 128 | + "最新版本:\u2000%2s\n\n" 129 | + "更新内容:%3s" 130 | + "\n\n立即更新?", 131 | curVersionName, 132 | versionName, 133 | content 134 | ), fileName); 135 | } else { 136 | Toast.makeText(getActivity(), "已是最新版本!", Toast.LENGTH_SHORT).show(); 137 | } 138 | } catch (JSONException | PackageManager.NameNotFoundException e) { 139 | e.printStackTrace(); 140 | } 141 | } 142 | 143 | private void showNewVersionDialog(String content, final String fileName) { 144 | if (getActivity() == null) 145 | return; 146 | 147 | new AlertDialog.Builder(getActivity()) 148 | .setTitle("新版本提示") 149 | .setMessage(content) 150 | .setPositiveButton("更新", new DialogInterface.OnClickListener() { 151 | @Override 152 | public void onClick(DialogInterface dialog, int which) { 153 | String url = BuildConfig.BASE_URL + BuildConfig.DOWNLOAD_URL; 154 | Uri uri = Uri.parse(String.format(Locale.CHINA, url, fileName)); 155 | Intent intent = new Intent(Intent.ACTION_VIEW, uri); 156 | startActivity(intent); 157 | 158 | } 159 | }) 160 | .setNegativeButton("知道了", null) 161 | .show(); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /app/src/main/java/jsc/exam/com/guidance/fragments/BaseFragment.java: -------------------------------------------------------------------------------- 1 | package jsc.exam.com.guidance.fragments; 2 | 3 | import android.content.Context; 4 | import android.support.v4.app.Fragment; 5 | 6 | public abstract class BaseFragment extends Fragment { 7 | 8 | private boolean isDataLoaded = false; 9 | 10 | abstract void onLoadData(Context context); 11 | 12 | @Override 13 | public void onResume() { 14 | super.onResume(); 15 | if (!isDataLoaded) { 16 | isDataLoaded = true; 17 | if (getActivity() != null) 18 | onLoadData(getActivity()); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/jsc/exam/com/guidance/fragments/GuidanceDialogFragment.java: -------------------------------------------------------------------------------- 1 | package jsc.exam.com.guidance.fragments; 2 | 3 | import android.animation.ObjectAnimator; 4 | import android.content.Context; 5 | import android.graphics.Bitmap; 6 | import android.graphics.Rect; 7 | import android.os.Bundle; 8 | import android.support.annotation.NonNull; 9 | import android.support.annotation.Nullable; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.widget.FrameLayout; 14 | import android.widget.ImageView; 15 | import android.widget.Toast; 16 | 17 | import jsc.exam.com.guidance.R; 18 | import jsc.kit.guidance.GuidanceDialog; 19 | import jsc.kit.guidance.GuidanceLayout; 20 | import jsc.kit.guidance.GuidanceRippleView; 21 | import jsc.kit.guidance.OnTargetClickListener; 22 | import jsc.kit.guidance.ViewDrawingCacheUtils; 23 | 24 | /** 25 | *
Email:1006368252@qq.com 26 | *
QQ:1006368252 27 | *
https://github.com/JustinRoom/GuidanceDemo 28 | * 29 | * @author jiangshicheng 30 | */ 31 | public class GuidanceDialogFragment extends BaseFragment { 32 | 33 | View btnShowAgain; 34 | 35 | @Nullable 36 | @Override 37 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 38 | View root = inflater.inflate(R.layout.fragment_guidance_dialog, container, false); 39 | btnShowAgain = root.findViewById(R.id.btn_show_again); 40 | btnShowAgain.setOnClickListener(new View.OnClickListener() { 41 | @Override 42 | public void onClick(View v) { 43 | showGuidanceDialog(); 44 | } 45 | }); 46 | return root; 47 | } 48 | 49 | @Override 50 | void onLoadData(Context context) { 51 | btnShowAgain.postDelayed(new Runnable() { 52 | @Override 53 | public void run() { 54 | showGuidanceDialog(); 55 | } 56 | }, 100); 57 | } 58 | 59 | private void showGuidanceDialog() { 60 | final GuidanceDialog dialog = new GuidanceDialog(getContext()); 61 | dialog.setTargetClickListener(new OnTargetClickListener() { 62 | @Override 63 | public boolean onTargetClick(GuidanceLayout layout) { 64 | Toast.makeText(layout.getContext(), "clicked me", Toast.LENGTH_SHORT).show(); 65 | switch (layout.getCurStepIndex()) { 66 | case 0: 67 | showStep(layout, R.id.item_layout_1); 68 | return true; 69 | case 1: 70 | showStep(layout, R.id.item_layout_2); 71 | return true; 72 | case 2: 73 | showStep(layout, R.id.item_layout_3); 74 | return true; 75 | default: 76 | return false; 77 | } 78 | } 79 | }); 80 | dialog.show(); 81 | GuidanceLayout guidanceLayout = dialog.getGuidanceLayout(); 82 | if (guidanceLayout == null) 83 | return; 84 | showStep(guidanceLayout, R.id.item_layout_0); 85 | } 86 | 87 | private void showStep(GuidanceLayout layout, int targetViewId) { 88 | layout.removeAllCustomViews(); 89 | showStep(layout, getView().findViewById(targetViewId)); 90 | } 91 | 92 | private void showStep(GuidanceLayout guidanceLayout, View target) { 93 | Context context = guidanceLayout.getContext(); 94 | int statusBarHeight = ViewDrawingCacheUtils.getStatusBarHeight(context); 95 | int actionBarHeight = ViewDrawingCacheUtils.getActionBarSize(context); 96 | int[] location = ViewDrawingCacheUtils.getWindowLocation(target); 97 | guidanceLayout.updateTargetViewLocation( 98 | target, location[0], 99 | location[1] - statusBarHeight, 100 | new GuidanceLayout.OnInitRippleViewSizeListener() { 101 | @Override 102 | public int onInitializeRippleViewSize(@NonNull Bitmap bitmap) { 103 | return bitmap.getHeight(); 104 | } 105 | }, 106 | true, 107 | new GuidanceLayout.OnRippleViewLocationUpdatedCallback() { 108 | @Override 109 | public void onRippleViewLocationUpdated(@NonNull GuidanceRippleView rippleView, @NonNull Rect targetRect) { 110 | 111 | } 112 | }); 113 | 114 | ImageView imageView = new ImageView(guidanceLayout.getContext()); 115 | imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 116 | imageView.setImageResource(R.drawable.hand_o_up); 117 | guidanceLayout.addCustomView(imageView, new GuidanceLayout.OnCustomViewAddListener() { 118 | 119 | @Override 120 | public void onViewInit(@NonNull ImageView customView, @NonNull FrameLayout.LayoutParams params, @NonNull Rect targetRect) { 121 | customView.measure(0, 0); 122 | params.topMargin = targetRect.bottom + 12; 123 | params.leftMargin = targetRect.left - (customView.getMeasuredWidth() - targetRect.width()) / 2; 124 | } 125 | 126 | @Override 127 | public void onViewAdded(@NonNull ImageView customView, @NonNull Rect targetRect) { 128 | ObjectAnimator animator = ObjectAnimator.ofFloat(customView, View.TRANSLATION_Y, 0, 32, 0) 129 | .setDuration(1200); 130 | animator.setRepeatCount(-1); 131 | animator.start(); 132 | } 133 | }, null); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /app/src/main/java/jsc/exam/com/guidance/fragments/GuidanceLayoutFragment.java: -------------------------------------------------------------------------------- 1 | package jsc.exam.com.guidance.fragments; 2 | 3 | import android.animation.ObjectAnimator; 4 | import android.content.Context; 5 | import android.graphics.Bitmap; 6 | import android.graphics.Rect; 7 | import android.os.Bundle; 8 | import android.support.annotation.NonNull; 9 | import android.support.annotation.Nullable; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.widget.Button; 14 | import android.widget.FrameLayout; 15 | import android.widget.ImageView; 16 | import android.widget.Toast; 17 | 18 | import jsc.exam.com.guidance.R; 19 | import jsc.kit.guidance.GuidanceLayout; 20 | import jsc.kit.guidance.GuidanceRippleView; 21 | import jsc.kit.guidance.ViewDrawingCacheUtils; 22 | 23 | /** 24 | *
Email:1006368252@qq.com 25 | *
QQ:1006368252 26 | *
https://github.com/JustinRoom/GuidanceDemo 27 | * 28 | * @author jiangshicheng 29 | */ 30 | public class GuidanceLayoutFragment extends BaseFragment { 31 | 32 | Button btnShowAgain; 33 | GuidanceLayout layout; 34 | 35 | @Nullable 36 | @Override 37 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 38 | View root = inflater.inflate(R.layout.fragment_guidance_layout, container, false); 39 | btnShowAgain = root.findViewById(R.id.btn_show_again); 40 | layout = root.findViewById(R.id.guidance_layout); 41 | layout.setTargetClickListener(new View.OnClickListener() { 42 | @Override 43 | public void onClick(View v) { 44 | Toast.makeText(layout.getContext(), "clicked me", Toast.LENGTH_SHORT).show(); 45 | switch (layout.getCurStepIndex()) { 46 | case 0: 47 | showStep(layout, R.id.item_layout_1); 48 | break; 49 | case 1: 50 | showStep(layout, R.id.item_layout_2); 51 | break; 52 | case 2: 53 | showStep(layout, R.id.item_layout_3); 54 | break; 55 | default: 56 | layout.setVisibility(View.GONE); 57 | break; 58 | } 59 | } 60 | }); 61 | btnShowAgain.setOnClickListener(new View.OnClickListener() { 62 | @Override 63 | public void onClick(View v) { 64 | layout.setVisibility(View.VISIBLE); 65 | layout.resetStepIndex(); 66 | showStep(layout, R.id.item_layout_0); 67 | } 68 | }); 69 | return root; 70 | } 71 | 72 | @Override 73 | void onLoadData(Context context) { 74 | layout.postDelayed(new Runnable() { 75 | @Override 76 | public void run() { 77 | showStep(layout, R.id.item_layout_0); 78 | } 79 | }, 100); 80 | } 81 | 82 | private void showStep(GuidanceLayout layout, int targetViewId) { 83 | layout.removeAllCustomViews(); 84 | showStep(layout, getView().findViewById(targetViewId)); 85 | } 86 | 87 | private void showStep(GuidanceLayout guidanceLayout, View target) { 88 | Context context = guidanceLayout.getContext(); 89 | int statusBarHeight = ViewDrawingCacheUtils.getStatusBarHeight(context); 90 | int actionBarHeight = ViewDrawingCacheUtils.getActionBarSize(context); 91 | int[] location = ViewDrawingCacheUtils.getWindowLocation(target); 92 | guidanceLayout.updateTargetViewLocation( 93 | target, location[0], 94 | location[1] - statusBarHeight - actionBarHeight, 95 | new GuidanceLayout.OnInitRippleViewSizeListener() { 96 | @Override 97 | public int onInitializeRippleViewSize(@NonNull Bitmap bitmap) { 98 | return bitmap.getHeight(); 99 | } 100 | }, 101 | true, 102 | new GuidanceLayout.OnRippleViewLocationUpdatedCallback() { 103 | @Override 104 | public void onRippleViewLocationUpdated(@NonNull GuidanceRippleView rippleView, @NonNull Rect targetRect) { 105 | 106 | } 107 | }); 108 | 109 | ImageView imageView = new ImageView(guidanceLayout.getContext()); 110 | imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 111 | imageView.setImageResource(R.drawable.hand_o_up); 112 | guidanceLayout.addCustomView(imageView, new GuidanceLayout.OnCustomViewAddListener() { 113 | 114 | @Override 115 | public void onViewInit(@NonNull ImageView customView, @NonNull FrameLayout.LayoutParams params, @NonNull Rect targetRect) { 116 | customView.measure(0, 0); 117 | params.topMargin = targetRect.bottom + 12; 118 | params.leftMargin = targetRect.left - (customView.getMeasuredWidth() - targetRect.width()) / 2; 119 | } 120 | 121 | @Override 122 | public void onViewAdded(@NonNull ImageView customView, @NonNull Rect targetRect) { 123 | ObjectAnimator animator = ObjectAnimator.ofFloat(customView, View.TRANSLATION_Y, 0, 32, 0) 124 | .setDuration(1200); 125 | animator.setRepeatCount(-1); 126 | animator.start(); 127 | } 128 | }, null); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /app/src/main/java/jsc/exam/com/guidance/fragments/GuidancePopupWindowFragment.java: -------------------------------------------------------------------------------- 1 | package jsc.exam.com.guidance.fragments; 2 | 3 | import android.animation.ObjectAnimator; 4 | import android.content.Context; 5 | import android.graphics.Bitmap; 6 | import android.graphics.Rect; 7 | import android.os.Bundle; 8 | import android.support.annotation.NonNull; 9 | import android.support.annotation.Nullable; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.widget.Button; 14 | import android.widget.FrameLayout; 15 | import android.widget.ImageView; 16 | import android.widget.Toast; 17 | 18 | import jsc.exam.com.guidance.R; 19 | import jsc.kit.guidance.GuidanceLayout; 20 | import jsc.kit.guidance.GuidancePopupWindow; 21 | import jsc.kit.guidance.GuidanceRippleView; 22 | import jsc.kit.guidance.OnTargetClickListener; 23 | import jsc.kit.guidance.ViewDrawingCacheUtils; 24 | 25 | /** 26 | *
Email:1006368252@qq.com 27 | *
QQ:1006368252 28 | *
https://github.com/JustinRoom/GuidanceDemo 29 | * 30 | * @author jiangshicheng 31 | */ 32 | public class GuidancePopupWindowFragment extends BaseFragment { 33 | 34 | Button btnContent; 35 | 36 | @Nullable 37 | @Override 38 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 39 | View root = inflater.inflate(R.layout.fragment_guidance_popup_window, container, false); 40 | btnContent = root.findViewById(R.id.btn_content); 41 | root.findViewById(R.id.btn_content).setOnClickListener(new View.OnClickListener() { 42 | @Override 43 | public void onClick(View v) { 44 | showContentGuidance(); 45 | } 46 | }); 47 | root.findViewById(R.id.btn_window).setOnClickListener(new View.OnClickListener() { 48 | @Override 49 | public void onClick(View v) { 50 | // showWindowGuidanceDialog(); 51 | } 52 | }); 53 | return root; 54 | } 55 | 56 | @Override 57 | void onLoadData(Context context) { 58 | btnContent.postDelayed(new Runnable() { 59 | @Override 60 | public void run() { 61 | showContentGuidance(); 62 | } 63 | }, 100); 64 | } 65 | 66 | private void showContentGuidance() { 67 | final GuidancePopupWindow popupWindow = new GuidancePopupWindow(getActivity()); 68 | popupWindow.setTargetClickListener(new OnTargetClickListener() { 69 | @Override 70 | public boolean onTargetClick(GuidanceLayout layout) { 71 | Toast.makeText(layout.getContext(), "clicked me", Toast.LENGTH_SHORT).show(); 72 | switch (layout.getCurStepIndex()) { 73 | case 0: 74 | layout.removeAllCustomViews(); 75 | showStep(layout, R.id.item_layout_1); 76 | return true; 77 | case 1: 78 | layout.removeAllCustomViews(); 79 | showStep(layout, R.id.item_layout_2); 80 | return true; 81 | case 2: 82 | layout.removeAllCustomViews(); 83 | showStep(layout, R.id.item_layout_3); 84 | return true; 85 | default: 86 | return false; 87 | } 88 | } 89 | }); 90 | popupWindow.show(); 91 | GuidanceLayout guidanceLayout = popupWindow.getGuidanceLayout(); 92 | showStep(guidanceLayout, R.id.item_layout_0); 93 | 94 | } 95 | 96 | private void showStep(GuidanceLayout layout, int targetViewId) { 97 | layout.removeAllCustomViews(); 98 | showStep(layout, getView().findViewById(targetViewId)); 99 | } 100 | 101 | private void showStep(GuidanceLayout guidanceLayout, View target) { 102 | int statusBarHeight = ViewDrawingCacheUtils.getStatusBarHeight(getContext()); 103 | int actionBarHeight = ViewDrawingCacheUtils.getActionBarSize(getContext()); 104 | int[] location = ViewDrawingCacheUtils.getWindowLocation(target); 105 | guidanceLayout.updateTargetViewLocation( 106 | target, location[0], 107 | location[1] - statusBarHeight - actionBarHeight, 108 | new GuidanceLayout.OnInitRippleViewSizeListener() { 109 | @Override 110 | public int onInitializeRippleViewSize(@NonNull Bitmap bitmap) { 111 | return bitmap.getHeight(); 112 | } 113 | }, true, 114 | new GuidanceLayout.OnRippleViewLocationUpdatedCallback() { 115 | @Override 116 | public void onRippleViewLocationUpdated(@NonNull GuidanceRippleView rippleView, @NonNull Rect targetRect) { 117 | 118 | } 119 | }); 120 | 121 | ImageView imageView = new ImageView(guidanceLayout.getContext()); 122 | imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 123 | imageView.setImageResource(R.drawable.hand_o_up); 124 | guidanceLayout.addCustomView(imageView, new GuidanceLayout.OnCustomViewAddListener() { 125 | @Override 126 | public void onViewInit(@NonNull ImageView customView, @NonNull FrameLayout.LayoutParams params, @NonNull Rect targetRect) { 127 | customView.measure(0, 0); 128 | params.topMargin = targetRect.bottom + 12; 129 | params.leftMargin = targetRect.left - (customView.getMeasuredWidth() - targetRect.width()) / 2; 130 | } 131 | 132 | @Override 133 | public void onViewAdded(@NonNull ImageView customView, @NonNull Rect targetRect) { 134 | ObjectAnimator animator = ObjectAnimator.ofFloat(customView, View.TRANSLATION_Y, 0, 32, 0) 135 | .setDuration(1200); 136 | animator.setRepeatCount(-1); 137 | animator.start(); 138 | } 139 | }, null); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /app/src/main/java/jsc/exam/com/guidance/fragments/GuidanceRippleViewFragment.java: -------------------------------------------------------------------------------- 1 | package jsc.exam.com.guidance.fragments; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.annotation.NonNull; 6 | import android.support.annotation.Nullable; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | 11 | import jsc.exam.com.guidance.R; 12 | import jsc.kit.guidance.GuidanceRippleView; 13 | 14 | /** 15 | *
Email:1006368252@qq.com 16 | *
QQ:1006368252 17 | *
https://github.com/JustinRoom/GuidanceDemo 18 | * 19 | * @author jiangshicheng 20 | */ 21 | public class GuidanceRippleViewFragment extends BaseFragment { 22 | 23 | GuidanceRippleView rippleView1; 24 | GuidanceRippleView rippleView2; 25 | GuidanceRippleView rippleView3; 26 | GuidanceRippleView rippleView4; 27 | 28 | @Nullable 29 | @Override 30 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 31 | View root = inflater.inflate(R.layout.fragment_guidance_ripple_view, container, false); 32 | rippleView1 = root.findViewById(R.id.ripple_view_1); 33 | rippleView2 = root.findViewById(R.id.ripple_view_2); 34 | rippleView3 = root.findViewById(R.id.ripple_view_3); 35 | rippleView4 = root.findViewById(R.id.ripple_view_4); 36 | 37 | root.findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() { 38 | @Override 39 | public void onClick(View v) { 40 | rippleView1.start(); 41 | rippleView2.start(); 42 | rippleView3.start(); 43 | rippleView4.start(); 44 | getView().findViewById(R.id.btn_start).setEnabled(false); 45 | getView().findViewById(R.id.btn_pause).setEnabled(true); 46 | getView().findViewById(R.id.btn_resume).setEnabled(false); 47 | getView().findViewById(R.id.btn_stop).setEnabled(true); 48 | } 49 | }); 50 | root.findViewById(R.id.btn_pause).setOnClickListener(new View.OnClickListener() { 51 | @Override 52 | public void onClick(View v) { 53 | rippleView1.pause(); 54 | rippleView2.pause(); 55 | rippleView3.pause(); 56 | rippleView4.pause(); 57 | getView().findViewById(R.id.btn_start).setEnabled(false); 58 | getView().findViewById(R.id.btn_pause).setEnabled(false); 59 | getView().findViewById(R.id.btn_resume).setEnabled(true); 60 | getView().findViewById(R.id.btn_stop).setEnabled(true); 61 | } 62 | }); 63 | root.findViewById(R.id.btn_resume).setOnClickListener(new View.OnClickListener() { 64 | @Override 65 | public void onClick(View v) { 66 | rippleView1.resume(); 67 | rippleView2.resume(); 68 | rippleView3.resume(); 69 | rippleView4.resume(); 70 | getView().findViewById(R.id.btn_start).setEnabled(false); 71 | getView().findViewById(R.id.btn_pause).setEnabled(true); 72 | getView().findViewById(R.id.btn_resume).setEnabled(false); 73 | getView().findViewById(R.id.btn_stop).setEnabled(true); 74 | } 75 | }); 76 | root.findViewById(R.id.btn_stop).setOnClickListener(new View.OnClickListener() { 77 | @Override 78 | public void onClick(View v) { 79 | rippleView1.stop(); 80 | rippleView2.stop(); 81 | rippleView3.stop(); 82 | rippleView4.stop(); 83 | getView().findViewById(R.id.btn_start).setEnabled(true); 84 | getView().findViewById(R.id.btn_pause).setEnabled(false); 85 | getView().findViewById(R.id.btn_resume).setEnabled(false); 86 | getView().findViewById(R.id.btn_stop).setEnabled(false); 87 | } 88 | }); 89 | return root; 90 | } 91 | 92 | @Override 93 | void onLoadData(Context context) { 94 | 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /app/src/main/java/jsc/exam/com/guidance/retrofit/ApiService.java: -------------------------------------------------------------------------------- 1 | package jsc.exam.com.guidance.retrofit; 2 | 3 | import io.reactivex.Observable; 4 | import jsc.exam.com.guidance.BuildConfig; 5 | import retrofit2.http.GET; 6 | 7 | public interface ApiService { 8 | 9 | @GET(BuildConfig.VERSION_URL) 10 | Observable getVersionInfo(); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/jsc/exam/com/guidance/retrofit/CustomHttpClient.java: -------------------------------------------------------------------------------- 1 | package jsc.exam.com.guidance.retrofit; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.util.Pair; 6 | 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | import io.reactivex.annotations.NonNull; 14 | import jsc.exam.com.guidance.utils.ConnectivityHelper; 15 | import okhttp3.Cache; 16 | import okhttp3.CacheControl; 17 | import okhttp3.Interceptor; 18 | import okhttp3.OkHttpClient; 19 | import okhttp3.Request; 20 | import okhttp3.Response; 21 | import okhttp3.logging.HttpLoggingInterceptor; 22 | 23 | /** 24 | * 25 | *
Email:1006368252@qq.com 26 | *
QQ:1006368252 27 | *
https://github.com/JustinRoom/Guidance 28 | * 29 | * create time: 6/6/2018 6:02 PM 30 | * @author jiangshicheng 31 | */ 32 | public class CustomHttpClient { 33 | private boolean showLog = false; 34 | private int connectTimeout = 10_000; 35 | private int readTimeout = 10_000; 36 | private int writeTimeout = 10_000; 37 | private List> headers = null; 38 | private List interceptors = null; 39 | private Context context; 40 | private Cache cache; 41 | 42 | public CustomHttpClient setShowLog(boolean showLog) { 43 | this.showLog = showLog; 44 | return this; 45 | } 46 | 47 | public CustomHttpClient setConnectTimeout(int connectTimeout) { 48 | this.connectTimeout = connectTimeout; 49 | return this; 50 | } 51 | 52 | public CustomHttpClient setReadTimeout(int readTimeout) { 53 | this.readTimeout = readTimeout; 54 | return this; 55 | } 56 | 57 | public CustomHttpClient setWriteTimeout(int writeTimeout) { 58 | this.writeTimeout = writeTimeout; 59 | return this; 60 | } 61 | 62 | public CustomHttpClient addHeader(@NonNull Pair header) { 63 | if (headers == null) { 64 | headers = new ArrayList<>(); 65 | } 66 | headers.add(header); 67 | return this; 68 | } 69 | 70 | public CustomHttpClient addInterceptor(Interceptor interceptor) { 71 | if (interceptors == null) { 72 | interceptors = new ArrayList<>(); 73 | } 74 | interceptors.add(interceptor); 75 | return this; 76 | } 77 | 78 | 79 | public CustomHttpClient setContext(Application applicationContext) { 80 | this.context = applicationContext; 81 | return this; 82 | } 83 | 84 | /** 85 | * Set network cache config. 86 | * You must call method {@link #setContext(Application)} before calling this method. 87 | * 88 | * @param cacheFileName cache file name. 89 | * @param maxCacheSize cache max size. 90 | * @return CustomHttpClient 91 | */ 92 | public CustomHttpClient setCache(String cacheFileName, long maxCacheSize) { 93 | if (context != null){ 94 | File cacheFile = new File(context.getCacheDir(), cacheFileName); 95 | cache = new Cache(cacheFile, maxCacheSize); 96 | } 97 | return this; 98 | } 99 | 100 | public OkHttpClient createOkHttpClient() { 101 | OkHttpClient.Builder builder = new OkHttpClient.Builder(); 102 | builder.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS) 103 | .readTimeout(readTimeout, TimeUnit.MILLISECONDS) 104 | .writeTimeout(writeTimeout, TimeUnit.MILLISECONDS); 105 | //拦截服务器端的Log日志并打印,如果未debug状态就打印日志,否则就什么都不做 106 | builder.addInterceptor(new HttpLoggingInterceptor().setLevel(showLog ? HttpLoggingInterceptor.Level.BODY : HttpLoggingInterceptor.Level.NONE)); 107 | 108 | if (headers != null && !headers.isEmpty()) 109 | builder.addInterceptor(createHeaderInterceptor()); 110 | 111 | if (interceptors != null && !interceptors.isEmpty()) { 112 | for (Interceptor it : interceptors) { 113 | builder.addInterceptor(it); 114 | } 115 | } 116 | 117 | if (cache != null) { 118 | builder.cache(cache); 119 | builder.addNetworkInterceptor(createCacheInterceptor()); 120 | } 121 | return builder.build(); 122 | } 123 | 124 | private Interceptor createHeaderInterceptor() { 125 | return new Interceptor() { 126 | @Override 127 | public Response intercept(Chain chain) throws IOException { 128 | Request original = chain.request(); 129 | Request.Builder requestBuilder = original.newBuilder(); 130 | for (Pair header : headers) { 131 | requestBuilder.addHeader(header.first, header.second); 132 | } 133 | Request request = requestBuilder.build(); 134 | return chain.proceed(request); 135 | } 136 | }; 137 | } 138 | 139 | private Interceptor createCacheInterceptor() { 140 | return new Interceptor() { 141 | @Override 142 | public Response intercept(Chain chain) throws IOException { 143 | Request request = chain.request(); 144 | // Add FORCE_CACHE cache control for each request if network is not available. 145 | if (!ConnectivityHelper.isNetworkAvailable(context)) { 146 | request = request.newBuilder() 147 | .cacheControl(CacheControl.FORCE_CACHE) 148 | .build(); 149 | } 150 | 151 | Response originalResponse = chain.proceed(request); 152 | if (ConnectivityHelper.isNetworkAvailable(context)) { 153 | String cacheControl = request.cacheControl().toString(); 154 | // Add cache control header for response same as request's while network is available. 155 | return originalResponse.newBuilder() 156 | .header("Cache-Control", cacheControl) 157 | .build(); 158 | } else { 159 | // Add cache control header for response to FORCE_CACHE while network is not available. 160 | return originalResponse.newBuilder() 161 | .header("Cache-Control", CacheControl.FORCE_CACHE.toString()) 162 | .build(); 163 | } 164 | } 165 | }; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /app/src/main/java/jsc/exam/com/guidance/retrofit/CustomRetrofit.java: -------------------------------------------------------------------------------- 1 | package jsc.exam.com.guidance.retrofit; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import io.reactivex.annotations.NonNull; 7 | import okhttp3.OkHttpClient; 8 | import retrofit2.CallAdapter; 9 | import retrofit2.Converter; 10 | import retrofit2.Retrofit; 11 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; 12 | import retrofit2.converter.gson.GsonConverterFactory; 13 | import retrofit2.converter.scalars.ScalarsConverterFactory; 14 | 15 | /** 16 | *
Email:1006368252@qq.com 17 | *
QQ:1006368252 18 | *
https://github.com/JustinRoom/Guidance 19 | * 20 | * @author jiangshicheng 21 | */ 22 | public class CustomRetrofit { 23 | private String baseUrl; 24 | private OkHttpClient okHttpClient; 25 | private List converterFactories = new ArrayList<>(); 26 | private List callAdapterFactories = new ArrayList<>(); 27 | private boolean isDefaultFactoriesAdded = false; 28 | 29 | public CustomRetrofit setBaseUrl(@NonNull String baseUrl) { 30 | this.baseUrl = baseUrl; 31 | return this; 32 | } 33 | 34 | public CustomRetrofit setOkHttpClient(@NonNull OkHttpClient okHttpClient) { 35 | this.okHttpClient = okHttpClient; 36 | return this; 37 | } 38 | 39 | private CustomRetrofit addConverterFactory(@NonNull Converter.Factory factory){ 40 | checkIsDefaultFactoriesAdded(); 41 | converterFactories.add(factory); 42 | return this; 43 | } 44 | 45 | private CustomRetrofit addCallAdapterFactory(@NonNull CallAdapter.Factory factory){ 46 | checkIsDefaultFactoriesAdded(); 47 | callAdapterFactories.add(factory); 48 | return this; 49 | } 50 | 51 | public Retrofit createRetrofit() { 52 | if (baseUrl == null || baseUrl.trim().length() == 0) 53 | throw new IllegalArgumentException("Base url can't be null."); 54 | 55 | if (okHttpClient == null) 56 | throw new IllegalArgumentException("You have not initialized OkHttpClient."); 57 | 58 | Retrofit.Builder builder = new Retrofit.Builder(); 59 | builder.baseUrl(baseUrl).client(okHttpClient); 60 | checkIsDefaultFactoriesAdded(); 61 | for (Converter.Factory factory : converterFactories) { 62 | builder.addConverterFactory(factory); 63 | } 64 | for (CallAdapter.Factory factory : callAdapterFactories) { 65 | builder.addCallAdapterFactory(factory); 66 | } 67 | return builder.build(); 68 | } 69 | 70 | private void checkIsDefaultFactoriesAdded(){ 71 | if (isDefaultFactoriesAdded) 72 | return; 73 | 74 | //增加返回值为String的支持 75 | converterFactories.add(ScalarsConverterFactory.create()); 76 | //增加返回值为Gson的支持(以实体类返回) 77 | converterFactories.add(GsonConverterFactory.create()); 78 | //增加返回值为Oservable的支持 79 | callAdapterFactories.add(RxJava2CallAdapterFactory.create()); 80 | isDefaultFactoriesAdded = true; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/java/jsc/exam/com/guidance/retrofit/LoadingDialogObserver.java: -------------------------------------------------------------------------------- 1 | package jsc.exam.com.guidance.retrofit; 2 | 3 | import android.app.Dialog; 4 | import android.content.DialogInterface; 5 | import android.os.Handler; 6 | import android.os.Looper; 7 | import android.os.Message; 8 | 9 | import io.reactivex.Observer; 10 | import io.reactivex.disposables.Disposable; 11 | 12 | /** 13 | * 14 | *
Email:1006368252@qq.com 15 | *
QQ:1006368252 16 | *
https://github.com/JustinRoom/Guidance 17 | * 18 | * create time: 6/7/2018 1:00 PM 19 | * @author jiangshicheng 20 | */ 21 | public abstract class LoadingDialogObserver implements Observer, DialogInterface.OnCancelListener { 22 | 23 | private final int SHOW_DIALOG = 0x6990; 24 | private final int HIDE_DIALOG = 0x6991; 25 | private Dialog loadingDialog; 26 | private boolean ifShowDialog; 27 | private Handler handler = new Handler(Looper.getMainLooper(), new Handler.Callback() { 28 | @Override 29 | public boolean handleMessage(Message message) { 30 | switch (message.what) { 31 | case SHOW_DIALOG: 32 | if (loadingDialog != null && !loadingDialog.isShowing()) 33 | loadingDialog.show(); 34 | break; 35 | case HIDE_DIALOG: 36 | if (loadingDialog != null && loadingDialog.isShowing()) 37 | loadingDialog.dismiss(); 38 | break; 39 | } 40 | return true; 41 | } 42 | }); 43 | private Disposable disposable; 44 | 45 | /** 46 | * Constructor. 47 | */ 48 | public LoadingDialogObserver() { 49 | this(null); 50 | } 51 | 52 | /** 53 | * Constructor. 54 | * @param loadingDialog loading dialog 55 | */ 56 | public LoadingDialogObserver(Dialog loadingDialog) { 57 | this(loadingDialog, true); 58 | } 59 | 60 | /** 61 | * Constructor. 62 | * 63 | * @param loadingDialog loading dialog 64 | * @param ifShowDialog Show loadingDialog if true else not. 65 | */ 66 | public LoadingDialogObserver(Dialog loadingDialog, boolean ifShowDialog) { 67 | this.loadingDialog = loadingDialog; 68 | this.ifShowDialog = ifShowDialog; 69 | if (loadingDialog != null) 70 | loadingDialog.setOnCancelListener(this); 71 | } 72 | 73 | @Override 74 | public void onSubscribe(Disposable d) { 75 | disposable = d; 76 | if (ifShowDialog) 77 | handler.sendEmptyMessage(SHOW_DIALOG); 78 | onStart(d); 79 | } 80 | 81 | @Override 82 | public void onNext(T t) { 83 | try { 84 | onResult(t); 85 | } catch (Exception e){ 86 | onError(e); 87 | } 88 | } 89 | 90 | @Override 91 | public void onError(Throwable e) { 92 | if (ifShowDialog) 93 | handler.sendEmptyMessage(HIDE_DIALOG); 94 | onException(e); 95 | onCompleteOrCancel(disposable); 96 | } 97 | 98 | @Override 99 | public void onComplete() { 100 | if (ifShowDialog) 101 | handler.sendEmptyMessage(HIDE_DIALOG); 102 | onCompleteOrCancel(disposable); 103 | } 104 | 105 | @Override 106 | public void onCancel(DialogInterface dialog) { 107 | disposable.dispose(); 108 | onCompleteOrCancel(disposable); 109 | } 110 | 111 | /** 112 | * Show loading dialog here if necessary. 113 | * @param disposable disposable, the same as the return of {@link io.reactivex.Observable#subscribe(Observer)}. 114 | */ 115 | public abstract void onStart(Disposable disposable); 116 | 117 | /** 118 | * Call back the response. 119 | * @param t response 120 | */ 121 | public abstract void onResult(T t); 122 | 123 | /** 124 | * Call back when a exception appears. 125 | * @param e exception 126 | */ 127 | public abstract void onException(Throwable e); 128 | 129 | /** 130 | * Call back when {@link Observer#onComplete()} or loading dialog is canceled. 131 | * @param disposable disposable, the same as the return of {@link io.reactivex.Observable#subscribe(Observer)}. 132 | */ 133 | public abstract void onCompleteOrCancel(Disposable disposable); 134 | } 135 | -------------------------------------------------------------------------------- /app/src/main/java/jsc/exam/com/guidance/utils/CompatResourceUtils.java: -------------------------------------------------------------------------------- 1 | package jsc.exam.com.guidance.utils; 2 | 3 | import android.content.Context; 4 | import android.graphics.drawable.Drawable; 5 | import android.support.annotation.ColorRes; 6 | import android.support.annotation.DimenRes; 7 | import android.support.annotation.DrawableRes; 8 | import android.support.annotation.NonNull; 9 | import android.support.v4.app.Fragment; 10 | import android.util.TypedValue; 11 | import android.view.View; 12 | 13 | /** 14 | *
Email:1006368252@qq.com 15 | *
QQ:1006368252 16 | *
https://github.com/JustinRoom/Guidance 17 | * 18 | * @author jiangshicheng 19 | */ 20 | public class CompatResourceUtils { 21 | 22 | //color 23 | public static int getColor(@NonNull Context context, @ColorRes int resId){ 24 | int color; 25 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M){ 26 | color = context.getResources().getColor(resId, context.getTheme()); 27 | } else { 28 | color = context.getResources().getColor(resId); 29 | } 30 | return color; 31 | } 32 | 33 | //drawable-xxhdpi 34 | public static Drawable getDrawable(@NonNull Context context, @DrawableRes int resId){ 35 | Drawable drawable; 36 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M){ 37 | drawable = context.getResources().getDrawable(resId, context.getTheme()); 38 | } else { 39 | drawable = context.getResources().getDrawable(resId); 40 | } 41 | return drawable; 42 | } 43 | 44 | //dimension 45 | public static int getDimensionPixelSize(@NonNull Context context, @DimenRes int id){ 46 | return context.getResources().getDimensionPixelSize(id); 47 | } 48 | 49 | public static int getDimensionPixelSize(@NonNull View view, @DimenRes int id){ 50 | return view.getResources().getDimensionPixelSize(id); 51 | } 52 | 53 | public static int getDimensionPixelSize(@NonNull Fragment fragment, @DimenRes int id){ 54 | return fragment.getResources().getDimensionPixelSize(id); 55 | } 56 | 57 | public static int dp2Px(@NonNull Context context, float dp){ 58 | return (int) (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics()) + .5f); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/jsc/exam/com/guidance/utils/ConnectivityHelper.java: -------------------------------------------------------------------------------- 1 | package jsc.exam.com.guidance.utils; 2 | 3 | import android.content.Context; 4 | import android.net.ConnectivityManager; 5 | import android.net.NetworkInfo; 6 | 7 | import java.util.Objects; 8 | 9 | /** 10 | * 11 | * ConnectivityHelper 网络工具 12 | * 13 | * 14 | */ 15 | public class ConnectivityHelper { 16 | 17 | public static boolean isNetworkAvailable(Context context) { 18 | ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 19 | Objects.requireNonNull(cm); 20 | NetworkInfo networkInfo = cm.getActiveNetworkInfo(); 21 | return networkInfo != null && networkInfo.isAvailable(); 22 | } 23 | 24 | /** 25 | * 判断网络是否可用 26 | * 27 | * @param context context 28 | * @return boolean 29 | */ 30 | public static boolean isConnectivityAvailable(Context context) { 31 | // 判断网络是否可用 32 | ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 33 | Objects.requireNonNull(cm); 34 | NetworkInfo info = cm.getActiveNetworkInfo(); 35 | if (info == null || !info.isConnected()) { 36 | return false; 37 | } 38 | return info.isAvailable() || info.isRoaming(); 39 | } 40 | 41 | 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/jsc/exam/com/guidance/utils/WindowUtils.java: -------------------------------------------------------------------------------- 1 | package jsc.exam.com.guidance.utils; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.content.res.TypedArray; 6 | import android.graphics.drawable.Drawable; 7 | import android.os.Build; 8 | import android.support.annotation.NonNull; 9 | import android.util.TypedValue; 10 | 11 | /** 12 | *
Email:1006368252@qq.com 13 | *
QQ:1006368252 14 | *
https://github.com/JustinRoom/Guidance 15 | * 16 | * @author jiangshicheng 17 | */ 18 | public final class WindowUtils { 19 | 20 | /** 21 | * Get status bar height. 22 | * 23 | * @param context context 24 | * @return the height of status bar 25 | */ 26 | public static int getStatusBarHeight(@NonNull Context context) { 27 | int statusBarHeight = 0; 28 | int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); 29 | if (resourceId > 0) { 30 | statusBarHeight = CompatResourceUtils.getDimensionPixelSize(context, resourceId); 31 | } 32 | return statusBarHeight; 33 | } 34 | 35 | /** 36 | * Get action bar height. 37 | * 38 | * @param context context 39 | * @return the height of action bar 40 | */ 41 | public static int getActionBarSize(@NonNull Context context) { 42 | TypedValue typedValue = new TypedValue(); 43 | int[] attribute = new int[]{android.R.attr.actionBarSize}; 44 | TypedArray array = context.obtainStyledAttributes(typedValue.resourceId, attribute); 45 | int actionBarSize = array.getDimensionPixelSize(0, 0); 46 | array.recycle(); 47 | return actionBarSize; 48 | } 49 | 50 | /** 51 | * Get system selectable item background borderless. 52 | * @param context context 53 | * @return selectable item background borderless 54 | */ 55 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 56 | public static Drawable getSelectableItemBackgroundBorderless(Context context){ 57 | TypedValue typedValue = new TypedValue(); 58 | context.getTheme().resolveAttribute(android.R.attr.selectableItemBackgroundBorderless, typedValue, true); 59 | int[] attribute = new int[]{android.R.attr.selectableItemBackgroundBorderless}; 60 | TypedArray typedArray = context.obtainStyledAttributes(typedValue.resourceId, attribute); 61 | Drawable drawable = typedArray.getDrawable(0); 62 | typedArray.recycle(); 63 | return drawable; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/jsc/exam/com/guidance/widgets/DotView.java: -------------------------------------------------------------------------------- 1 | package jsc.exam.com.guidance.widgets; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.graphics.Outline; 6 | import android.graphics.Path; 7 | import android.os.Build; 8 | import android.support.annotation.IntDef; 9 | import android.support.annotation.IntRange; 10 | import android.support.annotation.Nullable; 11 | import android.support.v7.widget.AppCompatTextView; 12 | import android.util.AttributeSet; 13 | import android.util.Log; 14 | import android.view.Gravity; 15 | import android.view.View; 16 | import android.view.ViewOutlineProvider; 17 | 18 | import java.lang.annotation.Retention; 19 | import java.lang.annotation.RetentionPolicy; 20 | 21 | /** 22 | *
Email:1006368252@qq.com 23 | *
QQ:1006368252 24 | *
https://github.com/JustinRoom/Guidance 25 | * 26 | * @author jiangshicheng 27 | */ 28 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 29 | public class DotView extends AppCompatTextView { 30 | 31 | public final static int CIRCULAR = 0; 32 | public final static int SQUARE = 1; 33 | public final static int ROUND_CORNER_SQUARE = 2; 34 | public final static int TRIANGLE = 3; 35 | @IntDef({CIRCULAR, SQUARE, ROUND_CORNER_SQUARE, TRIANGLE}) 36 | @Retention(RetentionPolicy.SOURCE) 37 | public @interface DotShape { 38 | } 39 | 40 | int shape = CIRCULAR; 41 | float radius = 0; 42 | int unReadCount = 0; 43 | 44 | public DotView(Context context) { 45 | super(context); 46 | initAttr(context, null, 0); 47 | } 48 | 49 | public DotView(Context context, @Nullable AttributeSet attrs) { 50 | super(context, attrs); 51 | initAttr(context, attrs, 0); 52 | } 53 | 54 | public DotView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 55 | super(context, attrs, defStyleAttr); 56 | initAttr(context, attrs, defStyleAttr); 57 | } 58 | 59 | private void initAttr(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 60 | setGravity(Gravity.CENTER); 61 | setClipToOutline(true); 62 | setOutlineProvider(new ViewOutlineProvider() { 63 | @Override 64 | public void getOutline(View view, Outline outline) { 65 | switch (shape){ 66 | case CIRCULAR: 67 | outline.setOval(0, 0, view.getWidth(), view.getHeight()); 68 | break; 69 | case SQUARE: 70 | outline.setRect(0, 0, view.getWidth(), view.getHeight()); 71 | break; 72 | case ROUND_CORNER_SQUARE: 73 | outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), radius); 74 | break; 75 | case TRIANGLE: 76 | Path path = new Path(); 77 | path.moveTo(view.getWidth() / 2.0f, 0); 78 | path.lineTo(0, view.getHeight()); 79 | path.lineTo(view.getWidth(), view.getHeight()); 80 | path.close(); 81 | Log.i("DotView", "getOutline: isConvex =" + path.isConvex()); 82 | outline.setConvexPath(path); 83 | break; 84 | } 85 | } 86 | }); 87 | } 88 | 89 | public int getUnReadCount() { 90 | return unReadCount; 91 | } 92 | 93 | public void setUnReadCount(@IntRange(from = 0) int unReadCount) { 94 | this.unReadCount = unReadCount; 95 | if (unReadCount > 99) 96 | setText("99+"); 97 | else if (unReadCount > 0) 98 | setText(String.valueOf(unReadCount)); 99 | else 100 | setText(""); 101 | } 102 | 103 | public void setShape(@DotShape int shape) { 104 | setShape(shape, 0); 105 | } 106 | 107 | public void setShape(@DotShape int dotShape, float roundRadius) { 108 | this.shape = dotShape; 109 | this.radius = roundRadius; 110 | invalidateOutline(); 111 | } 112 | 113 | @Override 114 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 115 | super.onMeasure(widthMeasureSpec, widthMeasureSpec); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /app/src/main/java/jsc/exam/com/guidance/widgets/JSCItemLayout.java: -------------------------------------------------------------------------------- 1 | package jsc.exam.com.guidance.widgets; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Color; 6 | import android.support.annotation.DrawableRes; 7 | import android.support.annotation.Nullable; 8 | import android.support.annotation.StringRes; 9 | import android.support.v7.widget.AppCompatImageView; 10 | import android.util.AttributeSet; 11 | import android.util.TypedValue; 12 | import android.view.Gravity; 13 | import android.view.ViewGroup; 14 | import android.widget.FrameLayout; 15 | import android.widget.ImageView; 16 | import android.widget.LinearLayout; 17 | import android.widget.TextView; 18 | 19 | import jsc.exam.com.guidance.R; 20 | import jsc.exam.com.guidance.utils.CompatResourceUtils; 21 | 22 | /** 23 | *
Email:1006368252@qq.com 24 | *
QQ:1006368252 25 | *
https://github.com/JustinRoom/Guidance 26 | * 27 | * @author jiangshicheng 28 | */ 29 | public class JSCItemLayout extends FrameLayout { 30 | 31 | private ImageView iconView; 32 | private TextView labelView; 33 | private TextView valueView; 34 | private DotView dotView; 35 | private ImageView arrowView; 36 | 37 | public JSCItemLayout(Context context) { 38 | super(context); 39 | initAttr(context, null, 0); 40 | } 41 | 42 | public JSCItemLayout(Context context, @Nullable AttributeSet attrs) { 43 | super(context, attrs); 44 | initAttr(context, attrs, 0); 45 | } 46 | 47 | public JSCItemLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 48 | super(context, attrs, defStyleAttr); 49 | initAttr(context, attrs, defStyleAttr); 50 | } 51 | 52 | private void initAttr(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 53 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.JSCItemLayout, defStyleAttr, 0); 54 | 55 | LayoutParams contentParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 56 | contentParams.gravity = Gravity.CENTER_VERTICAL; 57 | LinearLayout layout = new LinearLayout(context); 58 | layout.setOrientation(LinearLayout.HORIZONTAL); 59 | layout.setGravity(Gravity.CENTER_VERTICAL); 60 | addView(layout, contentParams); 61 | 62 | //icon 63 | iconView = new AppCompatImageView(context); 64 | iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 65 | layout.addView(iconView, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)); 66 | iconView.setImageResource(a.getResourceId(R.styleable.JSCItemLayout_il_icon, R.drawable.kit_ic_assignment_blue_24dp)); 67 | 68 | //label 69 | labelView = new TextView(context); 70 | labelView.setTextColor(0xFF666666); 71 | labelView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); 72 | layout.addView(labelView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)); 73 | if (a.hasValue(R.styleable.JSCItemLayout_il_label)) { 74 | labelView.setText(a.getString(R.styleable.JSCItemLayout_il_label)); 75 | } 76 | 77 | //value 78 | valueView = new TextView(context); 79 | valueView.setTextColor(0xFF333333); 80 | valueView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); 81 | layout.addView(valueView, new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1)); 82 | if (a.hasValue(R.styleable.JSCItemLayout_il_value)) { 83 | valueView.setText(a.getString(R.styleable.JSCItemLayout_il_value)); 84 | } 85 | 86 | //red dot 87 | int size = CompatResourceUtils.getDimensionPixelSize(this, R.dimen.space_8); 88 | dotView = new DotView(context); 89 | dotView.setBackgroundColor(Color.RED); 90 | dotView.setTextColor(Color.WHITE); 91 | if (a.hasValue(R.styleable.JSCItemLayout_il_dotSize)){ 92 | size = a.getDimensionPixelSize(R.styleable.JSCItemLayout_il_dotSize, 0); 93 | } 94 | dotView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10); 95 | layout.addView(dotView, new LinearLayout.LayoutParams(size, size)); 96 | showDotView(a.getBoolean(R.styleable.JSCItemLayout_il_showDot, false)); 97 | 98 | //right arrow 99 | arrowView = new AppCompatImageView(context); 100 | arrowView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 101 | layout.addView(arrowView, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)); 102 | arrowView.setImageResource(a.getResourceId(R.styleable.JSCItemLayout_il_arrowIcon, R.drawable.kit_ic_chevron_right_gray_24dp)); 103 | 104 | a.recycle(); 105 | } 106 | 107 | public ImageView getIconView() { 108 | return iconView; 109 | } 110 | 111 | public TextView getLabelView() { 112 | return labelView; 113 | } 114 | 115 | public TextView getValueView() { 116 | return valueView; 117 | } 118 | 119 | public DotView getDotView() { 120 | return dotView; 121 | } 122 | 123 | public ImageView getArrowView() { 124 | return arrowView; 125 | } 126 | 127 | public void showIconView(boolean show) { 128 | iconView.setVisibility(show ? VISIBLE : GONE); 129 | } 130 | 131 | public void showDotView(boolean show) { 132 | dotView.setVisibility(show ? VISIBLE : GONE); 133 | } 134 | 135 | public void showArrowView(boolean show){ 136 | arrowView.setVisibility(show ? VISIBLE : GONE); 137 | } 138 | 139 | public int getUnreadCount() { 140 | return dotView.getUnReadCount(); 141 | } 142 | 143 | public void setUnreadCount(int unreadCount) { 144 | dotView.setUnReadCount(unreadCount); 145 | } 146 | 147 | public void setIcon(@DrawableRes int resId) { 148 | iconView.setImageResource(resId); 149 | } 150 | 151 | public void setLabel(CharSequence label) { 152 | labelView.setText(label); 153 | } 154 | 155 | public void setLabel(@StringRes int resId) { 156 | labelView.setText(resId); 157 | } 158 | 159 | public void setValue(CharSequence label) { 160 | valueView.setText(label); 161 | } 162 | 163 | public void setValue(@StringRes int resId) { 164 | valueView.setText(resId); 165 | } 166 | 167 | public void setArrow(@DrawableRes int resId) { 168 | arrowView.setImageResource(resId); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /app/src/main/java/jsc/exam/com/guidance/widgets/SquareImageView.java: -------------------------------------------------------------------------------- 1 | package jsc.exam.com.guidance.widgets; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.AppCompatImageView; 5 | import android.util.AttributeSet; 6 | 7 | public class SquareImageView extends AppCompatImageView { 8 | public SquareImageView(Context context) { 9 | super(context); 10 | } 11 | 12 | public SquareImageView(Context context, AttributeSet attrs) { 13 | super(context, attrs); 14 | } 15 | 16 | public SquareImageView(Context context, AttributeSet attrs, int defStyleAttr) { 17 | super(context, attrs, defStyleAttr); 18 | } 19 | 20 | @Override 21 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 22 | super.onMeasure(widthMeasureSpec, widthMeasureSpec); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/jsc/exam/com/guidance/widgets/dialog/BottomShowDialog.java: -------------------------------------------------------------------------------- 1 | package jsc.exam.com.guidance.widgets.dialog; 2 | 3 | import android.app.Dialog; 4 | import android.content.Context; 5 | import android.graphics.Bitmap; 6 | import android.graphics.Color; 7 | import android.os.Bundle; 8 | import android.support.annotation.NonNull; 9 | import android.util.TypedValue; 10 | import android.view.Gravity; 11 | import android.view.ViewGroup; 12 | import android.view.Window; 13 | import android.view.WindowManager; 14 | import android.widget.ImageView; 15 | import android.widget.LinearLayout; 16 | import android.widget.TextView; 17 | 18 | import jsc.exam.com.guidance.R; 19 | 20 | /** 21 | *
Email:1006368252@qq.com 22 | *
QQ:1006368252 23 | *
https://github.com/JustinRoom/Guidance 24 | * 25 | * @author jiangshicheng 26 | */ 27 | public class BottomShowDialog extends Dialog { 28 | 29 | CharSequence title; 30 | Bitmap bitmap; 31 | 32 | public BottomShowDialog( @NonNull Context context) { 33 | this(context, R.style.Theme_AppCompat_Dialog); 34 | } 35 | 36 | public BottomShowDialog( @NonNull Context context, int themeResId) { 37 | super(context, themeResId); 38 | } 39 | 40 | @Override 41 | protected void onCreate(Bundle savedInstanceState) { 42 | super.onCreate(savedInstanceState); 43 | requestWindowFeature(Window.FEATURE_NO_TITLE); 44 | LinearLayout layout = new LinearLayout(getContext()); 45 | layout.setOrientation(LinearLayout.VERTICAL); 46 | layout.setGravity(Gravity.CENTER_HORIZONTAL); 47 | // 48 | TextView textView = new TextView(getContext()); 49 | textView.setBackgroundColor(getContext().getResources().getColor(R.color.colorAccent)); 50 | textView.setGravity(Gravity.CENTER_HORIZONTAL); 51 | textView.setText(title); 52 | textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); 53 | textView.setTextColor(0xFF333333); 54 | textView.setPadding(0, 16, 0, 16); 55 | layout.addView(textView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 56 | // 57 | LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 58 | params.topMargin = 24; 59 | params.bottomMargin = 24; 60 | ImageView imageView = new ImageView(getContext()); 61 | imageView.setImageBitmap(bitmap); 62 | layout.addView(imageView, params); 63 | setContentView(layout); 64 | 65 | if (getWindow() != null) { 66 | getWindow().setGravity(Gravity.BOTTOM); 67 | getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT); 68 | getWindow().setBackgroundDrawable(null); 69 | getWindow().getDecorView().setBackgroundColor(Color.WHITE); 70 | } 71 | } 72 | 73 | @Override 74 | public void show() { 75 | super.show(); 76 | } 77 | 78 | @Override 79 | public void setTitle(CharSequence title) { 80 | this.title = title; 81 | } 82 | 83 | public void setBitmap(Bitmap bitmap) { 84 | this.bitmap = bitmap; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ripple_round_corner_white_r4.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/hand_o_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustinRoom/GuidanceDemo/a595e8381ddaa8c727bab4714e3aaf0435846b8c/app/src/main/res/drawable-xhdpi/hand_o_up.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/btn_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustinRoom/GuidanceDemo/a595e8381ddaa8c727bab4714e3aaf0435846b8c/app/src/main/res/drawable-xxhdpi/btn_background.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_chevron_left_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustinRoom/GuidanceDemo/a595e8381ddaa8c727bab4714e3aaf0435846b8c/app/src/main/res/drawable-xxhdpi/ic_chevron_left_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/kit_ic_assignment_blue_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustinRoom/GuidanceDemo/a595e8381ddaa8c727bab4714e3aaf0435846b8c/app/src/main/res/drawable-xxhdpi/kit_ic_assignment_blue_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/kit_ic_chevron_right_gray_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustinRoom/GuidanceDemo/a595e8381ddaa8c727bab4714e3aaf0435846b8c/app/src/main/res/drawable-xxhdpi/kit_ic_chevron_right_gray_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/guidance_demo_qr_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustinRoom/GuidanceDemo/a595e8381ddaa8c727bab4714e3aaf0435846b8c/app/src/main/res/drawable/guidance_demo_qr_code.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ripple_round_corner_white_r4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_abount.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | 18 | 22 | 23 | 31 | 32 | 40 | 41 | 49 | 50 | 51 | 52 | 57 | 58 |