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"/> +