update 封装base库;集成flowwindow库;悬浮窗功能;赞助和分享;集成企微sdk;界面更新;添加待办修复;滚动手势优化;建群达到上限检查;兼容减号和括号搜索;应用保活;其他已知缺陷修复

This commit is contained in:
gallonyin
2022-12-09 16:15:49 +08:00
parent b4b5746f5c
commit 3636ffdc2f
89 changed files with 3908 additions and 272 deletions

1
floatwindow/.gitignore vendored Normal file
View File

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

38
floatwindow/build.gradle Normal file
View File

@@ -0,0 +1,38 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 30
defaultConfig {
minSdkVersion 24
targetSdkVersion 30
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
api "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
api 'androidx.core:core-ktx:1.3.2'
api 'androidx.appcompat:appcompat:1.3.1'
api 'com.google.android.material:material:1.4.0'
//工具集
api 'com.blankj:utilcodex:1.31.0'
}

21
floatwindow/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.yameida.floatwindow">
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application android:label="@string/app_name">
<service android:name=".DefaultFloatService" />
</application>
</manifest>

View File

@@ -0,0 +1,706 @@
package org.yameida.floatwindow;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.CountDownTimer;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.provider.Settings;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import com.blankj.utilcode.util.BarUtils;
import com.blankj.utilcode.util.ScreenUtils;
import com.blankj.utilcode.util.Utils;
import org.yameida.floatwindow.view.HiderView;
public abstract class BaseFloatWindow extends Service {
protected final String TAG = this.getClass().getSimpleName();
/**
* 悬浮球 坐落 左 右 标记
*/
public static final int LEFT = 0;
public static final int RIGHT = 1;
/**
* 记录 logo 停放的位置,以备下次恢复
*/
protected final String LOCATION_X = TAG + "_x";
protected final String LOCATION_Y = TAG + "_y";
/**
* 停靠默认位置
*/
protected int mDefaultLocation = RIGHT;
/**
* 悬浮窗 坐落 位置
*/
protected int mHintLocation = mDefaultLocation;
/**
* 记录当前手指位置在屏幕上的横坐标值
*/
private float mXInScreen;
/**
* 记录当前手指位置在屏幕上的纵坐标值
*/
private float mYInScreen;
/**
* 记录手指按下时在屏幕上的横坐标的值
*/
private float mXDownInScreen;
/**
* 记录手指按下时在屏幕上的纵坐标的值
*/
private float mYDownInScreen;
/**
* 记录手指按下时在小悬浮窗的View上的横坐标的值
*/
private float mXInView;
/**
* 记录手指按下时在小悬浮窗的View上的纵坐标的值
*/
private float mYinview;
/**
* 记录屏幕的宽度
*/
private int mScreenWidth;
protected WindowManager mWindowManager;
protected WindowManager.LayoutParams params;
private Context context = Utils.getApp();
/**
* 退出悬浮窗区域
*/
private HiderView mHiderView = new HiderView(context);
private boolean showHider = true;
/**
* 用于 定时 隐藏 logo的定时器
*/
private CountDownTimer mHideTimer;
/**
* float menu的高度
*/
private Handler mHandler = new Handler(Looper.getMainLooper());
/**
* 悬浮窗左右移动到默认位置 动画的 加速器
*/
private Interpolator mLinearInterpolator = new LinearInterpolator();
/**
* 标记是否拖动中
*/
private boolean isDrag = false;
/**
* 用于恢复悬浮球的location的属性动画值
*/
private int mResetLocationValue;
/**
* 限制拖动的y轴坐标
*/
protected int minY = 0;
protected int maxY = ScreenUtils.getScreenHeight();
@Override
public IBinder onBind(Intent intent) {
return null;
}
/**
* 这个事件用于处理移动、自定义点击或者其它事情return true可以保证onclick事件失效
*/
private View.OnTouchListener touchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
floatEventDown(event);
break;
case MotionEvent.ACTION_MOVE:
floatEventMove(event);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
floatEventUp();
break;
}
return true;
}
};
ValueAnimator valueAnimator = null;
protected boolean isExtended = false;
protected View logoView;
protected View rightView;
protected View leftView;
private GetViewCallback mGetViewCallback;
protected BaseFloatWindow() {
initFloatWindow();
initTimer();
initFloatView();
}
private void initFloatView() {
LayoutInflater inflater = LayoutInflater.from(context);
logoView = mGetViewCallback == null ? getLogoView(inflater) : mGetViewCallback.getLogoView(inflater);
leftView = mGetViewCallback == null ? getLeftView(inflater) : mGetViewCallback.getLeftView(inflater);
rightView = mGetViewCallback == null ? getRightView(inflater) : mGetViewCallback.getRightView(inflater);
if (logoView == null) {
throw new IllegalArgumentException("Must impl GetViewCallback or impl " + this.getClass().getSimpleName() + "and make getLogoView() not return null !");
}
logoView.setOnTouchListener(touchListener);//恢复touch事件
}
/**
* 初始化 隐藏悬浮球的定时器
*/
private void initTimer() {
mHideTimer = new CountDownTimer(2000, 10) { //悬浮窗超过5秒没有操作的话会自动隐藏
@Override
public void onTick(long millisUntilFinished) {
if (isExtended) {
mHideTimer.cancel();
}
}
@Override
public void onFinish() {
if (isExtended) {
mHideTimer.cancel();
return;
}
if (!isDrag) {
if (mHintLocation == LEFT) {
if (mGetViewCallback == null) {
shrinkLeftLogoView(logoView);
} else {
mGetViewCallback.shrinkLeftLogoView(logoView);
}
} else {
if (mGetViewCallback == null) {
shrinkRightLogoView(logoView);
} else {
mGetViewCallback.shrinkRightLogoView(logoView);
}
}
}
}
};
}
/**
* 初始化悬浮球 window
*/
private void initFloatWindow() {
params = new WindowManager.LayoutParams();
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
if (Build.VERSION.SDK_INT >= 23) {
if (!Settings.canDrawOverlays(context)) {
if (Build.VERSION.SDK_INT >= 26) {
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
} else {
if (Build.VERSION.SDK_INT >= 26) {
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
}
} else {
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
mScreenWidth = mWindowManager.getDefaultDisplay().getWidth();
int screenHeigth = mWindowManager.getDefaultDisplay().getHeight();
params.format = PixelFormat.RGBA_8888;
params.gravity = Gravity.LEFT | Gravity.TOP;
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
mHintLocation = getSetting(LOCATION_X, mDefaultLocation);
int defaultY = ((screenHeigth) / 2) / 3;
int y = getSetting(LOCATION_Y, defaultY);
if (mHintLocation == LEFT) {
params.x = 0;
} else {
params.x = mScreenWidth;
}
if (y != 0 && y != defaultY) {
params.y = y;
} else {
params.y = defaultY;
}
params.alpha = 1;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
}
/**
* 悬浮窗touch事件的 down 事件
*/
private void floatEventDown(MotionEvent event) {
isDrag = false;
mHideTimer.cancel();
if (mGetViewCallback == null) {
resetLogoViewSize(mHintLocation, logoView);
} else {
mGetViewCallback.resetLogoViewSize(mHintLocation, logoView);
}
mXInView = event.getX();
mYinview = event.getY();
mXDownInScreen = event.getRawX();
mYDownInScreen = event.getRawY();
mXInScreen = event.getRawX();
mYInScreen = event.getRawY();
}
/**
* 悬浮窗touch事件的 move 事件
*/
private void floatEventMove(MotionEvent event) {
mXInScreen = event.getRawX();
mYInScreen = event.getRawY();
//连续移动的距离超过3则更新一次视图位置
if (Math.abs(mXInScreen - mXDownInScreen) > logoView.getWidth() / 4 || Math.abs(mYInScreen - mYDownInScreen) > logoView.getWidth() / 4) {
params.x = (int) (mXInScreen - mXInView);
params.y = (int) (mYInScreen - mYinview) - logoView.getHeight() / 2;
updateViewPosition(); // 手指移动的时候更新小悬浮窗的位置
// double a = mScreenWidth / 2;
// float offset = (float) ((a - (Math.abs(params.x - a))) / a);
// if (mGetViewCallback == null) {
// dragLogoViewOffset(logoView, isDrag, false, offset);
// } else {
// mGetViewCallback.dragLogoViewOffset(logoView, isDrag, false, offset);
// }
mHiderView.attachToWindow();
} else {
// isDrag = false;
//// logoView.setDrag(false, 0, true);
// if (mGetViewCallback == null) {
// dragLogoViewOffset(logoView, false, true, 0);
// } else {
// mGetViewCallback.dragLogoViewOffset(logoView, false, true, 0);
// }
}
}
/**
* 悬浮窗touch事件的 up 事件
*/
private void floatEventUp() {
if (mXInScreen < mScreenWidth / 2) { //在左边
mHintLocation = LEFT;
} else { //在右边
mHintLocation = RIGHT;
}
valueAnimator = ValueAnimator.ofInt(64);
valueAnimator.setInterpolator(mLinearInterpolator);
valueAnimator.setDuration(1000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mResetLocationValue = (int) animation.getAnimatedValue();
mHandler.post(updatePositionRunnable);
}
});
valueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
if (Math.abs(params.x) < 0) {
params.x = 0;
} else if (Math.abs(params.x) > mScreenWidth) {
params.x = mScreenWidth;
}
updateViewPosition();
isDrag = false;
if (mGetViewCallback == null) {
dragLogoViewOffset(logoView, false, true, 0);
} else {
mGetViewCallback.dragLogoViewOffset(logoView, false, true, 0);
}
mHideTimer.start();
}
@Override
public void onAnimationCancel(Animator animation) {
if (Math.abs(params.x) < 0) {
params.x = 0;
} else if (Math.abs(params.x) > mScreenWidth) {
params.x = mScreenWidth;
}
updateViewPosition();
isDrag = false;
if (mGetViewCallback == null) {
dragLogoViewOffset(logoView, false, true, 0);
} else {
mGetViewCallback.dragLogoViewOffset(logoView, false, true, 0);
}
mHideTimer.start();
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
if (!valueAnimator.isRunning()) {
valueAnimator.start();
}
//这里需要判断如果如果手指所在位置和logo所在位置在一个宽度内则不移动,
if (Math.abs(mXInScreen - mXDownInScreen) > logoView.getWidth() / 5F || Math.abs(mYInScreen - mYDownInScreen) > logoView.getHeight() / 5F) {
isDrag = false;
} else {
openMenu();
}
mHiderView.release();
if (mHiderView.mLp != null) {
int lx = mScreenWidth / 2 - mHiderView.getWidth() / 2;
int ly = mHiderView.mLp.y + BarUtils.getStatusBarHeight();
int rx = lx + mHiderView.getWidth() + logoView.getWidth();
int ry = ly + mHiderView.getHeight() + logoView.getHeight();
if (mXInScreen >= lx && mXInScreen <= rx
&& mYInScreen >= ly && mYInScreen <= ry) {
valueAnimator.cancel();
// 复原X
if (mXDownInScreen < mScreenWidth / 2) { //在左边
mHintLocation = LEFT;
params.x = 0;
} else { //在右边
mHintLocation = RIGHT;
params.x = mScreenWidth;
}
// 复原Y
params.y = (int) mYDownInScreen;
// 滑动的位置在隐藏视图内
hide();
}
}
}
/**
* 手指离开屏幕后 用于恢复 悬浮球的 logo 的左右位置
*/
private Runnable updatePositionRunnable = new Runnable() {
@Override
public void run() {
isDrag = true;
checkPosition();
}
};
/**
* 用于检查并更新悬浮球的位置
*/
private void checkPosition() {
if (params.x > 0 && params.x < mScreenWidth) {
if (mHintLocation == LEFT) {
params.x = params.x - mResetLocationValue;
} else {
params.x = params.x + mResetLocationValue;
}
updateViewPosition();
double a = mScreenWidth / 2;
float offset = (float) ((a - (Math.abs(params.x - a))) / a);
// logoView.setDrag(isDrag, offset, true);
if (mGetViewCallback == null) {
dragLogoViewOffset(logoView, false, true, 0);
} else {
mGetViewCallback.dragLogoViewOffset(logoView, isDrag, true, offset);
}
return;
}
if (Math.abs(params.x) < 0) {
params.x = 0;
} else if (Math.abs(params.x) > mScreenWidth) {
params.x = mScreenWidth;
}
if (valueAnimator.isRunning()) {
valueAnimator.cancel();
}
updateViewPosition();
isDrag = false;
}
public void show() {
try {
dismiss();
if (mWindowManager != null && params != null && logoView != null) {
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
mWindowManager.addView(logoView, params);
}
if (mHideTimer != null) {
mHideTimer.start();
} else {
initTimer();
mHideTimer.start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void hide() {
dismiss();
}
/**
* 打开菜单/关闭菜单
*/
protected void openMenu() {
if (isDrag) return;
if (!isExtended) {
// logoView.setDrawDarkBg(false);
try {
mWindowManager.removeViewImmediate(logoView);
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.height = WindowManager.LayoutParams.MATCH_PARENT;
if (mHintLocation == RIGHT) {
mWindowManager.addView(rightView, params);
if (mGetViewCallback == null) {
rightViewOpened(rightView);
} else {
mGetViewCallback.rightViewOpened(rightView);
}
} else {
mWindowManager.addView(leftView, params);
if (mGetViewCallback == null) {
leftViewOpened(leftView);
} else {
mGetViewCallback.leftViewOpened(leftView);
}
}
} catch (Exception e) {
e.printStackTrace();
}
isExtended = true;
mHideTimer.cancel();
} else {
// logoView.setDrawDarkBg(true);
try {
mWindowManager.removeViewImmediate(mHintLocation == LEFT ? leftView : rightView);
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
mWindowManager.addView(logoView, params);
if (mGetViewCallback == null) {
leftOrRightViewClosed(logoView);
} else {
mGetViewCallback.leftOrRightViewClosed(logoView);
}
} catch (Exception e) {
e.printStackTrace();
}
isExtended = false;
mHideTimer.start();
}
}
/**
* 更新悬浮窗在屏幕中的位置。
*/
private void updateViewPosition() {
isDrag = true;
try {
if (!isExtended) {
if (minY == 0) minY = logoView.getHeight();
if (maxY == ScreenUtils.getScreenHeight()) maxY -= logoView.getHeight();
if (params.y < minY) params.y = minY;
if (params.y > maxY) params.y = maxY;
mWindowManager.updateViewLayout(logoView, params);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 移除所有悬浮窗 释放资源
*/
public void dismiss() {
//记录上次的位置logo的停放位置以备下次恢复
saveSetting(LOCATION_X, mHintLocation);
saveSetting(LOCATION_Y, params.y);
logoView.clearAnimation();
try {
mHideTimer.cancel();
if (isExtended) {
mWindowManager.removeViewImmediate(mHintLocation == LEFT ? leftView : rightView);
} else {
if (logoView.getParent() != null) {
mWindowManager.removeViewImmediate(logoView);
}
}
isExtended = false;
isDrag = false;
if (mGetViewCallback == null) {
onDestroyed();
} else {
mGetViewCallback.onDestroyed();
}
} catch (Exception e) {
e.printStackTrace();
}
}
protected abstract View getLeftView(LayoutInflater inflater);
protected abstract View getRightView(LayoutInflater inflater);
protected abstract View getLogoView(LayoutInflater inflater);
protected abstract void resetLogoViewSize(int hintLocation, View logoView);//logo恢复原始大小
protected abstract void dragLogoViewOffset(View logoView, boolean isDrag, boolean isResetPosition, float offset);
protected abstract void shrinkLeftLogoView(View logoView);//logo左边收缩
protected abstract void shrinkRightLogoView(View logoView);//logo右边收缩
protected abstract void leftViewOpened(View leftView);//左菜单打开
protected abstract void rightViewOpened(View rightView);//右菜单打开
protected abstract void leftOrRightViewClosed(View logoView);
protected abstract void onDestroyed();
public interface GetViewCallback {
View getLeftView(LayoutInflater inflater);
View getRightView(LayoutInflater inflater);
View getLogoView(LayoutInflater inflater);
void resetLogoViewSize(int hintLocation, View logoView);//logo恢复原始大小
void dragLogoViewOffset(View logoView, boolean isDrag, boolean isResetPosition, float offset);//logo正被拖动或真在恢复原位
void shrinkLeftLogoView(View logoView);//logo左边收缩
void shrinkRightLogoView(View logoView);//logo右边收缩
void leftViewOpened(View leftView);//左菜单打开
void rightViewOpened(View rightView);//右菜单打开
void leftOrRightViewClosed(View logoView);
void onDestroyed();
}
/**
* 用于保存悬浮球的位置记录
*
* @param key String
* @param defaultValue int
* @return int
*/
private int getSetting(String key, int defaultValue) {
try {
SharedPreferences sharedata = context.getSharedPreferences("floatLogo", 0);
return sharedata.getInt(key, defaultValue);
} catch (Exception e) {
e.printStackTrace();
}
return defaultValue;
}
/**
* 用于保存悬浮球的位置记录
*
* @param key String
* @param value int
*/
public void saveSetting(String key, int value) {
try {
SharedPreferences.Editor sharedata = context.getSharedPreferences("floatLogo", 0).edit();
sharedata.putInt(key, value);
sharedata.apply();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
} else {
}
mScreenWidth = mWindowManager.getDefaultDisplay().getWidth();
if (mHintLocation == RIGHT) {
params.x = mScreenWidth;
}
show();
}
}

View File

@@ -0,0 +1,192 @@
package org.yameida.floatwindow
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.os.Binder
import android.os.Build
import android.os.IBinder
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import com.blankj.utilcode.util.*
import org.yameida.floatwindow.listener.FloatWindowListener
import kotlinx.android.synthetic.main.layout_menu_left.view.*
import kotlinx.android.synthetic.main.layout_menu_logo.view.*
import kotlinx.android.synthetic.main.layout_menu_right.view.*
import org.yameida.floatwindow.listener.OnClickListener
/**
* Created by Gallon on 2019/9/7.
*/
class DefaultFloatService : BaseFloatWindow(), View.OnClickListener {
// private var currentStatus = RecordStatusManager.PLAY_STATUS_STOP
private var active = false
private var context = Utils.getApp()
private var manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
private val CHANNEL_ID_STRING = "907"
var floatWindowListener: FloatWindowListener? = null
var onClickListener: OnClickListener? = null
inner class DefaultFloatServiceBinder : Binder() {
fun getService() = this@DefaultFloatService
}
init {
minY = SizeUtils.dp2px(100F)
maxY = ScreenUtils.getScreenHeight() - SizeUtils.dp2px(150F)
}
override fun onBind(intent: Intent?): IBinder? = DefaultFloatServiceBinder()
override fun onCreate() {
super.onCreate()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val mChannel = NotificationChannel(CHANNEL_ID_STRING, "float", NotificationManager.IMPORTANCE_HIGH)
manager.createNotificationChannel(mChannel)
val notification = Notification.Builder(context, CHANNEL_ID_STRING).build()
startForeground(1, notification)
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
LogUtils.d(TAG, "onStartCommand: ${intent?.data}")
show()
return super.onStartCommand(intent, flags, startId)
}
override fun show() {
super.show()
// FloatWindowManager.isShow = true
floatWindowListener?.show()
}
override fun hide() {
super.hide()
// FloatWindowManager.isShow = false
floatWindowListener?.hide()
}
override fun onClick(v: View) {
LogUtils.v(TAG, "float onClick: ")
openMenu()
if (v == leftView.fl_window_background_left || v == rightView.fl_window_background_right) {
onClickListener?.onClick(v,-1)
}
if (v == leftView.iv_logo_left || v == rightView.iv_logo_right || v == leftView.iv_logo_left2 || v == rightView.iv_logo_right2) {
onClickListener?.onClick(v, 0)
}
if (v == leftView.iv_start_left || v == rightView.iv_start_right) {
onClickListener?.onClick(v,1)
}
if (v == leftView.iv_shot_left || v == rightView.iv_shot_right) {
onClickListener?.onClick(v,2)
}
if (v == leftView.iv_back_left || v == rightView.iv_back_right) {
onClickListener?.onClick(v,3)
}
if (v == leftView.iv_settings_left || v == rightView.iv_settings_right) {
onClickListener?.onClick(v,4)
}
if (v == leftView.iv_resume_pause_left || v == rightView.iv_resume_pause_right) {
onClickListener?.onClick(v,5)
}
if (v == leftView.iv_stop_left || v == rightView.iv_stop_right) {
onClickListener?.onClick(v,6)
}
}
override fun getLeftView(inflater: LayoutInflater): View {
leftView = inflater.inflate(R.layout.layout_menu_left, null)
leftView.iv_logo_left.setOnClickListener(this)
leftView.iv_logo_left2.setOnClickListener(this)
leftView.iv_start_left.setOnClickListener(this)
leftView.iv_shot_left.setOnClickListener(this)
leftView.iv_back_left.setOnClickListener(this)
leftView.iv_resume_pause_left.setOnClickListener(this)
leftView.iv_stop_left.setOnClickListener(this)
leftView.iv_settings_left.setOnClickListener(this)
leftView.fl_window_background_left.setOnClickListener(this)
return leftView
}
override fun getRightView(inflater: LayoutInflater): View {
rightView = inflater.inflate(R.layout.layout_menu_right, null)
rightView.iv_logo_right.setOnClickListener(this)
rightView.iv_logo_right2.setOnClickListener(this)
rightView.iv_start_right.setOnClickListener(this)
rightView.iv_shot_right.setOnClickListener(this)
rightView.iv_back_right.setOnClickListener(this)
rightView.iv_resume_pause_right.setOnClickListener(this)
rightView.iv_stop_right.setOnClickListener(this)
rightView.iv_settings_right.setOnClickListener(this)
rightView.fl_window_background_right.setOnClickListener(this)
return rightView
}
override fun getLogoView(inflater: LayoutInflater): View {
return inflater.inflate(R.layout.layout_menu_logo, null)
}
override fun resetLogoViewSize(hintLocation: Int, logoView: View) {
logoView.clearAnimation()
logoView.translationX = 0f
logoView.scaleX = 1f
logoView.scaleY = 1f
logoView.alpha = 1f
logoView.tv_float_time.textSize = 10f
}
override fun dragLogoViewOffset(logoView: View, isDrag: Boolean, isResetPosition: Boolean, offset: Float) {
if (isDrag && offset > 0) {
logoView.scaleX = 1 + offset
logoView.scaleY = 1 + offset
} else {
logoView.translationX = 0f
logoView.scaleX = 1f
logoView.scaleY = 1f
}
logoView.rotation = offset * 360
}
public override fun shrinkLeftLogoView(smallView: View) {
smallView.translationX = (-smallView.width / 4).toFloat()
smallView.alpha = 0.35f
logoView.tv_float_time.textSize = 7f
}
public override fun shrinkRightLogoView(smallView: View) {
smallView.translationX = (smallView.width / 4).toFloat()
smallView.alpha = 0.35f
logoView.tv_float_time.textSize = 7f
}
public override fun leftViewOpened(leftView: View) {
val layoutParams = leftView.fl_window_measure_left.layoutParams as FrameLayout.LayoutParams
leftView.fl_window_measure_left.measure(0, 0)
layoutParams.topMargin = (params.y - leftView.fl_window_measure_left.measuredHeight / 2 - leftView.iv_logo_left.measuredHeight / 2)
leftView.fl_window_measure_left.layoutParams = layoutParams
// ToastUtils.showShort("左边的菜单被打开了")
}
public override fun rightViewOpened(rightView: View) {
val layoutParams = rightView.fl_window_measure_right.layoutParams as FrameLayout.LayoutParams
rightView.fl_window_measure_right.measure(0, 0)
layoutParams.topMargin = params.y - rightView.fl_window_measure_right.measuredHeight / 2 - rightView.iv_logo_right.measuredHeight / 2
layoutParams.leftMargin = ScreenUtils.getScreenWidth() - rightView.fl_window_measure_right.measuredWidth
rightView.fl_window_measure_right.layoutParams = layoutParams
// ToastUtils.showShort("右边的菜单被打开了")
}
public override fun leftOrRightViewClosed(smallView: View) {
// Toast.makeText(context, "菜单被关闭了", Toast.LENGTH_SHORT).show()
}
override fun onDestroyed() {}
}

View File

@@ -0,0 +1,39 @@
package org.yameida.floatwindow
import android.content.Intent
import android.os.Build
import com.blankj.utilcode.util.Utils
/**
* Created by Gallon on 2019/9/5.
*/
object FloatWindowManager {
private val TAG = FloatWindowManager::class.java.simpleName
private var context = Utils.getApp()
fun show(service: Class<out BaseFloatWindow>, intent: Intent? = null) {
startServiceSafe(Intent(context, service).apply {
if (intent != null) {
this.putExtras(intent)
}
})
}
fun hide(service: Class<out BaseFloatWindow>, intent: Intent? = null) {
startServiceSafe(Intent(context, service).apply {
if (intent != null) {
this.putExtras(intent)
}
})
}
private fun startServiceSafe(intent: Intent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
}
}

View File

@@ -0,0 +1,10 @@
package org.yameida.floatwindow.listener
/**
* Created by Gallon on 2019/8/11.
*/
interface FloatWindowListener {
fun show()
fun hide()
}

View File

@@ -0,0 +1,11 @@
package org.yameida.floatwindow.listener
import android.view.View
/**
* Created by Gallon on 2019/8/11.
*/
interface OnClickListener {
fun onClick(v: View, event: Int)
}

View File

@@ -0,0 +1,118 @@
package org.yameida.floatwindow.view;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.PixelFormat;
import android.os.Build;
import android.provider.Settings;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.WindowManager;
import android.widget.LinearLayout;
import org.yameida.floatwindow.R;
public class HiderView extends LinearLayout {
private Context mContext;
private WindowManager mWindowManager;
public WindowManager.LayoutParams mLp;
private ValueAnimator valueAnimator;
private int mResetLocationValue;
public HiderView(Context context) {
super(context);
this.mContext = context;
init();
}
public HiderView(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
init();
}
private void init() {
inflate(mContext, R.layout.widget_hiderview_ll, this);
}
public void attachToWindow() {
if (this.getParent() != null) {
return;
}
mWindowManager = (WindowManager) mContext
.getSystemService(Context.WINDOW_SERVICE);
mLp = new WindowManager.LayoutParams();
if (Build.VERSION.SDK_INT >= 23) {
if (!Settings.canDrawOverlays(getContext())) {
if (Build.VERSION.SDK_INT >= 26) {
mLp.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
mLp.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
} else {
if (Build.VERSION.SDK_INT >= 26) {
mLp.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
mLp.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
}
} else {
mLp.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
// mLp.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
mLp.format = PixelFormat.RGBA_8888;
mLp.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
mLp.gravity = Gravity.CENTER | Gravity.TOP;
mLp.width = WindowManager.LayoutParams.WRAP_CONTENT;
mLp.height = WindowManager.LayoutParams.WRAP_CONTENT;
int y = mWindowManager.getDefaultDisplay().getHeight();
mLp.y = y;
mWindowManager.addView(this, mLp);
valueAnimator = ValueAnimator.ofInt(0, y / 4);
valueAnimator.setDuration(200);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mResetLocationValue = (int) animation.getAnimatedValue();
mLp.y = y - getHeight() - mResetLocationValue;
mWindowManager.updateViewLayout(HiderView.this, mLp);
}
});
valueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {}
@Override
public void onAnimationEnd(Animator animation) {
mWindowManager.updateViewLayout(HiderView.this, mLp);
}
@Override
public void onAnimationCancel(Animator animation) {
mWindowManager.updateViewLayout(HiderView.this, mLp);
}
@Override
public void onAnimationRepeat(Animator animation) {}
});
if (!valueAnimator.isRunning()) {
valueAnimator.start();
}
}
public void release() {
if (this.getParent() != null) {
if (valueAnimator.isRunning()) {
valueAnimator.cancel();
}
mWindowManager.removeView(this);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 697 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 734 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 663 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape>
<solid android:color="#ddd" />
<corners android:radius="5dp" />
</shape>
</item>
<item android:state_enabled="false">
<shape>
<solid android:color="@android:color/transparent" />
<corners android:radius="5dp" />
<stroke android:width="1dp" android:color="@color/c8c8c8" />
</shape>
</item>
<item>
<shape>
<solid android:color="#aaa" />
<corners android:radius="5dp" />
</shape>
</item>
</selector>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape>
<solid android:color="@color/color_dashen_passed" />
<corners android:radius="5dp" />
</shape>
</item>
<item android:state_enabled="false">
<shape>
<solid android:color="@android:color/transparent" />
<corners android:radius="5dp" />
<stroke android:width="1dp" android:color="@color/c8c8c8" />
</shape>
</item>
<item>
<shape>
<solid android:color="@color/color_dashen" />
<corners android:radius="5dp" />
</shape>
</item>
</selector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
</vector>

View File

@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fl_window_background_left"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#66333333">
<FrameLayout
android:id="@+id/fl_window_measure_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="190dp"
android:visibility="visible">
<ImageView
android:id="@+id/iv_start_left"
android:layout_width="@dimen/float_size"
android:layout_height="@dimen/float_size"
android:layout_marginEnd="@dimen/float_margin_start"
android:src="@drawable/float_icon_record" />
<ImageView
android:id="@+id/iv_shot_left"
android:layout_width="@dimen/float_size"
android:layout_height="@dimen/float_size"
android:layout_marginStart="@dimen/float_margin_start"
android:layout_marginTop="29dp"
android:src="@drawable/float_icon_pause" />
<ImageView
android:id="@+id/iv_back_left"
android:layout_width="@dimen/float_size"
android:layout_height="@dimen/float_size"
android:layout_below="@id/iv_shot_left"
android:layout_marginStart="@dimen/float_margin_start"
android:layout_marginTop="2dp"
android:src="@drawable/float_icon_home" />
<ImageView
android:id="@+id/iv_logo_left"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_below="@id/iv_start_left"
android:layout_marginStart="5dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="55dp"
android:src="@mipmap/ic_launcher_round" />
<ImageView
android:id="@+id/iv_settings_left"
android:layout_width="@dimen/float_size"
android:layout_height="@dimen/float_size"
android:layout_below="@id/iv_logo_left"
android:layout_marginTop="10dp"
android:layout_marginEnd="@dimen/float_margin_start"
android:scaleX="1.05"
android:scaleY="1.05"
android:src="@drawable/float_icon_setting" />
</RelativeLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="190dp"
android:gravity="center_vertical"
android:paddingStart="0dp"
android:paddingEnd="2dp"
android:visibility="gone">
<ImageView
android:id="@+id/iv_logo_left2"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginStart="2dp"
android:src="@mipmap/ic_launcher_round" />
<ImageView
android:id="@+id/iv_resume_pause_left"
android:layout_width="@dimen/float_size"
android:layout_height="@dimen/float_size"
android:layout_marginStart="2dp"
android:src="@drawable/float_icon_pause" />
<ImageView
android:id="@+id/iv_stop_left"
android:layout_width="@dimen/float_size"
android:layout_height="@dimen/float_size"
android:layout_marginStart="-5dp"
android:src="@drawable/float_icon_stop" />
</LinearLayout>
</FrameLayout>
</FrameLayout>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="@dimen/float_stand_size"
android:gravity="center">
<ImageView
android:layout_width="@dimen/float_stand_size"
android:layout_height="@dimen/float_stand_size"
android:scaleType="centerInside"
android:src="@mipmap/ic_launcher_round" />
<TextView
android:id="@+id/tv_float_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="@color/white"
android:textSize="10sp"
android:visibility="gone" />
</FrameLayout>

View File

@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fl_window_background_right"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#66333333">
<FrameLayout
android:id="@+id/fl_window_measure_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="190dp"
android:visibility="visible">
<ImageView
android:id="@+id/iv_start_right"
android:layout_width="@dimen/float_size"
android:layout_height="@dimen/float_size"
android:layout_marginStart="@dimen/float_margin_start"
android:src="@drawable/float_icon_record" />
<ImageView
android:id="@+id/iv_shot_right"
android:layout_width="@dimen/float_size"
android:layout_height="@dimen/float_size"
android:layout_marginTop="29dp"
android:src="@drawable/float_icon_pause" />
<ImageView
android:id="@+id/iv_back_right"
android:layout_width="@dimen/float_size"
android:layout_height="@dimen/float_size"
android:layout_below="@id/iv_shot_right"
android:layout_marginTop="2dp"
android:src="@drawable/float_icon_home" />
<ImageView
android:id="@+id/iv_logo_right"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_below="@id/iv_start_right"
android:layout_marginStart="55dp"
android:layout_marginTop="10dp"
android:src="@mipmap/ic_launcher_round" />
<ImageView
android:id="@+id/iv_settings_right"
android:layout_width="@dimen/float_size"
android:layout_height="@dimen/float_size"
android:layout_below="@id/iv_logo_right"
android:layout_marginStart="@dimen/float_margin_start"
android:layout_marginTop="10dp"
android:scaleX="1.05"
android:scaleY="1.05"
android:src="@drawable/float_icon_setting" />
</RelativeLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="190dp"
android:gravity="center_vertical"
android:paddingEnd="2dp"
android:paddingStart="0dp"
android:visibility="gone">
<ImageView
android:id="@+id/iv_stop_right"
android:layout_width="@dimen/float_size"
android:layout_height="@dimen/float_size"
android:src="@drawable/float_icon_stop" />
<ImageView
android:id="@+id/iv_resume_pause_right"
android:layout_width="@dimen/float_size"
android:layout_height="@dimen/float_size"
android:layout_marginStart="-5dp"
android:src="@drawable/float_icon_pause" />
<ImageView
android:id="@+id/iv_logo_right2"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginStart="2dp"
android:src="@mipmap/ic_launcher_round" />
</LinearLayout>
</FrameLayout>
</FrameLayout>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
>
<!-- android:background="@color/balloonperformer_translucent"-->
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/widget_float_hide" />
</LinearLayout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="background">@color/color_f5f5f5</color>
<color name="list_divider_line">@color/color_e5e5e5</color>
<color name="white">#fff</color>
<color name="color_333333">#333333</color>
<color name="color_999999">#999999</color>
<color name="color_b2000000">#b2000000</color>
<color name="color_dashen">#ff5215</color>
<color name="color_dashen_passed">#e54b12</color>
<color name="float_time_color">#f58220</color>
<color name="color_e5e5e5">#e5e5e5</color>
<color name="color_f5f5f5">#f5f5f5</color>
<color name="c8c8c8">#c8c8c8</color>
</resources>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
<color name="bar_gray">#37474F</color>
<color name="while_bg">#F7F7F7</color>
</resources>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="setting_start_padding">22dp</dimen>
<dimen name="setting_end_start_padding">25dp</dimen>
<dimen name="setting_end_padding">10dp</dimen>
<dimen name="setting_vertical_padding">10dp</dimen>
<dimen name="setting_end_font_width">60dp</dimen>
<dimen name="setting_start_image_width">20dp</dimen>
<dimen name="size_button">18sp</dimen>
<dimen name="btn_top_bottom_padding">10dp</dimen>
<dimen name="btn_height">50dp</dimen>
<dimen name="float_size">60dp</dimen>
<dimen name="float_margin_start">50dp</dimen>
<dimen name="float_stand_size">36dp</dimen>
<dimen name="setting_start_font_size">15sp</dimen>
<dimen name="setting_end_font_size">13sp</dimen>
</resources>

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">floatwindow</string>
</resources>