Trying to make a TextView clock, app freezes on launch - android

I'm trying to simply pull the system time, and input it into the TextView, wait one second, then update it. I use a while loop for this, though when the app starts, it freezes, with no display. Am I doing something wrong? perhaps a better way to do this? I need the method to have the ability to pull the integers so I can match them to set ones, I hope to add in an alarm feature.
MainActivity.java:
package com.example.polyphaser;
import java.text.SimpleDateFormat;
import java.util.Date;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.widget.TextView;
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
timeUpdate();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
public void timeUpdate() {
boolean loop = true;
while (loop = true) {
String currentTimeString = new SimpleDateFormat("HH:mm:ss").format(new Date());
// textView is the TextView view that should display it
TextView sysTime = (TextView) findViewById(R.id.timeDisplay);
sysTime.setText(currentTimeString);
Thread.sleep(1000);
}
}
}
activity_main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="#+id/timeDisplay"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="50sp" />
</LinearLayout>
</LinearLayout>

This show how to use a handler:
public class MainActivity extends Activity {
private Handler handler = new Handler();
private Runnable runnable;
private TextView sysTime;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sysTime = (TextView) findViewById(R.id.timeDisplay);
runnable = new Runnable() {
#Override
public void run() {
timeUpdate();
handler.postDelayed(this, 1000);
}
};
}
#Override
protected void onResume() {
handler.postDelayed(runnable, 1000);
super.onResume();
}
#Override
protected void onPause() {
handler.removeCallbacks(runnable);
super.onPause();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
public void timeUpdate() {
String currentTimeString = new SimpleDateFormat("HH:mm:ss").format(new Date());
// textView is the TextView view that should display it
sysTime.setText(currentTimeString);
}
}

A much better way to do this is with a handler.
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
sysTime.setText(...
}
}, 1000);
I'm pretty sure the while loop locks up your main thread which is why it freezes.
Check out the handler at the dev pages.
http://developer.android.com/reference/android/os/Handler.html

You need move while loop into its own thread, so far you are sleep on UI thread, which means all UI drawing things are blocked. When you need update UI in second counting thread, call Activity.runInUIThread(new Runnable(...){...}) to post the update.

The big problem wrong, look at this line in your code:
while (loop = true)
I'm guessing the compiler optimizes out that statement (loop is already true, loop = true does not evaluate to a boolean, and furthermore, loop cannot be accessed by anything to change its value to false) and so you have a while (false) which results in the body of your loop never executing.
You should have that update occur in another read and update the textview using runonuithread.

Related

Firing an event as soon as activity is visible to the user

