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());
In order to detect finish of app bar collapsing I called addOnOffsetChangedListener. In listener's onOffsetChanged I catch and handle the moment of collapsing done. Then I need to stop listen for offset changes.
In most examples here is call of removeOnOffsetChangedListener(this) from inside of onOffsetChanged. But looking in AppBarLayout.java I see:
private void dispatchOffsetUpdates(int offset) {
// Iterate backwards through the list so that most recently added listeners
// get the first chance to decide
if (mListeners != null) {
for (int i = 0, z = mListeners.size(); i < z; i++) {
final OnOffsetChangedListener listener = mListeners.get(i);
if (listener != null) {
listener.onOffsetChanged(this, offset);
}
}
}
}
So if there is more than one listener installed, calling removeOnOffsetChangedListener(this) naturally results in IndexOutOfBoundsException.
Have I missed something? Is there safe way to 'unsubscribe' from listening for offset updates?
Interestingly, this wouldn't be a problem if their code actually did what the comment says. Anyway, we can defer the removal by putting the call to removeOnOffsetChangedListener() in a Runnable, and posting it to the AppBarLayout in onOffsetChanged(), instead of calling it directly there.
For example:
AppBarLayout.OnOffsetChangedListener listener = new AppBarLayout.OnOffsetChangedListener() {
#Override
public void onOffsetChanged(final AppBarLayout abl, int offset) {
abl.post(new Runnable() {
#Override
public void run() {
abl.removeOnOffsetChangedListener(listener);
}
}
);
}
};
I have a RecyclerView that is inside a CardView. The CardView has a height of 500dp, but I want to shorten this height if the RecyclerView is smaller.
So I wonder if there is any listener that is called when the RecyclerView has finished laying down its items for the first time, making it possible to set the RecyclerView's height to the CardView's height (if smaller than 500dp).
I also needed to execute code after my recycler view finished inflating all elements. I tried checking in onBindViewHolder in my Adapter, if the position was the last, and then notified the observer. But at that point, the recycler view still was not fully populated.
As RecyclerView implements ViewGroup, this anwser was very helpful. You simply need to add an OnGlobalLayoutListener to the recyclerView:
View recyclerView = findViewById(R.id.myView);
recyclerView
.getViewTreeObserver()
.addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// At this point the layout is complete and the
// dimensions of recyclerView and any child views
// are known.
recyclerView
.getViewTreeObserver()
.removeOnGlobalLayoutListener(this);
}
});
Working modification of #andrino anwser.
As #Juancho pointed in comment above. This method is called several times. In this case we want it to be triggered only once.
Create custom listener with instance e.g
private RecyclerViewReadyCallback recyclerViewReadyCallback;
public interface RecyclerViewReadyCallback {
void onLayoutReady();
}
Then set OnGlobalLayoutListener on your RecyclerView
recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (recyclerViewReadyCallback != null) {
recyclerViewReadyCallback.onLayoutReady();
}
recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
after that you only need implement custom listener with your code
recyclerViewReadyCallback = new RecyclerViewReadyCallback() {
#Override
public void onLayoutReady() {
//
//here comes your code that will be executed after all items are laid down
//
}
};
If you use Kotlin, then there is a more compact solution.
Sample from here.
This layout listener is usually used to do something after a View is measured, so you typically would need to wait until width and height are greater than 0.
... it can be used by any object that extends View and also be able to access to all its specific functions and properties from the listener.
// define 'afterMeasured' layout listener:
inline fun <T: View> T.afterMeasured(crossinline f: T.() -> Unit) {
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
if (measuredWidth > 0 && measuredHeight > 0) {
viewTreeObserver.removeOnGlobalLayoutListener(this)
f()
}
}
})
}
// using 'afterMeasured' handler:
myRecycler.afterMeasured {
// do the scroll (you can use the RecyclerView functions and properties directly)
// ...
}
The best way that I found to know when has finished laying down the items was using the LinearLayoutManager.
For example:
private RecyclerView recyclerView;
...
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false){
#Override
public void onLayoutCompleted(RecyclerView.State state) {
super.onLayoutCompleted(state);
// TODO
}
);
...
I improved the answer of android developer to fix this problem. It's a Kotlin code but should be simple to understand even if you know only Java.
I wrote a subclass of LinearLayoutManager which lets you listen to the onLayoutCompleted() event:
/**
* This class calls [mCallback] (instance of [OnLayoutCompleteCallback]) when all layout
* calculations are complete, e.g. following a call to
* [RecyclerView.Adapter.notifyDataSetChanged()] (or related methods).
*
* In a paginated listing, we will decide if load more needs to be called in the said callback.
*/
class NotifyingLinearLayoutManager(context: Context) : LinearLayoutManager(context, VERTICAL, false) {
var mCallback: OnLayoutCompleteCallback? = null
override fun onLayoutCompleted(state: RecyclerView.State?) {
super.onLayoutCompleted(state)
mCallback?.onLayoutComplete()
}
fun isLastItemCompletelyVisible() = findLastCompletelyVisibleItemPosition() == itemCount - 1
interface OnLayoutCompleteCallback {
fun onLayoutComplete()
}
}
Now I set the mCallback like below:
mLayoutManager.mCallback = object : NotifyingLinearLayoutManager.OnLayoutCompleteCallback {
override fun onLayoutComplete() {
// here we know that the view has been updated.
// now you can execute your code here
}
}
Note: what is different from the linked answer is that I use onLayoutComplete() which is only invoked once, as the docs say:
void onLayoutCompleted (RecyclerView.State state)
Called after a full layout calculation is finished. The layout
calculation may include multiple onLayoutChildren(Recycler, State)
calls due to animations or layout measurement but it will include only
one onLayoutCompleted(State) call. This method will be called at the
end of layout(int, int, int, int) call.
This is a good place for the LayoutManager to do some cleanup like
pending scroll position, saved state etc.
I tried this and it worked for me. Here is the Kotlin extension
fun RecyclerView.runWhenReady(action: () -> Unit) {
val globalLayoutListener = object: ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
action()
viewTreeObserver.removeOnGlobalLayoutListener(this)
}
}
viewTreeObserver.addOnGlobalLayoutListener(globalLayoutListener)
}
then call it
myRecyclerView.runWhenReady {
// Your action
}
Also in same cases you can use RecyclerView.post() method to run your code after list/grid items are popped up. In my cases it was pretty enough.
I have been struggling with trying to remove OnGlobalLayoutListener once it gets triggered but that throws an IllegalStateException. Since what I need is to scroll my recyclerView to the second element what I did was to check if it already have children and if it is the first time this is true, only then I do the scroll:
public class MyActivity extends BaseActivity implements BalanceView {
...
private boolean firstTime = true;
...
#Override
protected void onCreate(Bundle savedInstanceState) {
...
ViewTreeObserver vto = myRecyclerView.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (myRecyclerView.getChildCount() > 0 && MyActivity.this.firstTime){
MyActivity.this.firstTime = false;
scrollToSecondPosition();
}
}
});
}
...
private void scrollToSecondPosition() {
// do the scroll
}
}
HTH someone!
(Of course, this was inspired on #andrino and #Phatee answers)
Here is an alternative way:
You can load your recycler view in a thread. Like this
First, create a TimerTask
void threadAliveChecker(final Thread thread){
Timer timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
if(!thread.isAlive()){
runOnUiThread(new Runnable() {
#Override
public void run() {
// stop your progressbar here
}
});
}
}
},500,500);
}
Second, create a runnable
Runnable myRunnable = new Runnable() {
#Override
public void run() {
runOnUiThread(new Runnable() {
#Override
public void run() {
// load recycler view from here
// you can also toast here
}
});
}
};
Third, create a thread
Thread myThread = new Thread(myRunnable);
threadAliveChecker();
// start showing progress bar according to your need (call a method)
myThread.start();
Understanding the above code now:
TimerTask - It will run and will check the thread
(every 500 milliseconds) is running or completed.
Runnable - runnable is just like a method, here you have written the code that is needed to be done in that thread. So our recycler view will be called from this runnable.
Thread - Runnable will be called using this thread. So we have started this thread and when the recyclerView load (runnable code load) then this thread will be completed (will not live in programming words).
So our timer is checking the thread is alive or not and when the thread.isAlive is false then we will remove the progress Bar.
If you are using the android-ktx library and if you need to perform an action after positioning all elements of the Activity, you can use this method:
// define 'afterMeasured' Activity listener:
fun Activity.afterMeasured(f: () -> Unit) {
window.decorView.findViewById<View>(android.R.id.content).doOnNextLayout {
f()
}
}
// in Activity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(...)
afterMeasured {
// do something here
}
}
This is how I did it
recyclerView.setLayoutManager(new LinearLayoutManager(this){
#Override
public void onLayoutCompleted(RecyclerView.State state) {
super.onLayoutCompleted(state);
//code to run after loading recyclerview
new GuideView.Builder(MainActivity.this)
.setTargetView(targetView)
.setGravity(Gravity.auto)
.setDismissType(DismissType.outside)
.setContentTextSize(18)
.build()
.show();
}
});
I wish this will help you.
You can use with this approach
if ((adapterPosition + 1) == mHistoryResponse.size) {
Log.d("debug", "process done")
}
get the adapterPosition with plus 1 and check it with your data classes size, if it has same size, the process is practically complete.
For those that are not using Kotlin and are still struggling, I took a fast look at the doOnNextLayout(crossinline action: (view: T) -> Unit) solution they implemented, and it is pretty simple.
IF you are NOT working with a custom RecyclerView (CustomRecyclerView extends RecyclerView), you may want to rethink it as this will bring a lot of benefits you may want to add in the future (smooth scroll to position, vertical dividers, etc..)
Inside the CustomRecyclerView.class
public void doOnNextLayout(Consumer<List<View>> onChange) {
addOnLayoutChangeListener(
new OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
onChange.accept(getChildren());
removeOnLayoutChangeListener(this);
}
}
);
}
The getChildren() method is building a List of size getChildCount(); and a add(getChild(i)) on each iteration.
Now...
One important aspect about the code is this: removeOnLayoutChangeListener(this);
This means that the devs are asking for you to execute this before each list submission to the adapter.
In theory we could only place the listener ONCE upon RecyclerView creation (which IMO would be cheaper/better) + because we are retrieving the views, we could retrieve their respective binds with DataBindingUtils. and get whatever data the adapter gave the view onBind via their DataBind.
To do this tho it requires more code.
First the adapter needs to be aware of the Fragment they inhabit, OR the RecyclerView::setAdapter needs to provide a ViewLifeCyclerOwner, a third easier option is to provide the adapter with a onViewDestroy() method, and execute it on Fragment's onDestroyView() method.
#Override
public void onDestroyView() {
super.onDestroyView();
adater.onViewDestroyed();
}
by overriding the onAttachedToRecyclerView, we are able to attach them as observers.
private final List<Runnable> submitter = new ArrayList<>();
#Override
public void onAttachedToRecyclerView(#NonNull RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
if (recyclerView instanceof CustomRecyclerView) {
submitter.add(((CustomRecyclerView) recyclerView)::onSubmit);
}
}
Where the onSubmit method on the CustomRecyclerView side will provide a boolean that will tell the recyclerView whether a list is being submitted.
private boolean submitting;
public void doOnNextLayout(Consumer<List<View>> onChange) {
addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
if (submitting) {
onChange.accept(getChildren());
submitting = false;
}
}
);
}
public void onSubmit() {
submitting = true;
}
Each Runnable will be executed at the moment of list submission:
In the case of the ListAdapter there are 2 possible entry points:
private void notifyRVs() {
for (Runnable r:submitter
) {
r.run();
}
}
#Override
public void submitList(#Nullable List<X> list, #Nullable Runnable commitCallback) {
notifyRVs();
super.submitList(list, commitCallback);
}
#Override
public void submitList(#Nullable List<X> list) {
notifyRVs();
super.submitList(list);
}
Now to prevent memory leaks we must clear the List of Runnables on ViewDestroyed()
inside the Adapter...
public void onViewDestroyed() {
submitter.clear();
}
Now because the functionality of the method changed we should rename it, and decouple the Consumer<List> from the LayoutChangeListener()
private Consumer<List<View>> onChange = views -> {};
public void setOnListSubmitted(Consumer<List<View>> onChange) {
this.onChange = onChange;
}
public CustomRecyclerView(#NonNull Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
//Read attributes
setOnListSubmissionListener();
}
private void setOnListSubmissionListener() {
addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
if (submitting) {
onChange.accept(getChildren());
submitting = false;
}
}
);
}
What worked for me was to add the listener after setting the RecyclerView adapter.
ServerRequest serverRequest = new ServerRequest(this);
serverRequest.fetchAnswersInBackground(question_id, new AnswersDataCallBack()
{
#Override
public void done(ArrayList<AnswerObject> returnedListOfAnswers)
{
mAdapter = new ForumAnswerRecyclerViewAdapter(returnedListOfAnswers, ForumAnswerActivity.this);
recyclerView.setAdapter(mAdapter);
recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener()
{
#Override
public void onGlobalLayout()
{
progressDialog.dismiss();
}
});
}
});
This dismisses the "progressDialog" after the global layout state or the visibility of views within the view tree changes.
// Another way
// Get the values
Maybe<List<itemClass>> getItemClass(){
return /* */
}
// Create a listener
void getAll(DisposableMaybeObserver<List<itemClass>> dmo) {
getItemClass().subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(dmo);
}
// In the code where you want to track the end of loading in recyclerView:
DisposableMaybeObserver<List<itemClass>> mSubscriber = new DisposableMaybeObserver<List<itemClass>>() {
#Override
public void onSuccess(List<itemClass> item_list) {
adapter.setWords(item_list);
adapter.notifyDataSetChanged();
Log.d("RECYCLER", "DONE");
}
#Override
public void onError(Throwable e) {
Log.d("RECYCLER", "ERROR " + e.getMessage());
}
#Override
public void onComplete() {
Log.d("RECYCLER", "COMPLETE");
}
};
void getAll(mSubscriber);
//and
#Override
public void onDestroy() {
super.onDestroy();
mSubscriber.dispose();
Log.d("RECYCLER","onDestroy");
}
recyclerView.getChildAt(recyclerView.getChildCount() - 1).postDelayed(new Runnable() {
#Override
public void run() {
//do something
}
}, 300);
RecyclerView only lays down specific number of items at a time, we can get the number by calling getChildCount(). Next, we need to get the last item by calling getChildAt (int index). The index is getChildCount() - 1.
I'm inspired by this person answer and I can't find his post again. He said it's important to use postDelayed() instead of regular post() if you want to do something to the last item. I think it's to avoid NullPointerException. 300 is delayed time in ms. You can change it to 50 like that person did.
I have an app where I need to have a delay after each touch in an ImageButton.
I tried the Thread.sleep() method, but I am not sure if this is the best way to deal with it.
What do you guys recommend?
Any help is appreciatted!
ONE MORE THING: I want the content of the onTouch() event to be fired THEN I want to delay "X" seconds the next onTouch() event. It's like to prevent the user to click too many times in the button.
Since all touch events are handled by UI thread, Thread.sleep() will block your UI thread which is (I hope) not what you are looking for.
I think the most correct way to solve your problem would be using postDelayed(Runnable, long) interface in your onClick handler which allows your to delay execution:
#Override
public void onClick(View v)
{
postDelayed(new Runnable()
{
#Override
public void run()
{
// do your stuff here
}
}, 10000); //10sec delay
}
UPDATE:
If you want user to prevent clicking too fast on your image view, I strongly recommend go with onClick rather than onTouch (unless there are serious reasons for that)
However, please see the code snippet which might help you:
private boolean blocked = false;
private Handler handler = new Handler();
#Override
public boolean onTouchEvent(MotionEvent event)
{
if (event.getAction() == MotionEvent.ACTION_DOWN)
{
if (!blocked)
{
blocked = true;
handler.postDelayed(new Runnable()
{
#Override
public void run()
{
blocked = false;
}
}, 1000);
} else
{
return false;
}
}
return super.onTouchEvent(event);
}
How can I specify a value that when visible the HorizontalScrollView will be already scrolled by the value?
I already tried the scrollTo method, but it's only work on a post layout Context, I'm not able to start the HorizontalScrollView on a specific position.
How can I do that?
I solved this way, any better solution is also welcome:
public void scrollWhenAvailable(final int x) {
if (x == getScrollX())
return;
scrollTo(x, 0);
postDelayed(new Runnable() {
#Override
public void run() {
scrollWhenAvailable(x);
}
}, 10);
}
you will probably have to use scrollTo anyway, since it does exactly what you want, albeit you may need to call it at the right time. (in some onPostSomething, i guess)
I believe that it is unecessary to to a postDelayed, a simple post is sufficient since it will be executed after all the view dimensions have been calculated, you can even add this in onCreate(..).
scrollView.post(new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
scrollView.setScrollX(x);
}
});
Also note that setScrollX is probably what you want to use if your SDK level is high enough.