├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── ndh │ │ └── floatingball │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── ndh │ │ │ └── floatingball │ │ │ ├── MainActivity.java │ │ │ ├── SelectActivity.java │ │ │ ├── sdk │ │ │ ├── ActionChangeListener.java │ │ │ ├── ActionManager.java │ │ │ ├── Config.java │ │ │ ├── FloatingLayout.java │ │ │ ├── FloatingService.java │ │ │ ├── FloatingView.java │ │ │ ├── FloatingViewListener.java │ │ │ ├── FloatingViewManager.java │ │ │ ├── FloatingWindowManager.java │ │ │ ├── MenuItemManager.java │ │ │ ├── NotProguard.java │ │ │ ├── SelectLayout.java │ │ │ ├── camera │ │ │ │ └── CameraActivity.java │ │ │ ├── lockscreen │ │ │ │ ├── LockScreenActivity.java │ │ │ │ └── MyAdmin.java │ │ │ └── screenshot │ │ │ │ └── ScreenShotActivity.java │ │ │ └── util │ │ │ ├── PermissionUtils.java │ │ │ └── Utils.java │ └── res │ │ ├── anim │ │ ├── slide_in.xml │ │ └── slide_out.xml │ │ ├── color │ │ └── bg_select_text.xml │ │ ├── drawable │ │ ├── ball.png │ │ ├── bg_action.xml │ │ ├── bg_button.xml │ │ ├── bg_button_normal.xml │ │ ├── bg_button_press.xml │ │ ├── bg_menu.xml │ │ ├── bg_menu_enable.xml │ │ ├── bg_menu_normal.xml │ │ ├── bg_setting_checked.xml │ │ ├── bg_setting_not_checked.xml │ │ ├── ic_assist.xml │ │ ├── ic_checked.xml │ │ ├── ic_no_checked.xml │ │ └── screenshot.png │ │ ├── layout │ │ ├── activity_main.xml │ │ └── activity_select.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ ├── values │ │ ├── arrays.xml │ │ ├── colors.xml │ │ ├── dialog.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ └── anim.xml │ └── test │ └── java │ └── com │ └── example │ └── ndh │ └── floatingball │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── install.sh └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FloatingBall 2 | 悬浮球,提供一些快捷操作。比如一键静音,一键锁频,一键截屏,一键回桌面,手电筒等 3 | ![screen shot](https://github.com/killer8000/FloatingBall/blob/master/app/src/main/res/drawable/screenshot.png) 4 | 5 | 代码中有参考意义的地方有: 6 | 1、自定义悬浮球,该悬浮球只能在一个圆形范围拖动(类FloatingView) 7 | 2、代码混淆(网上摘录,http://www.trinea.cn/android/android-proguard-tip-not-proguard/) (类NotProguard) 8 | 3、6.0以上动态权限申请(网上摘录) (类PermissionUtils) 9 | 4、三击事件处理 (类MainActivity中toggle方法中有实现) 10 | 5、悬浮窗实现 (类FloatingWindowManager) 11 | 6、截屏的实现 (类ScreenShotActivity) 12 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion '23.0.2' 6 | defaultConfig { 7 | applicationId "com.example.ndh.floatingball" 8 | minSdkVersion 21 9 | targetSdkVersion 23 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | signingConfigs { 15 | release { 16 | keyAlias 'key0' 17 | keyPassword 'NDH@321.com' 18 | storeFile file('/Users/ndh/Documents/keystore/assist.jks') 19 | storePassword 'NDH@321.com' 20 | } 21 | } 22 | buildTypes { 23 | release { 24 | minifyEnabled false 25 | shrinkResources true // 无用的资源不打进去 26 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 27 | signingConfig signingConfigs.release 28 | debuggable true 29 | jniDebuggable false 30 | zipAlignEnabled true 31 | } 32 | debug { 33 | minifyEnabled false 34 | } 35 | } 36 | } 37 | 38 | dependencies { 39 | compile fileTree(dir: 'libs', include: ['*.jar']) 40 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 41 | exclude group: 'com.android.support', module: 'support-annotations' 42 | }) 43 | compile 'com.android.support:appcompat-v7:23.+' 44 | testCompile 'junit:junit:4.12' 45 | } 46 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/ndh/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -optimizationpasses 5 #指定代码压缩级别 19 | -dontusemixedcaseclassnames #混淆时不会产生形形色色的类名 20 | -dontskipnonpubliclibraryclasses #指定不忽略非公共类库 21 | -dontpreverify #不预校验,如果需要预校验,是-dontoptimize 22 | -ignorewarnings #屏蔽警告 23 | -verbose #混淆时记录日志 24 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* #优化 25 | -keep public class * extends android.app.Activity 26 | -keep public class * extends android.app.Application 27 | -keep public class * extends android.app.Fragment 28 | -keep public class * extends android.app.Service 29 | -keep public class * extends android.content.BroadcastReceiver 30 | -keep public class * extends android.content.ContentProvider 31 | -keep public class * extends android.app.backup.BackupAgentHelper 32 | -keep public class * extends android.preference.Preference 33 | -keep public class com.android.vending.licensing.ILicensingService 34 | -keepclasseswithmembernames class * { # 保持 native 方法不被混淆 35 | native ; 36 | } 37 | -keepclasseswithmembers class * { # 保持自定义控件类不被混淆 38 | public (android.content.Context, android.util.AttributeSet); 39 | } 40 | 41 | -keepclasseswithmembers class * { 42 | public (android.content.Context, android.util.AttributeSet, int); # 保持自定义控件类不被混淆 43 | } 44 | 45 | -keepclassmembers class * extends android.app.Activity { # 保持自定义控件类不被混淆 46 | public void *(android.view.View); 47 | } 48 | -keepclassmembers enum * { # 保持枚举 enum 类不被混淆 49 | public static **[] values(); 50 | public static ** valueOf(java.lang.String); 51 | } 52 | 53 | # keep annotated by NotProguard 54 | -keep @com.example.ndh.floatingball.sdk.NotProguard class * {*;} 55 | -keep class * { 56 | @com.example.ndh.floatingball.sdk.NotProguard ; 57 | } 58 | -keepclassmembers class * { 59 | @com.example.ndh.floatingball.sdk.NotProguard ; 60 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/ndh/floatingball/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.example.ndh.floatingball; 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 | * Instrumentation 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() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.example.ndh.floatingball", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 28 | 34 | 35 | 36 | 37 | 38 | 39 | 46 | 47 | 48 | 56 | 60 | 67 | 70 | 71 | 75 | 77 | 78 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/ndh/floatingball/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.ndh.floatingball; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Intent; 5 | import android.content.pm.PackageManager; 6 | import android.database.sqlite.SQLiteDatabase; 7 | import android.graphics.drawable.Drawable; 8 | import android.net.Uri; 9 | import android.os.Build; 10 | import android.os.Handler; 11 | import android.os.PersistableBundle; 12 | import android.os.Bundle; 13 | import android.os.SystemClock; 14 | import android.preference.Preference; 15 | import android.provider.Settings; 16 | import android.support.v4.app.ActivityCompat; 17 | import android.support.v7.app.AppCompatActivity; 18 | import android.view.View; 19 | import android.widget.Button; 20 | import android.widget.TextView; 21 | import android.widget.Toast; 22 | import android.util.Log; 23 | 24 | import com.example.ndh.floatingball.sdk.ActionChangeListener; 25 | import com.example.ndh.floatingball.sdk.ActionManager; 26 | import com.example.ndh.floatingball.sdk.Config; 27 | import com.example.ndh.floatingball.sdk.FloatingService; 28 | import com.example.ndh.floatingball.sdk.FloatingWindowManager; 29 | import com.example.ndh.floatingball.util.PermissionUtils; 30 | import com.example.ndh.floatingball.util.Utils; 31 | 32 | public class MainActivity extends AppCompatActivity implements View.OnClickListener, PermissionUtils.PermissionGrant { 33 | 34 | private TextView mUp; 35 | private TextView mDown; 36 | private TextView mLeft; 37 | private TextView mRight; 38 | private TextView mMenu_1; 39 | private TextView mMenu_2; 40 | private TextView mMenu_3; 41 | private TextView mMenu_4; 42 | private TextView mMenu_5; 43 | private String mTextUp; 44 | private String mTextDown; 45 | private String mTextLeft; 46 | private String mTextRight; 47 | private String mTextMenu1; 48 | private String mTextMenu2; 49 | private String mTextMenu3; 50 | private String mTextMenu4; 51 | private String mTextMenu5; 52 | private TextView mOverLay; 53 | private TextView mActive; 54 | private String mTextOverlay; 55 | private String mTextActive; 56 | private Drawable mOverLayRightDrawble; 57 | private Drawable mActiveDrawble; 58 | private Button mButton; 59 | private TextView mUnInstall; 60 | 61 | @TargetApi(Build.VERSION_CODES.M) 62 | @Override 63 | protected void onCreate(Bundle savedInstanceState) { 64 | PermissionUtils.requestMultiPermissions(this, this); 65 | 66 | super.onCreate(savedInstanceState); 67 | setContentView(R.layout.activity_main); 68 | 69 | initView(); 70 | boolean toggle = Utils.isServiceWork(getApplicationContext(), FloatingService.class.getName()); 71 | if (toggle) { 72 | mButton.setText(getString(R.string.stop_use)); 73 | } else { 74 | mButton.setText(getString(R.string.start_use)); 75 | } 76 | mButton.setOnClickListener(new View.OnClickListener() { 77 | @Override 78 | public void onClick(View v) { 79 | toggle(); 80 | } 81 | }); 82 | ActionManager.create().registActionChangeListener(new ActionChangeListener() { 83 | @Override 84 | public void onChange(String position, String action) { 85 | setText(); 86 | FloatingWindowManager.create(MainActivity.this).removeAllView(); 87 | FloatingWindowManager.create(MainActivity.this).init(); 88 | } 89 | }); 90 | } 91 | 92 | private long[] mHint = new long[3]; 93 | 94 | private void toggle() { 95 | Intent intent = new Intent(MainActivity.this, FloatingService.class); 96 | boolean toggle = Utils.isServiceWork(getApplicationContext(), FloatingService.class.getName()); 97 | if (toggle) { 98 | // 正在运行点击开关就关闭服务 99 | System.arraycopy(mHint, 1, mHint, 0, mHint.length - 1); 100 | mHint[mHint.length - 1] = SystemClock.uptimeMillis(); 101 | if (mHint[0] >= SystemClock.uptimeMillis() - 500) { 102 | //500 毫秒点击了3次 103 | intent.getBooleanExtra("toggle", false); 104 | stopService(intent); 105 | mButton.setText(getString(R.string.start_use)); 106 | } 107 | } else { 108 | intent.getBooleanExtra("toggle", true); 109 | startService(intent); 110 | mButton.setText(getString(R.string.stop_use)); 111 | } 112 | 113 | 114 | } 115 | 116 | private void initView() { 117 | mButton = (Button) findViewById(R.id.bt); 118 | mUnInstall = (TextView) findViewById(R.id.uninstall); 119 | mUp = (TextView) findViewById(R.id.up); 120 | mDown = (TextView) findViewById(R.id.down); 121 | mLeft = (TextView) findViewById(R.id.left); 122 | mRight = (TextView) findViewById(R.id.right); 123 | mMenu_1 = (TextView) findViewById(R.id.menu_1); 124 | mMenu_2 = (TextView) findViewById(R.id.menu_2); 125 | mMenu_3 = (TextView) findViewById(R.id.menu_3); 126 | mMenu_4 = (TextView) findViewById(R.id.menu_4); 127 | mMenu_5 = (TextView) findViewById(R.id.menu_5); 128 | 129 | mOverLay = (TextView) findViewById(R.id.over_lay); 130 | mActive = (TextView) findViewById(R.id.active); 131 | 132 | mUp.setTag(Config.MenuPosition.UP); 133 | mDown.setTag(Config.MenuPosition.DOWN); 134 | mLeft.setTag(Config.MenuPosition.LEFT); 135 | mRight.setTag(Config.MenuPosition.RIGHT); 136 | mMenu_1.setTag(Config.MenuPosition.MENU_1); 137 | mMenu_2.setTag(Config.MenuPosition.MENU_2); 138 | mMenu_3.setTag(Config.MenuPosition.MENU_3); 139 | mMenu_4.setTag(Config.MenuPosition.MENU_4); 140 | mMenu_5.setTag(Config.MenuPosition.MENU_5); 141 | 142 | mUp.setOnClickListener(this); 143 | mDown.setOnClickListener(this); 144 | mLeft.setOnClickListener(this); 145 | mRight.setOnClickListener(this); 146 | mMenu_1.setOnClickListener(this); 147 | mMenu_2.setOnClickListener(this); 148 | mMenu_3.setOnClickListener(this); 149 | mMenu_4.setOnClickListener(this); 150 | mMenu_5.setOnClickListener(this); 151 | mUnInstall.setOnClickListener(this); 152 | setText(); 153 | } 154 | 155 | private void setText() { 156 | 157 | mTextUp = getResources().getString(R.string.up); 158 | mTextDown = getResources().getString(R.string.down); 159 | mTextLeft = getResources().getString(R.string.left); 160 | mTextRight = getResources().getString(R.string.right); 161 | mTextMenu1 = getResources().getString(R.string.menu_1); 162 | mTextMenu2 = getResources().getString(R.string.menu_2); 163 | mTextMenu3 = getResources().getString(R.string.menu_3); 164 | mTextMenu4 = getResources().getString(R.string.menu_4); 165 | mTextMenu5 = getResources().getString(R.string.menu_5); 166 | 167 | mTextOverlay = getResources().getString(R.string.over_lay); 168 | mTextActive = getResources().getString(R.string.active); 169 | 170 | mTextUp = String.format(mTextUp, Utils.getStringBySP(this, Config.MenuPosition.UP, Config.Action.DEST)); 171 | mTextDown = String.format(mTextDown, Utils.getStringBySP(this, Config.MenuPosition.DOWN, Config.Action.LOCK_SCREEN)); 172 | mTextLeft = String.format(mTextLeft, Utils.getStringBySP(this, Config.MenuPosition.LEFT, Config.Action.MUTE)); 173 | mTextRight = String.format(mTextRight, Utils.getStringBySP(this, Config.MenuPosition.RIGHT, Config.Action.CAMERA)); 174 | mTextMenu1 = String.format(mTextMenu1, Utils.getStringBySP(this, Config.MenuPosition.MENU_1, Config.Action.FLASH)); 175 | mTextMenu2 = String.format(mTextMenu2, Utils.getStringBySP(this, Config.MenuPosition.MENU_2, Config.Action.CALENDER)); 176 | mTextMenu3 = String.format(mTextMenu3, Utils.getStringBySP(this, Config.MenuPosition.MENU_3, Config.Action.WIFI)); 177 | mTextMenu4 = String.format(mTextMenu4, Utils.getStringBySP(this, Config.MenuPosition.MENU_4, Config.Action.CALL)); 178 | mTextMenu5 = String.format(mTextMenu5, Utils.getStringBySP(this, Config.MenuPosition.MENU_5, Config.Action.CONTACT)); 179 | 180 | mUp.setText(mTextUp); 181 | mDown.setText(mTextDown); 182 | mLeft.setText(mTextLeft); 183 | mRight.setText(mTextRight); 184 | mMenu_1.setText(mTextMenu1); 185 | mMenu_2.setText(mTextMenu2); 186 | mMenu_3.setText(mTextMenu3); 187 | mMenu_4.setText(mTextMenu4); 188 | mMenu_5.setText(mTextMenu5); 189 | 190 | } 191 | 192 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 193 | @Override 194 | protected void onResume() { 195 | 196 | if (Utils.canDrawOverlays(this)) { 197 | mTextOverlay = String.format(mTextOverlay, getResources().getString(R.string.granted)); 198 | mOverLayRightDrawble = getResources().getDrawable(R.drawable.ic_checked, null); 199 | mOverLay.setOnClickListener(null); 200 | } else { 201 | mTextOverlay = String.format(mTextOverlay, getResources().getString(R.string.not_granted)); 202 | mOverLayRightDrawble = getDrawable(R.drawable.ic_no_checked); 203 | mOverLay.setOnClickListener(this); 204 | } 205 | mOverLay.setText(mTextOverlay); 206 | mOverLayRightDrawble.setBounds(0, 0, mOverLayRightDrawble.getMinimumWidth(), mOverLayRightDrawble.getMinimumHeight()); 207 | mOverLay.setCompoundDrawables(null, null, mOverLayRightDrawble, null); 208 | 209 | if (Utils.isActive(this)) { 210 | mTextActive = String.format(mTextActive, getString(R.string.actived)); 211 | mActiveDrawble = getResources().getDrawable(R.drawable.ic_checked, null); 212 | mActive.setOnClickListener(null); 213 | 214 | } else { 215 | mTextActive = String.format(mTextActive, getString(R.string.not_actived)); 216 | mActiveDrawble = getDrawable(R.drawable.ic_no_checked); 217 | mActive.setOnClickListener(this); 218 | } 219 | mActiveDrawble.setBounds(0, 0, mActiveDrawble.getMinimumWidth(), mActiveDrawble.getMinimumHeight()); 220 | mActive.setText(mTextActive); 221 | mActive.setCompoundDrawables(null, null, mActiveDrawble, null); 222 | super.onResume(); 223 | } 224 | 225 | 226 | @Override 227 | public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) { 228 | super.onCreate(savedInstanceState, persistentState); 229 | 230 | } 231 | 232 | @Override 233 | public void onClick(View v) { 234 | switch (v.getId()) { 235 | case R.id.up: 236 | case R.id.down: 237 | case R.id.left: 238 | case R.id.right: 239 | case R.id.menu_1: 240 | case R.id.menu_2: 241 | case R.id.menu_3: 242 | case R.id.menu_4: 243 | case R.id.menu_5: 244 | String tag = (String) v.getTag(); 245 | Intent intent = new Intent(this, SelectActivity.class); 246 | intent.putExtra("position", tag); 247 | startActivityForResult(intent, 0); 248 | break; 249 | case R.id.active: 250 | if (Utils.canDrawOverlays(this)) { 251 | Utils.startActivePage(this); 252 | } else { 253 | Toast.makeText(this, getResources().getString(R.string.grant_overlay), Toast.LENGTH_SHORT).show(); 254 | } 255 | break; 256 | case R.id.over_lay: 257 | Utils.startOverlayGrante(this); 258 | break; 259 | case R.id.uninstall: 260 | System.arraycopy(mHint, 1, mHint, 0, mHint.length - 1); 261 | mHint[mHint.length - 1] = SystemClock.uptimeMillis(); 262 | if (mHint[0] >= SystemClock.uptimeMillis() - 500) { 263 | //500 毫秒点击了3次 264 | Utils.removeAdmin(this); 265 | new Handler().postDelayed(new Runnable() { 266 | @Override 267 | public void run() { 268 | if (!Utils.isActive(MainActivity.this.getApplicationContext())) 269 | Utils.unInstallPackage(MainActivity.this.getApplicationContext(), getPackageName()); 270 | else 271 | Toast.makeText(MainActivity.this.getApplicationContext(), getResources().getString(R.string.remove_admin), Toast.LENGTH_SHORT).show(); 272 | } 273 | }, 50); 274 | } 275 | break; 276 | } 277 | 278 | } 279 | 280 | @Override 281 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 282 | super.onActivityResult(requestCode, resultCode, data); 283 | setText(); 284 | } 285 | 286 | @Override 287 | public void onPermissionGranted(int requestCode) { 288 | 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/ndh/floatingball/SelectActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.ndh.floatingball; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.graphics.Color; 6 | import android.os.Bundle; 7 | import android.support.annotation.Nullable; 8 | import android.view.Gravity; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.view.WindowManager; 12 | import android.widget.TextView; 13 | import android.widget.Toast; 14 | 15 | import com.example.ndh.floatingball.sdk.ActionManager; 16 | import com.example.ndh.floatingball.sdk.Config; 17 | import com.example.ndh.floatingball.sdk.SelectLayout; 18 | import com.example.ndh.floatingball.util.Utils; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | import android.util.Log; 24 | 25 | /** 26 | * Created by ndh on 16/12/27. 27 | */ 28 | 29 | public class SelectActivity extends Activity { 30 | List list = new ArrayList<>(); 31 | private String mPositionName; 32 | 33 | @Override 34 | protected void onCreate(@Nullable Bundle savedInstanceState) { 35 | overridePendingTransition(R.anim.slide_in, R.anim.slide_out); 36 | setContentView(R.layout.activity_select); 37 | try { 38 | Intent intent = getIntent(); 39 | mPositionName = intent.getStringExtra("position"); 40 | } catch (Exception e) { 41 | Log.e("ndh--", "e==" + e.toString()); 42 | 43 | } 44 | SelectLayout selectLayout = (SelectLayout) findViewById(R.id.sl); 45 | String[] myData = ActionManager.create().getAllAction(); 46 | list.clear(); 47 | for (int i = 0; i < myData.length; i++) { 48 | TextView textView = new TextView(this); 49 | textView.setText(myData[i]); 50 | textView.setTextColor(getResources().getColor(R.color.bg_select_text)); 51 | textView.setGravity(Gravity.CENTER); 52 | textView.setOnClickListener(new View.OnClickListener() { 53 | @Override 54 | public void onClick(View v) { 55 | String text = ((TextView) v).getText().toString(); 56 | Utils.putStringBySP(SelectActivity.this, mPositionName, text); 57 | ActionManager.create().post(mPositionName, text); 58 | finish(); 59 | // Toast.makeText(SelectActivity.this, text, Toast.LENGTH_SHORT).show(); 60 | } 61 | }); 62 | list.add(textView); 63 | } 64 | Log.d("ndh--", "selectLayout=" + selectLayout + ""); 65 | selectLayout.createChild(list, this); 66 | selectLayout.requestLayout(); 67 | super.onCreate(savedInstanceState); 68 | } 69 | 70 | @Override 71 | public void onAttachedToWindow() { 72 | super.onAttachedToWindow(); 73 | View decorView = getWindow().getDecorView(); 74 | WindowManager.LayoutParams layoutParams = (WindowManager.LayoutParams) decorView.getLayoutParams(); 75 | layoutParams.gravity = Gravity.BOTTOM; 76 | layoutParams.width = getResources().getDisplayMetrics().widthPixels; 77 | layoutParams.height = getResources().getDisplayMetrics().heightPixels / 3; 78 | getWindowManager().updateViewLayout(decorView, layoutParams); 79 | 80 | } 81 | 82 | @Override 83 | 84 | public void finish() { 85 | 86 | super.finish(); 87 | overridePendingTransition(R.anim.slide_in, R.anim.slide_out); 88 | 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/ndh/floatingball/sdk/ActionChangeListener.java: -------------------------------------------------------------------------------- 1 | package com.example.ndh.floatingball.sdk; 2 | 3 | /** 4 | * Created by ndh on 16/12/27. 5 | */ 6 | @NotProguard 7 | public interface ActionChangeListener { 8 | void onChange(String position, String action); 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/ndh/floatingball/sdk/ActionManager.java: -------------------------------------------------------------------------------- 1 | package com.example.ndh.floatingball.sdk; 2 | 3 | 4 | import android.Manifest; 5 | import android.content.ComponentName; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.pm.PackageManager; 9 | import android.content.pm.ResolveInfo; 10 | import android.hardware.Camera; 11 | import android.hardware.camera2.CameraManager; 12 | import android.media.AudioManager; 13 | import android.media.projection.MediaProjectionManager; 14 | import android.net.Uri; 15 | import android.net.wifi.WifiManager; 16 | import android.os.Build; 17 | import android.os.PowerManager; 18 | import android.provider.ContactsContract; 19 | import android.provider.MediaStore; 20 | import android.support.v4.app.ActivityCompat; 21 | import android.telephony.PhoneNumberUtils; 22 | import android.view.View; 23 | import android.widget.Toast; 24 | import android.util.Log; 25 | 26 | import com.example.ndh.floatingball.sdk.camera.CameraActivity; 27 | import com.example.ndh.floatingball.sdk.lockscreen.LockScreenActivity; 28 | import com.example.ndh.floatingball.sdk.screenshot.ScreenShotActivity; 29 | import com.example.ndh.floatingball.util.PermissionUtils; 30 | import com.example.ndh.floatingball.util.Utils; 31 | 32 | import java.lang.reflect.Field; 33 | import java.lang.reflect.InvocationTargetException; 34 | import java.lang.reflect.Method; 35 | import java.util.HashMap; 36 | import java.util.Map; 37 | 38 | 39 | /** 40 | * 事件能力输出 41 | * Created by ndh on 16/12/22. 42 | */ 43 | 44 | public class ActionManager { 45 | 46 | private ActionManager() { 47 | } 48 | 49 | @NotProguard 50 | public static ActionManager create() { 51 | return ActionManager.SingleInstance.INSTANCE; 52 | } 53 | 54 | private void lock(Context context) { 55 | Intent lockIntent = new Intent(context, LockScreenActivity.class); 56 | lockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 57 | context.startActivity(lockIntent); 58 | } 59 | 60 | private void goHome(Context context) { 61 | Intent home = new Intent(Intent.ACTION_MAIN); 62 | home.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 63 | home.addCategory(Intent.CATEGORY_HOME); 64 | home.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 65 | context.startActivity(home); 66 | } 67 | 68 | private void startCamera(Context context) { 69 | Intent intent = new Intent (context, CameraActivity.class); 70 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 71 | context.startActivity(intent); 72 | } 73 | 74 | @NotProguard 75 | public void initAction(Context context) { 76 | setAction(Config.MenuPosition.UP, Utils.getStringBySP(context, Config.MenuPosition.UP, Config.Action.DEST)); 77 | setAction(Config.MenuPosition.DOWN, Utils.getStringBySP(context, Config.MenuPosition.DOWN, Config.Action.LOCK_SCREEN)); 78 | setAction(Config.MenuPosition.LEFT, Utils.getStringBySP(context, Config.MenuPosition.LEFT, Config.Action.MUTE)); 79 | setAction(Config.MenuPosition.RIGHT, Utils.getStringBySP(context, Config.MenuPosition.RIGHT, Config.Action.CAMERA)); 80 | setAction(Config.MenuPosition.MENU_1, Utils.getStringBySP(context, Config.MenuPosition.MENU_1, Config.Action.FLASH)); 81 | setAction(Config.MenuPosition.MENU_2, Utils.getStringBySP(context, Config.MenuPosition.MENU_2, Config.Action.CALENDER)); 82 | setAction(Config.MenuPosition.MENU_3, Utils.getStringBySP(context, Config.MenuPosition.MENU_3, Config.Action.WIFI)); 83 | setAction(Config.MenuPosition.MENU_4, Utils.getStringBySP(context, Config.MenuPosition.MENU_4, Config.Action.CALL)); 84 | setAction(Config.MenuPosition.MENU_5, Utils.getStringBySP(context, Config.MenuPosition.MENU_5, Config.Action.CONTACT)); 85 | 86 | } 87 | 88 | private static class SingleInstance { 89 | public static final ActionManager INSTANCE = new ActionManager(); 90 | } 91 | 92 | private boolean isFlash; 93 | Camera camera; 94 | Camera.Parameters params; 95 | 96 | private void ToggleFlash(Context context) throws Exception { 97 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 98 | CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); 99 | if (!isFlash) 100 | manager.setTorchMode("0", true); 101 | else 102 | manager.setTorchMode("0", false); 103 | } else { 104 | if (!isFlash) { 105 | camera = Camera.open(); 106 | params = camera.getParameters(); 107 | params.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH); 108 | camera.setParameters(params); 109 | camera.startPreview(); 110 | } else { 111 | params.setFlashMode(Camera.Parameters.FLASH_MODE_OFF); 112 | camera.setParameters(params); 113 | camera.stopPreview(); 114 | camera.release(); 115 | camera = null; 116 | params = null; 117 | } 118 | } 119 | 120 | 121 | isFlash = !isFlash; 122 | } 123 | 124 | private void openCalander(Context context) { 125 | try { 126 | Intent t_intent = new Intent(Intent.ACTION_VIEW); 127 | t_intent.addCategory(Intent.CATEGORY_DEFAULT); 128 | t_intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK /*| Intent.FLAG_ACTIVITY_CLEAR_TASK 129 | | Intent.FLAG_ACTIVITY_TASK_ON_HOME*/); 130 | t_intent.setDataAndType(Uri.parse("content://com.android.calendar/"), "time/epoch"); 131 | context.startActivity(t_intent); 132 | } catch (Exception e) { 133 | 134 | e.printStackTrace(); 135 | Toast.makeText(context, "failed", Toast.LENGTH_SHORT).show(); 136 | } 137 | } 138 | 139 | private void startContact(Context context) { 140 | 141 | Intent intent = new Intent(); 142 | 143 | intent.setAction(Intent.ACTION_PICK); 144 | 145 | intent.setData(ContactsContract.Contacts.CONTENT_URI); 146 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 147 | context.startActivity(intent); 148 | } 149 | 150 | private void openPhoto(Context context) { 151 | Intent intent = new Intent( 152 | Intent.ACTION_PICK, 153 | android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI); 154 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 155 | context.startActivity(intent); 156 | } 157 | 158 | //TODO 159 | private void openRecent(Context context) { 160 | 161 | } 162 | 163 | private void mute(Context context) { 164 | AudioManager manager = (AudioManager) context.getApplicationContext().getSystemService(Context.AUDIO_SERVICE); 165 | /** 166 | //STREAM_ALARM 警报 167 | STREAM_MUSIC 音乐回放即媒体音量 168 | STREAM_NOTIFICATION 窗口顶部状态栏Notification, 169 | STREAM_RING 铃声 170 | STREAM_SYSTEM 系统 171 | STREAM_VOICE_CALL 通话 172 | STREAM_DTMF 双音多频,拨号键的声音 173 | */ 174 | manager.setStreamVolume(AudioManager.STREAM_ALARM, 0, 0); 175 | manager.setStreamVolume(AudioManager.STREAM_DTMF, 0, 0); 176 | manager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0); 177 | manager.setStreamVolume(AudioManager.STREAM_NOTIFICATION, 0, 0); 178 | manager.setStreamVolume(AudioManager.STREAM_RING, 0, 0); 179 | manager.setStreamVolume(AudioManager.STREAM_SYSTEM, 0, 0); 180 | int alarm = manager.getStreamVolume(AudioManager.STREAM_ALARM); 181 | int dtmf = manager.getStreamVolume(AudioManager.STREAM_DTMF); 182 | int music = manager.getStreamVolume(AudioManager.STREAM_MUSIC); 183 | int notification = manager.getStreamVolume(AudioManager.STREAM_NOTIFICATION); 184 | int ring = manager.getStreamVolume(AudioManager.STREAM_RING); 185 | int system = manager.getStreamVolume(AudioManager.STREAM_SYSTEM); 186 | Log.d("ndh---", "alarm=" + alarm + ",dtmf=" + dtmf + ",musit=" + music 187 | + ",notification=" + notification + ",ring=" + ring + ",system=" + system); 188 | } 189 | 190 | private void startAlarm(Context context) { 191 | PackageManager packageManager = context.getPackageManager(); 192 | if (packageManager != null) { 193 | //通常的rom 194 | Intent AlarmClockIntent = new Intent(Intent.ACTION_MAIN).addCategory( 195 | Intent.CATEGORY_LAUNCHER).setComponent( 196 | new ComponentName("com.android.deskclock", "com.android.deskclock.DeskClock")); 197 | 198 | ResolveInfo resolved = packageManager.resolveActivity(AlarmClockIntent, 199 | PackageManager.MATCH_DEFAULT_ONLY); 200 | if (resolved != null) { 201 | AlarmClockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 202 | context.startActivity(AlarmClockIntent); 203 | return; 204 | } else { 205 | // required activity can not be located! 206 | //像魅族什么的 ,对手机rom裁剪修改过大,导致默认的路径无法直接找到系统时钟,则跳转到系统设置页面 207 | Intent intent = new Intent(); 208 | ComponentName comp = new ComponentName("com.android.settings", 209 | "com.android.settings.Settings"); 210 | intent.setComponent(comp); 211 | intent.setAction("android.intent.action.VIEW"); 212 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 213 | context.startActivity(intent); 214 | } 215 | } 216 | 217 | 218 | } 219 | 220 | Map map = new HashMap(); 221 | 222 | @NotProguard 223 | public void setAction(String position, String action) { 224 | map.put(position, action); 225 | } 226 | 227 | @NotProguard 228 | public String getAction(String position) { 229 | return map.get(position); 230 | } 231 | 232 | @NotProguard 233 | public void doAction(Context context, String position) { 234 | switch (getAction(position)) { 235 | case Config.Action.CALENDER: 236 | openCalander(context); 237 | break; 238 | case Config.Action.CAMERA: 239 | startCamera(context); 240 | break; 241 | // case Config.Action.CLOCK: 242 | // startAlarm(context); 243 | // break; 244 | case Config.Action.CONTACT: 245 | startContact(context); 246 | break; 247 | case Config.Action.DEST: 248 | goHome(context); 249 | break; 250 | case Config.Action.FLASH: 251 | try { 252 | ToggleFlash(context); 253 | } catch (Exception e) { 254 | e.printStackTrace(); 255 | } 256 | break; 257 | case Config.Action.LOCK_SCREEN: 258 | lock(context); 259 | break; 260 | case Config.Action.MUTE: 261 | mute(context); 262 | break; 263 | // case Config.Action.PHOTO: 264 | // openPhoto(context); 265 | // break; 266 | 267 | case Config.Action.WIFI: 268 | //wifi 269 | openWifi(context); 270 | break; 271 | case Config.Action.CALL: 272 | //电话 273 | openCall(context); 274 | break; 275 | case Config.Action.SMS: 276 | //短信 277 | doSendSMSTo(context); 278 | break; 279 | case Config.Action.SCREENSHOT: 280 | doScreenshot(context); 281 | break; 282 | default: 283 | Toast.makeText(context, "功能暂未实现", Toast.LENGTH_SHORT).show(); 284 | 285 | } 286 | } 287 | 288 | private void doScreenshot(Context context) { 289 | Intent intent = new Intent(context, ScreenShotActivity.class); 290 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); 291 | context.startActivity(intent); 292 | } 293 | 294 | /** 295 | * 调起系统发短信功能 296 | */ 297 | private void doSendSMSTo(Context context) { 298 | Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse("smsto:")); 299 | intent.putExtra("sms_body", ""); 300 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 301 | context.startActivity(intent); 302 | /* if(PhoneNumberUtils.isGlobalPhoneNumber(phoneNumber)){ 303 | Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse("smsto:")); 304 | intent.putExtra("sms_body", ""); 305 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 306 | context.startActivity(intent); 307 | }*/ 308 | } 309 | 310 | private void openCall(Context context) { 311 | if (!PermissionUtils.checkPermission(context, PermissionUtils.CODE_CALL_PHONE, "请先打开电话权限")) { 312 | return; 313 | } 314 | Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:")); 315 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 316 | context.startActivity(intent); 317 | } 318 | 319 | private void openWifi(Context context) { 320 | WifiManager wm = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE); 321 | String toast = "wifi 即将关闭"; 322 | if (!wm.isWifiEnabled()) { 323 | toast = "wifi 即将打开"; 324 | } 325 | wm.setWifiEnabled(!wm.isWifiEnabled()); 326 | 327 | Toast.makeText(context.getApplicationContext(), toast, Toast.LENGTH_SHORT).show(); 328 | } 329 | 330 | @NotProguard 331 | public String[] getAllAction() { 332 | 333 | Class clazz = Config.Action.class; 334 | Field[] fields = clazz.getFields(); 335 | String[] rtStr = new String[fields.length]; 336 | for (int i = 0; i < fields.length; i++) { 337 | try { 338 | fields[i].setAccessible(true); 339 | String s = (String) (fields[i].get(fields[i].getName())); 340 | rtStr[i] = s; 341 | Log.d("ndh--", "action=" + s); 342 | } catch (IllegalAccessException e) { 343 | e.printStackTrace(); 344 | } 345 | } 346 | return rtStr; 347 | } 348 | 349 | ActionChangeListener mListener; 350 | 351 | @NotProguard 352 | public void registActionChangeListener(ActionChangeListener listener) { 353 | mListener = listener; 354 | } 355 | 356 | @NotProguard 357 | public void post(String position, String action) { 358 | if (null != mListener) { 359 | mListener.onChange(position, action); 360 | } 361 | } 362 | } 363 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/ndh/floatingball/sdk/Config.java: -------------------------------------------------------------------------------- 1 | package com.example.ndh.floatingball.sdk; 2 | 3 | /** 4 | * Created by ndh on 16/12/23. 5 | */ 6 | @NotProguard 7 | public class Config { 8 | //主要为了标识长按 9 | public static final int WAITING_TIME = 500; 10 | // 整个界面展开大小和其有关 11 | public static final int BASE = 45; 12 | //震动提醒时间 13 | public static final int VIBRATE_TIME = 50; 14 | // 展开/关闭动画时间 15 | public static final int DURATION = 300; 16 | @NotProguard 17 | public static class Action { 18 | public static final String DEST = "桌面"; 19 | public static final String MUTE = "静音"; 20 | public static final String LOCK_SCREEN = "锁屏"; 21 | public static final String CAMERA = "相机"; 22 | public static final String CONTACT = "联系人"; 23 | // public static final String PHOTO = "相册"; 24 | // public static final String CLOCK = "时钟"; 25 | public static final String CALENDER = "日历"; 26 | public static final String FLASH = "手电筒"; 27 | public static final String CALL = "打电话"; 28 | public static final String SMS = "发短信"; 29 | public static final String WIFI = "wifi"; 30 | // public static final String HOT_DOT="热点"; 31 | // public static final String SHUT_DOWN="关机"; 32 | // public static final String REBOOT="重启"; 33 | public static final String SCREENSHOT = "截屏"; 34 | } 35 | @NotProguard 36 | public static class MenuPosition { 37 | public static String UP = "up"; 38 | public static String DOWN = "down"; 39 | public static String LEFT = "left"; 40 | public static String RIGHT = "right"; 41 | public static String MENU_1 = "menu1"; 42 | public static String MENU_2 = "menu2"; 43 | public static String MENU_3 = "menu3"; 44 | public static String MENU_4 = "menu4"; 45 | public static String MENU_5 = "menu5"; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/ndh/floatingball/sdk/FloatingLayout.java: -------------------------------------------------------------------------------- 1 | package com.example.ndh.floatingball.sdk; 2 | 3 | import android.animation.Animator; 4 | import android.animation.ObjectAnimator; 5 | import android.animation.ValueAnimator; 6 | import android.content.Context; 7 | import android.graphics.Canvas; 8 | import android.graphics.Color; 9 | import android.graphics.Paint; 10 | import android.graphics.RectF; 11 | import android.util.AttributeSet; 12 | import android.util.Log; 13 | import android.view.MotionEvent; 14 | import android.view.View; 15 | import android.widget.FrameLayout; 16 | 17 | import java.util.ArrayList; 18 | 19 | 20 | /** 21 | * @deprecated Created by ndh on 16/12/14. 22 | */ 23 | 24 | public class FloatingLayout extends FrameLayout { 25 | private int layoutCount = 0; 26 | private float angle = 0; 27 | private float startAngle = 60; 28 | private float maxSweepAngle; 29 | private boolean drawArc = false; 30 | private boolean needOpen = false; 31 | private RectF mRfOut; 32 | private RectF mRfIn; 33 | 34 | public FloatingLayout(Context context) { 35 | this(context, null); 36 | } 37 | 38 | public FloatingLayout(Context context, AttributeSet attrs) { 39 | this(context, attrs, 0); 40 | } 41 | 42 | public FloatingLayout(Context context, AttributeSet attrs, int defStyleAttr) { 43 | super(context, attrs, defStyleAttr); 44 | init(); 45 | } 46 | 47 | Paint mPaint; 48 | 49 | private void init() { 50 | mPaint = new Paint(); 51 | mPaint.setAntiAlias(true); 52 | mPaint.setDither(true); 53 | //必须要有背景颜色,否则drawCircle 画不出来 54 | setBackgroundColor(Color.TRANSPARENT); 55 | } 56 | 57 | @Override 58 | protected void onDraw(Canvas canvas) { 59 | if (drawArc) { 60 | mPaint.setColor(Color.parseColor("#66000000")); 61 | mPaint.setAlpha((int) ((angle) * 102 / maxSweepAngle)); 62 | canvas.drawArc(mRfOut, startAngle, angle, true, mPaint); 63 | mPaint.setColor(Color.WHITE); 64 | mPaint.setAlpha((int) ((angle) * 102 / maxSweepAngle)); 65 | canvas.drawArc(mRfIn, startAngle, angle, true, mPaint); 66 | } 67 | } 68 | 69 | 70 | public boolean toggle() { 71 | needOpen = !needOpen; 72 | if (needOpen) { 73 | open(); 74 | return true; 75 | } else { 76 | close(); 77 | return false; 78 | } 79 | } 80 | 81 | private void close() { 82 | ObjectAnimator animator = ObjectAnimator.ofFloat(this, "", maxSweepAngle, 0f) 83 | .setDuration(800); 84 | animator.setRepeatCount(0); 85 | animator.addListener(new Animator.AnimatorListener() { 86 | @Override 87 | public void onAnimationStart(Animator animation) { 88 | drawArc = true; 89 | 90 | } 91 | 92 | @Override 93 | public void onAnimationEnd(Animator animation) { 94 | drawArc = false; 95 | isOpen = false; 96 | } 97 | 98 | @Override 99 | public void onAnimationCancel(Animator animation) { 100 | 101 | } 102 | 103 | @Override 104 | public void onAnimationRepeat(Animator animation) { 105 | 106 | } 107 | }); 108 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 109 | @Override 110 | public void onAnimationUpdate(ValueAnimator animation) { 111 | angle = (float) animation.getAnimatedValue(); 112 | angle = angle <= maxSweepAngle ? angle : maxSweepAngle; 113 | layoutCount = (int) (angle / (1.0f * maxSweepAngle / cCount)); 114 | postInvalidate(); 115 | requestLayout(); 116 | } 117 | }); 118 | animator.start(); 119 | 120 | } 121 | 122 | private void open() { 123 | ObjectAnimator animator = ObjectAnimator.ofFloat(this, "", 0f, maxSweepAngle) 124 | .setDuration(800); 125 | animator.setRepeatCount(0); 126 | animator.addListener(new Animator.AnimatorListener() { 127 | @Override 128 | public void onAnimationStart(Animator animation) { 129 | drawArc = true; 130 | 131 | } 132 | 133 | @Override 134 | public void onAnimationEnd(Animator animation) { 135 | isOpen = true; 136 | } 137 | 138 | @Override 139 | public void onAnimationCancel(Animator animation) { 140 | 141 | } 142 | 143 | @Override 144 | public void onAnimationRepeat(Animator animation) { 145 | 146 | } 147 | }); 148 | 149 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 150 | @Override 151 | public void onAnimationUpdate(ValueAnimator animation) { 152 | angle = (float) animation.getAnimatedValue(); 153 | angle = angle <= maxSweepAngle ? angle : maxSweepAngle; 154 | layoutCount = (int) (angle / (1.0f * maxSweepAngle / cCount)); 155 | postInvalidate(); 156 | requestLayout(); 157 | } 158 | }); 159 | animator.start(); 160 | 161 | } 162 | 163 | @Override 164 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 165 | measureChildren(widthMeasureSpec, heightMeasureSpec); 166 | 167 | 168 | inCircle = getChildAt(0).getWidth() / 2; 169 | outCircle = inCircle + getChildAt(1).getWidth(); 170 | //控件的大小为2倍 FloatingView大小 171 | setMeasuredDimension(outCircle * 2, outCircle * 2); 172 | } 173 | 174 | int outCircle = 0; 175 | int inCircle = 0; 176 | int measuredWidth = 0; 177 | int measuredHeight = 0; 178 | int cCount = 0; 179 | 180 | @Override 181 | public boolean onTouchEvent(MotionEvent event) { 182 | return false; 183 | } 184 | 185 | @Override 186 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 187 | mRfOut = new RectF(measuredWidth / 2 - outCircle, measuredHeight / 2 - outCircle, measuredWidth / 2 + outCircle, measuredHeight / 2 + outCircle); 188 | mRfIn = new RectF(measuredWidth / 2 - (inCircle), measuredHeight / 2 - (inCircle), measuredWidth / 2 + (inCircle), measuredHeight / 2 + (inCircle)); 189 | measuredWidth = getMeasuredWidth(); 190 | measuredHeight = getMeasuredHeight(); 191 | cCount = getChildCount(); 192 | maxSweepAngle = (cCount + 2) * 30; 193 | int cWidth = 0; 194 | int cHeight = 0; 195 | MarginLayoutParams cParams = null; 196 | 197 | double base = getChildAt(0).getWidth() * 0.5 + getChildAt(1).getWidth() / 2; 198 | /** 199 | * 遍历所有childView根据其宽和高,以及margin进行布局 200 | */ 201 | for (int i = 0; i < cCount; i++) { 202 | View childView = getChildAt(i); 203 | cWidth = childView.getMeasuredWidth(); 204 | cHeight = childView.getMeasuredHeight(); 205 | cParams = (MarginLayoutParams) childView.getLayoutParams(); 206 | 207 | int cl = 0, ct = 0, cr = 0, cb = 0; 208 | if (i == 0) { 209 | cl = getMeasuredWidth() / 2 - cWidth / 2 + cParams.leftMargin; 210 | ct = getMeasuredHeight() / 2 - cHeight / 2 + cParams.topMargin; 211 | } else { 212 | if (i <= layoutCount) { 213 | cl = getWidth() / 2 - cWidth / 2 - cParams.leftMargin 214 | - cParams.rightMargin + (int) (base * Math.cos(Math.PI * (45 * i + 45) / 180)); 215 | ct = getHeight() / 2 - cHeight / 2 + cParams.topMargin + (int) (base * Math.sin(Math.PI * (i * 45 + 45) / 180)); 216 | } else { 217 | cl = 1000000; 218 | ct = 1000000; 219 | } 220 | } 221 | cr = cl + cWidth + cParams.rightMargin; 222 | cb = cHeight + ct + cParams.bottomMargin; 223 | childView.layout(cl, ct, cr, cb); 224 | } 225 | } 226 | 227 | boolean isOpen; 228 | 229 | public boolean isOpen() { 230 | return isOpen; 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/ndh/floatingball/sdk/FloatingService.java: -------------------------------------------------------------------------------- 1 | package com.example.ndh.floatingball.sdk; 2 | 3 | import android.app.Notification; 4 | import android.app.NotificationManager; 5 | import android.app.PendingIntent; 6 | import android.app.Service; 7 | import android.content.Context; 8 | import android.content.Intent; 9 | import android.content.res.Configuration; 10 | import android.os.IBinder; 11 | import android.support.annotation.Nullable; 12 | 13 | import com.example.ndh.floatingball.MainActivity; 14 | import com.example.ndh.floatingball.R; 15 | 16 | /** 17 | * Created by ndh on 16/12/14. 18 | */ 19 | 20 | public class FloatingService extends Service { 21 | 22 | private NotificationManager mManager; 23 | private Notification mNotification; 24 | 25 | @Nullable 26 | @Override 27 | public IBinder onBind(Intent intent) { 28 | return null; 29 | } 30 | 31 | @Override 32 | public void onCreate() { 33 | createView(); 34 | super.onCreate(); 35 | } 36 | 37 | 38 | @Override 39 | public int onStartCommand(Intent intent, int flags, int startId) { 40 | return START_STICKY; 41 | } 42 | 43 | private static final int ID = 0; 44 | 45 | private void createView() { 46 | FloatingWindowManager.create(this).init(); 47 | createNotification(); 48 | 49 | } 50 | 51 | private void createNotification() { 52 | Intent notificationIntent = new Intent(getApplicationContext(), MainActivity.class); 53 | PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, notificationIntent, 0); 54 | //START 让应用通过通知置于前台 55 | Notification.Builder builder = new Notification.Builder(this) 56 | .setSmallIcon(R.drawable.ic_assist) 57 | .setContentText("快捷助手正在运行...") 58 | .setContentIntent(pendingIntent); 59 | mNotification = builder.build(); 60 | mNotification.flags |= Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT; 61 | mManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 62 | mManager.notify(ID, mNotification); 63 | startForeground(ID, mNotification); 64 | // END 让应用通过通知置于前台 65 | } 66 | 67 | @Override 68 | public void onDestroy() { 69 | super.onDestroy(); 70 | removeAllView(); 71 | stopForeground(true); 72 | mManager.cancel(ID); 73 | android.os.Process.killProcess(android.os.Process.myPid()); 74 | } 75 | 76 | @Override 77 | public void onConfigurationChanged(Configuration newConfig) { 78 | //横竖屏切换时,需要重置再重建 79 | removeAllView(); 80 | createView(); 81 | super.onConfigurationChanged(newConfig); 82 | } 83 | 84 | private void removeAllView() { 85 | FloatingWindowManager.create(this).removeAllView(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/ndh/floatingball/sdk/FloatingView.java: -------------------------------------------------------------------------------- 1 | package com.example.ndh.floatingball.sdk; 2 | 3 | import android.content.Context; 4 | import android.content.pm.ApplicationInfo; 5 | import android.content.pm.PackageManager; 6 | import android.graphics.Bitmap; 7 | import android.graphics.BitmapFactory; 8 | import android.graphics.Canvas; 9 | import android.graphics.Color; 10 | import android.graphics.Matrix; 11 | import android.graphics.Paint; 12 | import android.graphics.RectF; 13 | import android.graphics.Typeface; 14 | import android.os.Vibrator; 15 | import android.text.TextUtils; 16 | import android.util.AttributeSet; 17 | import android.util.Log; 18 | import android.view.MotionEvent; 19 | import android.view.View; 20 | 21 | import com.example.ndh.floatingball.R; 22 | import com.example.ndh.floatingball.util.Utils; 23 | 24 | /** 25 | * Created by ndh on 16/12/13. 26 | */ 27 | 28 | public class FloatingView extends View { 29 | //阻力效果 30 | private int scale = 10; 31 | private int width = 25; 32 | private int r = width; 33 | private float startX; 34 | private float startY; 35 | //记录手指的移动 36 | private float moveX; 37 | private float moveY; 38 | //中间的圆球 39 | private Bitmap pinWheelBmp; 40 | private String mLeft = "left"; 41 | private String mUp = "up"; 42 | private String mRight = "right"; 43 | private String mDown = "down"; 44 | private float textAlpha = 0; 45 | private Paint mPaint; 46 | //长按震动,提醒用户可以在屏幕上拖拽控件到任意位置 47 | private static Vibrator sVibrator; 48 | boolean isMoving; 49 | 50 | private String drawText; 51 | private float textX; 52 | private float textY; 53 | //定位文字的位置 54 | private RectF rectFtop; 55 | private RectF rectFleft; 56 | private RectF rectFright; 57 | private RectF rectFdown; 58 | private RectF textRect; 59 | //控制圆球的拖动距离 60 | private float x; 61 | private float y; 62 | // 让圆球可以有一定拖出圆圈范围的效果 63 | private final float circleScale = 3.0f / 4; 64 | //判断是否点击 65 | private boolean click; 66 | //判断是否长按 67 | private boolean longClick; 68 | 69 | public FloatingView(Context context) { 70 | this(context, null); 71 | } 72 | 73 | public FloatingView(Context context, AttributeSet attrs) { 74 | this(context, attrs, 0); 75 | } 76 | 77 | public FloatingView(Context context, AttributeSet attrs, int defStyleAttr) { 78 | super(context, attrs, defStyleAttr); 79 | init(); 80 | } 81 | 82 | private void init() { 83 | mPaint = new Paint(); 84 | mPaint.setAntiAlias(true); 85 | mPaint.setDither(true); 86 | mPaint.setTypeface(Typeface.DEFAULT_BOLD); 87 | mPaint.setTextSize(10); 88 | mPaint.setTextAlign(Paint.Align.CENTER); 89 | mPaint.setTypeface(Typeface.DEFAULT_BOLD); 90 | setBackgroundColor(Color.TRANSPARENT); 91 | r = width = Utils.dp2pix(getContext(), width); 92 | sVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); 93 | } 94 | 95 | @Override 96 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 97 | setMeasuredDimension(2 * r, 2 * r); 98 | } 99 | 100 | @Override 101 | protected void onDraw(Canvas canvas) { 102 | Log.d("ndh--", "alpha=" + textAlpha + ",r=" + r * circleScale + "r--" + r + "circleScale=" + circleScale); 103 | mPaint.setARGB((int) (100), 0, 0, 0); 104 | int width = getMeasuredWidth() / 2; 105 | int height = getMeasuredHeight() / 2; 106 | // 限定圆球移动的圆圈,该圆圈小于圆球的移动范围 107 | canvas.drawCircle(width, height, r * circleScale, mPaint); 108 | pinWheelBmp = BitmapFactory.decodeResource(getResources(), R.drawable.ball); 109 | // 限定圆球的大小 110 | pinWheelBmp = Bitmap.createScaledBitmap(pinWheelBmp, r, r, true); 111 | Matrix matrix = new Matrix(); 112 | if (within(moveX, moveY)) { 113 | x = moveX; 114 | y = moveY; 115 | } 116 | //通过matrix控制圆球的移动 117 | matrix.preTranslate(getMeasuredWidth() / 2 - pinWheelBmp.getWidth() / 2 + x / scale, getMeasuredHeight() / 2 - pinWheelBmp.getHeight() / 2 + y / scale); 118 | canvas.drawBitmap(pinWheelBmp, matrix, null); 119 | if (!TextUtils.isEmpty(drawText)) { 120 | mPaint.setColor(Color.WHITE); 121 | mPaint.setAlpha((int) ((textAlpha / r) * 255)); 122 | canvas.drawText(drawText, textX, textY, mPaint); 123 | mPaint.setAlpha(255); 124 | } 125 | // canvas.drawPicture(); 126 | 127 | } 128 | 129 | private static Runnable sRunnable = new Runnable() { 130 | @Override 131 | public void run() { 132 | Log.d("ndh--", "vibrate---"); 133 | sVibrator.vibrate(Config.VIBRATE_TIME); 134 | } 135 | }; 136 | 137 | @Override 138 | public boolean onTouchEvent(MotionEvent event) { 139 | 140 | switch (event.getAction()) { 141 | case MotionEvent.ACTION_DOWN: 142 | //rawX表示距离整个屏幕的x距离 getX表示距离父控件x距离 143 | startX = event.getRawX(); 144 | startY = event.getRawY(); 145 | click = true; 146 | longClick = true; 147 | if (!FloatingWindowManager.create(getContext()).isOpen()) { 148 | postDelayed(sRunnable, Config.WAITING_TIME); 149 | } 150 | 151 | case MotionEvent.ACTION_MOVE: 152 | // 该view 是通过FloatinigWindowManager加载到窗体,由于 menuItem也是通过该管理器加载的, 153 | //需要在menuItem加载进窗体的时候 禁止FloatingView的拖拽效果 154 | if (FloatingWindowManager.create(getContext()).isOpen()) { 155 | return true; 156 | } 157 | if (canDrag) { 158 | moveX = event.getRawX() - startX; 159 | moveY = event.getRawY() - startY; 160 | if (Math.abs(moveX) > r / 10 || Math.abs(moveY) > r / 10) { 161 | removeCallbacks(sRunnable); 162 | isMoving = true; 163 | click = false; 164 | longClick = false; 165 | drawText(); 166 | postInvalidate(); 167 | } 168 | } 169 | break; 170 | case MotionEvent.ACTION_UP: 171 | removeCallbacks(sRunnable); 172 | isMoving = false; 173 | if (!isLongClick(event) && click) { 174 | FloatingViewManager.create().post(FloatingViewListener.CLICK); 175 | break; 176 | } 177 | if (Math.abs(moveX / scale) > (r / 4) && Math.abs(moveX) > Math.abs(moveY)) { 178 | //左右 179 | if (moveX < 0) { 180 | FloatingViewManager.create().post(FloatingViewListener.RIGHT); 181 | } else { 182 | FloatingViewManager.create().post(FloatingViewListener.LEFT); 183 | } 184 | } 185 | if (Math.abs(moveY / scale) > (r / 4) && Math.abs(moveX) < Math.abs(moveY)) { 186 | //上下 187 | if (moveY < 0) { 188 | FloatingViewManager.create().post(FloatingViewListener.DOWN); 189 | } else { 190 | FloatingViewManager.create().post(FloatingViewListener.UP); 191 | } 192 | } 193 | moveX = 0; 194 | moveY = 0; 195 | textAlpha = 0; 196 | postInvalidate(); 197 | break; 198 | } 199 | return true; 200 | 201 | } 202 | 203 | private boolean isLongClick(MotionEvent event) { 204 | long downTime = event.getDownTime(); 205 | long upTime = event.getEventTime(); 206 | if (longClick && (upTime - downTime > Config.WAITING_TIME)) { 207 | FloatingViewManager.create().post(FloatingViewListener.LONG_CLICK); 208 | click = false; 209 | longClick = false; 210 | return true; 211 | } 212 | return false; 213 | } 214 | 215 | // 画 四个方位的字 216 | private void drawText() { 217 | if (Math.abs(moveX) > r / 10 && Math.abs(moveX) > Math.abs(moveY)) { 218 | textAlpha = Math.abs((int) (moveX * 1.5) / scale); 219 | //左右方向 220 | if (moveX < 0) { 221 | drawText = mRight; 222 | textRect = rectFright; 223 | textX = getMeasuredWidth() / 2 + getMeasuredWidth() / 4; 224 | } else { 225 | drawText = mLeft; 226 | textRect = rectFleft; 227 | textX = getMeasuredWidth() / 2 - getMeasuredWidth() / 4; 228 | } 229 | 230 | } 231 | if (Math.abs(moveY) > r / 10 && Math.abs(moveX) < Math.abs(moveY)) { 232 | textAlpha = Math.abs((int) (moveY * 1.5) / scale); 233 | //上下方向 234 | if (moveY < 0) { 235 | drawText = mDown; 236 | textRect = rectFdown; 237 | } else { 238 | drawText = mUp; 239 | textRect = rectFtop; 240 | } 241 | textX = getMeasuredWidth() / 2; 242 | } 243 | textAlpha = textAlpha >= r ? r : textAlpha; 244 | if (null != textRect) 245 | textY = (textRect.bottom + textRect.top - mPaint.getFontMetrics().bottom - mPaint.getFontMetrics().top) / 2; 246 | } 247 | 248 | // 限制圆球只能在一个圆形范围移动,该方法只能放在onDraw里面,千万不要放到action_move里面,因为postInvalidate有延时,会导致实际滑动距离偏大 249 | private boolean within(float startX, float startY) { 250 | if (null == pinWheelBmp) { 251 | return false; 252 | } 253 | double temp = (Math.sqrt((startX / scale) * (startX / scale) + (startY / scale) * (startY / scale))); 254 | return temp <= r / 2; 255 | } 256 | 257 | /** 258 | * 设置左上右下的文字提示 259 | * 260 | * @param left 261 | * @param up 262 | * @param right 263 | * @param down 264 | */ 265 | @NotProguard 266 | public void setTips(String left, String up, String right, String down) { 267 | mLeft = left; 268 | mUp = up; 269 | mRight = right; 270 | mDown = down; 271 | } 272 | @NotProguard 273 | public boolean isMoving() { 274 | return isMoving; 275 | } 276 | 277 | boolean canDrag = true; 278 | @NotProguard 279 | public void setCanDrag(boolean flag) { 280 | canDrag = flag; 281 | } 282 | 283 | /** 284 | * the width/height of this view 285 | * 286 | * @return 287 | */ 288 | @NotProguard 289 | public int[] getSize() { 290 | int[] size = new int[2]; 291 | size[0] = getMeasuredWidth(); 292 | size[1] = getMeasuredHeight(); 293 | // PackageManager pm=get 294 | // ApplicationInfo applicationInfo 295 | return size; 296 | } 297 | 298 | @Override 299 | protected void onDetachedFromWindow() { 300 | super.onDetachedFromWindow(); 301 | 302 | 303 | } 304 | 305 | @Override 306 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 307 | super.onLayout(changed, left, top, right, bottom); 308 | //以下四个矩形是为了定位 上下左右 四个方位上的字的位置 309 | rectFtop = new RectF(getMeasuredWidth() / 2 - r * circleScale, getMeasuredHeight() / 2 - r * circleScale, getMeasuredWidth() / 2 + r * circleScale, getMeasuredHeight() / 2); 310 | rectFleft = new RectF(getMeasuredWidth() / 2 - r * circleScale, getMeasuredHeight() / 2 - r * circleScale, getMeasuredWidth() / 2, getMeasuredHeight() / 2 + r * circleScale); 311 | rectFright = new RectF(getMeasuredWidth() / 2, getMeasuredHeight() / 2 - r * circleScale, getMeasuredWidth() / 2 + r * circleScale, getMeasuredHeight() / 2 + r * circleScale); 312 | rectFdown = new RectF(getMeasuredWidth() / 2 - r * circleScale, getMeasuredHeight() / 2, getMeasuredWidth() / 2 + r * circleScale, getMeasuredHeight() / 2 + r * circleScale); 313 | 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/ndh/floatingball/sdk/FloatingViewListener.java: -------------------------------------------------------------------------------- 1 | package com.example.ndh.floatingball.sdk; 2 | 3 | /** 4 | * Created by ndh on 16/12/13. 5 | */ 6 | 7 | public interface FloatingViewListener { 8 | int UP = 0; 9 | int DOWN = 1; 10 | int LEFT = 2; 11 | int RIGHT = 3; 12 | int CLICK = 4; 13 | int LONG_CLICK = 5; 14 | 15 | void onFinish(int flag); 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/ndh/floatingball/sdk/FloatingViewManager.java: -------------------------------------------------------------------------------- 1 | package com.example.ndh.floatingball.sdk; 2 | 3 | /** 4 | * Created by ndh on 16/12/13. 5 | */ 6 | 7 | public class FloatingViewManager { 8 | 9 | private FloatingViewManager() { 10 | } 11 | 12 | private FloatingViewListener mListener; 13 | @NotProguard 14 | public static FloatingViewManager create() { 15 | return SingleInstance.INSTANCE; 16 | } 17 | 18 | private static class SingleInstance { 19 | public static final FloatingViewManager INSTANCE = new FloatingViewManager(); 20 | } 21 | @NotProguard 22 | public void register(FloatingViewListener listener) { 23 | mListener = listener; 24 | } 25 | @NotProguard 26 | public boolean post(int flag) { 27 | if (null != mListener) { 28 | mListener.onFinish(flag); 29 | return true; 30 | } 31 | return false; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/ndh/floatingball/sdk/FloatingWindowManager.java: -------------------------------------------------------------------------------- 1 | package com.example.ndh.floatingball.sdk; 2 | 3 | import android.animation.Animator; 4 | import android.animation.ObjectAnimator; 5 | import android.animation.ValueAnimator; 6 | import android.content.Context; 7 | import android.graphics.PixelFormat; 8 | import android.graphics.Point; 9 | import android.util.Log; 10 | import android.view.Gravity; 11 | import android.view.MotionEvent; 12 | import android.view.View; 13 | import android.view.WindowManager; 14 | 15 | import com.example.ndh.floatingball.util.Utils; 16 | 17 | import java.lang.ref.WeakReference; 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | /** 22 | * Created by ndh on 16/12/16. 23 | */ 24 | 25 | public class FloatingWindowManager { 26 | private static WeakReference mContext; 27 | //弱引用 28 | private WindowManager.LayoutParams wmParams; 29 | private WindowManager mWindowManager; 30 | private int duration = Config.DURATION; 31 | private boolean canLayout = true;//避免快速点击圆球,展开和关闭的动画重复执行,这将导致界面布局异常 32 | private Point p = new Point(); 33 | private List attachToWindows = new ArrayList<>(); 34 | /** 35 | * true 表示可以开启 false 表示可以关闭 36 | */ 37 | private boolean toggle = true; 38 | private int base = Config.BASE; 39 | private ObjectAnimator mOpenAnimator; 40 | private ObjectAnimator mCloseAnimator; 41 | 42 | private FloatingWindowManager() { 43 | Log.d("ndh--", "createFloatView"); 44 | wmParams = new WindowManager.LayoutParams(); 45 | //获取的是WindowManagerImpl.CompatModeWrapper 46 | mWindowManager = (WindowManager) mContext.get().getSystemService(mContext.get().WINDOW_SERVICE); 47 | //设置window type 搞成toast方式 不需要添加权限 48 | wmParams.type = WindowManager.LayoutParams.TYPE_TOAST; 49 | //设置图片格式,效果为背景透明 50 | wmParams.format = PixelFormat.RGBA_8888; 51 | //设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作) 52 | wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 53 | //调整悬浮窗显示的停靠位置为左侧置顶 54 | wmParams.gravity = Gravity.START | Gravity.TOP; 55 | // 以屏幕左上角为原点,设置x、y初始值,相对于gravity 56 | p = Utils.getScreenSize(mContext.get()); 57 | base = Utils.dp2pix(mContext.get(), base); 58 | FloatingViewManager.create().register(new FloatingViewListener() { 59 | @Override 60 | public void onFinish(int flag) { 61 | switch (flag) { 62 | case FloatingViewListener.DOWN: 63 | 64 | ActionManager.create().doAction(mContext.get(), Config.MenuPosition.DOWN); 65 | break; 66 | case FloatingViewListener.UP: 67 | //回到桌面 68 | ActionManager.create().doAction(mContext.get(), Config.MenuPosition.UP); 69 | break; 70 | case FloatingViewListener.LEFT: 71 | //静音 72 | ActionManager.create().doAction(mContext.get(), Config.MenuPosition.LEFT); 73 | break; 74 | case FloatingViewListener.RIGHT: 75 | // 指定开启系统相机的Action 76 | ActionManager.create().doAction(mContext.get(), Config.MenuPosition.RIGHT); 77 | break; 78 | case FloatingViewListener.CLICK: 79 | int[] points = new int[2]; 80 | mFloatView.getLocationOnScreen(points); 81 | points[0] = points[0] + mFloatView.getMeasuredWidth() / 2; 82 | points[1] = points[1]; 83 | FloatingWindowManager.create(mContext.get()).toggle(points); 84 | 85 | } 86 | } 87 | }); 88 | } 89 | 90 | public static FloatingWindowManager create(Context context) { 91 | mContext = new WeakReference(context); 92 | 93 | return SingleInstance.INSTANCE; 94 | } 95 | 96 | @NotProguard 97 | public void init() { 98 | if (isAttachToWindow()) { 99 | removeAllView(); 100 | } 101 | ActionManager.create().initAction(mContext.get()); 102 | MenuItemManager.create(mContext.get()).createMenuItem(); 103 | createFloatView(); 104 | } 105 | 106 | private static class SingleInstance { 107 | public static final FloatingWindowManager INSTANCE = new FloatingWindowManager(); 108 | } 109 | 110 | @NotProguard 111 | public void toggle(int[] centerPositions) { 112 | if (!canLayout) { 113 | return; 114 | } 115 | 116 | List list = MenuItemManager.create(mContext.get()).getListOfViews(); 117 | 118 | if (toggle) { 119 | open(list, centerPositions); 120 | } else { 121 | close(list); 122 | } 123 | toggle = !toggle; 124 | } 125 | 126 | private void attach(View v, int x, int y) { 127 | if (wmParams == null || mWindowManager == null || v == null) { 128 | throw new RuntimeException("windowManager not exists / view is null"); 129 | } 130 | //说明已经当前view已经添加到window上了 131 | if (attachToWindows.contains(v)) { 132 | return; 133 | } 134 | wmParams.x = x; 135 | wmParams.y = y; 136 | //设置悬浮窗口长宽数据 137 | wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT; 138 | wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT; 139 | mWindowManager.addView(v, wmParams); 140 | attachToWindows.add(v); 141 | Log.d("ndh--", "attach--v=" + v + ",x=" + x + ",y=" + y); 142 | } 143 | 144 | private void setPosition(int x, int y) { 145 | if (null != wmParams) { 146 | wmParams.x = x; 147 | wmParams.y = y; 148 | } 149 | 150 | } 151 | 152 | private void update(View v) { 153 | if (!toggle) { 154 | return; 155 | } 156 | if (null != mWindowManager && null != v) 157 | mWindowManager.updateViewLayout(v, wmParams); 158 | 159 | } 160 | 161 | /** 162 | * 所有的view的移除都由该方法提供 163 | * 164 | * @param v 165 | */ 166 | private void detach(View v) { 167 | Log.d("ndh--", "detach--v=" + v); 168 | if (attachToWindows.contains(v) && null != mWindowManager && null != v) { 169 | mWindowManager.removeView(v); 170 | attachToWindows.remove(v); 171 | } 172 | toggle = true; 173 | } 174 | 175 | private boolean isAttachToWindow() { 176 | return attachToWindows.size() > 0 ? true : false; 177 | } 178 | 179 | private int getExSize() { 180 | return base; 181 | } 182 | 183 | private void close(final List list) { 184 | //只有5个menu 没个menu摆放距离相差角度45度 185 | mCloseAnimator = ObjectAnimator.ofFloat(this, "", 0, 45 * 5f) 186 | .setDuration(duration); 187 | mCloseAnimator.setRepeatCount(0); 188 | mCloseAnimator.addListener(new Animator.AnimatorListener() { 189 | @Override 190 | public void onAnimationStart(Animator animation) { 191 | tempClose.clear(); 192 | canLayout = false; 193 | } 194 | 195 | @Override 196 | public void onAnimationEnd(Animator animation) { 197 | if (null != mCloseAnimator) { 198 | mCloseAnimator = null; 199 | } 200 | canLayout = true; 201 | } 202 | 203 | @Override 204 | public void onAnimationCancel(Animator animation) { 205 | if (null != mCloseAnimator) { 206 | mCloseAnimator = null; 207 | } 208 | canLayout = true; 209 | } 210 | 211 | @Override 212 | public void onAnimationRepeat(Animator animation) { 213 | 214 | } 215 | }); 216 | mCloseAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 217 | @Override 218 | public void onAnimationUpdate(ValueAnimator animation) { 219 | removeView(animation, list); 220 | 221 | } 222 | }); 223 | mCloseAnimator.start(); 224 | 225 | } 226 | 227 | private void removeView(ValueAnimator animation, List list) { 228 | float value = (float) animation.getAnimatedValue(); 229 | if (list.size() == 0) { 230 | return; 231 | } 232 | int count = (int) (value / (45 * 5 / list.size())); 233 | for (int i = 0; i < count; i++) { 234 | //避免重复移除view 235 | if (tempClose.contains(list.get(list.size() - i - 1))) { 236 | continue; 237 | } 238 | detach(list.get(list.size() - i - 1)); 239 | tempClose.add(list.get(list.size() - i - 1)); 240 | } 241 | 242 | } 243 | 244 | List tempOpen = new ArrayList(); 245 | List tempClose = new ArrayList(); 246 | 247 | 248 | private void open(final List list, final int[] centerPositions) { 249 | 250 | mOpenAnimator = ObjectAnimator.ofFloat(this, "", 0f, 45 * 5) 251 | .setDuration(duration); 252 | mOpenAnimator.setRepeatCount(0); 253 | mOpenAnimator.addListener(new Animator.AnimatorListener() { 254 | @Override 255 | public void onAnimationStart(Animator animation) { 256 | // 动画前清空集合,在动画执行过程中会 为集合赋值 见addView方法 257 | tempOpen.clear(); 258 | canLayout = false; 259 | } 260 | 261 | @Override 262 | public void onAnimationEnd(Animator animation) { 263 | //动画 264 | if (null != mOpenAnimator) { 265 | mOpenAnimator = null; 266 | } 267 | canLayout = true; 268 | } 269 | 270 | @Override 271 | public void onAnimationCancel(Animator animation) { 272 | if (null != mOpenAnimator) { 273 | mOpenAnimator = null; 274 | } 275 | canLayout = true; 276 | } 277 | 278 | @Override 279 | public void onAnimationRepeat(Animator animation) { 280 | 281 | } 282 | }); 283 | 284 | mOpenAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 285 | @Override 286 | public void onAnimationUpdate(ValueAnimator animation) { 287 | addView(animation, list, centerPositions); 288 | } 289 | }); 290 | mOpenAnimator.start(); 291 | 292 | } 293 | 294 | private void addView(ValueAnimator animation, List list, int[] centerPositions) { 295 | float value = (float) animation.getAnimatedValue(); 296 | if (list.size() == 0) { 297 | return; 298 | } 299 | // 这里通过改变count实现依次展开效果 300 | int count = (int) (value / (45 * 5 / list.size())); 301 | for (int i = 0; i < count; i++) { 302 | // 避免重复添加view 303 | if (tempOpen.contains(list.get(i))) { 304 | continue; 305 | } 306 | //必须先测量一下 否则拿不到宽/高 307 | if (0 == list.get(i).getMeasuredWidth()) { 308 | list.get(i).measure(View.MeasureSpec.makeMeasureSpec(0, 309 | View.MeasureSpec.UNSPECIFIED), View.MeasureSpec 310 | .makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); 311 | } 312 | if (p.x / 2 > centerPositions[0]) { 313 | //圆球在屏幕右方 则menu 在圆球左方展开 注意45为弧度,需要转换为角度才能使用 314 | wmParams.x = centerPositions[0] - list.get(i).getMeasuredWidth() / 2 - (int) (base * Math.cos(Math.PI * (45 * i + 90) / 180)); 315 | } else { 316 | //圆球在屏幕左方 则menu 在圆球右方展开 317 | wmParams.x = centerPositions[0] - list.get(i).getMeasuredWidth() / 2 + (int) (base * Math.cos(Math.PI * (45 * i + 90) / 180)); 318 | 319 | } 320 | wmParams.y = centerPositions[1] - list.get(i).getMeasuredHeight() / 2 + (int) (base * Math.sin(Math.PI * (i * 45 + 90) / 180)); 321 | attach(list.get(i), wmParams.x, wmParams.y); 322 | tempOpen.add(list.get(i)); 323 | } 324 | } 325 | 326 | @NotProguard 327 | public boolean isOpen() { 328 | return !toggle; 329 | } 330 | 331 | FloatingView mFloatView; 332 | 333 | @NotProguard 334 | public void createFloatView() { 335 | 336 | mFloatView = new FloatingView(mContext.get().getApplicationContext()); 337 | FloatingWindowManager.create(mContext.get().getApplicationContext()).attach(mFloatView, Utils.getScreenSize(mContext.get().getApplicationContext()).x, Utils.getScreenSize(mContext.get().getApplicationContext()).y / 2); 338 | mFloatView.setTips(ActionManager.create().getAction(Config.MenuPosition.LEFT), ActionManager.create().getAction(Config.MenuPosition.UP), ActionManager.create().getAction(Config.MenuPosition.RIGHT), ActionManager.create().getAction(Config.MenuPosition.DOWN)); 339 | final int width = mFloatView.getMeasuredWidth() * 2; 340 | final int height = mFloatView.getMeasuredHeight() * 2; 341 | //设置监听浮动窗口的触摸移动 342 | mFloatView.setOnTouchListener(new View.OnTouchListener() { 343 | @Override 344 | public boolean onTouch(View v, MotionEvent event) { 345 | 346 | if (mFloatView.isMoving() || event.getEventTime() - event.getDownTime() < Config.WAITING_TIME) { 347 | //在非移动的情况下,将触摸事件给会floatingView 348 | return false; 349 | } 350 | 351 | switch (event.getAction()) { 352 | case MotionEvent.ACTION_DOWN: 353 | break; 354 | case MotionEvent.ACTION_MOVE: 355 | int[] posionts = new int[2]; 356 | mFloatView.getLocationOnScreen(posionts); 357 | //getRawX是触摸位置相对于屏幕的坐标 358 | if (Math.abs(event.getRawX() - posionts[0]) > width || Math.abs(event.getRawY() - posionts[1]) > height) { 359 | setPosition((int) event.getRawX() - width, (int) event.getRawY() - height - 25); 360 | update(mFloatView); 361 | } 362 | break; 363 | case MotionEvent.ACTION_UP: 364 | int[] posionts1 = new int[2]; 365 | mFloatView.getLocationOnScreen(posionts1); 366 | // 屏幕上方预留 展开的高度 367 | // if (Math.abs(posionts1[0]) < getExSize()) { 368 | // setPosition((int) event.getRawX() - width + getExSize(), getExSize()); 369 | // } 370 | // if (Utils.getScreenSize(mContext.get().getApplicationContext()).x - (Math.abs(posionts1[0])) - width < getExSize()) { 371 | // setPosition(Utils.getScreenSize(mContext.get().getApplicationContext()).x - (Math.abs(posionts1[0])) - getExSize(), getExSize()); 372 | // } 373 | if (Math.abs(posionts1[1]) < getExSize()) { 374 | setPosition((int) event.getRawX() - width, getExSize()); 375 | // update(mFloatView); 376 | } 377 | //屏幕下方预留 展开的高度 378 | if (Utils.getScreenSize(mContext.get().getApplicationContext()).y - (Math.abs(posionts1[1]) + mFloatView.getMeasuredHeight()) < getExSize()) { 379 | setPosition((int) event.getRawX() - width, Utils.getScreenSize(mContext.get().getApplicationContext()).y - getExSize() - mFloatView.getMeasuredHeight() - MenuItemManager.create(mContext.get().getApplicationContext()).getItemHeight() / 2); 380 | // update(mFloatView); 381 | } 382 | update(mFloatView); 383 | } 384 | 385 | return true; 386 | } 387 | }); 388 | } 389 | 390 | @NotProguard 391 | public void removeAllView() { 392 | if (mFloatView != null) { 393 | detach(mFloatView); 394 | //移除悬浮窗口 395 | mFloatView = null; 396 | } 397 | List list = MenuItemManager.create(mContext.get()).getListOfViews(); 398 | for (int i = 0; i < list.size(); i++) { 399 | if (list.get(i) != null) { 400 | detach(list.get(i)); 401 | } 402 | } 403 | MenuItemManager.create(mContext.get()).clear(); 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/ndh/floatingball/sdk/MenuItemManager.java: -------------------------------------------------------------------------------- 1 | package com.example.ndh.floatingball.sdk; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.view.Gravity; 6 | import android.view.View; 7 | import android.util.Log; 8 | import android.widget.TextView; 9 | 10 | import com.example.ndh.floatingball.R; 11 | 12 | import java.lang.ref.WeakReference; 13 | import java.util.concurrent.CopyOnWriteArrayList; 14 | 15 | /** 16 | * Created by ndh on 16/12/16. 17 | */ 18 | 19 | public class MenuItemManager implements View.OnClickListener { 20 | private static final int ID_FLASH = 1; 21 | private static final int ID_CALANDER = 2; 22 | private static final int ID_ALARM = 3; 23 | private static final int ID_PHOTO = 4; 24 | private static final int ID_CONTACT = 5; 25 | CopyOnWriteArrayList list = new CopyOnWriteArrayList<>(); 26 | static WeakReference mContext; 27 | 28 | private MenuItemManager() { 29 | } 30 | @NotProguard 31 | public static MenuItemManager create(Context context) { 32 | mContext = new WeakReference(context); 33 | return SingleInstance.INSTANCE; 34 | } 35 | 36 | @Override 37 | public void onClick(View view) { 38 | ActionManager.create().doAction(view.getContext(), (String) view.getTag()); 39 | if (FloatingWindowManager.create(view.getContext()).isOpen()) { 40 | FloatingWindowManager.create(view.getContext()).toggle(new int[]{}); 41 | } 42 | } 43 | 44 | private static class SingleInstance { 45 | public static final MenuItemManager INSTANCE = new MenuItemManager(); 46 | } 47 | 48 | int height = 0; 49 | 50 | private void addView(View view) { 51 | if (view.getMeasuredHeight() == 0) { 52 | view.measure(0, 0); 53 | } 54 | height = view.getMeasuredHeight() > height ? view.getMeasuredHeight() : height; 55 | list.add(view); 56 | Log.d("ndh--", "menuItem add to list"); 57 | } 58 | @NotProguard 59 | public int getItemHeight() { 60 | Log.d("ndh--", "itemHeight=" + height); 61 | return height; 62 | } 63 | @NotProguard 64 | public CopyOnWriteArrayList getListOfViews() { 65 | return list; 66 | } 67 | @NotProguard 68 | public boolean clear() { 69 | list.clear(); 70 | return true; 71 | } 72 | 73 | int textSize = 10; 74 | @NotProguard 75 | public void createMenuItem() { 76 | TextView textView = new TextView(mContext.get()); 77 | textView.setTextSize(textSize); 78 | textView.setTextColor(Color.WHITE); 79 | textView.setText(ActionManager.create().getAction(Config.MenuPosition.MENU_1)); 80 | textView.setTag(Config.MenuPosition.MENU_1); 81 | textView.setGravity(Gravity.CENTER); 82 | textView.setBackgroundResource(R.drawable.bg_menu); 83 | TextView textView1 = new TextView(mContext.get()); 84 | textView1.setTextColor(Color.WHITE); 85 | textView1.setTextSize(textSize); 86 | textView1.setTag(Config.MenuPosition.MENU_2); 87 | textView1.setText(ActionManager.create().getAction(Config.MenuPosition.MENU_2)); 88 | textView1.setGravity(Gravity.CENTER); 89 | textView1.setBackgroundResource(R.drawable.bg_menu); 90 | TextView textView2 = new TextView(mContext.get()); 91 | textView2.setTextSize(textSize); 92 | textView2.setTextColor(Color.WHITE); 93 | textView2.setText(ActionManager.create().getAction(Config.MenuPosition.MENU_3)); 94 | textView2.setTag(Config.MenuPosition.MENU_3); 95 | textView2.setGravity(Gravity.CENTER); 96 | textView2.setBackgroundResource(R.drawable.bg_menu); 97 | TextView textView3 = new TextView(mContext.get()); 98 | textView3.setTextSize(textSize); 99 | textView3.setTextColor(Color.WHITE); 100 | textView3.setText(ActionManager.create().getAction(Config.MenuPosition.MENU_4)); 101 | textView3.setTag(Config.MenuPosition.MENU_4); 102 | textView3.setGravity(Gravity.CENTER); 103 | textView3.setBackgroundResource(R.drawable.bg_menu); 104 | TextView textView4 = new TextView(mContext.get()); 105 | textView4.setTextSize(textSize); 106 | textView4.setTextColor(Color.WHITE); 107 | textView4.setText(ActionManager.create().getAction(Config.MenuPosition.MENU_5)); 108 | textView4.setTag(Config.MenuPosition.MENU_5); 109 | textView4.setGravity(Gravity.CENTER); 110 | textView4.setBackgroundResource(R.drawable.bg_menu); 111 | addView(textView); 112 | addView(textView1); 113 | addView(textView2); 114 | addView(textView3); 115 | addView(textView4); 116 | textView.setOnClickListener(this); 117 | textView1.setOnClickListener(this); 118 | textView2.setOnClickListener(this); 119 | textView3.setOnClickListener(this); 120 | textView4.setOnClickListener(this); 121 | 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/ndh/floatingball/sdk/NotProguard.java: -------------------------------------------------------------------------------- 1 | package com.example.ndh.floatingball.sdk; 2 | 3 | /** 4 | * Created by ndh on 17/1/13. 5 | */ 6 | 7 | import java.lang.annotation.ElementType; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | /** 13 | * NotProguard, Means not proguard something, like class, method, field
14 | * 15 | * @author Trinea 2015-08-07 16 | */ 17 | @Retention(RetentionPolicy.CLASS) 18 | @Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD}) 19 | public @interface NotProguard { 20 | 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/ndh/floatingball/sdk/SelectLayout.java: -------------------------------------------------------------------------------- 1 | package com.example.ndh.floatingball.sdk; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.AttrRes; 5 | import android.support.annotation.NonNull; 6 | import android.support.annotation.Nullable; 7 | import android.util.AttributeSet; 8 | import android.view.Gravity; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.FrameLayout; 12 | import android.util.Log; 13 | import android.widget.TextView; 14 | 15 | import com.example.ndh.floatingball.R; 16 | import com.example.ndh.floatingball.util.Utils; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | import static android.content.ContentValues.TAG; 22 | 23 | /** 24 | * Created by ndh on 16/12/27. 25 | */ 26 | 27 | public class SelectLayout extends FrameLayout { 28 | public void createChild(List views, Context context) { 29 | for (int i = 0; i < views.size(); i++) { 30 | views.get(i).setBackgroundResource(R.drawable.bg_action); 31 | FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(Utils.getScreenSize(context).x / 4, LayoutParams.WRAP_CONTENT); 32 | params.setMargins(Utils.getScreenSize(context).x / (4 * 4), Utils.dp2pix(context, 10), 0, Utils.dp2pix(context, 10)); 33 | views.get(i).setLayoutParams(params); 34 | this.addView(views.get(i)); 35 | } 36 | } 37 | 38 | public SelectLayout(@NonNull Context context) { 39 | super(context); 40 | } 41 | 42 | public SelectLayout(@NonNull Context context, @Nullable AttributeSet attrs) { 43 | super(context, attrs); 44 | } 45 | 46 | public SelectLayout(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { 47 | super(context, attrs, defStyleAttr); 48 | } 49 | 50 | /** 51 | * 存储所有的View,按行记录 52 | */ 53 | private List> mAllViews = new ArrayList>(); 54 | /** 55 | * 记录每一行的最大高度 56 | */ 57 | private List mLineHeight = new ArrayList(); 58 | 59 | @Override 60 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 61 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 62 | // 获得它的父容器为它设置的测量模式和大小 63 | int sizeWidth = MeasureSpec.getSize(widthMeasureSpec); 64 | int sizeHeight = MeasureSpec.getSize(heightMeasureSpec); 65 | int modeWidth = MeasureSpec.getMode(widthMeasureSpec); 66 | int modeHeight = MeasureSpec.getMode(heightMeasureSpec); 67 | 68 | Log.e(TAG, sizeWidth + "," + sizeHeight); 69 | 70 | // 如果是warp_content情况下,记录宽和高 71 | int width = 0; 72 | int height = 0; 73 | /** 74 | * 记录每一行的宽度,width不断取最大宽度 75 | */ 76 | int lineWidth = 0; 77 | /** 78 | * 每一行的高度,累加至height 79 | */ 80 | int lineHeight = 0; 81 | 82 | int cCount = getChildCount(); 83 | 84 | // 遍历每个子元素 85 | for (int i = 0; i < cCount; i++) { 86 | View child = getChildAt(i); 87 | // 测量每一个child的宽和高 88 | measureChild(child, widthMeasureSpec, heightMeasureSpec); 89 | // 得到child的lp 90 | MarginLayoutParams lp = (MarginLayoutParams) child 91 | .getLayoutParams(); 92 | // 当前子空间实际占据的宽度 93 | int childWidth = child.getMeasuredWidth() + lp.leftMargin 94 | + lp.rightMargin; 95 | // 当前子空间实际占据的高度 96 | int childHeight = child.getMeasuredHeight() + lp.topMargin 97 | + lp.bottomMargin; 98 | /** 99 | * 如果加入当前child,则超出最大宽度,则的到目前最大宽度给width,类加height 然后开启新行 100 | */ 101 | if (lineWidth + childWidth > sizeWidth) { 102 | width = Math.max(lineWidth, childWidth);// 取最大的 103 | lineWidth = childWidth; // 重新开启新行,开始记录 104 | // 叠加当前高度, 105 | height += lineHeight; 106 | // 开启记录下一行的高度 107 | lineHeight = childHeight; 108 | } else 109 | // 否则累加值lineWidth,lineHeight取最大高度 110 | { 111 | lineWidth += childWidth; 112 | lineHeight = Math.max(lineHeight, childHeight); 113 | } 114 | // 如果是最后一个,则将当前记录的最大宽度和当前lineWidth做比较 115 | if (i == cCount - 1) { 116 | width = Math.max(width, lineWidth); 117 | height += lineHeight; 118 | } 119 | 120 | } 121 | setMeasuredDimension((modeWidth == MeasureSpec.EXACTLY) ? sizeWidth 122 | : width, (modeHeight == MeasureSpec.EXACTLY) ? sizeHeight 123 | : height); 124 | 125 | } 126 | 127 | @Override 128 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 129 | mAllViews.clear(); 130 | mLineHeight.clear(); 131 | 132 | int width = getWidth(); 133 | 134 | int lineWidth = 0; 135 | int lineHeight = 0; 136 | // 存储每一行所有的childView 137 | List lineViews = new ArrayList(); 138 | int cCount = getChildCount(); 139 | // 遍历所有的孩子 140 | for (int i = 0; i < cCount; i++) { 141 | View child = getChildAt(i); 142 | MarginLayoutParams lp = (MarginLayoutParams) child 143 | .getLayoutParams(); 144 | int childWidth = child.getMeasuredWidth(); 145 | int childHeight = child.getMeasuredHeight(); 146 | 147 | // 如果已经需要换行 148 | if (childWidth + lp.leftMargin + lp.rightMargin + lineWidth > width) { 149 | // 记录这一行所有的View以及最大高度 150 | mLineHeight.add(lineHeight); 151 | // 将当前行的childView保存,然后开启新的ArrayList保存下一行的childView 152 | mAllViews.add(lineViews); 153 | lineWidth = 0;// 重置行宽 154 | lineViews = new ArrayList(); 155 | } 156 | /** 157 | * 如果不需要换行,则累加 158 | */ 159 | lineWidth += childWidth + lp.leftMargin + lp.rightMargin; 160 | lineHeight = Math.max(lineHeight, childHeight + lp.topMargin 161 | + lp.bottomMargin); 162 | lineViews.add(child); 163 | } 164 | // 记录最后一行 165 | mLineHeight.add(lineHeight); 166 | mAllViews.add(lineViews); 167 | 168 | int left = 0; 169 | int top = 0; 170 | // 得到总行数 171 | int lineNums = mAllViews.size(); 172 | for (int i = 0; i < lineNums; i++) { 173 | // 每一行的所有的views 174 | lineViews = mAllViews.get(i); 175 | // 当前行的最大高度 176 | lineHeight = mLineHeight.get(i); 177 | // 遍历当前行所有的View 178 | for (int j = 0; j < lineViews.size(); j++) { 179 | View child = lineViews.get(j); 180 | if (child.getVisibility() == View.GONE) { 181 | continue; 182 | } 183 | MarginLayoutParams lp = (MarginLayoutParams) child 184 | .getLayoutParams(); 185 | 186 | //计算childView的left,top,right,bottom 187 | int lc = left + lp.leftMargin; 188 | int tc = top + lp.topMargin; 189 | int rc = lc + child.getMeasuredWidth(); 190 | int bc = tc + child.getMeasuredHeight(); 191 | 192 | Log.e(TAG, child + " , l = " + lc + " , t = " + tc + " , r =" 193 | + rc + " , b = " + bc); 194 | 195 | child.layout(lc, tc, rc, bc); 196 | 197 | left += child.getMeasuredWidth() + lp.rightMargin 198 | + lp.leftMargin; 199 | } 200 | left = 0; 201 | top += lineHeight; 202 | } 203 | 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/ndh/floatingball/sdk/camera/CameraActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.ndh.floatingball.sdk.camera; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.graphics.Color; 6 | import android.net.Uri; 7 | import android.os.Bundle; 8 | import android.os.Environment; 9 | import android.provider.MediaStore; 10 | import android.support.annotation.NonNull; 11 | import android.support.annotation.Nullable; 12 | import android.widget.Toast; 13 | 14 | import com.example.ndh.floatingball.util.PermissionUtils; 15 | 16 | import java.io.File; 17 | import java.io.IOException; 18 | import java.text.SimpleDateFormat; 19 | 20 | /** 21 | * Created by ndh on 17/1/13. 22 | */ 23 | 24 | public class CameraActivity extends Activity { 25 | private static final int REQUEST_CODE = 0; 26 | private String mNameImage; 27 | private File mFileImage; 28 | 29 | @Override 30 | protected void onCreate(@Nullable Bundle savedInstanceState) { 31 | getWindow().setStatusBarColor(Color.TRANSPARENT); 32 | super.onCreate(savedInstanceState); 33 | if (checkPermission()) return; 34 | String nameImage = prepareFileDir(); 35 | Intent intent = new Intent(); 36 | intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); 37 | intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(nameImage))); 38 | startActivityForResult(intent, REQUEST_CODE); 39 | } 40 | 41 | private boolean checkPermission() { 42 | if (!PermissionUtils.checkPermission(this, PermissionUtils.CODE_CAMERA, "请先授予相机权限")) { 43 | return true; 44 | } 45 | if (!PermissionUtils.checkPermission(this, PermissionUtils.CODE_WRITE_EXTERNAL_STORAGE, "请先授予存储空间权限")) { 46 | return true; 47 | } 48 | return false; 49 | } 50 | 51 | @NonNull 52 | private String prepareFileDir() { 53 | SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy_MM_dd_hh_mm_ss"); 54 | String strDate = dateFormat.format(new java.util.Date()); 55 | String pathImage = Environment.getExternalStorageDirectory().getPath() + "/DCIM/"; 56 | mNameImage = pathImage + strDate + ".png"; 57 | 58 | File dir = new File(pathImage); 59 | if (!dir.exists() && !dir.mkdirs()) { 60 | //最多创建两次文件夹 61 | dir.mkdirs(); 62 | 63 | } 64 | mFileImage = new File(mNameImage); 65 | try { 66 | if (!mFileImage.exists() && !mFileImage.createNewFile()) { 67 | mFileImage.createNewFile(); 68 | } 69 | } catch (IOException e) { 70 | e.printStackTrace(); 71 | } 72 | return mNameImage; 73 | } 74 | 75 | @Override 76 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 77 | super.onActivityResult(requestCode, resultCode, data); 78 | if (requestCode == 0) { 79 | switch (resultCode) { 80 | case RESULT_OK: 81 | Toast.makeText(this, "图片已经保存在:" + mNameImage, Toast.LENGTH_SHORT).show(); 82 | break; 83 | default: 84 | if (null != mFileImage && mFileImage.exists() && mFileImage.isFile()) { 85 | if (!mFileImage.delete()) { 86 | // 最多删两次,避免偶尔删不掉问题 87 | mFileImage.delete(); 88 | } 89 | } 90 | break; 91 | } 92 | } 93 | finish(); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/ndh/floatingball/sdk/lockscreen/LockScreenActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.ndh.floatingball.sdk.lockscreen; 2 | 3 | import android.app.Activity; 4 | import android.app.admin.DevicePolicyManager; 5 | import android.content.ComponentName; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.net.Uri; 9 | import android.os.Build; 10 | import android.os.Bundle; 11 | import android.provider.Settings; 12 | import android.util.Log; 13 | import android.widget.Toast; 14 | 15 | import com.example.ndh.floatingball.util.Utils; 16 | 17 | 18 | /** 19 | * Created by ndh on 16/12/15. 20 | */ 21 | 22 | public class LockScreenActivity extends Activity { 23 | DevicePolicyManager policyManager; 24 | ComponentName componentName; 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | //获取设备管理服务 30 | policyManager = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE); 31 | componentName = new ComponentName(this.getApplicationContext(), MyAdmin.class); 32 | 33 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this.getApplicationContext())) { 34 | Toast.makeText(this, "请先允许快捷助手能顶层显示", Toast.LENGTH_SHORT).show(); 35 | Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + this.getApplicationContext().getPackageName())); 36 | startActivityForResult(intent, 1); 37 | } else { 38 | lock(); 39 | } 40 | 41 | } 42 | 43 | private void lock() { 44 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this.getApplicationContext())) { 45 | finish(); 46 | return; 47 | } 48 | if (!Utils.isActive(this)) {//若无权限 49 | activeManage();//去获得权限 50 | } else { 51 | lockScreen(); 52 | } 53 | } 54 | 55 | private void activeManage() { 56 | // 启动设备管理(隐式Intent) - 在AndroidManifest.xml中设定相应过滤器 57 | Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN); 58 | 59 | //权限列表 60 | intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, componentName); 61 | 62 | //描述(additional explanation) 63 | intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, "------ 其他描述 ------"); 64 | 65 | startActivityForResult(intent, 0); 66 | } 67 | 68 | @Override 69 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 70 | Log.d("ndh--", "requestcode=" + requestCode + ",resultColde=" + resultCode); 71 | if (requestCode == 0 && resultCode == -1) { 72 | Toast.makeText(this, "激活成功,请放心使用", Toast.LENGTH_SHORT).show(); 73 | lockScreen(); 74 | } 75 | if (resultCode == 0 && requestCode == 0) { 76 | Toast.makeText(this, "激活失败...", Toast.LENGTH_SHORT).show(); 77 | finish(); 78 | } 79 | if (requestCode == 1 && resultCode == 0) { 80 | lock(); 81 | } else { 82 | finish(); 83 | } 84 | super.onActivityResult(requestCode, resultCode, data); 85 | } 86 | 87 | private void lockScreen() { 88 | policyManager.lockNow(); 89 | finish(); 90 | // android.os.Process.killProcess(android.os.Process.myPid()); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/ndh/floatingball/sdk/lockscreen/MyAdmin.java: -------------------------------------------------------------------------------- 1 | package com.example.ndh.floatingball.sdk.lockscreen; 2 | 3 | import android.app.admin.DeviceAdminReceiver; 4 | 5 | /** 6 | * Created by ndh on 16/12/15. 7 | */ 8 | 9 | public class MyAdmin extends DeviceAdminReceiver { 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/ndh/floatingball/sdk/screenshot/ScreenShotActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.ndh.floatingball.sdk.screenshot; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.graphics.Bitmap; 7 | import android.graphics.Color; 8 | import android.hardware.display.DisplayManager; 9 | import android.hardware.display.VirtualDisplay; 10 | import android.media.Image; 11 | import android.media.ImageReader; 12 | import android.media.projection.MediaProjection; 13 | import android.media.projection.MediaProjectionManager; 14 | import android.net.Uri; 15 | import android.os.Bundle; 16 | import android.os.Environment; 17 | import android.os.Handler; 18 | import android.os.Looper; 19 | import android.support.annotation.Nullable; 20 | import android.util.Log; 21 | import android.view.Surface; 22 | import android.widget.Toast; 23 | 24 | import com.example.ndh.floatingball.R; 25 | import com.example.ndh.floatingball.util.PermissionUtils; 26 | import com.example.ndh.floatingball.util.Utils; 27 | 28 | import java.io.File; 29 | import java.io.FileNotFoundException; 30 | import java.io.FileOutputStream; 31 | import java.io.IOException; 32 | import java.nio.ByteBuffer; 33 | import java.text.SimpleDateFormat; 34 | 35 | /** 36 | * Created by ndh on 17/1/9. 37 | */ 38 | 39 | public class ScreenShotActivity extends Activity implements PermissionUtils.PermissionGrant { 40 | 41 | private MediaProjectionManager mManager; 42 | private ImageReader mImageReader; 43 | private Image mImage; 44 | VirtualDisplay mVirtualDisplay; 45 | private Surface mSurface; 46 | private static final int SUCESS = 0; 47 | 48 | @Override 49 | protected void onCreate(@Nullable Bundle savedInstanceState) { 50 | //6.0 动态权限 51 | getWindow().setStatusBarColor(Color.TRANSPARENT); 52 | super.onCreate(savedInstanceState); 53 | if (!PermissionUtils.checkPermission(ScreenShotActivity.this, PermissionUtils.CODE_WRITE_EXTERNAL_STORAGE, "请先授予存储空间权限")) { 54 | return; 55 | } 56 | mManager = (MediaProjectionManager) getApplicationContext().getSystemService(Context.MEDIA_PROJECTION_SERVICE); 57 | Intent intent = mManager.createScreenCaptureIntent(); 58 | startActivityForResult(intent, 0); 59 | mImageReader = ImageReader.newInstance(Utils.getScreenSize(this).x, Utils.getScreenSize(this).y, 0x1, 2); 60 | mSurface = mImageReader.getSurface(); 61 | } 62 | 63 | @Override 64 | protected void onActivityResult(int requestCode, final int resultCode, final Intent data) { 65 | super.onActivityResult(requestCode, resultCode, data); 66 | Toast.makeText(this, "正在生成截图...", Toast.LENGTH_SHORT).show(); 67 | new Thread() { 68 | @Override 69 | public void run() { 70 | savePic(resultCode, data); 71 | } 72 | }.start(); 73 | 74 | } 75 | 76 | private void savePic(int resultCode, Intent data) { 77 | final MediaProjection mediaProjection = mManager.getMediaProjection(resultCode, data); 78 | if (mediaProjection == null) { 79 | Log.e("@@", "media projection is null"); 80 | return; 81 | } 82 | //ImageFormat.RGB_565 83 | 84 | mVirtualDisplay = mediaProjection.createVirtualDisplay("ndh", 85 | Utils.getScreenSize(this).x, Utils.getScreenSize(this).y, Utils.getScreenInfo(this).densityDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, 86 | mSurface, null, null); 87 | Handler handler2 = new Handler(getMainLooper()); 88 | handler2.postDelayed(new Runnable() { 89 | public void run() { 90 | //capture the screen 91 | mImage = mImageReader.acquireLatestImage(); 92 | if (mImage == null) { 93 | Log.d("ndh--", "img==null"); 94 | return; 95 | } 96 | int width = mImage.getWidth(); 97 | int height = mImage.getHeight(); 98 | final Image.Plane[] planes = mImage.getPlanes(); 99 | final ByteBuffer buffer = planes[0].getBuffer(); 100 | int pixelStride = planes[0].getPixelStride(); 101 | int rowStride = planes[0].getRowStride(); 102 | int rowPadding = rowStride - pixelStride * width; 103 | Bitmap bitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888); 104 | bitmap.copyPixelsFromBuffer(buffer); 105 | bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height); 106 | mImage.close(); 107 | 108 | 109 | SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy_MM_dd_hh_mm_ss"); 110 | String strDate = dateFormat.format(new java.util.Date()); 111 | String pathImage = Environment.getExternalStorageDirectory().getPath() + "/DCIM/"; 112 | String nameImage = pathImage + strDate + ".png"; 113 | 114 | if (bitmap != null) { 115 | try { 116 | 117 | File dir = new File(pathImage); 118 | if (!dir.exists() && !dir.mkdirs()) { 119 | //最多创建两次文件夹 120 | dir.mkdirs(); 121 | 122 | } 123 | File fileImage = new File(nameImage); 124 | if (!fileImage.exists() && !fileImage.createNewFile()) { 125 | fileImage.createNewFile(); 126 | } 127 | FileOutputStream out = new FileOutputStream(fileImage); 128 | if (out != null) { 129 | bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); 130 | out.flush(); 131 | out.close(); 132 | Intent media = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); 133 | Uri contentUri = Uri.fromFile(fileImage); 134 | media.setData(contentUri); 135 | ScreenShotActivity.this.sendBroadcast(media); 136 | Toast.makeText(ScreenShotActivity.this, "图片保存成功:" + nameImage, Toast.LENGTH_SHORT).show(); 137 | mediaProjection.stop(); 138 | finish(); 139 | } 140 | } catch (FileNotFoundException e) { 141 | e.printStackTrace(); 142 | } catch (IOException e) { 143 | e.printStackTrace(); 144 | } finally { 145 | finish(); 146 | } 147 | } 148 | } 149 | }, 50); 150 | 151 | } 152 | 153 | @Override 154 | public void onPermissionGranted(int requestCode) { 155 | switch (requestCode) { 156 | case SUCESS: 157 | break; 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/ndh/floatingball/util/PermissionUtils.java: -------------------------------------------------------------------------------- 1 | package com.example.ndh.floatingball.util; 2 | 3 | import android.Manifest; 4 | import android.annotation.TargetApi; 5 | import android.app.Activity; 6 | import android.content.Context; 7 | import android.content.DialogInterface; 8 | import android.content.Intent; 9 | import android.content.pm.PackageManager; 10 | import android.net.Uri; 11 | import android.os.Build; 12 | import android.provider.Settings; 13 | import android.support.annotation.NonNull; 14 | import android.support.v4.app.ActivityCompat; 15 | import android.support.v7.app.AlertDialog; 16 | import android.util.Log; 17 | import android.widget.Toast; 18 | 19 | import com.example.ndh.floatingball.R; 20 | 21 | import java.util.ArrayList; 22 | import java.util.HashMap; 23 | import java.util.List; 24 | import java.util.Map; 25 | 26 | /** 27 | * Created by qianxiaoai on 2016/7/7. 28 | */ 29 | public class PermissionUtils { 30 | 31 | private static final String TAG = PermissionUtils.class.getSimpleName(); 32 | public static final int CODE_RECORD_AUDIO = 0; 33 | public static final int CODE_GET_ACCOUNTS = 1; 34 | public static final int CODE_READ_PHONE_STATE = 2; 35 | public static final int CODE_CALL_PHONE = 3; 36 | public static final int CODE_CAMERA = 4; 37 | public static final int CODE_ACCESS_FINE_LOCATION = 5; 38 | public static final int CODE_ACCESS_COARSE_LOCATION = 6; 39 | public static final int CODE_READ_EXTERNAL_STORAGE = 7; 40 | public static final int CODE_WRITE_EXTERNAL_STORAGE = 8; 41 | public static final int CODE_MULTI_PERMISSION = 100; 42 | 43 | public static final String PERMISSION_RECORD_AUDIO = Manifest.permission.RECORD_AUDIO; 44 | public static final String PERMISSION_GET_ACCOUNTS = Manifest.permission.GET_ACCOUNTS; 45 | public static final String PERMISSION_READ_PHONE_STATE = Manifest.permission.READ_PHONE_STATE; 46 | public static final String PERMISSION_CALL_PHONE = Manifest.permission.CALL_PHONE; 47 | public static final String PERMISSION_CAMERA = Manifest.permission.CAMERA; 48 | public static final String PERMISSION_ACCESS_FINE_LOCATION = Manifest.permission.ACCESS_FINE_LOCATION; 49 | public static final String PERMISSION_ACCESS_COARSE_LOCATION = Manifest.permission.ACCESS_COARSE_LOCATION; 50 | public static final String PERMISSION_READ_EXTERNAL_STORAGE = Manifest.permission.READ_EXTERNAL_STORAGE; 51 | public static final String PERMISSION_WRITE_EXTERNAL_STORAGE = Manifest.permission.WRITE_EXTERNAL_STORAGE; 52 | 53 | private static final String[] requestPermissions = { 54 | PERMISSION_RECORD_AUDIO, 55 | PERMISSION_GET_ACCOUNTS, 56 | PERMISSION_READ_PHONE_STATE, 57 | PERMISSION_CALL_PHONE, 58 | PERMISSION_CAMERA, 59 | PERMISSION_ACCESS_FINE_LOCATION, 60 | PERMISSION_ACCESS_COARSE_LOCATION, 61 | PERMISSION_READ_EXTERNAL_STORAGE, 62 | PERMISSION_WRITE_EXTERNAL_STORAGE 63 | }; 64 | 65 | public interface PermissionGrant { 66 | void onPermissionGranted(int requestCode); 67 | } 68 | 69 | /** 70 | * Requests permission. 71 | * 72 | * @param activity 73 | * @param requestCode request code, e.g. if you need request CAMERA permission,parameters is PermissionUtils.CODE_CAMERA 74 | */ 75 | public static void requestPermission(final Activity activity, final int requestCode, PermissionGrant permissionGrant) { 76 | if (activity == null) { 77 | return; 78 | } 79 | 80 | if (requestCode < 0 || requestCode >= requestPermissions.length) { 81 | return; 82 | } 83 | 84 | final String requestPermission = requestPermissions[requestCode]; 85 | 86 | //如果是6.0以下的手机,ActivityCompat.checkSelfPermission()会始终等于PERMISSION_GRANTED, 87 | // 但是,如果用户关闭了你申请的权限,ActivityCompat.checkSelfPermission(),会导致程序崩溃(java.lang.RuntimeException: Unknown exception code: 1 msg null), 88 | // 你可以使用try{}catch(){},处理异常,也可以在这个地方,低于23就什么都不做, 89 | // 个人建议try{}catch(){}单独处理,提示用户开启权限。 90 | // if (Build.VERSION.SDK_INT < 23) { 91 | // return; 92 | // } 93 | 94 | int checkSelfPermission; 95 | try { 96 | checkSelfPermission = ActivityCompat.checkSelfPermission(activity, requestPermission); 97 | } catch (RuntimeException e) { 98 | Toast.makeText(activity, "please open this permission", Toast.LENGTH_SHORT) 99 | .show(); 100 | return; 101 | } 102 | 103 | if (checkSelfPermission != PackageManager.PERMISSION_GRANTED) { 104 | 105 | if (ActivityCompat.shouldShowRequestPermissionRationale(activity, requestPermission)) { 106 | shouldShowRationale(activity, requestCode, requestPermission); 107 | 108 | } else { 109 | ActivityCompat.requestPermissions(activity, new String[]{requestPermission}, requestCode); 110 | } 111 | 112 | } else { 113 | Toast.makeText(activity, "opened:" + requestPermissions[requestCode], Toast.LENGTH_SHORT).show(); 114 | permissionGrant.onPermissionGranted(requestCode); 115 | } 116 | } 117 | 118 | private static void requestMultiResult(Activity activity, String[] permissions, int[] grantResults, PermissionGrant permissionGrant) { 119 | 120 | if (activity == null) { 121 | return; 122 | } 123 | Map perms = new HashMap<>(); 124 | 125 | ArrayList notGranted = new ArrayList<>(); 126 | for (int i = 0; i < permissions.length; i++) { 127 | perms.put(permissions[i], grantResults[i]); 128 | if (grantResults[i] != PackageManager.PERMISSION_GRANTED) { 129 | notGranted.add(permissions[i]); 130 | } 131 | } 132 | 133 | if (notGranted.size() == 0) { 134 | Toast.makeText(activity, "all permission success" + notGranted, Toast.LENGTH_SHORT) 135 | .show(); 136 | permissionGrant.onPermissionGranted(CODE_MULTI_PERMISSION); 137 | } else { 138 | openSettingActivity(activity, "those permission need granted!"); 139 | } 140 | 141 | } 142 | 143 | 144 | /** 145 | * 一次申请多个权限 146 | */ 147 | public static void requestMultiPermissions(final Activity activity, PermissionGrant grant) { 148 | 149 | final List permissionsList = getNoGrantedPermission(activity, false); 150 | final List shouldRationalePermissionsList = getNoGrantedPermission(activity, true); 151 | 152 | //TODO checkSelfPermission 153 | if (permissionsList == null || shouldRationalePermissionsList == null) { 154 | return; 155 | } 156 | 157 | if (permissionsList.size() > 0) { 158 | ActivityCompat.requestPermissions(activity, permissionsList.toArray(new String[permissionsList.size()]), 159 | CODE_MULTI_PERMISSION); 160 | 161 | } else if (shouldRationalePermissionsList.size() > 0) { 162 | showMessageOKCancel(activity, "should open those permission", 163 | new DialogInterface.OnClickListener() { 164 | @Override 165 | public void onClick(DialogInterface dialog, int which) { 166 | ActivityCompat.requestPermissions(activity, shouldRationalePermissionsList.toArray(new String[shouldRationalePermissionsList.size()]), 167 | CODE_MULTI_PERMISSION); 168 | } 169 | }); 170 | } else { 171 | grant.onPermissionGranted(CODE_MULTI_PERMISSION); 172 | } 173 | 174 | } 175 | 176 | 177 | private static void shouldShowRationale(final Activity activity, final int requestCode, final String requestPermission) { 178 | //TODO 179 | String[] permissionsHint = activity.getResources().getStringArray(R.array.permissions); 180 | showMessageOKCancel(activity, "Rationale: " + permissionsHint[requestCode], new DialogInterface.OnClickListener() { 181 | @Override 182 | public void onClick(DialogInterface dialog, int which) { 183 | ActivityCompat.requestPermissions(activity, 184 | new String[]{requestPermission}, 185 | requestCode); 186 | } 187 | }); 188 | } 189 | 190 | private static void showMessageOKCancel(final Activity context, String message, DialogInterface.OnClickListener okListener) { 191 | new AlertDialog.Builder(context) 192 | .setMessage(message) 193 | .setPositiveButton("OK", okListener) 194 | .setNegativeButton("Cancel", null) 195 | .create() 196 | .show(); 197 | 198 | } 199 | 200 | /** 201 | * @param activity 202 | * @param requestCode Need consistent with requestPermission 203 | * @param permissions 204 | * @param grantResults 205 | */ 206 | public static void requestPermissionsResult(final Activity activity, final int requestCode, @NonNull String[] permissions, 207 | @NonNull int[] grantResults, PermissionGrant permissionGrant) { 208 | 209 | if (activity == null) { 210 | return; 211 | } 212 | if (requestCode == CODE_MULTI_PERMISSION) { 213 | requestMultiResult(activity, permissions, grantResults, permissionGrant); 214 | return; 215 | } 216 | 217 | if (requestCode < 0 || requestCode >= requestPermissions.length) { 218 | Toast.makeText(activity, "illegal requestCode:" + requestCode, Toast.LENGTH_SHORT).show(); 219 | return; 220 | } 221 | if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 222 | permissionGrant.onPermissionGranted(requestCode); 223 | 224 | } else { 225 | String[] permissionsHint = activity.getResources().getStringArray(R.array.permissions); 226 | openSettingActivity(activity, "Result" + permissionsHint[requestCode]); 227 | } 228 | 229 | } 230 | 231 | private static void openSettingActivity(final Activity activity, String message) { 232 | 233 | showMessageOKCancel(activity, message, new DialogInterface.OnClickListener() { 234 | @Override 235 | public void onClick(DialogInterface dialog, int which) { 236 | Intent intent = new Intent(); 237 | intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); 238 | Uri uri = Uri.fromParts("package", activity.getPackageName(), null); 239 | intent.setData(uri); 240 | activity.startActivity(intent); 241 | } 242 | }); 243 | } 244 | 245 | 246 | /** 247 | * @param activity 248 | * @param isShouldRationale true: return no granted and shouldShowRequestPermissionRationale permissions, false:return no granted and !shouldShowRequestPermissionRationale 249 | * @return 250 | */ 251 | public static ArrayList getNoGrantedPermission(Activity activity, boolean isShouldRationale) { 252 | 253 | ArrayList permissions = new ArrayList<>(); 254 | 255 | for (int i = 0; i < requestPermissions.length; i++) { 256 | String requestPermission = requestPermissions[i]; 257 | 258 | 259 | //TODO checkSelfPermission 260 | int checkSelfPermission = -1; 261 | try { 262 | checkSelfPermission = ActivityCompat.checkSelfPermission(activity, requestPermission); 263 | } catch (RuntimeException e) { 264 | Toast.makeText(activity, "please open those permission", Toast.LENGTH_SHORT) 265 | .show(); 266 | return null; 267 | } 268 | 269 | if (checkSelfPermission != PackageManager.PERMISSION_GRANTED) { 270 | if (ActivityCompat.shouldShowRequestPermissionRationale(activity, requestPermission)) { 271 | if (isShouldRationale) { 272 | permissions.add(requestPermission); 273 | } 274 | 275 | } else { 276 | 277 | if (!isShouldRationale) { 278 | permissions.add(requestPermission); 279 | } 280 | } 281 | 282 | } 283 | } 284 | 285 | return permissions; 286 | } 287 | 288 | @TargetApi(Build.VERSION_CODES.M) 289 | public static boolean checkPermission(Activity activity, int requestCode) { 290 | String permission = requestPermissions[requestCode]; 291 | int result = activity.checkSelfPermission(permission); 292 | if (result == PackageManager.PERMISSION_GRANTED) { 293 | return true; 294 | } 295 | return false; 296 | } 297 | 298 | public static boolean checkPermission(Context context, int requestCode, String toastStr) { 299 | String permission = requestPermissions[requestCode]; 300 | int result = ActivityCompat.checkSelfPermission(context.getApplicationContext(), permission); 301 | if (result == PackageManager.PERMISSION_GRANTED) { 302 | return true; 303 | } else { 304 | Toast.makeText(context, toastStr, Toast.LENGTH_SHORT).show(); 305 | Intent intent = new Intent(); 306 | intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); 307 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 308 | Uri uri = Uri.fromParts("package", context.getPackageName(), null); 309 | intent.setData(uri); 310 | context.startActivity(intent); 311 | return false; 312 | } 313 | } 314 | 315 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/ndh/floatingball/util/Utils.java: -------------------------------------------------------------------------------- 1 | package com.example.ndh.floatingball.util; 2 | 3 | import android.app.Activity; 4 | import android.app.ActivityManager; 5 | import android.app.admin.DevicePolicyManager; 6 | import android.content.ComponentName; 7 | import android.content.Context; 8 | import android.content.Intent; 9 | import android.content.SharedPreferences; 10 | import android.content.pm.PackageManager; 11 | import android.graphics.Bitmap; 12 | import android.graphics.Point; 13 | import android.net.Uri; 14 | import android.os.Build; 15 | import android.os.Environment; 16 | import android.os.SystemClock; 17 | import android.provider.Settings; 18 | import android.support.v4.app.ActivityCompat; 19 | import android.telephony.TelephonyManager; 20 | import android.util.DisplayMetrics; 21 | import android.view.View; 22 | import android.view.WindowManager; 23 | 24 | import com.example.ndh.floatingball.sdk.lockscreen.MyAdmin; 25 | import com.example.ndh.floatingball.sdk.screenshot.ScreenShotActivity; 26 | 27 | import android.util.Log; 28 | import android.widget.Toast; 29 | 30 | import java.io.File; 31 | import java.io.FileNotFoundException; 32 | import java.io.FileOutputStream; 33 | import java.io.IOException; 34 | import java.util.List; 35 | 36 | /** 37 | * Created by ndh on 16/12/20. 38 | */ 39 | 40 | public class Utils { 41 | /** 42 | * 获取屏幕尺寸 43 | * 44 | * @param context 45 | * @return 46 | */ 47 | public static Point getScreenSize(Context context) { 48 | WindowManager mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 49 | Point p = new Point(); 50 | mWindowManager.getDefaultDisplay().getSize(p); 51 | return p; 52 | } 53 | 54 | public static int pix2dp(Context context, int px) { 55 | int dp; 56 | final float scale = context.getResources().getDisplayMetrics().density; 57 | dp = (int) (px / scale + 0.5f); 58 | return dp; 59 | } 60 | 61 | public static int dp2pix(Context context, int dp) { 62 | int px; 63 | final float scale = context.getResources().getDisplayMetrics().density; 64 | px = (int) (dp * scale + 0.5f); 65 | return px; 66 | } 67 | 68 | public static boolean isServiceWork(Context mContext, String serviceName) { 69 | boolean isWork = false; 70 | ActivityManager myAM = (ActivityManager) mContext 71 | .getSystemService(Context.ACTIVITY_SERVICE); 72 | List myList = myAM.getRunningServices(100); 73 | if (myList.size() <= 0) { 74 | return false; 75 | } 76 | for (int i = 0; i < myList.size(); i++) { 77 | String mName = myList.get(i).service.getClassName().toString(); 78 | if (mName.equals(serviceName)) { 79 | isWork = true; 80 | break; 81 | } 82 | } 83 | return isWork; 84 | } 85 | 86 | public static synchronized String getStringBySP(Context context, String name, String defaultValue) { 87 | SharedPreferences sp = context.getSharedPreferences("config", Context.MODE_PRIVATE); 88 | return sp.getString(name, defaultValue); 89 | } 90 | 91 | public static synchronized void putStringBySP(Context context, String name, String value) { 92 | SharedPreferences sp = context.getSharedPreferences("config", Context.MODE_PRIVATE); 93 | SharedPreferences.Editor editor = sp.edit(); 94 | editor.putString(name, value).commit(); 95 | } 96 | 97 | public static boolean canDrawOverlays(Context context) { 98 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(context.getApplicationContext())) { 99 | return false; 100 | } else { 101 | return true; 102 | } 103 | } 104 | 105 | public static boolean isActive(Context context) { 106 | DevicePolicyManager policyManager; 107 | ComponentName componentName; 108 | policyManager = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); 109 | componentName = new ComponentName(context.getApplicationContext(), MyAdmin.class); 110 | // policyManager.clearDeviceOwnerApp(context.getPackageName()); 111 | return policyManager.isAdminActive(componentName); 112 | } 113 | 114 | public static void unActive(Context context) { 115 | DevicePolicyManager policyManager; 116 | policyManager = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); 117 | policyManager.clearDeviceOwnerApp(context.getPackageName()); 118 | } 119 | 120 | public static void startActivePage(Activity context) { 121 | // 启动设备管理(隐式Intent) - 在AndroidManifest.xml中设定相应过滤器 122 | Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN); 123 | DevicePolicyManager policyManager; 124 | ComponentName componentName; 125 | policyManager = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); 126 | componentName = new ComponentName(context.getApplicationContext(), MyAdmin.class); 127 | //权限列表 128 | intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, componentName); 129 | //描述(additional explanation) 130 | intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, "------ 其他描述 ------"); 131 | context.startActivityForResult(intent, 0); 132 | } 133 | 134 | public static void startOverlayGrante(Context context) { 135 | Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + context.getApplicationContext().getPackageName())); 136 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 137 | context.startActivity(intent); 138 | } 139 | 140 | public static String showPhoneNum(Context context) { 141 | TelephonyManager phoneMgr = (TelephonyManager) context.getApplicationContext().getSystemService(Context.TELEPHONY_SERVICE); 142 | // String strPhoneNum = phoneMgr.getLine1Number(); 143 | // return strPhoneNum; 144 | return ""; 145 | } 146 | 147 | static long[] mHint; 148 | 149 | /** 150 | * 真正调用的时候还是把方法 构造到activit等里面,避免内存泄漏 151 | * 152 | * @param times 153 | * @param view 154 | */ 155 | public static void threeTimesClick(int times, final View view) { 156 | mHint = new long[times]; 157 | view.setOnClickListener(new View.OnClickListener() { 158 | @Override 159 | public void onClick(View v) { 160 | System.arraycopy(mHint, 1, mHint, 0, mHint.length - 1); 161 | mHint[mHint.length - 1] = SystemClock.uptimeMillis(); 162 | 163 | if (mHint[0] >= SystemClock.uptimeMillis() - 500) { 164 | //500 毫秒点击了3次 165 | Toast.makeText(view.getContext().getApplicationContext(), "3击事件", Toast.LENGTH_SHORT).show(); 166 | } 167 | } 168 | }); 169 | 170 | } 171 | 172 | public static void removeAdmin(Context context) { 173 | DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); 174 | ComponentName cn = new ComponentName(context, MyAdmin.class);//组件名字 175 | dpm.removeActiveAdmin(cn);//移除操作 176 | } 177 | 178 | public static DisplayMetrics getScreenInfo(Activity context) { 179 | DisplayMetrics dm = new DisplayMetrics(); 180 | context.getWindowManager().getDefaultDisplay().getMetrics(dm); 181 | // screenWidth = dm.widthPixels; 182 | // screenHeight = dm.heightPixels; 183 | // densityDpi = dm.densityDpi; 184 | // scale = dm.density; 185 | // fontScale = dm.scaledDensity; 186 | return dm; 187 | } 188 | 189 | public static void saveBitmap(Bitmap mBitmap, String bitName) { 190 | File f = new File("/sdcard/Note/" + bitName + ".jpg"); 191 | FileOutputStream fOut = null; 192 | try { 193 | fOut = new FileOutputStream(f); 194 | } catch (FileNotFoundException e) { 195 | e.printStackTrace(); 196 | } 197 | mBitmap.compress(Bitmap.CompressFormat.JPEG, 100, fOut); 198 | try { 199 | fOut.flush(); 200 | } catch (IOException e) { 201 | e.printStackTrace(); 202 | } 203 | try { 204 | fOut.close(); 205 | } catch (IOException e) { 206 | e.printStackTrace(); 207 | } 208 | } 209 | public static void installPackage(Context context,String packagePath){ 210 | String str = "/CanavaCancel.apk"; 211 | String fileName = Environment.getExternalStorageDirectory() + str; 212 | Intent intent = new Intent(Intent.ACTION_VIEW); 213 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);// 214 | intent.setDataAndType(Uri.fromFile(new File(fileName)), "application/vnd.android.package-archive"); 215 | context.startActivity(intent); 216 | } 217 | public static void unInstallPackage(Context context,String packageName){ 218 | Uri packageURI = Uri.parse("package:"+packageName); 219 | Intent uninstallIntent = new Intent(Intent.ACTION_DELETE, packageURI); 220 | uninstallIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 221 | context.startActivity(uninstallIntent); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/color/bg_select_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 10 | 11 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ball.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/killer8000/FloatingBall/50194196d01e82f4e6fc30d796ba902ffe9c4d51/app/src/main/res/drawable/ball.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_action.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_button_normal.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_button_press.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_menu_enable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_menu_normal.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_setting_checked.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_setting_not_checked.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_assist.xml: -------------------------------------------------------------------------------- 1 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_checked.xml: -------------------------------------------------------------------------------- 1 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_no_checked.xml: -------------------------------------------------------------------------------- 1 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/killer8000/FloatingBall/50194196d01e82f4e6fc30d796ba902ffe9c4d51/app/src/main/res/drawable/screenshot.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 14 | 15 | 24 | 26 | 27 | 30 | 33 | 34 | 42 | 43 | 47 | 56 | 60 | 64 | 68 | 72 | 76 | 80 | 84 | 88 | 92 | 93 | 101 | 102 | 103 | 112 | 118 | 119 | 120 | 128 | 129 |