I wanted to create countdown as soon as the user sees an activity. However, there does not seem to be an appropriate callback for that. Neither onResume nor onWindowFocusChanged seem to be the correct callbacks, because the whole code is executed before the user even see anything. As a result I end up with "Go!" on the screen right from the start.
In a nutshell:
Do you have any idea how to implement a countdown without any user interaction as soon as the activity is visible to the user?
EDIT:
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import java.util.concurrent.TimeUnit;
import android.widget.TextView;
public class ChallengeModeTutorial extends AppCompatActivity {
private void delayOneSec()
{
try
{
TimeUnit.SECONDS.sleep(2);
}
catch (InterruptedException e)
{
assert true;
}
}
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_challenge_mode_tutorial);
}
#Override
public void onWindowFocusChanged(boolean hasFocus)
{
super.onWindowFocusChanged(hasFocus);
TextView readySteadyGo = (TextView) findViewById(R.id.challengeModeTutorialReadySteadyGoTextView);
// TextView tutorialText = (TextView) findViewById(R.id.challengeModeTutorialTextTextView);
TextView timeUntilStart = (TextView) findViewById(R.id.challengeModeTutorialReadyTimeTextView);
readySteadyGo.setText("");
timeUntilStart.setText("5");
delayOneSec();
timeUntilStart.setText("4");
delayOneSec();
timeUntilStart.setText("3");
delayOneSec();
readySteadyGo.setText("Ready!");
timeUntilStart.setText("2");
delayOneSec();
readySteadyGo.setText("Steady!");
timeUntilStart.setText("1");
delayOneSec();
readySteadyGo.setText("");
readySteadyGo.setText("Go!");
}
}
The problem is that you're blocking the UI thread. When you call setText(String) it doesn't immediately gets drawn. The TextView gets invalidated and it will be draw on the next draw phase. But if you block the thread, this will never happen. You have to use a postDelayed() to execute the next setText(String) a second later.
I'm not sure if this works, but you could try using a ViewTreeObserver in onCreate. It gets called once the layout is drawn. (Replace LinearLayout with whatever Layout your "activity_challenge_mode_tutorial" actually is.)
#Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_challenge_mode_tutorial);
final LinearLayout layout = (LinearLayout) findViewById(R.id.YOUR_VIEW_ID);
ViewTreeObserver vto = layout.getViewTreeObserver();
vto.addOnGlobalLayoutListener (new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// Remove listener to prevent repeat calls.
layout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
// Start countdown here.
}
});
}
Code is taken from this answer: https://stackoverflow.com/a/7735122/8118328
You can start your countdown, inside of your onStart method, which you need to overwrite.
The visible lifetime of an activity happens between a call to onStart() until a corresponding call to onStop(). During this time the user can see the activity on-screen, though it may not be in the foreground and interacting with the user.
From:
https://developer.android.com/reference/android/app/Activity
#Override
protected void onStart() {
super.onStart();
}
I finally got it to work like this:
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
public class ChallengeModeTutorial extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_challenge_mode_tutorial);
}
#Override
protected void onStart()
{
super.onStart();
final TextView readySteadyGo = (TextView) findViewById(R.id.challengeModeTutorialReadySteadyGoTextView);
final TextView timeUntilStart = (TextView) findViewById(R.id.challengeModeTutorialReadyTimeTextView);
readySteadyGo.setText("");
Thread t=new Thread(){
#Override
public void run(){
while(continueThread){
try {
Thread.sleep(1000);
runOnUiThread(new Runnable() {
#Override
public void run() {
if(currentCount > 0)
{
timeUntilStart.setText(String.valueOf(currentCount));
}
else
{
timeUntilStart.setText("Go!");
}
switch (currentCount)
{
case 2: readySteadyGo.setText("Ready!"); break;
case 1: readySteadyGo.setText("Steady!"); break;
default: readySteadyGo.setText(""); break;
}
currentCount--;
if (currentCount == 0)
{
continueThread = false;
}
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t.start();
}
private boolean continueThread = true;
private int currentCount = 5;
}

No text appears on TextView

This is my code, it should print a random value (between 0 and 1) but it doesn't do that!
I don't know how to fix it! I tried multiple things, none of them are working out!
Here's the code:
package com.example.lode.coder;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import android.widget.TextView;
import java.util.Random;
public class Coder extends AppCompatActivity {
TextView display;
String one;
boolean bl= true;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_coder);
display = (TextView) findViewById(R.id.text);
}
void all() {
while (bl) {
Random rand = new Random();
int n = rand.nextInt(2);
one = n + (String) display.getText();
display.setText(one);
}
}
}
You don't show where the all() method is called. If it's not called anywhere, that would explain why display never gets any text set.
But assuming that it is called somewhere, there's still a big problem: the all() method is an infinite loop. Until you return control to the framework, any changes you make to display won't show up on the screen. (In fact, your app will likely then be killed off by the framework when it notices that the app has become unresponsive.)
If you want to change the text continuously, look into using a Handler. You can create a Runnable that does the actual change and then reschedules itself to run again after a short time. Don't use a loop like you currently have in all().
Something like this would work:
public class Coder extends AppCompatActivity {
private static long UPDATE_INTERVAL = 500; // every half second -- adjust as needed
TextView display;
String one;
Handler handler;
Runnable updater;
Random rand = new Random();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_coder);
display = (TextView) findViewById(R.id.text);
Handler handler = new Handler();
updater = new Runnable() {
#Override public void run() {
int n = rand.nextInt(2);
one = n + display.getText().toString();
display.setText(one);
handler.postDelayed(updater, UPDATE_INTERVAL);
}
}
}
#Override protected void onStart() { // or override onResume() instead
super.onStart();
startUpdates();
}
#Override protected void onStop() { // or override onPause() instead
super.onStop();
stopUpdates();
}
void startUpdates() {
handler.post(updater);
}
void stopUpdates() {
handler.removeCallbacks(updater);
}
}
you haven't called all() function anywhere and even if you do, the logic would still be wrong because bl always stays true and so the while loop is an infinite loop which never stops. Try this code:
public class Coder extends AppCompatActivity {
TextView display;
String one;
boolean bl= true;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_coder);
display = (TextView) findViewById(R.id.text);
all();
}
void all() {
Random rand = new Random();
int n = rand.nextInt(2);
one = n + display.getText().toString();
display.setText(one);
}
}
if you want the loop you need to set your bl variable to false at some point to stop the loop from going on infinitely.

