I'm working on a Tetris-app and I have a Thread.sleep command to animate the falling of the tetriminos. But that creates a conflict with the UI. So I tried runOnUiThread() like this:
while (gameover == false) {
tetriminoSettled = false;
try {
Thread.sleep(1000 - (counter * 3));
} catch (InterruptedException e) {
e.printStackTrace();
}
runOnUiThread(new Runnable() {
#Override
public void run() {
Random ran = new Random();
int x = ran.nextInt(7) + 1;
addTetrimino(createTetrimino(x)); //the UI operation
gridView.invalidate();
}
});
But the UI gets only updated after the game ends.
Do you think AsyncTask is a good approach?
Please try to keep in mind that I later need additional UI-Threads for shifting the tetriminos left and right and so on.
Thanks!
Judging from the code you posted, it looks like you are using a gridview for a game. It is possible but not worth the effort. Just use a SurfaceView as shown in this short tutorial. You'll have an onDraw callback in which you can update whatever you like every drawing cycle. Have fun, coding games is really hard :).
Try gridView.postInvalidate();
The title says it all. I want to make a view into an infinite loop of random visibility and invisibility. This is how I approached it :-
First I created two methods to get Random time getRandomWaitTime() and getRandomDisplayTime(). These methods are well defined and tested (in Log) and are working as desired. Both these methods return a random value as int in millisecond as
getRandomWaitTime() : 3000-6000
getRandomDisplayTime() : 3000-5000
Now I created one ImageView fruit[0], set it initially as invisible and after that the following code is executed :
fruit[0].postDelayed(new Runnable() {
#Override
public void run() {
fruit[0].setVisibility(View.VISIBLE);
fruit[0].postDelayed(new Runnable() {
#Override
public void run() {
fruit[0].setVisibility(View.INVISIBLE);
}
}, getRandomDisplayTime());
fruit[0].postDelayed(this, getRandomWaitTime());
}
}, getRandomWaitTime());
The code compiles, executes as well, the ImageView goes through infinite cycle of visibility and invisibility but the time for which it is set as visible or invisible doesn't seem to have minimum value of 3000ms Sometimes it feels like it is visible for a 500ms and then gone invisible. I have tried a ton of things such as using Handler.postDelayed instead of View.postDelayed but this doesn't seem to work.
Interesting thing happens when I remove all getRandomWaitTime() and getRandomDisplayTime() methods from postDelayed method and replace then with a constant like 3000.
The activity starts. At first fruit[0] is set to invisible. After 3000ms it comes visible and stays there. Nothing happens after it. No more switching to invisibility. I just stays there.
So what could be the possible reasons for all these sorcery problems?
The point is that, after the first
you call fruit[0].setVisibility(View.VISIBLE);:
fruit[0].postDelayed(new Runnable() {
#Override
public void run() {
fruit[0].setVisibility(View.INVISIBLE);
}
}, getRandomDisplayTime());
and:
fruit[0].postDelayed(this, getRandomWaitTime());
are executed almost at the same time (immediately).
So, let's say for example the getRandomDisplayTime() return 3000 and getRandomWaitTime() returns 3500, you will see the view visible after 3000 milliseconds and after 500 milliseconds more, it will disappear again.
You can change your code in this way:
fruit[0].postDelayed(new Runnable() {
#Override
public void run() {
final Runnable runnable = this;
fruit[0].setVisibility(View.VISIBLE);
fruit[0].postDelayed(new Runnable() {
#Override
public void run() {
fruit[0].setVisibility(View.INVISIBLE);
fruit[0].postDelayed(runnable, getRandomWaitTime());
}
}, getRandomDisplayTime());
}
}, getRandomWaitTime());
Your code is a bit messy, try this example. Based on the view itself, values will be chosen instead of nested postDelayed() methods.
fruit[0].postDelayed(new Runnable() {
#Override
public void run() {
fruit[0].setVisibility(backButton.getVisibility() == View.VISIBLE ? View.INVISIBLE : View.VISIBLE);
fruit[0].postDelayed(this, backButton.getVisibility() == View.VISIBLE ? getRandomDisplayTime() : getRandomWaitTime());
}
}, fruit[0].getVisibility() == View.VISIBLE ? getRandomDisplayTime() : getRandomWaitTime());
I've been building a game for some time, and just realized I did something very wrong the whole time, and still don't really know better.
I have a control class, my MainActivity, which pretty much only does the following:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
operator=new Operator(getBaseContext());
setContentView(R.layout.activity_main); //<- framelayout with the view and two buttons on top
}
//Buttons:
public void shoot(View view) {
operator.shoot(view.getId());
}
public void pause(View view) {
AndronXView.running=!AndronXView.running;
}
Then there is my View, which draws Actors and makes my workerthread compute everything:
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
animHandler = new Handler();
animHandler.postDelayed(loadLvl1, 5000);
arrayOfActors = operator.getActors(); //simplyfied to one array
if(arrayOfActors==null)
arrayOfActors=new Actor[0]; //if op hasn't loaded yet, prevent NPE in onDraw
//stripped of unimportant color and size computing stuff
}
protected void onDraw(final Canvas canvas){
//Should I copy player before doing this? Never got problems here so far.
canvas.drawBitmap(operator.player.getCurrentGraphic(), operator.player.getX(), operator.player.getY(), null);
for(Actor actor:arrayOfActors) {
canvas.drawBitmap(actor.getCurrentGraphic(), actor.getX(), actor.getY(), null);
}
if (running) {
operator.run(); //Compute. Realized my mistake here and changed the inside, wait for it.
animHandler.postDelayed(r, Operator.FRAME_RATE); //r=invalidate();
animHandler.post(loadActors); //get Arrays from op
}else animHandler.post(wait);
}
Runnable wait = new Runnable() {
#Override
public void run() {
if (running)
animHandler.post(r);
else animHandler.postDelayed(this, Operator.FRAME_RATE);
}
};
#Override
public boolean onTouchEvent(#NonNull MotionEvent event){
int action = event.getAction();
if(action==MotionEvent.ACTION_DOWN || action==MotionEvent.ACTION_MOVE){
operator.player.setSpeed((event.getX()-operator.player.getHorizontalMid())
/AndronX.PLAYER_MOVE_LAG,
(event.getY()-operator.player.getVerticalMid())/AndronX.PLAYER_MOVE_LAG);
}
return true;
}
And then there is my Operator, extending Thread, which computes movement and interactions of the actors in the background:
public Operator(Context baseContext) {
this.player = new Player(baseContext); //context to load drawable from ressource
arrayListOfActors=new ArrayList<>();
//Looper.prepare(); //it crashed and said only one Looper/Thread
//Looper.loop(); //so I removed it
opHandler = new Handler();
}
#Override
public void run() {
opHandler.post(gameStep); //before, I had the whole processing inside this run().
}
private Runnable gameStep = new Runnable(){
player.move();
computeEveryMemberOf(arrayListOfActors); //much computing happens here, usually
//contains 1-30 Actors that get cross-referenced
arrayOfActors = arrayListOfActors.toArray(new Actor[arrayListOfActors.size()]);
}
public Actor[] getActors(){
return arrayOfActors;
}
Before, I had the computign directly in my operators run() method, which I realized made the background thread useless. I#m not sure though if this is the right way, should I let the operator loop itself, would the two threads kinda stay in sync? Does it even matter?
Or should I go run(){ sleep(FRAME_RATE); compute();}?
Edit: A big problem arose, and I'm not sure if it's because of this, so I really need an answer here how to do this the right way.
With every step, I move some actors a little bit up or down, in a cosinus wave like speed (like it's projection to an axis), and currently, the actual movement doesn't get through to the view, they just jump from max to min and back, although they do it in the desired speed(looks like extreme lag).
Actually you can see for yourself what this problem is: https://dl.dropboxusercontent.com/u/28278772/AndronX.apk
long time watcher, first time writer :P
I got this problem:
I can't seem to change anything that has to do with the layout of android from my playSoundThread.
In this example, I use EventListeners. I already tried the simple way. I passed the ScrollView through, so that the thread can change it. But when it's happening, the thread stops immediately. And even when I use EventListeners, the same Problem occurs.
Changing a variable and posting log information works fine, but not layout Objects.
The first thing is, that I want to scroll a HorizontalScrollView from out the Thread's run() method.
the second case is, that, if the thread comes to it's end, I wanna fire an "i'm finished"-Event and change the image and function of an ImageButton
Here's the run()-method of the thread
public void run() {
if(this.playbackPosition < rhythm.tracks.get(0).sounds.size()) {
for (Track t : rhythm.tracks) {
if (t.sounds.get(this.playbackPosition).equals("1")) {
this.sp.play(t.SoundID, 1, 1, 1, 0, 1);
}
}
this.playbackPosition++;
if ( this.playbackPosition >= (this.scrollIndex*(192/this.zoom)) ){
this.scrollIndex++;
//Here I wanna fire the "Scroll" event
for(ScrollListener sl : scrollListeners){
sl.update(scrollPositions[scrollIndex]);
}
}
}
//This is the point where the playback is finished and the event to change a button is fired
else {
tmpListener.update();
}
}
}
The declaration of the OnPlaybackFinishedListener can be found in the class Player, which is the parent of the PlaySoundThread:
public void addOnPlaybackFinishedListener(){
tmpListener = new OnPlaybackFinishedListener() {
#Override
public void update() {
scheduledExecutorService.shutdown();
//this is a seconds Listener, which was implemented to test, if the problem still occurs with a little listener chain
shutdownListener.update();
}
};
}
public void addShutdownListener(OnExecutorShutdown sl){
this.shutdownListener = sl;
}
And here's the part of the MainActivity which is the parent class of Player and adds the shutdown listener and the ScrollListener:
awesomePlayer.addScrollListener(new ScrollListener(){
public void update(int position){
Log.i("ScrollListener update()","Running ScrollTo( "+position+", "+VIEW_rhythmscroll.getScrollY()+")");
VIEW_rhythmscroll.scrollTo(position, VIEW_rhythmscroll.getScrollY());
}
});
awesomePlayer.addOnPlaybackFinishedListener();
awesomePlayer.addShutdownListener(new OnExecutorShutdown() {
#Override
public void update() {
// TODO Auto-generated method stub
//This method changes the Pause Button to a Play Button with a new OnClickListener and a new Picture
BUTTON_STOP.performClick();
}
});
Can anyone help? Is there another way to avoid this problem? I'm developing on Android 2.2
Is it even possible to access UI elements from a thread?
Thanks in advance :)
You can't modify UI elements from a seperate thread, UI elements have to be modified from the main, UI Thread. There are a lot of topics on this, but you can update the UI by using an AsyncTask's onPostExecute(), onPreExecute(), or onProgressUpdate() methods, the Activity class's runOnUiThread(Runnable action), or by sending a Message to a Handler.
I would like a ScrollView to start all the way at the bottom. Any methods?
you should run the code inside the scroll.post like this:
scroll.post(new Runnable() {
#Override
public void run() {
scroll.fullScroll(View.FOCUS_DOWN);
}
});
scroll.fullScroll(View.FOCUS_DOWN) also should work.
Put this in a scroll.Post(Runnable run)
Kotlin Code
scrollView.post {
scrollView.fullScroll(View.FOCUS_DOWN)
}
scroll.fullScroll(View.FOCUS_DOWN) will lead to the change of focus. That will bring some strange behavior when there are more than one focusable views, e.g two EditText. There is another way for this question.
View lastChild = scrollLayout.getChildAt(scrollLayout.getChildCount() - 1);
int bottom = lastChild.getBottom() + scrollLayout.getPaddingBottom();
int sy = scrollLayout.getScrollY();
int sh = scrollLayout.getHeight();
int delta = bottom - (sy + sh);
scrollLayout.smoothScrollBy(0, delta);
This works well.
Kotlin Extension
fun ScrollView.scrollToBottom() {
val lastChild = getChildAt(childCount - 1)
val bottom = lastChild.bottom + paddingBottom
val delta = bottom - (scrollY+ height)
smoothScrollBy(0, delta)
}
Sometimes scrollView.post doesn't work
scrollView.post(new Runnable() {
#Override
public void run() {
scrollView.fullScroll(ScrollView.FOCUS_DOWN);
}
});
BUT if you use scrollView.postDelayed, it will definitely work
scrollView.postDelayed(new Runnable() {
#Override
public void run() {
scrollView.fullScroll(ScrollView.FOCUS_DOWN);
}
},1000);
What worked best for me is
scroll_view.post(new Runnable() {
#Override
public void run() {
// This method works but animates the scrolling
// which looks weird on first load
// scroll_view.fullScroll(View.FOCUS_DOWN);
// This method works even better because there are no animations.
scroll_view.scrollTo(0, scroll_view.getBottom());
}
});
I increment to work perfectly.
private void sendScroll(){
final Handler handler = new Handler();
new Thread(new Runnable() {
#Override
public void run() {
try {Thread.sleep(100);} catch (InterruptedException e) {}
handler.post(new Runnable() {
#Override
public void run() {
scrollView.fullScroll(View.FOCUS_DOWN);
}
});
}
}).start();
}
Note
This answer is a workaround for really old versions of android. Today the postDelayed has no more that bug and you should use it.
i tried that successful.
scrollView.postDelayed(new Runnable() {
#Override
public void run() {
scrollView.smoothScrollTo(0, scrollView.getHeight());
}
}, 1000);
Here is some other ways to scroll to bottom
fun ScrollView.scrollToBottom() {
// use this for scroll immediately
scrollTo(0, this.getChildAt(0).height)
// or this for smooth scroll
//smoothScrollBy(0, this.getChildAt(0).height)
// or this for **very** smooth scroll
//ObjectAnimator.ofInt(this, "scrollY", this.getChildAt(0).height).setDuration(2000).start()
}
Using
If you scrollview already laid out
my_scroll_view.scrollToBottom()
If your scrollview is not finish laid out (eg: you scroll to bottom in Activity onCreate method ...)
my_scroll_view.post {
my_scroll_view.scrollToBottom()
}
When the view is not loaded yet, you cannot scroll. You can do it 'later' with a post or sleep call as above, but this is not very elegant.
It is better to plan the scroll and do it on the next onLayout(). Example code here:
https://stackoverflow.com/a/10209457/1310343
One thing to consider is what NOT to set. Make certain your child controls, especially EditText controls, do not have the RequestFocus property set. This may be one of the last interpreted properties on the layout and it will override gravity settings on its parents (the layout or ScrollView).
Not exactly the answer to the question, but I needed to scroll down as soon as an EditText got the focus. However the accepted answer would make the ET also lose focus right away (to the ScrollView I assume).
My workaround was the following:
emailEt.setOnFocusChangeListener(new View.OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if(hasFocus){
Toast.makeText(getActivity(), "got the focus", Toast.LENGTH_LONG).show();
scrollView.postDelayed(new Runnable() {
#Override
public void run() {
scrollView.fullScroll(ScrollView.FOCUS_DOWN);
}
}, 200);
}else {
Toast.makeText(getActivity(), "lost the focus", Toast.LENGTH_LONG).show();
}
}
});
I actually found that calling fullScroll twice does the trick:
myScrollView.fullScroll(View.FOCUS_DOWN);
myScrollView.post(new Runnable() {
#Override
public void run() {
myScrollView.fullScroll(View.FOCUS_DOWN);
}
});
It may have something to do with the activation of the post() method right after performing the first (unsuccessful) scroll. I think this behavior occurs after any previous method call on myScrollView, so you can try replacing the first fullScroll() method by anything else that may be relevant to you.
Using there is another cool way to do this with Kotlin coroutines. The advantage of using a coroutine opposed to a Handler with a runnable (post/postDelayed) is that it does not fire up an expensive thread to execute a delayed action.
launch(UI){
delay(300)
scrollView.fullScroll(View.FOCUS_DOWN)
}
It is important to specify the coroutine's HandlerContext as UI otherwise the delayed action might not be called from the UI thread.
In those case were using just scroll.scrollTo(0, sc.getBottom()) don't work, use it using scroll.post
Example:
scroll.post(new Runnable() {
#Override
public void run() {
scroll.fullScroll(View.FOCUS_DOWN);
}
});
One possible reason of why scroll.fullScroll(View.FOCUS_DOWN) might not work even wrapped in .post() is that the view is not laid out. In this case View.doOnLayout() could be a better option:
scroll.doOnLayout(){
scroll.fullScroll(View.FOCUS_DOWN)
}
Or, something more elaborated for the brave souls: https://chris.banes.dev/2019/12/03/suspending-views/
A combination of all answers did the trick for me:
Extension Function PostDelayed
private fun ScrollView.postDelayed(
time: Long = 325, // ms
block: ScrollView.() -> Unit
) {
postDelayed({block()}, time)
}
Extension Function measureScrollHeight
fun ScrollView.measureScrollHeight(): Int {
val lastChild = getChildAt(childCount - 1)
val bottom = lastChild.bottom + paddingBottom
val delta = bottom - (scrollY+ height)
return delta
}
Extension Function ScrolltoBottom
fun ScrollView.scrollToBottom() {
postDelayed {
smoothScrollBy(0, measureScrollHeight())
}
}
Be aware that the minimum delay should be at least 325ms or the scrolling will be buggy (not scrolling to the entire bottom). The larger your delta between the current height and the bottom is, the larger should be the delayed time.
Some people here said that scrollView.post didn't work.
If you don't want to use scrollView.postDelayed, another option is to use a listener. Here is what I did in another use case :
ViewTreeObserver.OnPreDrawListener viewVisibilityChanged = new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
if (my_view.getVisibility() == View.VISIBLE) {
scroll_view.smoothScrollTo(0, scroll_view.getHeight());
}
return true;
}
};
You can add it to your view this way :
my_view.getViewTreeObserver().addOnPreDrawListener(viewVisibilityChanged);
If your minimum SDK is 29 or upper you could use this:
View childView = findViewById(R.id.your_view_id_in_the_scroll_view)
if(childView != null){
scrollview.post(() -> scrollview.scrollToDescendant(childView));
}
This works instantly. Without delay.
// wait for the scroll view to be laid out
scrollView.post(new Runnable() {
public void run() {
// then wait for the child of the scroll view (normally a LinearLayout) to be laid out
scrollView.getChildAt(0).post(new Runnable() {
public void run() {
// finally scroll without animation
scrollView.scrollTo(0, scrollView.getBottom());
}
}
}
}