Countdown on Button - Android - java

I want to implement a button that counts down when pressed. The idea is to allow the user to cancel the activation of the button. Once pressed, I would like for the button to change to red and read "CANCEL (3)" and countdown to "CANCEL (0)" and if it is pressed again, the countdown will stop and nothing will happen. If it is not canceled, the app will go to the next screen after time expires. Is there a way to update a button's text each second (for the countdown) and change its functionality?
Thanks

You can use postDelayed on a Handler to invoke your method later on the UI thread so that you can update the UI.
There's actually a very nice article about how this is done on Android already: http://developer.android.com/resources/articles/timed-ui-updates.html
You can get a Handler either by creating one in code running on the UI thread, or by calling getHandler() on a view (e.g. your button itself).
I'd provide an example, but the article linked already does so with great detail and clarity, so I'll defer to it.
Edit: here's a rough overview of how this will look. I don't have the Android SDK installed right now, so I can't verify that this works.
public class CountdownButton extends Button {
private boolean isCounting = false;
private int count;
private final Runnable countdownRunnable = new Runnable() {
#Override
public void run() {
setText(Integer.toString(count));
if (count > 0) {
count--;
getHandler().postDelayed(countdownRunnable, 1000);
} else {
// count reached zero
isCounting = false;
setText("boom");
}
}
}
private final View.OnClickListener onClickListener = new View.OnClickListener() {
#Override
public void onClick(View view) {
if (isCounting) {
// stop counting
isCounting = false;
getHandler().removeCallbacks(countdownRunnable);
setText("cancelled");
} else {
// start counting
isCounting = true;
count = 10;
countdownRunnable.run();
}
}
}
public CountdownButton(Context context) {
super(context);
setOnClickListener(onClickListener);
}
public CountdownButton(Context context, AttributeSet attrs) {
super(context, attrs);
setOnClickListener(onClickListener);
}
public CountdownButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setOnClickListener(onClickListener);
}
}
The timing may not be perfect (especially if there's a lot of other CPU work going on), but it should be good enough for your purposes.

Related

Media Player loading media again and again

I am making a chat application and I have implemented the feature for sending audio messages.But here I find one thing which I don't want it to happen.It is that whenever my adapter gets updated,The media player starts loading again. In this way there will be an issue for if someone is listening to an audio and the user at other end sends a message ,the media player stops and it loads again.Here is the code of my adapter.
final MediaPlayer mediaPlayer;
mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
handler = new Handler();
try {
mediaPlayer.setOnCompletionListener(mediaPlayer1 -> {
mediaPlayer1.stop();
binding.audioSeekbar.setProgress(0);
});
if (mediaPlayer.isPlaying()){
mediaPlayer.stop();
mediaPlayer.release();
}
mediaPlayer.setDataSource(finalUrlToLoad[1]);
mediaPlayer.setVolume(1f, 1f);
mediaPlayer.prepareAsync();
mediaPlayer.setOnPreparedListener(mediaPlayer1 -> {
int totalDuration = mediaPlayer1.getDuration();
binding.totalDurationAudio.setText(createTimeLabel(totalDuration));
binding.loadingAudio.setVisibility(GONE);
binding.playPauseAudio.setVisibility(VISIBLE);
});
} catch (IOException e) {e.printStackTrace();}
binding.playPauseAudio.setOnClickListener(view -> {
if (mediaPlayer.isPlaying()){
handler.removeCallbacks(runnable);
mediaPlayer.pause();
binding.playPauseAudio.setImageResource(R.drawable.pause_to_play);
Drawable drawable = binding.playPauseAudio.getDrawable();
if( drawable instanceof AnimatedVectorDrawable) {
AnimatedVectorDrawable animation = (AnimatedVectorDrawable) drawable;
animation.start();
}
}else {
mediaPlayer.seekTo(binding.audioSeekbar.getProgress());
mediaPlayer.start();
handler.post(runnable);
binding.playPauseAudio.setImageResource(R.drawable.play_to_pause);
Drawable drawable = binding.playPauseAudio.getDrawable();
if( drawable instanceof AnimatedVectorDrawable) {
AnimatedVectorDrawable animation = (AnimatedVectorDrawable) drawable;
animation.start();
}
}
});
runnable = () -> {
int totalTime = mediaPlayer.getDuration();
binding.audioSeekbar.setMax(totalTime);
int currentPosition = mediaPlayer.getCurrentPosition();
binding.audioSeekbar.setProgress(currentPosition);
binding.totalDurationAudio.setText(createTimeLabel(totalTime));
Log.d("time", String.valueOf(currentPosition));
handler.postDelayed(runnable,1000);
};
binding.audioSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
#Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
if (b){
mediaPlayer.seekTo(i);
seekBar.setProgress(i);
}
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
mediaPlayer.setOnBufferingUpdateListener((mediaPlayer1, i) -> binding.audioSeekbar.setSecondaryProgress(i));
Here finalurltoload[1] is the url for the audio.
Now what do I need to do in order to prevent loading it again and again.
I will be really grateful to who answer this question.
Thanks😊.
It's hard to tell from this code but I assume this is all set in your onBind event? If so, then this means every time RecyclerView creates a new holder and binds it, the associated media will be prepped and loaded, and whichever is the 'last holder to have been called with onBind, "wins" (and is what MediaPlayer will be loaded with). Since by default RecyclerView typically creates multiple holders up front, you are seeing your MediaPlayer being "loaded" multiple times.
You probably just don't want to do the initialization of each audio message in the onBind. Instead, just use the onBind event to initialize state variables (duration, progress, etc.) to some default value, hide them and bind the specific audio Uri. Then when the user takes some action like tapping on the holder, you unhide an indeterminate progress bar while the initialization takes place, and in the onPrepared() event unhide the state information (duration, progress, seekbar, etc.), and finally hide the indeterminate progress bar and start the audio.
I assume you are also sending over the sound file as part of your messaging app (i.e. not storing it on the web somewhere in a central location?), and this file gets stored in an app-specific storage location? If so, you don't need to worry about persisting the permission to that URI, but if that isn't the case you will.
First extract the media player code into singleton class like AudioManager.
Add few method like setMediaUpdateListener that set a callback for seek duration. and togglePlayPause to play or pause the audio.
Passed the message id or any unique identifier to the audio manager while playing the video.
In Adapter class onBind Method.
First Compare the id and playing Id is same like AudioManager.getInstance().isPlaying(messageId);
If yes then set the seekUpdatelistner to the audio manager class.
also update the play/pause icon based on AudioManager.isPlaying() method.
3.if user play other message by clicking play button. call AudioManager.play(message) method.In which we release the previous message and play the new one.
If current message is not playing then reset the view on non-playing state.
If Auto play is enabled then you need to check if audioManager is free then only you can play the last message otherwise ignored.
Like a class who are managing the audio for you and store all the state.
class AudioManager {
public static AudioManager instance;
final MediaPlayer mediaPlayer;
private AudioListener audioListener;
private Uri currentPlaying;
public AudioManager getInstance() {
if (instance == null) {
instance = new AudioManager();
}
}
public void play(Uri dataUri) {
if (mediaPlayer != null && currentPlaying == null || currentPlaying.equals(dataUri)) {
if (!mediaPlayer.isPlaying) {
mediaPlayer.play();
}
return;
} else if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.release();
}
mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
handler = new Handler();
try {
mediaPlayer.setOnCompletionListener(mediaPlayer1 -> {
mediaPlayer1.stop();
sendProgress(0);
});
if (mediaPlayer.isPlaying()) {
mediaPlayer.stop();
mediaPlayer.release();
}
mediaPlayer.setDataSource(dataUri;
mediaPlayer.setVolume(1f, 1f);
mediaPlayer.prepareAsync();
mediaPlayer.setOnPreparedListener(mediaPlayer1 -> {
int totalDuration = mediaPlayer1.getDuration();
sendTotalDuration(totalDuration);
});
} catch (IOException e) {
e.printStackTrace();
}
}
public void pause() {
// update the pause code.
}
public void sendProgress(int progress) {
if (audioListener != null) {
audioListener.onProgress(progress);
}
}
public void sendTotalDuration(int duration) {
if (audioListener != null) {
audioListener.onTotalDuraration(duration);
}
}
public void AudioListener(AudioListener audioListener) {
this.audioListener = audioListener;
}
public interface AudioListener {
void onProgress(int progress);
void onTotalDuraration(int duration);
void onAudioPlayed();
void onAudioPaused():
}
}

GestureOverlayView: changing gesture color while swiping

I have only a simple MyGestureOverlayView extends GestureOverlayView in my layout; I want that when the user swipes and the GestureOverlayView recognizes the gesture, it doesn't change color. I want that the color will change only when I call overlay.setGestureColor(int color), but in fact this method changes the color that will have the next gesture when recognized by the super class, not while the user is swiping.
So my question is: how can I change the color of the gesture while the user is performing the gesture?
public class MyGestureOverlayView extends GestureOverlayView implements
GestureOverlayView.OnGesturePerformedListener,
GestureOverlayView.OnGestureListener {
private static final String TAG = MyGestureOverlayView.class.getName();
private GiocoActivity mContext;
private boolean mRecognized;
private GestureLibrary gestures;
public MyGestureOverlayView(Context context, AttributeSet attrs) {
super(context, attrs);
if (context instanceof GiocoActivity)
mContext = (GiocoActivity) context;
gestures = GestureLibraries.fromRawResource(mContext, R.raw.gesture);
if (!gestures.load())
mContext.finish();
addOnGesturePerformedListener(this);
addOnGestureListener(this);
}
#Override
public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture) {
ArrayList<Prediction> predictions = gestures.recognize(gesture);
Log.d(TAG, "Gesture riconosciuta!");
// Sorry for my English :)
if (predictions.size() > 0 && predictions.get(0).score > 3.0) {
overlay.setGestureColor(Color.GREEN);
// The color doesn't change here, but it will change in the next gesture
String action = predictions.get(0).name;
mRecognized = true;
Toast.makeText(mContext, action, Toast.LENGTH_SHORT).show();
}
else {
overlay.setGestureColor(Color.RED);
// The color doesn't change here, but it will change in the next gesture
mRecognized = false;
}
Log.d(TAG, "Fine riconoscimento Gesture; esito: "+ mRecognized);
}
#Override
public void onGestureStarted(GestureOverlayView overlay, MotionEvent event) {
Log.d(TAG, "Gesture iniziata!");
}
#Override
public void onGesture(GestureOverlayView overlay, MotionEvent event) {
Log.d(TAG, "Gesture in corso!");
if (mRecognized) // Always false for the first gesture
overlay.setGestureColor(Color.GREEN); // Same as before
else
overlay.setGestureColor(Color.RED); // Same as before
}
#Override
public void onGestureEnded(GestureOverlayView overlay, MotionEvent event) {
Log.d(TAG, "Gesture finita!");
}
#Override
public void onGestureCancelled(GestureOverlayView overlay, MotionEvent event) {
Log.d(TAG, "Gesture cancellata!");
}
}
Besides, the callback method onGesturePerformed is called after onGestureEnded, so the boolean mRecognized in onGesture() is always false whem I perform a gesture for the first time and when it'll be recognized from the super class it'll be displayed red in any case (both recognized or not by my onGesturePerformedListener).
Sorry if I was complicate, this is my first question, and I found only one question similar to this, but it has no answers.
Thanks for your time.