Android Studio Progress Bar Move

I put a progress bar in my android studio project , and want it to move , but i don't know how to do that . I tried to look for tutorials on YouTube and follow along , but that didn't work out for me. Then i looked on google for tutorials but that didn't work for me . When i search them up i only see tutorials with the circle loading bars and I want mine to be horizontal . I only know where to start the loading bar but i really want to know how to make it move and then go into the game . I don't have any code because when i seen that it wasn't working i just deleted it . If anybody has a good tutorial or code that could help me i would appreciate very much . Thanks.
have very easy way to use progress bar within whole code
custom_progressbar.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical">
<ProgressBar
android:id="#+id/progressbar_view"
android:layout_width="#dimen/_70sdp"
android:layout_height="#dimen/_70sdp"
android:indeterminate="true"
android:padding="#dimen/_10sdp" />
</RelativeLayout>
CustomProgressBar.java
public class CustomProgressBar extends Dialog {
Activity activity;
public CustomProgressBar(Activity act) {
super(act);
this.activity = act;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.custom_progressbar);
setCancelable(false);
// set backgroung transparent
getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT));
}
}
and apply whenever you want activity or fragment
public CustomProgressBar customProgressBar;
customProgressBar = new CustomProgressBar(context);
customProgressBar.show();
public class MainActivity extends AppCompatActivity {
private ProgressBar progressBar;
private TextView loading;
private int progressStatus = 0;
private Handler pHandler = new Handler();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressBar = (ProgressBar) findViewById(R.id.progressBar);
loading = (TextView) findViewById(R.id.loading);
new Thread(new Runnable() {
#Override
public void run() {
while (progressStatus < 100) {
progressStatus ++;
android.os.SystemClock.sleep(50);
pHandler.post(new Runnable() {
#Override
public void run() {
progressBar.setProgress(progressStatus);
}
});
}
pHandler.post(new Runnable() {
#Override
public void run() {
loading.setText("COMPLETE");
}
});
}
}).start();
}
This code works. You should be able to use the default XML code for the ProgressBar. You will need to add the elements, for example, the actual progress bar in the visual design. You will need to add an extra '}' at the end I think.
Check this: Infact you have to code for the horizontal progress bar movement and set its progress, it's specially used in downloading to show percentage of operation completion.
XML code:
<ProgressBar
android:id="#+id/progressBar1"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="23dp"
android:layout_marginTop="20dp"
android:indeterminate="false"
android:max="100"
android:minHeight="50dp"
android:minWidth="200dp"
android:progress="1" />
java code:
public class MainActivity extends Activity {
private ProgressBar progressBar;
private int progressStatus = 0;
private TextView textView;
private Handler handler = new Handler();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressBar = (ProgressBar) findViewById(R.id.progressBar1);
textView = (TextView) findViewById(R.id.textView1);
//Long operation by thread
new Thread(new Runnable() {
public void run() {
while (progressStatus < 100) {
progressStatus += 1;
//Update progress bar with completion of operation
handler.post(new Runnable() {
public void run() {
progressBar.setProgress(progressStatus);
textView.setText(progressStatus+"/"+progressBar.getMax());
}
});
try {
// Sleep for 200 milliseconds.
//Just to display the progress slowly
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items
//to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}
.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:weightSum="50">
<ProgressBar
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="false"
android:id="#+id/Bar1"
android:max="10"
android:padding="15dp"
android:paddingTop="5dp"
android:paddingRight="20dp"
android:paddingLeft="20dp">
</ProgressBar>
<Button
android:id="#+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="startProgress"
android:text="Start" />
<ProgressBar
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/progressBar2"
android:layout_gravity="center_horizontal" />
</LinearLayout>
MainActivity.java
package com.example.dell.vlakna;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.ProgressBar;
import static com.example.dell.vlakna.R.layout.activity_main;
public class MainActivity extends Activity {
private ProgressBar bar;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(activity_main);
bar = (ProgressBar) findViewById(R.id.Bar1);
}
public void startProgress(View view) {
bar.setProgress(0);
new Thread(new Task()).start();
}
class Task implements Runnable {
#Override
public void run() {
for (int i = 0; i <= 10; i++) {
final int value = i;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
bar.setProgress(value);
}
}
}
}
this code run good.

How to add a layout to current Main Activity layout using Thread in Android

This is my Main Activity:
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
And this is class that will excute a Thread
public class Ads implements Runnable {
private Activity activity;
public Ads(Activity activity) {
// TODO Auto-generated constructor stub
this.activity = activity;
new Thread(this).start();
}
#Override
public void run() {
FrameLayout layout = new FrameLayout(activity);
layout.setBackgroundColor(Color.BLACK);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(300, 75, Gravity.CENTER);
TextView txt = new TextView(activity);
txt.setText("ASD");
layout.addView(txt);
activity.getWindow().addContentView(layout, params);
}
}
I ran this code but my application could'nt start. How to add a layout to current activity? Plz help me, tks
If Class Ads's run() executes in a thread, that is not the main UI thread (the one where onCreate() executes), then, inside run() you cannot make changes to UI views.
Hence, execute the code which makes changes to UI using runOnUIThread():
runOnUIThread(new Runnable{
#Override
public void run(){
//make changes to UI here--
}
});
By the way, you can place that code in onCreate() only, it's not doing much work.
Also, this training article explains communication with UI Thread in detail.
You can't initialize your UI from a worker thread. You have to do this in the main thread. A thread will be only use to make some complex calcul. Everything is here :http://developer.android.com/guide/components/processes-and-threads.html
You can add a initUI function
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initUI();
}
private void initUI() {
FrameLayout layout = new FrameLayout(this);
layout.setBackgroundColor(Color.BLACK);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(300, 75, Gravity.CENTER);
TextView txt = new TextView(this);
txt.setText("ASD");
layout.addView(txt);
getWindow().addContentView(layout, params);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}

Continuously increase integer value as the button is pressed

I'm new to Android so sorry if the question is easy to answer. I have two buttons, a decrease and an increase button, and in the middle of them a TextView which displays a value.
When I hit the decrease button, the value in the TextView decreases and increases when I hit the increase button, no problem with that, I got that working but the problem is the value only increases/decreases by 1 on a single click. What I'm trying to achieve is that as I continuously press the button (the increase button for example), the value is also continuously increasing and only stops when I release the increase button.
Is that possible? If so, can you show some sample code or references on how to implement that? Thanks!
Here is my main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center" >
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="44dp"
android:gravity="center_horizontal" >
<Button android:id="#+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:text="<" />
<TextView android:id="#+id/textView1"
android:layout_width="50dp"
android:layout_height="fill_parent"
android:layout_alignBottom="#+id/button1"
android:layout_toRightOf="#+id/button1"
android:gravity="center"
android:text="45" />
<Button android:id="#+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_toRightOf="#+id/textView1"
android:text=">" />
</RelativeLayout>
</RelativeLayout>
and here is my Main.java
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class Main extends Activity {
private Button _decrease;
private Button _increase;
private TextView _value;
private static int _counter = 45;
private String _stringVal;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
_decrease = (Button) findViewById(R.id.button1);
_increase = (Button) findViewById(R.id.button2);
_value = (TextView) findViewById(R.id.textView1);
_decrease.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Log.d("src", "Decreasing value...");
_counter--;
_stringVal = Integer.toString(_counter);
_value.setText(_stringVal);
}
});
_increase.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Log.d("src", "Increasing value...");
_counter++;
_stringVal = Integer.toString(_counter);
_value.setText(_stringVal);
}
});
}
}
For that to work, you need a thread that will update the integer value when you long press on a button.
Create a handler in your activity:
private Handler repeatUpdateHandler = new Handler();
And 2 vars which will state: is it increment or decrement? Only one set at a time.
private boolean mAutoIncrement = false;
private boolean mAutoDecrement = false;
And the present number value
public int mValue;
And a class that will run in another thread:
class RptUpdater implements Runnable {
public void run() {
if( mAutoIncrement ){
increment();
repeatUpdateHandler.postDelayed( new RptUpdater(), REP_DELAY );
} else if( mAutoDecrement ){
decrement();
repeatUpdateHandler.postDelayed( new RptUpdater(), REP_DELAY );
}
}
}
Add a long press listener to your button:
mBTIncrement.setOnLongClickListener(
new View.OnLongClickListener(){
public boolean onLongClick(View arg0) {
mAutoIncrement = true;
repeatUpdateHandler.post( new RptUpdater() );
return false;
}
}
);
mBTIncrement.setOnTouchListener( new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
if( (event.getAction()==MotionEvent.ACTION_UP || event.getAction()==MotionEvent.ACTION_CANCEL)
&& mAutoIncrement ){
mAutoIncrement = false;
}
return false;
}
});
In the above case the button is the increment one. Create another button which will set mAutoDecrement to true.
And decrement() will be a function, which will set your instance int variable like this:
public void decrement(){
mValue--;
_value.setText( ""+mValue );
}
You figure the increment out. Oh and REP_DELAY is a static int variable set to 50.
I see this is an excerpt from Jeffrey Cole's open source NumberPicker available at http://www.technologichron.net/ Proper author's attribution must be added.
While the accepted answer is totally correct, it can be simplified a bit.
Basically, we can optimize two things:
We don't need the OnTouchListener.
We can instantiate the runnable object just once instead of creating multiple objects.
So this is my version:
// global variables
Handler handler = new Handler();
Runnable runnable;
increaseView.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
runnable = new Runnable() {
#Override
public void run() {
if (!increaseView.isPressed()) return;
increaseValue();
handler.postDelayed(runnable, DELAY);
}
};
handler.postDelayed(runnable, DELAY);
return true;
}
});
Here the runnable object is reused. And when the view is not pressed anymore, it will stop calling itself.
The decrease view or button can be defined in a similar way.
I am late to answer this question but it may help any one who's looking for a better answer.
I created a CounterHandler class and its insanely easy to use to achieve the above mentioned continuous counter functionality.
You can find the class in following gist with a "how to use" example.
https://gist.github.com/nomanr/d142f4ccaf55ceba22e7f7122b55b9b6
Sample code
new CounterHandler.Builder()
.incrementalView(buttonPlus)
.decrementalView(buttonMinus)
.minRange(-50) // cant go any less than -50
.maxRange(50) // cant go any further than 50
.isCycle(true) // 49,50,-50,-49 and so on
.counterDelay(200) // speed of counter
.counterStep(2) // steps e.g. 0,2,4,6...
.listener(this) // to listen counter results and show them in app
.build();
Thats all. :)
My way to increment value on long click is to use Timer used to check periodically if button is still pressed and than increase value, otherwise cancel timer. To update UI use Handler.
vh.bttAdd.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
final Timer timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
if(vh.bttAdd.isPressed()) {
final int track = ((ChannelAudioTrack) channels.get(vh.getAdapterPosition())).goToNextTrack();
updateUI(vh,track);
}
else
timer.cancel();
}
},100,200);
return true;
}
});
Handler:
private void updateUI(final TrackViewHolder vh, final int track)
{
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
vh.tvTrackNumber.setText(Integer.toString(track));
}
}) ;
}
Just wanna share my own solution that worked out for me really well.
First, create a handler in your activity
private Handler mHandler = new Handler();
Then, create the runnables that will increment/decrement and display your number. In here, we will check if your button is still in its pressed state and increment then re-run the runnable if it is.
private Runnable incrementRunnable = new Runnable() {
#Override
public void run() {
mHandler.removeCallbacks(incrementRunnable); // remove our old runnable, though I'm not really sure if this is necessary
if(IncrementButton.isPressed()) { // check if the button is still in its pressed state
// increment the counter
// display the updated value here, if necessary
mHandler.postDelayed(incrementRunnable, 100); // call for a delayed re-check of the button's state through our handler. The delay of 100ms can be changed as needed.
}
}
}
Finally, use it in our button's onLongClickListener
IncrementButton.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View view) {
mHandler.postDelayed(incrementRunnable, 0); // initial call for our handler.
return true;
}
});
That's it!
Another way of doing it is declaring both the handler and runnable inside the OnLongClickListener itself although I myself am not sure if this is a good practice.
IncrementButton.setOnLongClickListener(new View.OnLongClickListener() {
private Handler mHandler = Handler();
private Runnable incrementRunnable = new Runnable() {
#Override
public void run() {
mHandler.removeCallbacks(incrementRunnable);
if(IncrementButton.isPressed()) {
// increment the counter
// display the updated value here, if necessary
mHandler.postDelayed(incrementRunnable, 100);
}
}
};
#Override
public boolean onLongClick(View view) {
mHandler.postDelayed(incrementRunnable, 0);
return true;
}
});
When doing this continuous increment, I would suggest to increase the increment value after a certain time/number of increments.
E.g. If the number_of_increment made is less than 10, we increment by 1. Otherwise, we increment by 3.
It seems there is no perfect solution to this question and there will always be some complexity involved.
This is my attempt, which incorporates Wiktor's answer, but gives a whole MainActivity that you can cut/paste.
In my example, the complex part is the onLongClickListener, and how deep it goes and how many levels of anonymous classes there are.
However, on the other hand, the simplicity is that everything is included in one relatively short class (MainActivity), and there is only one major block of code -- the onLongClickListener -- which is defined only once and it's very clear where the "action" code is:
package com.example.boober.aalongclickoptimizationunit;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.util.Timer;
import java.util.TimerTask;
public class MainActivity extends AppCompatActivity {
TextView valueDisplay;
Button minusButton;
Button plusButton;
Button[] arrayOfControlButtons;
Integer currentDisplayValue = 500;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
valueDisplay = findViewById(R.id.value);
minusButton = findViewById(R.id.minusButton);
plusButton = findViewById(R.id.plusButton);
arrayOfControlButtons = new Button[]{plusButton, minusButton}; // this could be a large set of buttons
updateDisplay(); // initial setting of display
for (Button b : arrayOfControlButtons) {
b.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(final View v) {
final Timer timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
if (v.isPressed()) { // important: checking if button still pressed
runOnUiThread(new Runnable() {
#Override
public void run() {
// --------------------------------------------------
// this is code that runs each time the
// long-click timer "goes off."
switch (v.getId()) {
// which button was pressed?
case R.id.plusButton: {
currentDisplayValue = currentDisplayValue + 10;
break;
}
case R.id.minusButton: {
currentDisplayValue = currentDisplayValue - 10;
break;
}
}
updateDisplay();
// --------------------------------------------------
}
});
} else
timer.cancel();
}
}, 100, 200);
// if set to false, then long clicks will propagate into single-clicks
// also, and we don't want that.
return true;
}
});
}
}
// ON-CLICKS (referred to from XML)
public void minusButtonPressed(View ignored) {
currentDisplayValue--;
updateDisplay();
}
public void plusButtonPressed(View ignored) {
currentDisplayValue++;
updateDisplay();
}
// INTERNAL
private void updateDisplay() {
valueDisplay.setText(currentDisplayValue.toString());
}
}
for kotlin user
myButton.setOnLongClickListener {
val handler = Handler(Looper.myLooper()!!)
val runnable : Runnable = object : Runnable {
val number = 0
override fun run() {
handler.removeCallbacks(this)
if (myButton.isPressed) {
val newNumber= number + 1
textView.text = "$newNumber Items"
handler.postDelayed(this, 100)
}
}
}
handler.postDelayed(runnable,0)
true
}
Version for Kotlin and Coroutines users (replace GlobalScope with any other scope if you'd like):
var job: Job? = null
viewClickable.setOnClickListener {
// single click
}
viewClickable.setOnLongClickListener {
if (job == null || !job!!.isActive) {
job = GlobalScope.launch(Dispatchers.Main.immediate) {
while (it.isPressed) {
// long press
delay(100)
}
}
}
true
}
import java.awt.;
import java.awt.event.;
public class LabelNumber extends Frame implements ActionListener
{
Button badd,bsub;
TextField t1;
void display()
{
setTitle("Label number change");
setSize(400,500);
setLayout(new FlowLayout());
setVisible(true);
badd=new Button("+");
t1=new TextField("0",6);
bsub= new Button("-");
add(bsub);add(t1);add(badd);
badd.addActionListener(this);
bsub.addActionListener(this);
addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
}
);
}
public void actionPerformed(ActionEvent e)
{
int count=0,add=0,sub,n1,n2;
count=Integer.parseInt(t1.getText());
if(e.getSource()==badd)
{
if(count<10)
{
count=count+1;
t1.setText(""+count);
}
else
{
t1.setText("limit:10");
}
}
if(e.getSource()==bsub)
{
if(count<10)
{
count=count-1;
t1.setText(""+count);
}
else
{
t1.setText("limit:10");
}
}
}
public static void main(String args[])
{
LabelNumber obj =new LabelNumber();
obj.display();
}
}
initlize and call method
int speed = 0;
button = findViewById(R.id.button);
button.setOnTouchListener((v, event) -> {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
v.setPressed(true);
increment();
} else if (event.getAction() == MotionEvent.ACTION_UP) {
v.setPressed(false);
speed = 0;
}
return true;
});
This is increase method
public void increment() {
new Handler().postDelayed(() -> {
Toast.makeText(FrequencyActivity.this, String.valueOf(speed), Toast.LENGTH_SHORT).show();
if (button.isPressed()) {
speed += 1;
increment();
}
}, 200); //200 ms for fast incrementing
}
To break this task into its basic requirements, this is what we need:
Function to be executed
Condition for re-execution (as function that can be called each time to check condition)
Delay before re-execution
Here is a function that can be copied into Utils class, and be called for anything that matches these requirements:
/**
* Execute given function, and if condition is met, re-execute recursively after delay
* #param function: function to be executed
* #param conditionToRepeat: condition to re-execute function
* #param delayMillis: delay after which function should be re-executed (if condition was met)
*/
fun executeRecursively(function: () -> Unit, conditionToRepeat: () -> Boolean, delayMillis: Long) {
function()
if (conditionToRepeat())
Handler(Looper.getMainLooper()).postDelayed(
{ executeRecursively(function, conditionToRepeat, delayMillis) },
delayMillis)
}
Example of usage for requested use-case:
binding.button.setOnLongClickListener {
executeRecursively( { increase() }, // function
{ binding.button.isPressed }, // condition to re-execute
DELAY // delay in millis before re-execution
)
true
}
This is all what you need. But many cases, you may want to decrease delay when long clicking, so the number will be increased/decreased faster. Here is an extended function for that case:
/**
* Execute given function, and if condition is met, re-execute recursively after delay
* #param function: function to be executed
* #param conditionToRepeat: condition to re-execute function
* #param delayMillis: delay after which function should be re-executed (if condition was met)
* #param minDelayMillis: minimal delay in milliseconds
* #param decreasingDelayMillis: amount to decrease delay for next re-execution (if minimal delay has not been reached)
*/
fun executeRecursivelyWithDecreasingDelay(function: () -> Unit, conditionToRepeat: () -> Boolean, delayMillis: Long, minDelayMillis: Long, decreasingDelayMillis: Long) {
function()
if (conditionToRepeat()) {
val delay = if (delayMillis <= minDelayMillis) minDelayMillis else delayMillis - decreasingDelayMillis
Handler(Looper.getMainLooper()).postDelayed(
{ executeRecursivelyWithDecreasingDelay(function, conditionToRepeat, delay, minDelayMillis, decreasingDelayMillis) },
delayMillis
)
}
}
In this function, just add minimal delay (for example 2 millis), and the rate in which delay should be decreased each time (for example 2 millis). decreasingDelayMillis is like delay negative velocity.
Best and easy solution i created ,check it out it works for me
public void increment() {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
check_amount = check_amount + 100;// increment by 100
tv_balance.setText("" + check_amount); // show continues incrementing value
if (check_amount != 5000) {
increment();
}
}
}, 1); //1 ms for fast incrementing
}

Categories

Resources