好吧,在我们的台式计算机上,我们可以轻松地还原窗口,在后台执行某些操作,并在需要时最大化窗口。但是我们在Android应用程序中看不到此功能。如今,我们可以看到android提供了Split Screen,但这是OS提供的功能,而不是应用程序的单独功能。让我们制作一个仅需单击按钮即可最小化和最大化自身的应用程序。此功能可以在很多方面为用户提供帮助。假设您正在阅读一些带有一些数学计算的pdf文档,然后在pdf查看器应用程序中使用最小化计算器将非常有帮助。有很多使用此功能的应用程序,例如剪贴板,MI计算器等。这是本文最终应用程序的演示。注意,我们将使用Java语言实现该项目。
分步实施
步骤1:创建一个新项目
要在Android Studio中创建新项目,请参阅如何在Android Studio中创建/启动新项目。请注意,选择Java作为编程语言。
步骤2:建立工作平台
- 为应用程序添加新的颜色:转到values-> colors.xml 。可以在此处添加任何自定义颜色。我们添加了这两种颜色。
XML
#FF2F8D46
#FF098043
XML
XML
Java
package com.wheic.floatingedittext.Common;
public class Common {
// The EditText String will be
// stored in this variable
// when MINIMIZE or MAXIMIZE
// button is pressed
public static String currentDesc = "";
// The EditText String will be
// stored in this variable
// when SAVE button is pressed
public static String savedDesc = "";
}
Java
private boolean isMyServiceRunning() {
// The ACTIVITY_SERVICE is needed to retrieve a
// ActivityManager for interacting with the global system
// It has a constant String value "activity".
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
// A loop is needed to get Service information that are currently running in the System.
// So ActivityManager.RunningServiceInfo is used. It helps to retrieve a
// particular service information, here its this service.
// getRunningServices() method returns a list of the services that are currently running
// and MAX_VALUE is 2147483647. So at most this many services can be returned by this method.
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
// If this service is found as a running, it will return true or else false.
if (FloatingWindowGFG.class.getName().equals(service.service.getClassName())) {
return true;
}
}
return false;
}
Java
private void requestOverlayDisplayPermission() {
// An AlertDialog is created
AlertDialog.Builder builder = new AlertDialog.Builder(this);
// This dialog can be closed, just by
// taping outside the dialog-box
builder.setCancelable(true);
// The title of the Dialog-box is set
builder.setTitle("Screen Overlay Permission Needed");
// The message of the Dialog-box is set
builder.setMessage("Enable 'Display over other apps' from System Settings.");
// The event of the Positive-Button is set
builder.setPositiveButton("Open Settings", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// The app will redirect to the 'Display over other apps' in Settings.
// This is an Implicit Intent. This is needed when any Action is needed
// to perform, here it is
// redirecting to an other app(Settings).
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
// This method will start the intent. It takes two parameter,
// one is the Intent and the other is
// an requestCode Integer. Here it is -1.
startActivityForResult(intent, RESULT_OK);
}
});
dialog = builder.create();
// The Dialog will show in the screen
dialog.show();
}
Java
private boolean checkOverlayDisplayPermission() {
// Android Version is lesser than Marshmallow
// or the API is lesser than 23
// doesn't need 'Display over other apps' permission enabling.
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
// If 'Display over other apps' is not enabled it
// will return false or else true
if (!Settings.canDrawOverlays(this)) {
return false;
} else {
return true;
}
} else {
return true;
}
}
Java
package com.wheic.floatingedittext;
import android.app.ActivityManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import com.wheic.floatingedittext.Common.Common;
public class MainActivity extends AppCompatActivity {
// The reference variables for the
// Button, AlertDialog, EditText
// classes are created
private Button minimizeBtn;
private AlertDialog dialog;
private EditText descEditArea;
private Button save;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// The Buttons and the EditText are connected with
// the corresponding component id used in layout file
minimizeBtn = findViewById(R.id.buttonMinimize);
descEditArea = findViewById(R.id.descEditText);
save = findViewById(R.id.saveBtn);
// If the app is started again while the
// floating window service is running
// then the floating window service will stop
if (isMyServiceRunning()) {
// onDestroy() method in FloatingWindowGFG
// class will be called here
stopService(new Intent(MainActivity.this, FloatingWindowGFG.class));
}
// currentDesc String will be empty
// at first time launch
// but the text written in floating
// window will not gone
descEditArea.setText(Common.currentDesc);
descEditArea.setSelection(descEditArea.getText().toString().length());
// The EditText string will be stored in
// currentDesc while writing
descEditArea.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
// Not Necessary
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
Common.currentDesc = descEditArea.getText().toString();
}
@Override
public void afterTextChanged(Editable editable) {
// Not Necessary
}
});
// Here the save button is used just to store the
// EditText string in saveDesc variable
save.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Common.savedDesc = descEditArea.getText().toString();
descEditArea.setCursorVisible(false);
descEditArea.clearFocus();
Toast.makeText(MainActivity.this, "Text Saved!!!", Toast.LENGTH_SHORT).show();
}
});
// The Main Button that helps to minimize the app
minimizeBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// First it confirms whether the
// 'Display over other apps' permission in given
if (checkOverlayDisplayPermission()) {
// FloatingWindowGFG service is started
startService(new Intent(MainActivity.this, FloatingWindowGFG.class));
// The MainActivity closes here
finish();
} else {
// If permission is not given,
// it shows the AlertDialog box and
// redirects to the Settings
requestOverlayDisplayPermission();
}
}
});
}
private boolean isMyServiceRunning() {
// The ACTIVITY_SERVICE is needed to retrieve a
// ActivityManager for interacting with the global system
// It has a constant String value "activity".
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
// A loop is needed to get Service information that
// are currently running in the System.
// So ActivityManager.RunningServiceInfo is used.
// It helps to retrieve a
// particular service information, here its this service.
// getRunningServices() method returns a list of the
// services that are currently running
// and MAX_VALUE is 2147483647. So at most this many services
// can be returned by this method.
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
// If this service is found as a running,
// it will return true or else false.
if (FloatingWindowGFG.class.getName().equals(service.service.getClassName())) {
return true;
}
}
return false;
}
private void requestOverlayDisplayPermission() {
// An AlertDialog is created
AlertDialog.Builder builder = new AlertDialog.Builder(this);
// This dialog can be closed, just by taping
// anywhere outside the dialog-box
builder.setCancelable(true);
// The title of the Dialog-box is set
builder.setTitle("Screen Overlay Permission Needed");
// The message of the Dialog-box is set
builder.setMessage("Enable 'Display over other apps' from System Settings.");
// The event of the Positive-Button is set
builder.setPositiveButton("Open Settings", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// The app will redirect to the 'Display over other apps' in Settings.
// This is an Implicit Intent. This is needed when any Action is needed
// to perform, here it is
// redirecting to an other app(Settings).
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
// This method will start the intent. It takes two parameter, one is the Intent and the other is
// an requestCode Integer. Here it is -1.
startActivityForResult(intent, RESULT_OK);
}
});
dialog = builder.create();
// The Dialog will
// show in the screen
dialog.show();
}
private boolean checkOverlayDisplayPermission() {
// Android Version is lesser than Marshmallow or
// the API is lesser than 23
// doesn't need 'Display over other apps' permission enabling.
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
// If 'Display over other apps' is not enabled
// it will return false or else true
if (!Settings.canDrawOverlays(this)) {
return false;
} else {
return true;
}
} else {
return true;
}
}
}
Java
package com.wheic.floatingedittext;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.IBinder;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.Nullable;
import com.wheic.floatingedittext.Common.Common;
public class FloatingWindowGFG extends Service {
// The reference variables for the
// ViewGroup, WindowManager.LayoutParams,
// WindowManager, Button, EditText classes are created
private ViewGroup floatView;
private int LAYOUT_TYPE;
private WindowManager.LayoutParams floatWindowLayoutParam;
private WindowManager windowManager;
private Button maximizeBtn;
private EditText descEditArea;
private Button saveBtn;
// As FloatingWindowGFG inherits Service class,
// it actually overrides the onBind method
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
// The screen height and width are calculated, cause
// the height and width of the floating window is set depending on this
DisplayMetrics metrics = getApplicationContext().getResources().getDisplayMetrics();
int width = metrics.widthPixels;
int height = metrics.heightPixels;
// To obtain a WindowManager of a different Display,
// we need a Context for that display, so WINDOW_SERVICE is used
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
// A LayoutInflater instance is created to retrieve the
// LayoutInflater for the floating_layout xml
LayoutInflater inflater = (LayoutInflater) getBaseContext().getSystemService(LAYOUT_INFLATER_SERVICE);
// inflate a new view hierarchy from the floating_layout xml
floatView = (ViewGroup) inflater.inflate(R.layout.floating_layout, null);
// The Buttons and the EditText are connected with
// the corresponding component id used in floating_layout xml file
maximizeBtn = floatView.findViewById(R.id.buttonMaximize);
descEditArea = floatView.findViewById(R.id.descEditText);
saveBtn = floatView.findViewById(R.id.saveBtn);
// Just like MainActivity, the text written
// in Maximized will stay
descEditArea.setText(Common.currentDesc);
descEditArea.setSelection(descEditArea.getText().toString().length());
descEditArea.setCursorVisible(false);
// WindowManager.LayoutParams takes a lot of parameters to set the
// the parameters of the layout. One of them is Layout_type.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// If API Level is more than 26, we need TYPE_APPLICATION_OVERLAY
LAYOUT_TYPE = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
// If API Level is lesser than 26, then we can
// use TYPE_SYSTEM_ERROR,
// TYPE_SYSTEM_OVERLAY, TYPE_PHONE, TYPE_PRIORITY_PHONE.
// But these are all
// deprecated in API 26 and later. Here TYPE_TOAST works best.
LAYOUT_TYPE = WindowManager.LayoutParams.TYPE_TOAST;
}
// Now the Parameter of the floating-window layout is set.
// 1) The Width of the window will be 55% of the phone width.
// 2) The Height of the window will be 58% of the phone height.
// 3) Layout_Type is already set.
// 4) Next Parameter is Window_Flag. Here FLAG_NOT_FOCUSABLE is used. But
// problem with this flag is key inputs can't be given to the EditText.
// This problem is solved later.
// 5) Next parameter is Layout_Format. System chooses a format that supports
// translucency by PixelFormat.TRANSLUCENT
floatWindowLayoutParam = new WindowManager.LayoutParams(
(int) (width * (0.55f)),
(int) (height * (0.58f)),
LAYOUT_TYPE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT
);
// The Gravity of the Floating Window is set.
// The Window will appear in the center of the screen
floatWindowLayoutParam.gravity = Gravity.CENTER;
// X and Y value of the window is set
floatWindowLayoutParam.x = 0;
floatWindowLayoutParam.y = 0;
// The ViewGroup that inflates the floating_layout.xml is
// added to the WindowManager with all the parameters
windowManager.addView(floatView, floatWindowLayoutParam);
// The button that helps to maximize the app
maximizeBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// stopSelf() method is used to stop the service if
// it was previously started
stopSelf();
// The window is removed from the screen
windowManager.removeView(floatView);
// The app will maximize again. So the MainActivity
// class will be called again.
Intent backToHome = new Intent(FloatingWindowGFG.this, MainActivity.class);
// 1) FLAG_ACTIVITY_NEW_TASK flag helps activity to start a new task on the history stack.
// If a task is already running like the floating window service, a new activity will not be started.
// Instead the task will be brought back to the front just like the MainActivity here
// 2) FLAG_ACTIVITY_CLEAR_TASK can be used in the conjunction with FLAG_ACTIVITY_NEW_TASK. This flag will
// kill the existing task first and then new activity is started.
backToHome.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(backToHome);
}
});
// The EditText string will be stored
// in currentDesc while writing
descEditArea.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
// Not Necessary
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
Common.currentDesc = descEditArea.getText().toString();
}
@Override
public void afterTextChanged(Editable editable) {
// Not Necessary
}
});
// Another feature of the floating window is, the window is movable.
// The window can be moved at any position on the screen.
floatView.setOnTouchListener(new View.OnTouchListener() {
final WindowManager.LayoutParams floatWindowLayoutUpdateParam = floatWindowLayoutParam;
double x;
double y;
double px;
double py;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
// When the window will be touched,
// the x and y position of that position
// will be retrieved
case MotionEvent.ACTION_DOWN:
x = floatWindowLayoutUpdateParam.x;
y = floatWindowLayoutUpdateParam.y;
// returns the original raw X
// coordinate of this event
px = event.getRawX();
// returns the original raw Y
// coordinate of this event
py = event.getRawY();
break;
// When the window will be dragged around,
// it will update the x, y of the Window Layout Parameter
case MotionEvent.ACTION_MOVE:
floatWindowLayoutUpdateParam.x = (int) ((x + event.getRawX()) - px);
floatWindowLayoutUpdateParam.y = (int) ((y + event.getRawY()) - py);
// updated parameter is applied to the WindowManager
windowManager.updateViewLayout(floatView, floatWindowLayoutUpdateParam);
break;
}
return false;
}
});
// Floating Window Layout Flag is set to FLAG_NOT_FOCUSABLE,
// so no input is possible to the EditText. But that's a problem.
// So, the problem is solved here. The Layout Flag is
// changed when the EditText is touched.
descEditArea.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
descEditArea.setCursorVisible(true);
WindowManager.LayoutParams floatWindowLayoutParamUpdateFlag = floatWindowLayoutParam;
// Layout Flag is changed to FLAG_NOT_TOUCH_MODAL which
// helps to take inputs inside floating window, but
// while in EditText the back button won't work and
// FLAG_LAYOUT_IN_SCREEN flag helps to keep the window
// always over the keyboard
floatWindowLayoutParamUpdateFlag.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
// WindowManager is updated with the Updated Parameters
windowManager.updateViewLayout(floatView, floatWindowLayoutParamUpdateFlag);
return false;
}
});
saveBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// saves the text in savedDesc variable
Common.savedDesc = descEditArea.getText().toString();
descEditArea.setCursorVisible(false);
WindowManager.LayoutParams floatWindowLayoutParamUpdateFlag = floatWindowLayoutParam;
floatWindowLayoutParamUpdateFlag.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
// The Layout Flag is changed back to FLAG_NOT_FOCUSABLE. and the Layout is updated with new Flag
windowManager.updateViewLayout(floatView, floatWindowLayoutParamUpdateFlag);
// INPUT_METHOD_SERVICE with Context is used
// to retrieve a InputMethodManager for
// accessing input methods which is the soft keyboard here
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
// The soft keyboard slides back in
inputMethodManager.hideSoftInputFromWindow(floatView.getApplicationWindowToken(), 0);
// A Toast is shown when the text is saved
Toast.makeText(FloatingWindowGFG.this, "Text Saved!!!", Toast.LENGTH_SHORT).show();
}
});
}
// It is called when stopService()
// method is called in MainActivity
@Override
public void onDestroy() {
super.onDestroy();
stopSelf();
// Window is removed from the screen
windowManager.removeView(floatView);
}
}
- 删除ActionBar :在Android Studio 4.1中,转到值->主题。有两个主题XML文件,一个用于亮模式,一个用于暗模式。在这两种XML中,在样式块中将父属性更改为Theme.MaterialComponents.DayNight.NoActionBar 。
- 更改应用程序的主色:在同一文件中,第一个项目块必须与应用程序的主色有关。在这里添加新添加的颜色。在项目块中,添加@ color / gfgTheme或@ color / gfgThemeTwo 。
步骤3:首先制作所有版式
3.a:开始处理activity_main.xml文件
该XML文件构成了应用程序主活动的布局。布局并不那么复杂。在ConstraintLayout中只有一个Button,TextView,EditText和一个接一个的Button。这是XML代码。
XML格式
3.b:开始处理floating_layout.xml文件
转到res-> layout 。右键单击布局->新建->布局资源文件。添加布局的名称(floating_layout这里)。该XML文件构成了浮动窗口的布局。它具有与主布局相同的组件,但大小限制略有不同。这是XML代码。
XML格式
步骤4:开始处理Java程序
我们在这里做了3节课。显然,第一个是MainActivity 。第二个是用于浮动窗口服务的FloatingWindowGFG ,最后一个是用于两个公共变量的Common类。
4.a:让我们为通用变量创建类
- 首先,让我们制作一个名为Common的包。右键单击项目包路径(此处为com.wheic.floatingedittext ) ->新建->包。
- 将会弹出一个窗口。预期的软件包名称已写出(对我来说是通用的)。将创建一个新的程序包。
- 右键单击新创建的包-> New-> Java Class 。预期的类名称已被写入(此处通用)。
- 创建了两个公共String变量,一个是currentDesc ,另一个是saveDesc 。两者均以空字符串启动。
- 这是通用代码。 Java类。
Java
package com.wheic.floatingedittext.Common;
public class Common {
// The EditText String will be
// stored in this variable
// when MINIMIZE or MAXIMIZE
// button is pressed
public static String currentDesc = "";
// The EditText String will be
// stored in this variable
// when SAVE button is pressed
public static String savedDesc = "";
}
4.b:开始使用MainActivity。 Java文件
- 首先,创建组件类的引用。创建了两个Button ,一个AlertDialog和一个EditText引用。
- 在转到onCreate()之前,将创建一些其他方法。
- isMyServiceRunning() :此方法有助于查找此应用程序的浮动窗口服务是否正在运行。当已经可见浮动窗口时,如果打开同一应用程序,则需要此函数,因为这时需要停止浮动窗口服务。这是带有逐行说明的代码:
Java
private boolean isMyServiceRunning() {
// The ACTIVITY_SERVICE is needed to retrieve a
// ActivityManager for interacting with the global system
// It has a constant String value "activity".
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
// A loop is needed to get Service information that are currently running in the System.
// So ActivityManager.RunningServiceInfo is used. It helps to retrieve a
// particular service information, here its this service.
// getRunningServices() method returns a list of the services that are currently running
// and MAX_VALUE is 2147483647. So at most this many services can be returned by this method.
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
// If this service is found as a running, it will return true or else false.
if (FloatingWindowGFG.class.getName().equals(service.service.getClassName())) {
return true;
}
}
return false;
}
- requestOverlayDisplayPermission() :该方法有助于将应用程序重定向到“设置”以启用“在其他应用程序上显示”。尽管为此,需要在AndroidManifest.xml文件中添加额外的一行。为此,转到app-> manifests-> AndroidManifest.xml 。在应用程序块之前添加以下行:
Java
private void requestOverlayDisplayPermission() {
// An AlertDialog is created
AlertDialog.Builder builder = new AlertDialog.Builder(this);
// This dialog can be closed, just by
// taping outside the dialog-box
builder.setCancelable(true);
// The title of the Dialog-box is set
builder.setTitle("Screen Overlay Permission Needed");
// The message of the Dialog-box is set
builder.setMessage("Enable 'Display over other apps' from System Settings.");
// The event of the Positive-Button is set
builder.setPositiveButton("Open Settings", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// The app will redirect to the 'Display over other apps' in Settings.
// This is an Implicit Intent. This is needed when any Action is needed
// to perform, here it is
// redirecting to an other app(Settings).
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
// This method will start the intent. It takes two parameter,
// one is the Intent and the other is
// an requestCode Integer. Here it is -1.
startActivityForResult(intent, RESULT_OK);
}
});
dialog = builder.create();
// The Dialog will show in the screen
dialog.show();
}
- checkOverlayDisplayPermission() :此方法实际上检查API级别是否大于23,然后在设置中是否启用了“通过其他应用程序显示”。这是此函数的代码:
Java
private boolean checkOverlayDisplayPermission() {
// Android Version is lesser than Marshmallow
// or the API is lesser than 23
// doesn't need 'Display over other apps' permission enabling.
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
// If 'Display over other apps' is not enabled it
// will return false or else true
if (!Settings.canDrawOverlays(this)) {
return false;
} else {
return true;
}
} else {
return true;
}
}
- 现在,这里是MainActivity的完整代码。 Java文件。
Java
package com.wheic.floatingedittext;
import android.app.ActivityManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import com.wheic.floatingedittext.Common.Common;
public class MainActivity extends AppCompatActivity {
// The reference variables for the
// Button, AlertDialog, EditText
// classes are created
private Button minimizeBtn;
private AlertDialog dialog;
private EditText descEditArea;
private Button save;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// The Buttons and the EditText are connected with
// the corresponding component id used in layout file
minimizeBtn = findViewById(R.id.buttonMinimize);
descEditArea = findViewById(R.id.descEditText);
save = findViewById(R.id.saveBtn);
// If the app is started again while the
// floating window service is running
// then the floating window service will stop
if (isMyServiceRunning()) {
// onDestroy() method in FloatingWindowGFG
// class will be called here
stopService(new Intent(MainActivity.this, FloatingWindowGFG.class));
}
// currentDesc String will be empty
// at first time launch
// but the text written in floating
// window will not gone
descEditArea.setText(Common.currentDesc);
descEditArea.setSelection(descEditArea.getText().toString().length());
// The EditText string will be stored in
// currentDesc while writing
descEditArea.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
// Not Necessary
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
Common.currentDesc = descEditArea.getText().toString();
}
@Override
public void afterTextChanged(Editable editable) {
// Not Necessary
}
});
// Here the save button is used just to store the
// EditText string in saveDesc variable
save.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Common.savedDesc = descEditArea.getText().toString();
descEditArea.setCursorVisible(false);
descEditArea.clearFocus();
Toast.makeText(MainActivity.this, "Text Saved!!!", Toast.LENGTH_SHORT).show();
}
});
// The Main Button that helps to minimize the app
minimizeBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// First it confirms whether the
// 'Display over other apps' permission in given
if (checkOverlayDisplayPermission()) {
// FloatingWindowGFG service is started
startService(new Intent(MainActivity.this, FloatingWindowGFG.class));
// The MainActivity closes here
finish();
} else {
// If permission is not given,
// it shows the AlertDialog box and
// redirects to the Settings
requestOverlayDisplayPermission();
}
}
});
}
private boolean isMyServiceRunning() {
// The ACTIVITY_SERVICE is needed to retrieve a
// ActivityManager for interacting with the global system
// It has a constant String value "activity".
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
// A loop is needed to get Service information that
// are currently running in the System.
// So ActivityManager.RunningServiceInfo is used.
// It helps to retrieve a
// particular service information, here its this service.
// getRunningServices() method returns a list of the
// services that are currently running
// and MAX_VALUE is 2147483647. So at most this many services
// can be returned by this method.
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
// If this service is found as a running,
// it will return true or else false.
if (FloatingWindowGFG.class.getName().equals(service.service.getClassName())) {
return true;
}
}
return false;
}
private void requestOverlayDisplayPermission() {
// An AlertDialog is created
AlertDialog.Builder builder = new AlertDialog.Builder(this);
// This dialog can be closed, just by taping
// anywhere outside the dialog-box
builder.setCancelable(true);
// The title of the Dialog-box is set
builder.setTitle("Screen Overlay Permission Needed");
// The message of the Dialog-box is set
builder.setMessage("Enable 'Display over other apps' from System Settings.");
// The event of the Positive-Button is set
builder.setPositiveButton("Open Settings", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// The app will redirect to the 'Display over other apps' in Settings.
// This is an Implicit Intent. This is needed when any Action is needed
// to perform, here it is
// redirecting to an other app(Settings).
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
// This method will start the intent. It takes two parameter, one is the Intent and the other is
// an requestCode Integer. Here it is -1.
startActivityForResult(intent, RESULT_OK);
}
});
dialog = builder.create();
// The Dialog will
// show in the screen
dialog.show();
}
private boolean checkOverlayDisplayPermission() {
// Android Version is lesser than Marshmallow or
// the API is lesser than 23
// doesn't need 'Display over other apps' permission enabling.
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
// If 'Display over other apps' is not enabled
// it will return false or else true
if (!Settings.canDrawOverlays(this)) {
return false;
} else {
return true;
}
} else {
return true;
}
}
}
4.c:开始在FloatingWindowGFG上工作。 Java文件
- 创建另一个名为FloatingWindowGFG的类。转到项目包路径(对我来说是com.wheic.floatingedittext ) ->新建-> Java类,然后会给出任何预期的名称(对我来说是FloatingWindowGFG )。
- 该类继承Service类。
- 现在,由于该类是从Service类继承的,因此可以将该类用作清单文件中的服务。因此,在AndroidManifest.xml中,在活动块之后和应用程序块结束之前添加此行。
- 现在,这里是FloatingWindowGFG的代码。 Java的。添加了注释以便更好地解释:
Java
package com.wheic.floatingedittext;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.IBinder;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.Nullable;
import com.wheic.floatingedittext.Common.Common;
public class FloatingWindowGFG extends Service {
// The reference variables for the
// ViewGroup, WindowManager.LayoutParams,
// WindowManager, Button, EditText classes are created
private ViewGroup floatView;
private int LAYOUT_TYPE;
private WindowManager.LayoutParams floatWindowLayoutParam;
private WindowManager windowManager;
private Button maximizeBtn;
private EditText descEditArea;
private Button saveBtn;
// As FloatingWindowGFG inherits Service class,
// it actually overrides the onBind method
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
// The screen height and width are calculated, cause
// the height and width of the floating window is set depending on this
DisplayMetrics metrics = getApplicationContext().getResources().getDisplayMetrics();
int width = metrics.widthPixels;
int height = metrics.heightPixels;
// To obtain a WindowManager of a different Display,
// we need a Context for that display, so WINDOW_SERVICE is used
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
// A LayoutInflater instance is created to retrieve the
// LayoutInflater for the floating_layout xml
LayoutInflater inflater = (LayoutInflater) getBaseContext().getSystemService(LAYOUT_INFLATER_SERVICE);
// inflate a new view hierarchy from the floating_layout xml
floatView = (ViewGroup) inflater.inflate(R.layout.floating_layout, null);
// The Buttons and the EditText are connected with
// the corresponding component id used in floating_layout xml file
maximizeBtn = floatView.findViewById(R.id.buttonMaximize);
descEditArea = floatView.findViewById(R.id.descEditText);
saveBtn = floatView.findViewById(R.id.saveBtn);
// Just like MainActivity, the text written
// in Maximized will stay
descEditArea.setText(Common.currentDesc);
descEditArea.setSelection(descEditArea.getText().toString().length());
descEditArea.setCursorVisible(false);
// WindowManager.LayoutParams takes a lot of parameters to set the
// the parameters of the layout. One of them is Layout_type.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// If API Level is more than 26, we need TYPE_APPLICATION_OVERLAY
LAYOUT_TYPE = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
// If API Level is lesser than 26, then we can
// use TYPE_SYSTEM_ERROR,
// TYPE_SYSTEM_OVERLAY, TYPE_PHONE, TYPE_PRIORITY_PHONE.
// But these are all
// deprecated in API 26 and later. Here TYPE_TOAST works best.
LAYOUT_TYPE = WindowManager.LayoutParams.TYPE_TOAST;
}
// Now the Parameter of the floating-window layout is set.
// 1) The Width of the window will be 55% of the phone width.
// 2) The Height of the window will be 58% of the phone height.
// 3) Layout_Type is already set.
// 4) Next Parameter is Window_Flag. Here FLAG_NOT_FOCUSABLE is used. But
// problem with this flag is key inputs can't be given to the EditText.
// This problem is solved later.
// 5) Next parameter is Layout_Format. System chooses a format that supports
// translucency by PixelFormat.TRANSLUCENT
floatWindowLayoutParam = new WindowManager.LayoutParams(
(int) (width * (0.55f)),
(int) (height * (0.58f)),
LAYOUT_TYPE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT
);
// The Gravity of the Floating Window is set.
// The Window will appear in the center of the screen
floatWindowLayoutParam.gravity = Gravity.CENTER;
// X and Y value of the window is set
floatWindowLayoutParam.x = 0;
floatWindowLayoutParam.y = 0;
// The ViewGroup that inflates the floating_layout.xml is
// added to the WindowManager with all the parameters
windowManager.addView(floatView, floatWindowLayoutParam);
// The button that helps to maximize the app
maximizeBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// stopSelf() method is used to stop the service if
// it was previously started
stopSelf();
// The window is removed from the screen
windowManager.removeView(floatView);
// The app will maximize again. So the MainActivity
// class will be called again.
Intent backToHome = new Intent(FloatingWindowGFG.this, MainActivity.class);
// 1) FLAG_ACTIVITY_NEW_TASK flag helps activity to start a new task on the history stack.
// If a task is already running like the floating window service, a new activity will not be started.
// Instead the task will be brought back to the front just like the MainActivity here
// 2) FLAG_ACTIVITY_CLEAR_TASK can be used in the conjunction with FLAG_ACTIVITY_NEW_TASK. This flag will
// kill the existing task first and then new activity is started.
backToHome.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(backToHome);
}
});
// The EditText string will be stored
// in currentDesc while writing
descEditArea.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
// Not Necessary
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
Common.currentDesc = descEditArea.getText().toString();
}
@Override
public void afterTextChanged(Editable editable) {
// Not Necessary
}
});
// Another feature of the floating window is, the window is movable.
// The window can be moved at any position on the screen.
floatView.setOnTouchListener(new View.OnTouchListener() {
final WindowManager.LayoutParams floatWindowLayoutUpdateParam = floatWindowLayoutParam;
double x;
double y;
double px;
double py;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
// When the window will be touched,
// the x and y position of that position
// will be retrieved
case MotionEvent.ACTION_DOWN:
x = floatWindowLayoutUpdateParam.x;
y = floatWindowLayoutUpdateParam.y;
// returns the original raw X
// coordinate of this event
px = event.getRawX();
// returns the original raw Y
// coordinate of this event
py = event.getRawY();
break;
// When the window will be dragged around,
// it will update the x, y of the Window Layout Parameter
case MotionEvent.ACTION_MOVE:
floatWindowLayoutUpdateParam.x = (int) ((x + event.getRawX()) - px);
floatWindowLayoutUpdateParam.y = (int) ((y + event.getRawY()) - py);
// updated parameter is applied to the WindowManager
windowManager.updateViewLayout(floatView, floatWindowLayoutUpdateParam);
break;
}
return false;
}
});
// Floating Window Layout Flag is set to FLAG_NOT_FOCUSABLE,
// so no input is possible to the EditText. But that's a problem.
// So, the problem is solved here. The Layout Flag is
// changed when the EditText is touched.
descEditArea.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
descEditArea.setCursorVisible(true);
WindowManager.LayoutParams floatWindowLayoutParamUpdateFlag = floatWindowLayoutParam;
// Layout Flag is changed to FLAG_NOT_TOUCH_MODAL which
// helps to take inputs inside floating window, but
// while in EditText the back button won't work and
// FLAG_LAYOUT_IN_SCREEN flag helps to keep the window
// always over the keyboard
floatWindowLayoutParamUpdateFlag.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
// WindowManager is updated with the Updated Parameters
windowManager.updateViewLayout(floatView, floatWindowLayoutParamUpdateFlag);
return false;
}
});
saveBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// saves the text in savedDesc variable
Common.savedDesc = descEditArea.getText().toString();
descEditArea.setCursorVisible(false);
WindowManager.LayoutParams floatWindowLayoutParamUpdateFlag = floatWindowLayoutParam;
floatWindowLayoutParamUpdateFlag.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
// The Layout Flag is changed back to FLAG_NOT_FOCUSABLE. and the Layout is updated with new Flag
windowManager.updateViewLayout(floatView, floatWindowLayoutParamUpdateFlag);
// INPUT_METHOD_SERVICE with Context is used
// to retrieve a InputMethodManager for
// accessing input methods which is the soft keyboard here
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
// The soft keyboard slides back in
inputMethodManager.hideSoftInputFromWindow(floatView.getApplicationWindowToken(), 0);
// A Toast is shown when the text is saved
Toast.makeText(FloatingWindowGFG.this, "Text Saved!!!", Toast.LENGTH_SHORT).show();
}
});
}
// It is called when stopService()
// method is called in MainActivity
@Override
public void onDestroy() {
super.onDestroy();
stopSelf();
// Window is removed from the screen
windowManager.removeView(floatView);
}
}
输出:
终于,项目准备好了。您可以在此GitHub链接中检查该项目。