Wait for completion of a calculation without UI freeze

I am trying to implement a search function in an Android app that takes text from an AutoCompleteTextView, waits if there hasn't been made a change in the last 1.5 seconds and shows the search results. For this I use the TextWatcher class.
However, all my tries to implement this behavior ran into trouble with some functions only being allowed in the UI thread itself (via runOnUIThread) or the thread having Looper.prepare() called before.
In all attempts, the app crashes randomly when entering additional characters or deleting some, does not show any search results or reload to the start activity.
The following is a simplyfied recreation of my most recent try, where I use a Handler.
search.getResults is the long computation and matches is an array that has to be filled before delayableAdapterCreation creates the ArrayAdapterWithSpaceFilter.
public class SearchFragment extends Fragment {
public final static int MAX_NUMBER_OF_SUGGESTIONS = 4; // only show a max of 4 suggestions if more were found
public final static int SEARCH_CHAR_AMOUNT = 3; // only search if at least 3 characters were typed
public final static long SEARCH_DELAY_MILLIS = (long) 1500; // the time to wait for no text changes in milliseconds
private Search search;
private AutoCompleteTextView textView;
private String[] matches;
private String userStartRequest;
private Entry[] suggestions;
private FragmentListenter sListener;
private EntryFunctions ef = new EntryFunctions();
private Runnable delayableSearch;
private Runnable delayableAdapterCreation;
private Handler delayableSearchHandler;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
delayableSearchHandler = new Handler();
delayableSearch = new Runnable() {
#Override
public void run() {
userStartRequest = textView.getText().toString();
sListener.onFragmentFinish(userStartRequest);
suggestions = search.getResults(userStartRequest);
matches = ef.fillMatches(suggestions);
}
};
delayableAdapterCreation = new Runnable() {
#Override
public void run() {
ArrayAdapterWithSpaceFilter<String> adapter =
new ArrayAdapterWithSpaceFilter<String>(getActivity(),
android.R.layout.simple_list_item_1,
matches);
textView.setAdapter(adapter);
}
};
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_search, container, false);
}
#Override
public void onStart() {
super.onStart();
textViewHandler();
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (!(context instanceof FragmentListenter)) throw new AssertionError();
sListener = (FragmentListenter) context;
}
/**
* Interface for communicate to activity
*/
public interface FragmentListenter {
void onFragmentFinish(String userStartRequest);
}
/**
* Handler for the AutoCompleteTextView
*/
private void textViewHandler() {
try {
textView = (AutoCompleteTextView) getView().findViewById
(R.id.startNaviAutoCompleteTextView);
search = new Search();
System.out.println("Created Search object");
textView.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
System.out.println("TextWatcher beforeTextChanged");
}
#Override
public void onTextChanged(CharSequence s, final int start, int before, int count) {
delayableSearchHandler.removeCallbacks(delayableSearch); userStartRequest = textView.getText().toString();
sListener.onFragmentFinish(userStartRequest);
if (textView.getText().length() >=
SEARCH_CHAR_AMOUNT) {
new Thread(delayableSearch).start();
delayableSearchHandler.postDelayed
(delayableAdapterCreation, SEARCH_DELAY_MILLIS);
}
}
#Override
public void afterTextChanged(Editable s) {
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
At this point, it does not matter to me, whether the calculation already starts whenever a new character is typed into the AutoCompleteTextView and an eventual old search is canceled or the search starts after the 1.5 seconds.
The above code does crash if the search term yields no results and there are problems with the results list. Sometimes it shows up for what has been entered a few keystrokes ago (so if I search for abcd slowly I get search results for abc), sometimes it doesn't show up at all. My guess would be a race condition or some problem with calling the textViewHandler or onTextChanged methods multiple times, even though delayableSearchHandler.removeCallbacks(delayableSearch) should prevent this from happening.
Can anyone explain, what the interaction between the worker thread and the UI thread would have to look like, so it is guaranteed that the search delivers it's results?
Thanks in advance,
Joe
Any long running operation (Network call, database search...) can take long time to execute thus blocking the UI. Prior to Ice cream sandwich this kind of behavior was tolerated by the android runtime.
This article might be a good read

Android adding onClick on soft keyboard on activity

I am trying to add an onClick on the soft keyboard for an activity. The reason why is that i want to check if the user is currently active. So what i have done is that if the user clicks on the app i will reset a inactivity timer. The problem is that when a user interacts with the soft keyboard it doesn't call the function onUserInteraction() which is a function I override in the activity. So i need help to find a way to keep track if the soft keyboard has been clicked for every textfield etc I have in the activity. (I know that i can insert a onclick listerner on every EditText field but i rather not do that, because if I would use many EditText fields it would not be so nice)
So this is what i ended up with. I was hoping for something else, but this solves the problem. Thanks for the help!
public class ActivityEditText extends android.support.v7.widget.AppCompatEditText {
private TextWatcher tw;
public ActivityEditText(Context c)
{
super(c);
this.setOurTCL();
}
public ActivityEditText(Context context, AttributeSet attrs) {
super(context, attrs);
this.setOurTCL();
}
public ActivityEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.setOurTCL();
}
private void setOurTCL()
{
this.tw = new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
InactivityManager.resetTime();
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
}
};
this.addTextChangedListener(this.tw);
}
#Override
public void removeTextChangedListener(TextWatcher watcher) {
if(!watcher.equals(this.tw))
super.removeTextChangedListener(watcher);
}
}
To handle an individual key press, implement onKeyDown() or onKeyUp() as appropriate. Usually, you should use onKeyUp() if you want to be sure that you receive only one event. If the user presses and holds the button, then onKeyDown() is called multiple times.
Create a class some thing like UserInteractionEditText and extend EditText and set the onclick lisetener in that class use that class in all the XML layouts as you use EditText you can do some thing like this:
public class UserInteractionEditText extends EditText implements View.OnClickListener {
public UserInteractionEditText(Context context) {
super(context);
}
public UserInteractionEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public UserInteractionEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
public void onClick(View v) {
//TODO:: Handle user Click Events
}
}
You can use onUserInteraction() function of activity. You need to override this function in your activity.
This function get calls when you perform any kind of interaction with your activity.
#Override
public void onUserInteraction() {
super.onUserInteraction();
// Your code goes here
}
You can refer this example and also this answer, refer the docs here
Hope this helps.

