问题
I wanted to create an Overlay, like a HUD, that resides on the screen during my applications activity stack (the task of my app) changes.
I found a couple of examples using WindowManager but I couldn't figure out the parameterization for the correct z-index if you want. It was either to weak the next activity would go ontop of my overlay or to strong the overlay was a system wide overlay that was visible also when the app moved into the background.
I aim for displaying a view on top of all activites belonging to my app or task (app would be preferred). I am aware that apps and tasks are two different things on android...
What I explicitly don't want is to use system wide windows that require android.permission.SYSTEM_ALERT_WINDOW
--- my usecase --
I am implementing a flow that includes an activity providing a form for user input. that user input has to be processed in a rather complex manner, yielding a possible outcome of +/- 10 states. The processing can take up to 10 minutes and depending on the outcome of that process I want to display a corresponding view. While the process runs I intent to keep the user updated but can not allow him to navigate the app (except aborting the process). Every possible outcome of the long running operation will be presented in a different activity.
I am well aware, that there are several approaches possible (for example having one activity only). But that decision has already been made and is out of scope of that question. I have implemented a solution that uses System Windows to display that overlay in. For hiding the overlay I have to count onStart, onStop events and interpret "App did to move into background" and "App did move into foreground". This feels dirty and I am not satisfied with that solution. I'd rather take a step back and display my overlay on top of the calling activity and upon finishing the process hiding it and moving forward to the activity displaying the result.
One more thing I tried is to move a view from one activity to another. But this shows some flickering and interruption of my animation that I don't like.
I would appreciate it if we could focus on the question of whether its possible to display a view on top of the application/task window rather than inside a system or activity window ;)
回答1:
I think the following will be of interest to you. You won't require android.permission.SYSTEM_ALERT_WINDOW
. The code is for the Application class.
Comments should help you understand the logic. The code should work right out of the box.
public class YourApplication extends Application {
// Popup to show persistent view
PopupWindow pw;
// View held by Popup
LinearLayout ll;
@Override
public void onCreate() {
super.onCreate();
// Register for Activity Lifecyle Callbacks
registerActivityLifecycleCallbacks(new YourCallBack());
// Initialize the view
ll = new LinearLayout(this);
ll.setLayoutParams(new LayoutParams(100, 100));
ll.setBackgroundColor(Color.BLUE);
// Initialize popup
pw = new PopupWindow(ll, 100, 100);
// Set popup's window layout type to TYPE_TOAST
Method[] methods = PopupWindow.class.getMethods();
for(Method m: methods){
if(m.getName().equals("setWindowLayoutType")) {
try{
m.invoke(pw, WindowManager.LayoutParams.TYPE_TOAST);
}catch(Exception e){
e.printStackTrace();
}
break;
}
}
}
@Override
public void onTerminate() {
super.onTerminate();
if (pw != null && pw.isShowing()) {
pw.dismiss();
}
};
private final class YourCallBack implements ActivityLifecycleCallbacks {
int numOfRunning = 0;
@Override
public void onActivityCreated(Activity arg0, Bundle arg1) { }
@Override
public void onActivityDestroyed(Activity arg0) { }
@Override
public void onActivityPaused(Activity arg0) {
// An activity has been paused
// Decrement count, but wait for a certain
// period of time, in case another activity
// from this application is being launched
numOfRunning--;
// Delay: 100 ms
// If no activity's onResumed() was called,
// its safe to assume that the application
// has been paused, in which case, dismiss
// the popup
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (numOfRunning == 0) {
pw.dismiss();
}
}
}, 100L);
}
@Override
public void onActivityResumed(Activity arg0) {
// If no activities were running, show the popup
if (numOfRunning == 0) {
pw.showAtLocation(ll, Gravity.BOTTOM, 0, 0);
}
// Now, one activity is running
numOfRunning++;
}
@Override
public void onActivitySaveInstanceState(Activity arg0, Bundle arg1) { }
@Override
public void onActivityStarted(Activity arg0) { }
@Override
public void onActivityStopped(Activity arg0) { }
};
}
回答2:
You really only have two options.
1) You can create an activity with the theme of Theme.Dialog. This will display a popup on top of your window. You can create the dialog to be modal-less (can click through). In my quick testing I wasn't able to get overlay to the edges of my screen although maybe modifying the theme would fix that.
MainActivity.java
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button test = (Button) this.findViewById(R.id.test);
test.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
((Button) v).setBackgroundColor(Color.RED);
}
});
Intent i = new Intent(this, SecondActivity.class);
startActivity(i);
}
}
SecondActivity.java
public class SecondActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.second_main);
Window window = getWindow();
window.setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
window.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT);
window.setGravity(Gravity.BOTTOM);
}
@Override
public void onBackPressed() {
//Override to prevent back button from closing the second activity dialog
}
}
Manifest
....
<activity
android:name="com.example.control.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.example.control.SecondActivity"
android:theme="@android:style/Theme.Dialog"
android:label="@string/app_name" >
....
</activity>
....
2) The second option is to use a SYSTEM_ALERT_WINDOW. I prefer this method significantly more. You are correct that it CAN be visible and on top of every other app, however, you can control when it is visible and when it isn't. I'm not going to post any source code but I will give you a general plan of attack.
When you create the service, bind to it using an AIDL. This way you'll be able to talk directly with the service to tell it when to 'hide' and 'show' the overlay. Speaking of hiding and showing, onPause and onResume can be used to tell the service to hide and show the overlay. Lastly, if you need to receive click events on your overlay, that will prove to be tricky as the touch events don't always act the way you expect them to.
Good Luck.
来源:https://stackoverflow.com/questions/18843868/android-overlay-on-window-over-activities-of-a-task