I have a need to show a minimally-intrusive non-blocking notification which is not tied to the activity it was shown in (like a Toast) and which is clickable. Anyone have any idea whether or not this is possible? Unfortunately, it appears that Toast notifications (custom or otherwise) are not clickable (i.e. setting an OnClickListener on its views has no effect). All the alternatives that I'm aware of (i.e. AlertDialog, PopupWindow and Crouton) seem to show a notification which is tied to the activity it was shown in (i.e. they won't continue showing when the activity finishes). Any suggestions?
You can use PopupWindow, add an onClickListener and add a handler to auto cancel it after n times (just like the behavior of a toast). Something like this:
public static void showToast(Activity a, String title, String message) {
// inflate your xml layout
LayoutInflater inflater = a.getLayoutInflater();
View layout = inflater.inflate(R.layout.custom_toast,
(ViewGroup) a.findViewById(R.id.toast_layout_root));
// set the custom display
((TextView) layout.findViewById(R.id.title)).setText(title);
((TextView) layout.findViewById(R.id.message)).setText(message);
// initialize your popupWindow and use your custom layout as the view
final PopupWindow pw = new PopupWindow(layout,
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT, true);
// set windowType to TYPE_TOAST (requires API 23 above)
// this will make popupWindow still appear even the activity was closed
pw.setWindowLayoutType(WindowManager.LayoutParams.TYPE_TOAST);
pw.showAtLocation(layout, Gravity.CENTER | Gravity.TOP, 0, 500);
// handle popupWindow click event
layout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// do anything when popupWindow was clicked
pw.dismiss(); // dismiss the window
}
});
// dismiss the popup window after 3sec
new Handler().postDelayed(new Runnable() {
public void run() {
pw.dismiss();
}
}, 3000);
}
xml layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/toast_layout_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000"
android:orientation="vertical"
android:elevation="10dp"
android:padding="20dp">
<TextView
android:id="#+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="#FFF"
android:textStyle="bold"/>
<TextView
android:id="#+id/message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="#FFF"/>
</LinearLayout>
You are right, a Toast object has no way to be interacted with, but there are many libraries out there that will give you the same look and feel as a toast, but with some interactivity. The one I use is https://github.com/JohnPersano/SuperToasts
I think what you need is in fact a PopupWindowwhich can be seen here "http://developer.android.com/reference/android/widget/PopupWindow.html".
Toasts have a very specific task, which is to inform the user, without any input from them. So instead of trying to extend the purpose of the Toast, use the PopupWindow which can be interacted with by the user.
A 'Dialog' type of activity will probably be your best bet.
In manifest:
<activity android:name=".ToastLikeActivity"
android:theme="#android:style/Theme.Dialog"
android:label="#string/label"
></activity>
And timeout the activity within the onCreate():
class ToastLikeActivity extends Activity {
#Override
public void onCreate(Bundle state)
// auto-kill activity after X seconds <-------------------------
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
ToastLikeActivity.this.finish(); // kill after X seconds
}
}
}, VisibleTimeSecs*1000);
}
To display the dialog start it as with any other activity:
Intent i = new Intent(this, ToastLikeActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(i);
And it will show up and automatically go away after X seconds.
Such a popup will not be tied to the caller activity. In fact - it will not even require a caller activity. You
can activate it (bad idea, but possible) even from a service.
You can implement basically any kind of sensitive (i.e. accepting user's clicks) interface you want to
the ToastLikeActivity. Especially: you can make its exteriors transparent, giving it a dialog-likke looks.
Related
I know that Android Presentations can have their own layouts which means I can create UI components like buttons etc. However, does anyone know if it is possible for Presentations to handle touch events?
I have tried adding a button on the Presentation layout and registering a button onClickListener but it seems to not be working. Is there another way?
Here is my code:
In my Presentation class
mHelloToast = (Button) findViewById(R.id.btn_presentation_hello_toast);
mHelloToast.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast toast = Toast.makeText(getContext(), "hello presentation", Toast.LENGTH_SHORT );
toast.show();
}
});
Edit: bump
Doc says:
A presentation is a special kind of dialog whose purpose is to present content on a secondary display. A Presentation is associated with the target Display at creation time and configures its context and resource configuration according to the display's metrics.
So I think Android Presention can handle touche event as Dialog.Here I will show you how Dialog handles touch event.
Step 1
Create your custom layout res/layout/dialog_signin.xml.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button
android:id="#+id/btn_presentation_hello_toast"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
Step 2
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
// Get the layout inflater
LayoutInflater inflater = getActivity().getLayoutInflater();
// Get the view which you want to receive touch event
//// Pass null as the parent view because its going in the dialog layout
View rootView = inflater.inflate(R.layout.dialog_signin, null);
Button bt = (Button)rootView.findViewById(R.id.btn_presentation_hello_toast);
// set click or touch listener
bt.setOnClickListener(....);
// set dialog's contents
builder.setView(rootView);
I am using a custom ProgressBar. Now while a task is going on, I am showing the progress bar, but user can still interact with the views and controls.
How do I disable the user interaction on whole view just like a ProgressDialog does , when it is visible.
Do I need to use a transparent view on top of main view and show the progress bar on that view and hide that view once a task is completed.
Or just get the id of my parentView and set it disabled ? But then I won't be able to dim the background, just like what happens when a dialog appears on the view/Activity/Fragment. Right?
I just want to know the way to disallow the user from any interaction while the progressbar is visible.
Thanks
Your question: How to disable the user interaction while ProgressBar is visible in android?
To disable the user interaction you just need to add the following code
getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
To get user interaction back you just need to add the following code
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
Here is an example:
Note:I am giving you just an example to show how to disable or retain user interaction
Add a progress bar in your xml.Something like this
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/progressBar"
android:visibility="gone"/>
In MainActivity when a button pressed you show the progressbar and disable the user interaction.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImageView = (ImageView) findViewById(R.id.imageView);
mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
mImageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mProgressBar.setVisibility(View.VISIBLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
}
});
}
And when user backPressed you remove the progressbar again retain the user interaction.Something like this
#Override
public void onBackPressed() {
super.onBackPressed();
mProgressBar.setVisibility(View.GONE);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
}
If you want to add a feature of disable and greyed out display, you need to add in your xml layout file a linear layout that fills the parent. Set its background to #B0000000 and its visibilty to GONE. Then programmatically set its visibility to VISIBLE.
Hope this help!
I have fixed this issue by adding root layout to the ProgressBar.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:clickable="true"
android:gravity="center"
android:visibility="gone"
android:id="#+id/progress">
<ProgressBar
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:indeterminate="true"
android:indeterminateTintMode="src_atop"
android:indeterminateTint="#color/primary"/>
</LinearLayout>
Made the root layout clickable
android:clickable="true"
NOTE: In my main view, I had RelativeLayout as root and have added above-mentioned code inside the root layout at the last position (last child).
Hope this helps!!
just set:
android:clickable="true"
in your xml
<ProgressBar...
Only this makes magic!
To extend (pun intended) on the accepted Answer :
When you use kotlin you can use extension functions. That way you have a quick and nice looking method for blocking and unblocking UI.
fun AppCompatActivity.blockInput() {
window.setFlags(
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
}
fun AppCompatActivity.unblockInput() {
window.clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
}
fun AppCompatActivity.blockInputForTask(task: () -> Unit) {
blockInput()
task.invoke()
unblockInput()
}
You can use the blocking and unblocking functions in your activity. Also, you can add more functionality like showing a Toast or something.
When using it in a custom view or any other view, you can simply cast the context to activity and use the functions.
Use blockInputForTask to surround simple linear tasks and blockInputand unblockInput when they are needed in different scopes.
You can use blockInputForTask like this:
blockInputForTask {
// Your lines of code
// Can be multiple lines
}
Use document default method progressbar.setCancelable(false)
Make a dialog with transparent background. The issue with getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE); is that when app will go in background and come back user will be able to interact with UI components, a lot more handling. So for blocking UI make a transparent dialog and if you want to set time for hide/show. Do this in a runnable thread. So the solution will be
public class TransparentDialogHelper {
private Dialog overlayDialog;
#Inject
public TransparentDialogHelper() {
}
public void showDialog(Context context) {
if (AcmaUtility.isContextFinishing(context)) {
return;
}
if (overlayDialog == null) {
overlayDialog = new Dialog(context, android.R.style.Theme_Panel);
overlayDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED);
}
overlayDialog.show();
}
public void hideDialog() {
if (overlayDialog == null || AcmaUtility.isContextFinishing(overlayDialog.getContext())) {
return;
}
overlayDialog.cancel();
}
}
-------- Timer
Handler handler = new Handler();
handler.postDelayed( () -> {
view.hideProgress();
}, 2000);
Make your parent layout as Relative Layout & add this :
<RelativeLayout ... >
<other layout elements over which prog bar will appear>
<RelativeLayout android:id="#+id/rl_progress_bar"
android:layout_width="match_parent" android:clickable="true"
android:layout_height="match_parent" >
<ProgressBar android:id="#+id/pb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:indeterminateOnly="true"
style="#android:style/Widget.DeviceDefault.ProgressBar"
android:theme="#style/AppTheme.MyProgressBar"
/>
</RelativeLayout>
If you have floating buttons in your UI, they still grab all the focus & remain clickable when the progress bar is visible. for this use : (when your prog bar is visible & re-enable them when you make your prog bar invisible/gone)
fb.setEnabled(false);
I have a dropdown spinner which is showed when click on a button looks like this:
<Button
android:id="#+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Spinner
android:id="#+id/spinMenu"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:spinnerMode="dropdown"
android:visibility="invisible" />
<ListView
android:id="#+id/lvWall"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
Here is snippet showing dropdown popup:
findViewById(R.id.btn).setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
spinMenu.performClick();
}
});
My spinner can show dropdown popup correctly. The problem is my layout has a listview which getting data from web service in background. When data is loading completely, all list items will be showed or refreshed, and the spinner's dropdown popup is dismiss (I even don't touch anything on screen). I think the problem is window has changed focus on other view. So how can I prevent it?
Update:
Here is my list after load data from background, it's very simple:
List<Feed> data = result;
FeedAdapter adapter = new FeedAdapter (this, data);
ListView lvWall = (ListView)findViewById(R.id.lvWall);
lvWall.setAdapter(adapter);
And data for spinner:
List<String> list = getMenus();
ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(this,
android.R.layout.simple_spinner_item, list);
dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinMenu.setAdapter(dataAdapter);
If I understand correctly, you have a Spinner view which you set as invisible with the only purpose of showing the popup menu, but not the Spinner view itself. In that case, the problem is probably related to this snippet in Spinner.java, more precisely in DropdownPopup.show():
public void show(int textDirection, int textAlignment) {
...
super.show();
...
// Make sure we hide if our anchor goes away.
// TODO: This might be appropriate to push all the way down to PopupWindow,
// but it may have other side effects to investigate first. (Text editing handles, etc.)
final ViewTreeObserver vto = getViewTreeObserver();
if (vto != null) {
final OnGlobalLayoutListener layoutListener = new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (!Spinner.this.isVisibleToUser()) {
dismiss();
} else {
computeContentWidth();
...
What does this mean? Basically that the Spinner is set up with a ViewTreeObserver to be notified whenever a layout pass changes the views in the screen. And if the Spinner is not visible after that happens, the popup is dismissed. Loading the ListView evidently causes a change in the view hierarchy, and it's being fired when the data arrives from the server.
For general usage this is completely logical: if the Spinner is hidden, or it goes off screen, or something like that, it would be reasonable to make the popup go away. However, it's interferring with what you're attempting to do. It would be nice if you could somehow override isVisibleToUser(), but unfortunately it's marked as #hide, so that's not possible.
Might I suggest a workaround, like setting the Spinner visible but really small? Like, with a height of 1px? I believe that should be enough to fool this method.
Another option, and probably a more sensible one, would be to forgo the Spinner altogether and use a PopupMenu instead. You can anchor it to the Button, load it dynamically, and show it when the button is pressed. The visual effect should be the same.
If you think the problem is due to the change of focus . You can set it with multiple ways.
first create a focuschangeListener and onfocuschange do whatever you like
yourView.setOnFocusChangeListener(testListener);
#Override
public void onFocusChange(View arg0,
boolean isFocused)
{
if(isFocused)
{
//do your work here
}
else
{
}
}
And second way to prevent view from focus..
<!-- Dummy item to prevent AutoCompleteTextView from receiving focus -->
<LinearLayout
android:focusable="true" android:focusableInTouchMode="true"
android:layout_width="0px" android:layout_height="0px"/>
<!-- :nextFocusUp and :nextFocusLeft have been set to the id of this component
to prevent the dummy from receiving focus again -->
<AutoCompleteTextView android:id="#+id/autotext"
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:nextFocusUp="#id/autotext" android:nextFocusLeft="#id/autotext"/>
In my Android application I have an activity featuring GoogleMaps. In case of notifications etc., I show a popup window. This works all quite fine. However, I also have another activity where I want to display the same information in the same way. The idea is to use the same popup window in the corresponding view (View2). The problem is that in this second activity/view the popup window does not appear and the code seems to crash at group.addView(popup, lp); (no explicit errors though; but I'm sure there's nothing null). I just don't see the essential difference between the two activities/views that might suggest why the popup windows works fine in the in the one view but not in the other. In the following I show the relevant code snippets.
Here is how I instantiate the popup in both activities. The only difference is the third parameter that refers to the ID of the parent view; a RelativeLayout in each case.
// GoogleMaps Activity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mapview);
[...]
this.popupPanel = new PopupPanel(this, R.layout.popup, R.id.relativeLayoutMap);
[...]
}
// View2 Activity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.view2);
[...]
this.popupPanel = new PopupPanel(this, R.layout.popup, R.id.relativeLayoutView2);
[...]
}
This is the main code for initializing the popup and for displaying. Only the value ''parentViewID'' differs between the activities.
public PopupPanel(Activity activity, int layout, int parentViewID) {
this.activity = activity;
this.viewID = viewID;
ViewGroup parent = (ViewGroup) this.activity.findViewById(parentViewID);
this.popup = activity.getLayoutInflater().inflate(layout, parent, false);
}
public void show(boolean alignTop) {
RelativeLayout.LayoutParams lp=new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT
);
lp.addRule(RelativeLayout.ALIGN_PARENT_TOP);
ViewGroup group = (ViewGroup) this.activity.findViewById(this.viewID);
group.addView(popup, lp); // this 'crashes' for the View2 activity
group.invalidate();
}
Finally, here are the snippets of the corresponding layouts. In both cases I refer to a RelativeLayout where I want to place my popup.
<? xml version="1.0" encoding="utf-8"?>
<LinearLayout [...]>
<RelativeLayout [...] android:id="#+id/relativeLayoutMap">
<com.google.android.maps.MapView [...]/>
</RelativeLayout>
</LinearLayout>
For activity/view View2:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout [...] android:id="#+id/relativeLayoutView2">
<LinearLayout [...] >
...
</LinearLayout>
<LinearLayout [...] >
...
</LinearLayout>
<ScrollView [...] >
<LinearLayout [...] >
...
</LinearLayout>
</ScrollView>
</RelativeLayout>
Any hints are much appreciated! I know that has been addressed in several question, but my 'problem' is that it basically works. Just only in one activity/view, and not in another. It seems that I miss something rather stupid here.
I figured out the difference, and as expected that it was something rather stupid: I forgot to put the code that displays the panel not within the runOnUiThread. I reallt should have know better by now.
runOnUiThread(new Runnable() {
public void run() {
// code for showing the popup
}
});
One side question: I got on the right track when I put the addView(...) call within a try/catch-block. Without it just hangs up there without throwing an error (in LogCat; I'm using Eclipse). Is there a way that such errors are shown by default? Thanks!
I'm trying to have an EditText and the possibility to display a keyboard at the bottom of the screen when the user taps the EditText. I'm aware of the InputMethodService and the SoftKeyboard example, but I can't use it in that fashion as my keyboard should only be available to this very EditText.
Furthermore, there should be a context menu, but that's not part of this question (I think).
I've read plenty of code fragments, but in many cases they contain methods that aren't available anymore (i.e., getViewInflate()) or are written in a context that I don't understand or can't translate into my code (mind that I'm a newbie regarding Android).
In most attempts I fail with this exception when I tap the EditText:
java.lang.IllegalArgumentException: width and height must be > 0
followed by a stack-trace that doesn't contain any of my classes. As you can see in the code below all sizes are set.
What you see below is the current status of the code (I removed some of the code and I hope it still makes sense). I also tried to use what's inside of handler.post() in the main thread, use the commented stuff instead of the handler.post() ...
What's not below is an attempt to use a RelativeLayout with the EditText and the KeyboardView in one layout-XML. There was a different exception, something like "invalid type 0x12" or something when creating the layout.
It just doesn't work or I just don't know how to do it. Can anyone please guide me through this? Please let me know if something is missing.
main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<EditText
android:id="#+id/field_input"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:inputType="textMultiLine|textImeMultiLine"
android:typeface="monospace"
android:gravity="top|left"
android:maxLength="255"
/>
</LinearLayout>
keyboard.xml:
<?xml version="1.0" encoding="utf-8"?>
<com.messenger.keyboard.LatinKeyboardView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/keyboard"
android:layout_alignParentBottom="true"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
LatinKeyboardView.java:
import android.inputmethodservice.KeyboardView;
public class LatinKeyboardView extends KeyboardView {
:
}
EditorActivity.java
import android.app.Activity;
public class EditorActivity extends Activity {
private View keyboardLayout;
#Override
public void onCreate(Bundle savedInstanceState) {
final EditText inputField;
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
keyboardLayout = (View)getLayoutInflater().inflate(R.layout.keyboard, null, false);
inputField = (EditText)findViewById(R.id.field_input);
registerForContextMenu(inputField);
inputField.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
#Override
public void run() {
LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//PopupWindow pw = new PopupWindow(inflater.inflate(R.layout.input, null, false), 100, 100, true);
PopupWindow pw = new PopupWindow(keyboardLayout, 100, 100, true);
pw.showAtLocation(findViewById(R.id.field_input), Gravity.CENTER, 0, 0);
}
});
/*
if (keyboardLayout.getVisibility() == View.GONE) {
// Show Media Player
TranslateAnimation mAnimUp =
new TranslateAnimation(
Animation.RELATIVE_TO_SELF, 0,
Animation.RELATIVE_TO_SELF, 0,
Animation.RELATIVE_TO_SELF, -keyboardLayout.getHeight(),
Animation.RELATIVE_TO_SELF, 0);
mAnimUp.setStartOffset(500);
mAnimUp.setDuration(500);
keyboardLayout.setVisibility(View.VISIBLE);
keyboardLayout.setAnimation(mAnimUp);
}
*/
}
});
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
:
}
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
:
}
#Override
public boolean onOptionsItemSelected(final MenuItem item) {
:
}
#Override
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
:
}
}
After hours of "research and trying" stuff, I finally understood my mistake, which seems to be "sjngm"'s one. In order for a virtual keyboard to render, you must
declare the view either by inflating a layout xml file, or by declaring in row your KeyboardView (just as you would do it for an other View).
And what was forgotten here: retrieve your KeyboardView with findViewById() and call on it:
keyboardViewInstance.setKeyboard(new Keyboard(...) );
That's it. You will be able to see your keyboardView on the screen! Of course, you need to either create your own Keyboard class, or to use the existing one with an xml resource file defining your keyboard keys (res/xml/keyboard.xml).
I'm currently reinventing my approach as I think that I didn't break the InputMethodService enough to have it work without itself. In other words I threw away the sample and started from scratch to get the layout working (it's now one layout instead of two) and then added the code from the sample to handle the inputs properly.
After further research I found a really helpful question about an App-specific soft-keyboard. If you run into my situation, look there.