CordovaWebView messes up with onBackPressed method in android

As title says CordovaWebView and onBackPressed in android in combination are giving weird results.
I have hybrid app. My main activity has DrawerLayout and CordovaWebView.
My onBackPressed:
#Override
public void onBackPressed(){
if(drawerIsOpen){
//close drawer
}else if(webviewIsIn){
//hide webview
}else{
super.onBackPressed();
}
}
When I use android's WebView the overridden method is called as expected. And when I change to CordovaWebView the method wouldn't even get called, instead native onBackPressed would be called instead.
I have tried overriding onKeyDown and onKeyUp but it gives me the same result, the methods are just not being called.
I'm using Cordova 2.9.0 and testing device is Galaxy Note 2, Android jellybean 4.2.2
DrawerLayout has the close on back pressed functionality I've just disabled it.
I hope you guys can understand the problem.
I encountered the same issue. My solution was to derive from CordovaWebView and override public boolean onKeyUp(int keyCode, KeyEvent event) with something like this (for Cordova 3.4.0, the code is a part of the CordovaWebView.onKeyUp(int, KeyEvent)):
public class CustomCordovaWebView extends CordovaWebView {
protected View mCustomView;
protected boolean bound;
public CustomCordovaWebView(final Context context) {
super(context);
}
public CustomCordovaWebView(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
public CustomCordovaWebView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
}
#TargetApi(11)
public CustomCordovaWebView(final Context context, final AttributeSet attrs, final int defStyle, final boolean privateBrowsing) {
super(context, attrs, defStyle, privateBrowsing);
}
#Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
// If back key
if (keyCode == KeyEvent.KEYCODE_BACK) {
// A custom view is currently displayed (e.g. playing a video)
if (mCustomView!=null){
this.hideCustomView();
}else{
// The webview is currently displayed
// If back key is bound, then send event to JavaScript
if (this.bound) {
this.loadUrl("javascript:cordova.fireDocumentEvent('backbutton');");
return true;
} else {
// If not bound
// Go to previous page in webview if it is possible to go back
if (this.backHistory()) {
return true;
}
// If not, then invoke default behavior
else {
//this.activityState = ACTIVITY_EXITING;
//return false;
// If they hit back button when app is initializing, app should exit instead of hang until initialization (CB2-458)
// this.cordova.getActivity().finish();
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this thing is closing your activity in CordovaWebView
}
}
}
} else {
return super.onKeyUp(keyCode, event);
}
return false;
}
#Override
public void hideCustomView() {
mCustomView = null;
super.hideCustomView();
}
#Override
public void showCustomView(final View view, final WebChromeClient.CustomViewCallback callback) {
mCustomView = view;
super.showCustomView(view, callback);
}
#Override
public void bindButton(final boolean override) {
bound = override;
super.bindButton(override);
}
}
If there is a better solution, I would be interested in it.

Categories