diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 9800fb3..d27aa5b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,12 +2,12 @@
-
+
@@ -23,7 +23,7 @@
-
+
@@ -61,6 +61,11 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/frank/ffmpeg/activity/MainActivity.java b/app/src/main/java/com/frank/ffmpeg/activity/MainActivity.java
index f1b40e4..ec97dd7 100644
--- a/app/src/main/java/com/frank/ffmpeg/activity/MainActivity.java
+++ b/app/src/main/java/com/frank/ffmpeg/activity/MainActivity.java
@@ -7,7 +7,13 @@ import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
+import android.view.animation.BounceInterpolator;
+
import com.frank.ffmpeg.R;
+import com.frank.ffmpeg.floating.FloatPlayerView;
+import com.frank.ffmpeg.floating.FloatWindow;
+import com.frank.ffmpeg.floating.MoveType;
+import com.frank.ffmpeg.floating.Screen;
/**
* 使用ffmpeg进行音视频处理入口
@@ -39,6 +45,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
findViewById(R.id.btn_live).setOnClickListener(this);
findViewById(R.id.btn_filter).setOnClickListener(this);
findViewById(R.id.btn_reverse).setOnClickListener(this);
+ findViewById(R.id.btn_floating).setOnClickListener(this);
}
@Override
@@ -69,6 +76,9 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
case R.id.btn_reverse://视频倒播
intent.setClass(MainActivity.this, VideoReverseActivity.class);
break;
+ case R.id.btn_floating://悬浮窗播放
+ floatingToPlay();
+ return;
default:
break;
}
@@ -84,4 +94,26 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
}
}
+ /**
+ * 悬浮窗播放
+ */
+ private void floatingToPlay(){
+ if (FloatWindow.get() != null) {
+ return;
+ }
+ FloatPlayerView floatPlayerView = new FloatPlayerView(getApplicationContext());
+ FloatWindow
+ .with(getApplicationContext())
+ .setView(floatPlayerView)
+ .setWidth(Screen.width, 0.4f)
+ .setHeight(Screen.width, 0.4f)
+ .setX(Screen.width, 0.8f)
+ .setY(Screen.height, 0.3f)
+ .setMoveType(MoveType.slide)
+ .setFilter(false)
+ .setMoveStyle(500, new BounceInterpolator())
+ .build();
+ FloatWindow.get().show();
+ }
+
}
diff --git a/app/src/main/java/com/frank/ffmpeg/activity/VideoHandleActivity.java b/app/src/main/java/com/frank/ffmpeg/activity/VideoHandleActivity.java
index cf50e19..4270ff2 100644
--- a/app/src/main/java/com/frank/ffmpeg/activity/VideoHandleActivity.java
+++ b/app/src/main/java/com/frank/ffmpeg/activity/VideoHandleActivity.java
@@ -10,9 +10,14 @@ import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.view.Window;
+import android.view.animation.BounceInterpolator;
import android.widget.ProgressBar;
import com.frank.ffmpeg.FFmpegCmd;
import com.frank.ffmpeg.R;
+import com.frank.ffmpeg.floating.FloatPlayerView;
+import com.frank.ffmpeg.floating.FloatWindow;
+import com.frank.ffmpeg.floating.MoveType;
+import com.frank.ffmpeg.floating.Screen;
import com.frank.ffmpeg.format.VideoLayout;
import com.frank.ffmpeg.util.FFmpegUtil;
import com.frank.ffmpeg.util.FileUtil;
@@ -290,7 +295,10 @@ public class VideoHandleActivity extends AppCompatActivity implements View.OnCli
String imagePath = PATH + File.separator + "Video2Image/";//图片保存路径
File imageFile = new File(imagePath);
if (!imageFile.exists()){
- imageFile.mkdir();
+ boolean result = imageFile.mkdir();
+ if (!result){
+ return;
+ }
}
int mStartTime = 10;//开始时间
int mDuration = 20;//持续时间(注意开始时间+持续时间之和不能大于视频总时长)
diff --git a/app/src/main/java/com/frank/ffmpeg/floating/FloatActivity.java b/app/src/main/java/com/frank/ffmpeg/floating/FloatActivity.java
new file mode 100644
index 0000000..21b1ff8
--- /dev/null
+++ b/app/src/main/java/com/frank/ffmpeg/floating/FloatActivity.java
@@ -0,0 +1,81 @@
+package com.frank.ffmpeg.floating;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.RequiresApi;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 用于在内部自动申请权限
+ * https://github.com/yhaolpz
+ */
+
+public class FloatActivity extends Activity {
+
+ private static List mPermissionListenerList;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (Build.VERSION.SDK_INT >= 23){
+ requestAlertWindowPermission();
+ }
+ }
+
+ @RequiresApi(api = 23)
+ private void requestAlertWindowPermission() {
+ Intent intent = new Intent("android.settings.action.MANAGE_OVERLAY_PERMISSION");
+ intent.setData(Uri.parse("package:" + getPackageName()));
+ startActivityForResult(intent, 123);
+ }
+
+
+ @RequiresApi(api = 23)
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (Build.VERSION.SDK_INT >= 23){
+ //todo 用23以上编译即可出现canDrawOverlays
+ if (Util.hasPermission(this)) {
+ mPermissionListener.onSuccess();
+ } else {
+ mPermissionListener.onFail();
+ }
+ }
+ finish();
+ }
+
+ static synchronized void request(Context context, PermissionListener permissionListener) {
+ if (mPermissionListenerList == null) {
+ mPermissionListenerList = new ArrayList<>();
+ mPermissionListener = new PermissionListener() {
+ @Override
+ public void onSuccess() {
+ for (PermissionListener listener : mPermissionListenerList) {
+ listener.onSuccess();
+ }
+ }
+
+ @Override
+ public void onFail() {
+ for (PermissionListener listener : mPermissionListenerList) {
+ listener.onFail();
+ }
+ }
+ };
+ Intent intent = new Intent(context, FloatActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+ }
+ mPermissionListenerList.add(permissionListener);
+ }
+
+
+ private static PermissionListener mPermissionListener;
+}
diff --git a/app/src/main/java/com/frank/ffmpeg/floating/FloatLifecycle.java b/app/src/main/java/com/frank/ffmpeg/floating/FloatLifecycle.java
new file mode 100644
index 0000000..9e69f3d
--- /dev/null
+++ b/app/src/main/java/com/frank/ffmpeg/floating/FloatLifecycle.java
@@ -0,0 +1,114 @@
+package com.frank.ffmpeg.floating;
+
+import android.app.Activity;
+import android.app.Application;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+
+/**
+ * Created by yhao on 17-12-1.
+ * 用于控制悬浮窗显示周期
+ * 使用了三种方法针对返回桌面时隐藏悬浮按钮
+ * 1.startCount计数,针对back到桌面可以及时隐藏
+ * 2.监听home键,从而及时隐藏
+ * 3.resumeCount计时,针对一些只执行onPause不执行onStop的奇葩情况
+ */
+
+class FloatLifecycle extends BroadcastReceiver implements Application.ActivityLifecycleCallbacks {
+
+ private static final long delay = 300;
+ private Handler mHandler;
+ private Class[] activities;
+ private boolean showFlag;
+ private int resumeCount;
+ private boolean appBackground;
+ private LifecycleListener mLifecycleListener;
+
+
+ FloatLifecycle(Context applicationContext, boolean showFlag, Class[] activities, LifecycleListener lifecycleListener) {
+ this.showFlag = showFlag;
+ this.activities = activities;
+ mLifecycleListener = lifecycleListener;
+ mHandler = new Handler();
+ ((Application) applicationContext).registerActivityLifecycleCallbacks(this);
+ applicationContext.registerReceiver(this, new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+ }
+
+
+ private boolean needShow(Activity activity) {
+ if (activities == null) {
+ return true;
+ }
+ for (Class a : activities) {
+ if (a.isInstance(activity)) {
+ return showFlag;
+ }
+ }
+ return !showFlag;
+ }
+
+
+ @Override
+ public void onActivityResumed(Activity activity) {
+ resumeCount++;
+ if (needShow(activity)) {
+ mLifecycleListener.onShow();
+ }
+
+ if (appBackground) {
+ appBackground = false;
+ }
+ }
+
+ @Override
+ public void onActivityPaused(Activity activity) {
+ resumeCount--;
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if (resumeCount == 0) {
+ appBackground = true;
+ //mLifecycleListener.onPostHide();
+ }
+ }
+ }, delay);
+
+ }
+
+ @Override
+ public void onActivityStarted(Activity activity) {
+ }
+
+
+ @Override
+ public void onActivityStopped(Activity activity) {
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+
+ }
+
+
+ @Override
+ public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+
+ }
+
+
+ @Override
+ public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
+
+ }
+
+ @Override
+ public void onActivityDestroyed(Activity activity) {
+
+ }
+
+
+}
diff --git a/app/src/main/java/com/frank/ffmpeg/floating/FloatPhone.java b/app/src/main/java/com/frank/ffmpeg/floating/FloatPhone.java
new file mode 100644
index 0000000..5d60aa0
--- /dev/null
+++ b/app/src/main/java/com/frank/ffmpeg/floating/FloatPhone.java
@@ -0,0 +1,111 @@
+package com.frank.ffmpeg.floating;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.os.Build;
+import android.view.View;
+import android.view.WindowManager;
+
+/**
+ * Created by yhao on 17-11-14.
+ * 7.1及以上需申请权限
+ */
+
+class FloatPhone extends FloatView {
+
+ private final Context mContext;
+
+ private final WindowManager mWindowManager;
+ private final WindowManager.LayoutParams mLayoutParams;
+ private View mView;
+ private int mX, mY;
+
+ FloatPhone(Context applicationContext) {
+ mContext = applicationContext;
+ mWindowManager = (WindowManager) applicationContext.getSystemService(Context.WINDOW_SERVICE);
+ mLayoutParams = new WindowManager.LayoutParams();
+ }
+
+ @Override
+ public void setSize(int width, int height) {
+ mLayoutParams.width = width;
+ mLayoutParams.height = height;
+ }
+
+ @Override
+ public void setView(View view) {
+ int layout_type;
+ layout_type = WindowManager.LayoutParams.TYPE_PHONE;
+ mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ mLayoutParams.type = layout_type;
+ mLayoutParams.windowAnimations = 0;
+ mView = view;
+ }
+
+ @Override
+ public void setGravity(int gravity, int xOffset, int yOffset) {
+ mLayoutParams.gravity = gravity;
+ mLayoutParams.x = mX = xOffset;
+ mLayoutParams.y = mY = yOffset;
+ }
+
+
+ @Override
+ public void init() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (Util.hasPermission(mContext)) {
+ mLayoutParams.format = PixelFormat.RGBA_8888;
+ mWindowManager.addView(mView, mLayoutParams);
+ } else {
+ FloatActivity.request(mContext, new PermissionListener() {
+ @Override
+ public void onSuccess() {
+ mLayoutParams.format = PixelFormat.RGBA_8888;
+ mWindowManager.addView(mView, mLayoutParams);
+ }
+
+ @Override
+ public void onFail() {
+
+ }
+ });
+ }
+ }
+ }
+
+ @Override
+ public void dismiss() {
+ mWindowManager.removeView(mView);
+ }
+
+ @Override
+ public void updateXY(int x, int y) {
+ mLayoutParams.x = mX = x;
+ mLayoutParams.y = mY = y;
+ mWindowManager.updateViewLayout(mView, mLayoutParams);
+ }
+
+ @Override
+ void updateX(int x) {
+ mLayoutParams.x = mX = x;
+ mWindowManager.updateViewLayout(mView, mLayoutParams);
+
+ }
+
+ @Override
+ void updateY(int y) {
+ mLayoutParams.y = mY = y;
+ mWindowManager.updateViewLayout(mView, mLayoutParams);
+ }
+
+ @Override
+ int getX() {
+ return mX;
+ }
+
+ @Override
+ int getY() {
+ return mY;
+ }
+}
diff --git a/app/src/main/java/com/frank/ffmpeg/floating/FloatPlayerView.java b/app/src/main/java/com/frank/ffmpeg/floating/FloatPlayerView.java
new file mode 100644
index 0000000..7382a42
--- /dev/null
+++ b/app/src/main/java/com/frank/ffmpeg/floating/FloatPlayerView.java
@@ -0,0 +1,64 @@
+package com.frank.ffmpeg.floating;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.VideoView;
+
+
+public class FloatPlayerView extends FrameLayout {
+
+ private final static String VIDEO_PATH = "rtsp://184.72.239.149/vod/mp4://BigBuckBunny_175k.mov";
+// private final static String VIDEO_PATH = "storage/emulated/0/beyond.mp4";
+
+ private VideoView mPlayer;
+
+ public FloatPlayerView(Context context) {
+ super(context);
+ init(context);
+ }
+
+ public FloatPlayerView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ public FloatPlayerView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(context);
+ }
+
+ private void init(Context context) {
+ mPlayer = new VideoView(context);
+
+ LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+ layoutParams.gravity = Gravity.CENTER;
+ addView(mPlayer, layoutParams);
+
+ mPlayer.setVideoPath(VIDEO_PATH);
+ mPlayer.requestFocus();
+ mPlayer.start();
+ }
+
+
+ public void onPause() {
+ if (mPlayer != null){
+ mPlayer.pause();
+ }
+ }
+
+ public void onResume() {
+ if (mPlayer != null){
+ mPlayer.resume();
+ }
+ }
+
+ public void onDestroy(){
+ if (mPlayer != null){
+ mPlayer.stopPlayback();
+ mPlayer = null;
+ }
+ }
+}
diff --git a/app/src/main/java/com/frank/ffmpeg/floating/FloatToast.java b/app/src/main/java/com/frank/ffmpeg/floating/FloatToast.java
new file mode 100644
index 0000000..0a3e002
--- /dev/null
+++ b/app/src/main/java/com/frank/ffmpeg/floating/FloatToast.java
@@ -0,0 +1,96 @@
+package com.frank.ffmpeg.floating;
+
+import android.content.Context;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Toast;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+/**
+ * 自定义 toast 方式,无需申请权限
+ */
+
+class FloatToast extends FloatView {
+
+
+ private Toast toast;
+
+ private Object mTN;
+ private Method show;
+ private Method hide;
+
+ private int mWidth;
+ private int mHeight;
+
+
+ FloatToast(Context applicationContext) {
+ toast = new Toast(applicationContext);
+ }
+
+
+ @Override
+ public void setSize(int width, int height) {
+ mWidth = width;
+ mHeight = height;
+ }
+
+ @Override
+ public void setView(View view) {
+ toast.setView(view);
+ initTN();
+ }
+
+ @Override
+ public void setGravity(int gravity, int xOffset, int yOffset) {
+ toast.setGravity(gravity, xOffset, yOffset);
+ }
+
+ @Override
+ public void init() {
+ try {
+ show.invoke(mTN);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void dismiss() {
+ try {
+ hide.invoke(mTN);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ private void initTN() {
+ try {
+ //todo android P test
+ Field tnField = toast.getClass().getDeclaredField("mTN");
+ tnField.setAccessible(true);
+ mTN = tnField.get(toast);
+ show = mTN.getClass().getMethod("show");
+ hide = mTN.getClass().getMethod("hide");
+
+ Field tnParamsField = mTN.getClass().getDeclaredField("mParams");
+ tnParamsField.setAccessible(true);
+ WindowManager.LayoutParams params = (WindowManager.LayoutParams) tnParamsField.get(mTN);
+ params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ params.width = mWidth;
+ params.height = mHeight;
+ params.windowAnimations = 0;
+ Field tnNextViewField = mTN.getClass().getDeclaredField("mNextView");
+ tnNextViewField.setAccessible(true);
+ tnNextViewField.set(mTN, toast.getView());
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+
+}
diff --git a/app/src/main/java/com/frank/ffmpeg/floating/FloatView.java b/app/src/main/java/com/frank/ffmpeg/floating/FloatView.java
new file mode 100644
index 0000000..4baf387
--- /dev/null
+++ b/app/src/main/java/com/frank/ffmpeg/floating/FloatView.java
@@ -0,0 +1,38 @@
+package com.frank.ffmpeg.floating;
+
+import android.view.View;
+
+/**
+ * Created by yhao on 17-11-14.
+ * https://github.com/yhaolpz
+ */
+
+abstract class FloatView {
+
+ abstract void setSize(int width, int height);
+
+ abstract void setView(View view);
+
+ abstract void setGravity(int gravity, int xOffset, int yOffset);
+
+ abstract void init();
+
+ abstract void dismiss();
+
+ void updateXY(int x, int y) {
+ }
+
+ void updateX(int x) {
+ }
+
+ void updateY(int y) {
+ }
+
+ int getX() {
+ return 0;
+ }
+
+ int getY() {
+ return 0;
+ }
+}
diff --git a/app/src/main/java/com/frank/ffmpeg/floating/FloatWindow.java b/app/src/main/java/com/frank/ffmpeg/floating/FloatWindow.java
new file mode 100644
index 0000000..687c1e4
--- /dev/null
+++ b/app/src/main/java/com/frank/ffmpeg/floating/FloatWindow.java
@@ -0,0 +1,171 @@
+package com.frank.ffmpeg.floating;
+
+import android.animation.TimeInterpolator;
+import android.content.Context;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Created by yhao on 2017/12/22.
+ * https://github.com/yhaolpz
+ */
+
+public class FloatWindow {
+
+ private FloatWindow() {
+
+ }
+
+ private static final String mDefaultTag = "default_float_window_tag";
+ private static Map mFloatWindowMap;
+
+ public static IFloatWindow get() {
+ return get(mDefaultTag);
+ }
+
+ public static IFloatWindow get(@NonNull String tag) {
+ return mFloatWindowMap == null ? null : mFloatWindowMap.get(tag);
+ }
+
+ @MainThread
+ public static WindowBuilder with(@NonNull Context applicationContext) {
+ return new WindowBuilder(applicationContext);
+ }
+
+ public static class WindowBuilder {
+ Context mApplicationContext;
+ View mView;
+ private int mLayoutId;
+ int mWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
+ int mHeight = ViewGroup.LayoutParams.WRAP_CONTENT;
+ int gravity = Gravity.TOP | Gravity.START;
+ int xOffset;
+ int yOffset;
+ boolean mShow = true;
+ Class[] mActivities;
+ int mMoveType = MoveType.fixed;
+ long mDuration = 300;
+ TimeInterpolator mInterpolator;
+ private String mTag = mDefaultTag;
+
+ WindowBuilder(Context applicationContext) {
+ mApplicationContext = applicationContext;
+ }
+
+ public WindowBuilder setView(@NonNull View view) {
+ mView = view;
+ return this;
+ }
+
+ public WindowBuilder setView(@LayoutRes int layoutId) {
+ mLayoutId = layoutId;
+ return this;
+ }
+
+ public WindowBuilder setWidth(int width) {
+ mWidth = width;
+ return this;
+ }
+
+ public WindowBuilder setHeight(int height) {
+ mHeight = height;
+ return this;
+ }
+
+ public WindowBuilder setWidth(@Screen.screenType int screenType, float ratio) {
+ mWidth = (int) ((screenType == Screen.width ?
+ Util.getScreenWidth(mApplicationContext) :
+ Util.getScreenHeight(mApplicationContext)) * ratio);
+ return this;
+ }
+
+
+ public WindowBuilder setHeight(@Screen.screenType int screenType, float ratio) {
+ mHeight = (int) ((screenType == Screen.width ?
+ Util.getScreenWidth(mApplicationContext) :
+ Util.getScreenHeight(mApplicationContext)) * ratio);
+ return this;
+ }
+
+
+ public WindowBuilder setX(int x) {
+ xOffset = x;
+ return this;
+ }
+
+ public WindowBuilder setY(int y) {
+ yOffset = y;
+ return this;
+ }
+
+ public WindowBuilder setX(@Screen.screenType int screenType, float ratio) {
+ xOffset = (int) ((screenType == Screen.width ?
+ Util.getScreenWidth(mApplicationContext) :
+ Util.getScreenHeight(mApplicationContext)) * ratio);
+ return this;
+ }
+
+ public WindowBuilder setY(@Screen.screenType int screenType, float ratio) {
+ yOffset = (int) ((screenType == Screen.width ?
+ Util.getScreenWidth(mApplicationContext) :
+ Util.getScreenHeight(mApplicationContext)) * ratio);
+ return this;
+ }
+
+
+ /**
+ * 设置 Activity 过滤器,用于指定在哪些界面显示悬浮窗,默认全部界面都显示
+ *
+ * @param show 过滤类型,子类类型也会生效
+ * @param activities 过滤界面
+ */
+ public WindowBuilder setFilter(boolean show, @NonNull Class... activities) {
+ mShow = show;
+ mActivities = activities;
+ return this;
+ }
+
+
+ public WindowBuilder setMoveType(@MoveType.MOVE_TYPE int moveType) {
+ mMoveType = moveType;
+ return this;
+ }
+
+ public WindowBuilder setMoveStyle(long duration, @Nullable TimeInterpolator interpolator) {
+ mDuration = duration;
+ mInterpolator = interpolator;
+ return this;
+ }
+
+ public WindowBuilder setTag(@NonNull String tag) {
+ mTag = tag;
+ return this;
+ }
+
+ public void build() {
+ if (mFloatWindowMap == null) {
+ mFloatWindowMap = new HashMap<>();
+ }
+ if (mFloatWindowMap.containsKey(mTag)) {
+ throw new IllegalArgumentException("FloatWindow of this tag has been added, Please set a new tag for the new FloatWindow");
+ }
+ if (mView == null && mLayoutId == 0) {
+ throw new IllegalArgumentException("View has not been set!");
+ }
+ if (mView == null) {
+ mView = Util.inflate(mApplicationContext, mLayoutId);
+ }
+ IFloatWindow floatWindowImpl = new IFloatWindowImpl(this);
+ mFloatWindowMap.put(mTag, floatWindowImpl);
+ }
+
+ }
+}
diff --git a/app/src/main/java/com/frank/ffmpeg/floating/IFloatWindow.java b/app/src/main/java/com/frank/ffmpeg/floating/IFloatWindow.java
new file mode 100644
index 0000000..691e6fe
--- /dev/null
+++ b/app/src/main/java/com/frank/ffmpeg/floating/IFloatWindow.java
@@ -0,0 +1,30 @@
+package com.frank.ffmpeg.floating;
+
+import android.view.View;
+
+/**
+ * Created by yhao on 2017/12/22.
+ * https://github.com/yhaolpz
+ */
+
+public abstract class IFloatWindow {
+ public abstract void show();
+
+ public abstract void hide();
+
+ public abstract int getX();
+
+ public abstract int getY();
+
+ public abstract void updateX(int x);
+
+ public abstract void updateX(@Screen.screenType int screenType,float ratio);
+
+ public abstract void updateY(int y);
+
+ public abstract void updateY(@Screen.screenType int screenType,float ratio);
+
+ public abstract View getView();
+
+ abstract void dismiss();
+}
diff --git a/app/src/main/java/com/frank/ffmpeg/floating/IFloatWindowImpl.java b/app/src/main/java/com/frank/ffmpeg/floating/IFloatWindowImpl.java
new file mode 100644
index 0000000..e909cfb
--- /dev/null
+++ b/app/src/main/java/com/frank/ffmpeg/floating/IFloatWindowImpl.java
@@ -0,0 +1,248 @@
+package com.frank.ffmpeg.floating;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.os.Build;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+
+/**
+ * Created by yhao on 2017/12/22.
+ * https://github.com/yhaolpz
+ */
+
+public class IFloatWindowImpl extends IFloatWindow {
+
+
+ private FloatWindow.WindowBuilder mB;
+ private FloatView mFloatView;
+ private boolean isShow;
+ private boolean once = true;
+ private ValueAnimator mAnimator;
+ private TimeInterpolator mDecelerateInterpolator;
+
+ IFloatWindowImpl(FloatWindow.WindowBuilder b) {
+ mB = b;
+ if (mB.mMoveType == MoveType.fixed) {
+ if (Build.VERSION.SDK_INT >=25) {
+ mFloatView = new FloatPhone(b.mApplicationContext);
+ } else {
+ mFloatView = new FloatToast(b.mApplicationContext);
+ }
+ } else {
+ mFloatView = new FloatPhone(b.mApplicationContext);
+ initTouchEvent();
+ }
+ mFloatView.setSize(mB.mWidth, mB.mHeight);
+ mFloatView.setGravity(mB.gravity, mB.xOffset, mB.yOffset);
+ mFloatView.setView(mB.mView);
+ new FloatLifecycle(mB.mApplicationContext, mB.mShow, mB.mActivities, new LifecycleListener() {
+ @Override
+ public void onShow() {
+ show();
+ }
+
+ @Override
+ public void onHide() {
+ hide();
+ }
+
+ @Override
+ public void onPostHide() {
+ postHide();
+ }
+ });
+ }
+
+ @Override
+ public void show() {
+ if (once) {
+ mFloatView.init();
+ once = false;
+ isShow = true;
+ } else {
+ if (isShow) return;
+ getView().setVisibility(View.VISIBLE);
+ isShow = true;
+ }
+ }
+
+ @Override
+ public void hide() {
+ if (once || !isShow) return;
+ getView().setVisibility(View.INVISIBLE);
+ isShow = false;
+ }
+
+ @Override
+ void dismiss() {
+ mFloatView.dismiss();
+ isShow = false;
+ }
+
+ @Override
+ public void updateX(int x) {
+ checkMoveType();
+ mB.xOffset = x;
+ mFloatView.updateX(x);
+ }
+
+ @Override
+ public void updateY(int y) {
+ checkMoveType();
+ mB.yOffset = y;
+ mFloatView.updateY(y);
+ }
+
+ @Override
+ public void updateX(int screenType, float ratio) {
+ checkMoveType();
+ mB.xOffset = (int) ((screenType == Screen.width ?
+ Util.getScreenWidth(mB.mApplicationContext) :
+ Util.getScreenHeight(mB.mApplicationContext)) * ratio);
+ mFloatView.updateX(mB.xOffset);
+
+ }
+
+ @Override
+ public void updateY(int screenType, float ratio) {
+ checkMoveType();
+ mB.yOffset = (int) ((screenType == Screen.width ?
+ Util.getScreenWidth(mB.mApplicationContext) :
+ Util.getScreenHeight(mB.mApplicationContext)) * ratio);
+ mFloatView.updateY(mB.yOffset);
+
+ }
+
+ @Override
+ public int getX() {
+ return mFloatView.getX();
+ }
+
+ @Override
+ public int getY() {
+ return mFloatView.getY();
+ }
+
+
+ @Override
+ public View getView() {
+ return mB.mView;
+ }
+
+ void postHide() {
+ if (once || !isShow) return;
+ getView().post(new Runnable() {
+ @Override
+ public void run() {
+ getView().setVisibility(View.INVISIBLE);
+ }
+ });
+ isShow = false;
+ }
+
+ private void checkMoveType() {
+ if (mB.mMoveType == MoveType.fixed) {
+ throw new IllegalArgumentException("FloatWindow of this tag is not allowed to move!");
+ }
+ }
+
+ private void initTouchEvent() {
+ switch (mB.mMoveType) {
+ case MoveType.free:
+ break;
+ default:
+getView().setOnTouchListener(new View.OnTouchListener() {
+ float lastX, lastY, changeX, changeY;
+ int newX, newY;
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ lastX = event.getRawX();
+ lastY = event.getRawY();
+ cancelAnimator();
+ break;
+ case MotionEvent.ACTION_MOVE:
+ changeX = event.getRawX() - lastX;
+ changeY = event.getRawY() - lastY;
+ newX = (int) (mFloatView.getX() + changeX);
+ newY = (int) (mFloatView.getY() + changeY);
+ mFloatView.updateXY(newX, newY);
+ lastX = event.getRawX();
+ lastY = event.getRawY();
+ break;
+ case MotionEvent.ACTION_UP:
+ switch (mB.mMoveType) {
+ case MoveType.slide:
+ int startX = mFloatView.getX();
+ int endX = (startX * 2 + v.getWidth() >
+ Util.getScreenWidth(mB.mApplicationContext)) ?
+ Util.getScreenWidth(mB.mApplicationContext) - v.getWidth() : 0;
+ mAnimator = ObjectAnimator.ofInt(startX, endX);
+ mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ int x = (int) animation.getAnimatedValue();
+ mFloatView.updateX(x);
+ }
+ });
+ startAnimator();
+ break;
+ case MoveType.back:
+ PropertyValuesHolder pvhX = PropertyValuesHolder.ofInt("x", mFloatView.getX(), mB.xOffset);
+ PropertyValuesHolder pvhY = PropertyValuesHolder.ofInt("y", mFloatView.getY(), mB.yOffset);
+ mAnimator = ObjectAnimator.ofPropertyValuesHolder(pvhX, pvhY);
+ mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ int x = (int) animation.getAnimatedValue("x");
+ int y = (int) animation.getAnimatedValue("y");
+ mFloatView.updateXY(x, y);
+ }
+ });
+ startAnimator();
+ break;
+ }
+ break;
+
+ }
+ return false;
+ }
+});
+ }
+ }
+
+ private void startAnimator() {
+ if (mB.mInterpolator == null) {
+ if (mDecelerateInterpolator == null) {
+ mDecelerateInterpolator = new DecelerateInterpolator();
+ }
+ mB.mInterpolator = mDecelerateInterpolator;
+ }
+ mAnimator.setInterpolator(mB.mInterpolator);
+ mAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimator.removeAllUpdateListeners();
+ mAnimator.removeAllListeners();
+ mAnimator = null;
+ }
+ });
+ mAnimator.setDuration(mB.mDuration).start();
+ }
+
+ private void cancelAnimator() {
+ if (mAnimator != null && mAnimator.isRunning()) {
+ mAnimator.cancel();
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/frank/ffmpeg/floating/LifecycleListener.java b/app/src/main/java/com/frank/ffmpeg/floating/LifecycleListener.java
new file mode 100644
index 0000000..6f1c3d3
--- /dev/null
+++ b/app/src/main/java/com/frank/ffmpeg/floating/LifecycleListener.java
@@ -0,0 +1,15 @@
+package com.frank.ffmpeg.floating;
+
+/**
+ * Created by yhao on 2017/12/22.
+ * https://github.com/yhaolpz
+ */
+
+interface LifecycleListener {
+
+ void onShow();
+
+ void onHide();
+
+ void onPostHide();
+}
diff --git a/app/src/main/java/com/frank/ffmpeg/floating/MoveType.java b/app/src/main/java/com/frank/ffmpeg/floating/MoveType.java
new file mode 100644
index 0000000..0b69495
--- /dev/null
+++ b/app/src/main/java/com/frank/ffmpeg/floating/MoveType.java
@@ -0,0 +1,24 @@
+package com.frank.ffmpeg.floating;
+
+import android.support.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Created by yhao on 2017/12/22.
+ * https://github.com/yhaolpz
+ */
+
+public class MoveType {
+ static final int fixed = 0;
+ public static final int free = 1;
+ public static final int active = 2;
+ public static final int slide = 3;
+ public static final int back = 4;
+
+ @IntDef({fixed, free, active, slide, back})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface MOVE_TYPE {
+ }
+}
diff --git a/app/src/main/java/com/frank/ffmpeg/floating/PermissionListener.java b/app/src/main/java/com/frank/ffmpeg/floating/PermissionListener.java
new file mode 100644
index 0000000..53dcc8b
--- /dev/null
+++ b/app/src/main/java/com/frank/ffmpeg/floating/PermissionListener.java
@@ -0,0 +1,11 @@
+package com.frank.ffmpeg.floating;
+
+/**
+ * Created by yhao on 2017/11/14.
+ * https://github.com/yhaolpz
+ */
+interface PermissionListener {
+ void onSuccess();
+
+ void onFail();
+}
diff --git a/app/src/main/java/com/frank/ffmpeg/floating/Screen.java b/app/src/main/java/com/frank/ffmpeg/floating/Screen.java
new file mode 100644
index 0000000..d59acfc
--- /dev/null
+++ b/app/src/main/java/com/frank/ffmpeg/floating/Screen.java
@@ -0,0 +1,21 @@
+package com.frank.ffmpeg.floating;
+
+import android.support.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Created by yhao on 2017/12/23.
+ * https://github.com/yhaolpz
+ */
+
+public class Screen {
+ public static final int width = 0;
+ public static final int height = 1;
+
+ @IntDef({width, height})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface screenType {
+ }
+}
diff --git a/app/src/main/java/com/frank/ffmpeg/floating/Util.java b/app/src/main/java/com/frank/ffmpeg/floating/Util.java
new file mode 100644
index 0000000..04d2831
--- /dev/null
+++ b/app/src/main/java/com/frank/ffmpeg/floating/Util.java
@@ -0,0 +1,50 @@
+package com.frank.ffmpeg.floating;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.os.Build;
+import android.provider.Settings;
+import android.support.annotation.RequiresApi;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+
+/**
+ * Created by yhao on 2017/12/22.
+ * https://github.com/yhaolpz
+ */
+
+public class Util {
+
+
+static View inflate(Context applicationContext, int layoutId) {
+ LayoutInflater inflate = (LayoutInflater) applicationContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ return inflate.inflate(layoutId, null);
+}
+
+ @RequiresApi(api = Build.VERSION_CODES.M)
+ public static boolean hasPermission(Context context) {
+ return Settings.canDrawOverlays(context);
+ }
+
+
+ private static Point sPoint;
+
+ static int getScreenWidth(Context context) {
+ if (sPoint == null) {
+ sPoint = new Point();
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ wm.getDefaultDisplay().getSize(sPoint);
+ }
+ return sPoint.x;
+ }
+
+ static int getScreenHeight(Context context) {
+ if (sPoint == null) {
+ sPoint = new Point();
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ wm.getDefaultDisplay().getSize(sPoint);
+ }
+ return sPoint.y;
+ }
+}
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 0c2eefd..acc4131 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -74,4 +74,13 @@
android:layout_centerHorizontal="true"
android:layout_below="@+id/btn_filter"/>
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 16c905e..ffde8b5 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -39,6 +39,7 @@
视频降噪
视频转图片
视频画中画
+ 悬浮窗播放
切换
开始