Android Wait Until An Image Has Loaded To the ImageView - android

I have problem that scroll through images on disk which are rather large (about 1 Mb each) Now the problem I have is not the latency between each image but rather that while loading the image the scroll of the image (which I use to signal a change) keeps being detected while the image is loaded resulting in a moved image when it finally loads. My question is simple. Is there a way to wait until the image is loaded (that way I can block screen commands until the image is completely loaded).
Thanks for any answers!
PD: By the way I'm loading the pages in an ImageView;
EDIT:
Posting AsyincCode:
This is in my test activity itests.java
public class ImageLoader extends AsyncTask<String, Void, Void > {
private final ProgressDialog Loading = new ProgressDialog(itests.this);
protected void onPreExecute(){
Loading.setMessage("Loading...");
Loading.show();
}
#Override
protected Void doInBackground(String... params) {
pvm.setPages(params[0], params[1], params[2]);
return null;
}
protected void onPostExecute(Void... params){
if (Loading.isShowing()){
Loading.dismiss();
}
}
Now pvm is my PageViewManager (PageView is my Custom ImageView that receives all my touch events)
Here is what it does:
public void setPages(String left, String center, String right){
//if (!timer.isAlive()){
Left = left;
Center = center;
Right = right;
DoublePageMode = true;
toggleDoublePageMode();
System.err.println("Waiting...");
//centerPage.blockCommands();
//timer.start();
//}
}
#Override
public void toggleDoublePageMode(){
if (DoublePageMode){
//Going into Normal View
threePageLayout.setOrientation(LinearLayout.HORIZONTAL);
leftPage.setVisibility(View.VISIBLE);
LinearLayout.LayoutParams cll = new LinearLayout.LayoutParams(DWidth,DHeight);
LinearLayout.LayoutParams zero = new LinearLayout.LayoutParams(0,DHeight);
centerPage.setImage(Center, DWidth, DHeight, PageState.NORMAL);
centerPage.setLayoutParams(cll);
leftPage.setImage(Left, DWidth, DHeight, PageState.NORMAL);
leftPage.setLayoutParams(zero);
rightPage.setImage(Right, DWidth, DHeight, PageState.NORMAL);
rightPage.setLayoutParams(zero);
DoublePageMode = false;
}
else{
DoublePageMode = true;
threePageLayout.setOrientation(LinearLayout.VERTICAL);
leftPage.setVisibility(View.GONE);
LinearLayout.LayoutParams cll = new LinearLayout.LayoutParams(DWidth,DHeight,1f);
LinearLayout.LayoutParams zero = new LinearLayout.LayoutParams(DWidth,DHeight,1f);
centerPage.setImage(Center, DWidth, DHeight, PageState.TWOPAGEMODE);
centerPage.setLayoutParams(cll);
rightPage.setImage(Right, DWidth, DHeight, PageState.TWOPAGEMODE);
rightPage.setLayoutParams(zero);
}
}
The #Override notation is because the function also has to be called from the PageView on a LongTouch. However I'm not doing any sort of long press so it's not a problem. Since the AsyncTask tries to move the layout of the activity showing the images I get the error I told you, which makes perfect sense.
When I need to call this method It comes from this code (on itests.java):
public void movePages(boolean next) {
//Code that initializes left,center,right
new ImageLoader().execute(left, center, right);
}
This method is called by the PageViewManager (pvm) through an inteface.
I hope this gives you an idea!
Thank you very much!
EDIT2:
I've solved it! Actually more like I found a way around it but I works beautifully!
All I did is create a Timer class that lets me know when a certain time has passed. When I need to load the images I start it (it is another thread) and set a boolean that makes my pages ignore touch events (by returning false in onTouchEvent). When it is done it call a function in my pvm that simply set that boolean again to true and the effect is just what I wanted. The timer waits for only about 500 miliseconds.
Thanks for all the help!

I hope your using a aSyncTask to load the data! You could simply add a progress dialog whilst loading the data. this can be done by adding:
pDialog = ProgressDialog.show(this, "Loading Data", "Please Wait...", true);
before you start loading the data, then once all your data is ready simply call pDialog.dismiss();

One (longish) solution I can think of is to implement your own ImageView class, which loads a BitmapDrawable and draws it in onDraw(). This way, you can know for sure when onDraw() completes, and call a different callback (e.g. onDrawComplete() ) to toggle touch event handling.

Related

Android - Update Bitmap from timer thread

I got an Android project composed by a single Layout with an ImageView.
public class MainActivity extends AppCompatActivity {
/* original and stretched sized bitmaps */
private Bitmap bitmapOriginal;
private Bitmap bitmapStretched;
/* the only view */
private ImageView iv;
....
}
This ImageView is updated by this runnable function
runnable = new Runnable() {
#Override
public void run() {
iv.setImageBitmap(bitmapStretched);
}
};
and the runnable is ran by a temporized JNI function, running on a background thread, that call it 60 times per second.
public void jniTemporizedCallback(int buf[]) {
/* set data to original sized bitmap */
bitmapOriginal.setPixels(buf, 0, origWidth, 0, 0, origWidth, origHeight);
/* calculate the stretched one */
bitmapStretched = Bitmap.createScaledBitmap(bitmapOriginal, width, height, false);
/* tell the main thread to update the image view */
runOnUiThread(runnable);
}
After some frame is drawn, the app crashes with the following message.
A/OpenGLRenderer: Task is already in the queue!
I guess this is because the renderer didn't finish to fully render the previous frame of the ImageView and gets angry.
If i remove runOnUiThread(runnable); the problem disappear (obviously)
How can avoid this? How can i syncronize my application with the openGL renderer?
I also tried to extend ImageView and draw the bitmap on canvas into the onDraw function but i got the same result
I guess you're trying create bitmapOriginal ouside the thread. Therefore, when compiler is trying to call again after 60 seconds, it's getting same objects and couldn't identify the task. I would suggest better as below.
public void jniTemporizedCallback(int buf[]) {
// Initialize
bitmapOriginal = Bitmap.createBitmap(///)
/* set data to original sized bitmap */
bitmapOriginal.setPixels(buf, 0, origWidth, 0, 0, origWidth, origHeight);
/* calculate the stretched one */
bitmapStretched = Bitmap.createScaledBitmap(bitmapOriginal, width, height,false);
/* tell the main thread to update the image view */
runOnUiThread(runnable);
}
The proper way to synchronize your drawing logic with the device's frame rate is to use a SurfaceView instead of an ImageView. Instead of pushing frames to the View with your own timer, you should create a rendering Thread that tries to render frames as fast as possible. When you call surfaceHolder.lockCanvas(), the Android system will automatically block until it is time to render the frame. When you unlock the canvas using unlockCanvasAndPost(), the system will draw the buffer to the screen.
See https://developer.android.com/guide/topics/graphics/2d-graphics.html#on-surfaceview for more info. Hope this helps!
Problem was totally unrelated to the Bitmap itself....
It was the real time clock signal that messed with Android RenderThread.
Further explanation here:
Android and JNI real time clock
Provide here purposes of use such method for rendering? What you want to do?, there are great animation functionality in android engine, may be this task can be done with this animation.
One more if you will use codes like yours battery of phone will run to zero very fast coz this will load cpu/gpu to max.
in anyway - try to place blocks from running task, set bool taskRun = true on start and check if (!taskRun){ taskRun = true; //here start your task..} and on ui thread after updating ui you can switch to taskRun = false; Using this you can skip some frames, but should not crash.
The problem is that the Handler of the main thread is keeping a reference to your Runnable. When you want to run your Runnable for the second time, the old Runnable is already in the Message Queue, hence Task is already in the queue message. If you create a Runnable every time u want to execute the Runnable like in the code below, I think the problem will be solved.
public void jniTemporizedCallback(int buf[]) {
/* set data to original sized bitmap */
bitmapOriginal.setPixels(buf, 0, origWidth, 0, 0, origWidth, origHeight);
/* calculate the stretched one */
bitmapStretched = Bitmap.createScaledBitmap(bitmapOriginal, width, height, false);
/* tell the main thread to update the image view */
runOnUiThread(new Runnable() {
#Override
public void run() {
iv.setImageBitmap(bitmapStretched);
}
});
}
I think you are right with reason, because you cannot be sure, that Android render images in 60 FPS. And yeah, I think you need just synchronize Bitmap Native Callback with Android Render. So, lets start.
I prefer using Lock from concurrency stack Java. Because you see, when you lock object, and when you unlock. In case of using volatile (for example, sure there also reference restrictions) on Bitmap object, you need to check locking this object in very places, where you using Bitmap.
Also I think you should use Lock from THIS EXAMPLE (to unlock Lock object from any other Thread). So, here is example. Example below will work properly. Just don't forget about Context deleting and stopping task:
public class MainActivity extends AppCompatActivity {
/* Initialize lock (avoid lazy init, with your methods) */
private ReentrantLock lock = new ReentrantLock();
............
private runnableDrawImage = new Runnable() {
#Override
public void run() {
iv.setImageBitmap(bitmapStretched);
lock.unlock();
}
};
..........
public void jniTemporizedCallback(int buf[]) {
/* synchronize by locking state*/
lock.lock();
bitmapOriginal = Bitmap.createBitmap(///)
bitmapOriginal.setPixels(buf, 0, origWidth, 0, 0, origWidth, origHeight);
bitmapStretched = Bitmap.createScaledBitmap(bitmapOriginal, width, height,false);
MainActivity.this.runOnUiThread(runnableDrawImage);
}
}

Trouble implementing a Slide Show from an ArrayList of Bitmap

I'm trying to implement a slide show for my App and I seem to be getting stuck on what seems like a small detail.
I'm new to Android and haven't been programming long so I'm not great with threading yet.
Anyways I have an ArrayList which is great. When I try to loop through the ArrayList replacing the images in my ImageView with images in the ArrayList however, I only end up seeing the final image in the List.
I can see in my LogCat that the images are being set (at least that section of the code is running. It is sleeping as I asked (I can notice the 1000ms in the LogCat entries.
I'm doing this in an AsyncTask and trying to set the results from the onPostExecute(). Here is that code...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_slide_show);
screenSize = MediaHelper.getScreenSize(this);
Bundle extras = getIntent().getExtras();
Long projectId;
imageList = new ArrayList<Bitmap>();
ivSlide = (ImageView)findViewById(R.id.iv_slide);
tvLoading = (TextView)findViewById(R.id.tv_loading);
tvLoading.setVisibility(View.INVISIBLE);
if(extras != null){
projectId = extras.getLong("projectId");
new GetImagesTask().execute(projectId);
}
}
class GetImagesTask extends AsyncTask<Long, Void, ArrayList<Bitmap>>{
#Override
protected ArrayList<Bitmap> doInBackground(Long... id) {
long projectId = id[0];
Project project = getProject(projectId);
ArrayList<Bitmap> i = new ArrayList<Bitmap>();
File[] files = MediaHelper.getProjectFilenames(project.getImagePath());
if(files != null && files.length > 0){
for(File aFile : files){
Bitmap bitmap = MediaHelper.getScaledImage(aFile, screenSize.y / 2, screenSize.x / 2);
i.add(bitmap);
Log.d(TAG, "image added...size = " + bitmap.getByteCount());
}
}
return i;
}
#Override
protected void onPostExecute(ArrayList<Bitmap> result) {
if(result != null && result.size() > 0){
imageList = result;
if(imageList.size() > 0){
ivSlide.post(new Runnable(){
#Override
public void run(){
try{
for(Bitmap bm : imageList){
ivSlide.setImageBitmap(bm);
Thread.sleep(1000);
Log.d(TAG, "Setting ImageView image");
}
}catch (InterruptedException e){
Log.d(TAG, "Thread interrupted unexpectedly.");
}
}
});
}
}
Any help is much appreciated!!
I haven't used View.post(Runnable) before so I'm not sure about its behaviour but if it's not running on the UI Thread that "setImageBitmap" is not applied instantly, I think it's only applied when some other thread asks your view to reload its layout.
If it runs on the UI Thread, the Thread.sleep(1000) is asking the UI to stop working for a whole second, making the setImageBitmap effect impossible to notice to the user.
While facing a similar problem some time ago I used a Thread with a while in it that calls to runOnUiThread(Runnable) each time it needed to swipe the pictures. That way, the loading task was ran on the UI Thread and the waiting on a background one.
Hope it helps.
EDIT:
Could you try replacing your AsyncTask with this one, as suggested on my comment? gist.github.com/Arasthel/9788601
Your problem is likely Thread.sleep(). This will sleep the main thread and likely prevent the ui from updating till the last image when you don't sleep the thread.
Use Handler.postDelayed(Runnable, long) instead of sleeping the thread.
Obviously only the last item will be updated, Because you are running the loop and updating the same variable "ivSlide". For each iteration the same variable is getting updated with the different Bitmap at last it stops with the last Bitmap updated.
To overcome this you have to update each ivslide view variable with the arraylist index.
Please update full code so I can answer better.

Android splashscreen without creating a new activity

Edit
After moving my loading / creation code to an Async Task (see below) - I still have the initial problems that I had with my original splashscreen.
Those being that:
1) On starting the Async task in onCreate, everything is loaded but my Dialog can only be shown when onStart() is called which makes the whole process kind of pointless as there is a long pause with a blank screen, then after everything has loaded, the 'loading' dialog flashes up for a split second before disappearing.
2) I can't move object loading / creation etc to onStart because a) it will be run again even when the app is resumed after being sent to the background which I don't want to happen, and b) when when calling restoring the savedInstanceState in onCreate() I'll get a nullPointerException because i'm restoring properties of objects that won't have yet been created.
Would really appreciate if someone could advise how to get around these problems and create a simple splashscreen. Thanks!
Background
My app uses only one activity and I would like to keep it that way if possible.
I've struggled with this for over a week so really hope someone can help me out.
All I want to do is use a splashscreen with a simple 'loading' message displayed on the screen while my resources load (and objects are created etc.) There are a couple of points:
Conditions
1) The splashscreen should not have it's own activity - everything needs to be contained in a single-activity
2) The splashscreen should not use an XML layout (I have created a Splashscreen class which uses View to display a loading PNG)
3) My app is openGL ES 2.0 so the textures need to be loaded on the OpenGL Thread (creation of objects etc that don't use GL calls are OK to go on another thread if necessary).
What I've attempted so far
What I did so far was to create a dialog and display it in my onStart() method with:
Dialog.show();
then let everything load in my onSurfaceCreated method before getting rid of it with:
Dialog.dismiss();
However I needed to change this for varioius reasons so now I create my objects from a call within my onCreate() method and just let the textures load in my GL Renderer's onSurfaceCreated method.
However, this means that because the dialogue isn't displayed until after onCreate, I still get a delay (blank screen) while everything is created before the splash-screen is shown, this then stays on the screen until the textures have loaded. There are other issues with this too which can wait for another day!
So my approach is obviouly very wrong. I read every tutorial I could and every splash-screen related question I could find on SO and Gamedev.SE but I still can't find an explanation (that makes sense to me), of how this can be achieved.
I'm hope someone here can explain.
You should be able to use AsyncTask to load resources in the background and then just dismiss your splash
Here's an AsyncTask that I use to load data from a remote db. This displays a loading progress circle until the task is complete but should be easily re-purposed to display your splash
AsyncTask that runs in the background
private class SyncList extends AsyncTask<Void, ULjException, Void> {
private static final String TAG = "SyncList";
private final class ViewHolder {
LinearLayout progress;
LinearLayout list;
}
private ViewHolder m;
/**
* Setup everything
*/
protected void onPreExecute() {
Log.d(TAG, "Preparing ASyncTask");
m = new ViewHolder();
m.progress = (LinearLayout) findViewById(R.id.linlaHeaderProgress);
m.list = (LinearLayout) findViewById(R.id.listContainer);
m.list.setVisibility(View.INVISIBLE); //Set the ListView that contains data invisible
m.progress.setVisibility(View.VISIBLE); //Set the loading circle visible you can sub in Dialog.show() here
}
/**
* Async execution performs the loading
*/
#Override
protected Void doInBackground(Void... arg0) {
try {
Log.d(TAG, "Syncing list in background");
dba.open(ListActivity.this);
dba.sync();
} catch (ULjException e) {
publishProgress(e);
}
return null;
}
/**
* Display exception toast on the UI thread
*/
protected void onProgressUpdate(ULjException... values) {
Log.e(TAG, values[0].getMessage());
Toast.makeText(ListActivity.this, "Sync failed", Toast.LENGTH_LONG).show();
}
/**
* Finish up
*/
protected void onPostExecute(Void result) {
Log.d(TAG, "ASyncTask completed, cleaning up and posting data");
fillData();
m.list.setVisibility(View.VISIBLE); //Show the list with data in it
m.progress.setVisibility(View.INVISIBLE); //Hide the loading circle sub in Dialog.dismiss()
}
}
Calling the Task
protected void onStart() {
super.onStart();
// Init the dba
dba = DBAccessor.getInstance();
new SyncList().execute();
}
It should be noted that the AsyncTask is an inner class of the Activity its related to here
Edit
onCreate Method
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout);
Dialog.show();
//This launches a new thread meaning execution will continue PAST this call
//to onStart and your loading will be done concurrently
//Make sure to not try to access anything that you're waiting to be loaded in onStart or onResume let your game start from onPostExectue
new AsyncTask.execute();
}
doInBackground
protected Void doInBackground(Void... arg0) {
Load all resources here
}
onPostExecute
protected void onPostExecute(Void result) {
Dialog.dismiss();
Call a method that starts your game logic using your newly loaded resources
}

ImageViews which is in ListView doesn't displayed

I have ListView with EventAdapter.
public class EventAdapter extends BaseAdapter {
...
public View getView(int position, View view, ViewGroup parent) {
...
cache.imgIcon.setImageDrawable(ImgCache.setImg(url, progressBar));
...
}
ImgCache its class for caching images.
public class ImgCache {
public static HashMap<String, Drawable> imgCache;
// get img from cache if exist, or download and put in cache
public static Drawable setImg(final String link, final ProgressBar progressBar) {
final Drawable[] image = {null};
if (imgCache.containsKey(link)) {
image[0] = imgCache.get(link);
progressBar.setVisibility(View.INVISIBLE);
} else {
new AsyncTask<Void, Void, Drawable>() {
#Override
protected Drawable doInBackground(Void... params) {
URL url = null;
try {
url = new URL(link);
URLConnection connection = url.openConnection();
image[0] = Drawable.createFromStream(connection.getInputStream(), "src");
} catch (Exception e) {
e.printStackTrace();
}
imgCache.put(link, image[0]);
return image[0];
}
#Override
protected void onPostExecute(Drawable result) {
progressBar.setVisibility(View.INVISIBLE);
}
}.execute();
}
return image[0];
}
}
What the problem is?
After I open my Activity with ListView all images begin loading. But after the loading is finished they don't displayed. It is looks like:
Then I try to scroll 2 items down and then return to previous position. After this manipulation I can see 2 upper items with images. Also all images down are also visible when I scroll to them.
According to your problem, it seems like you need to refresh your ListView after the images has been downloaded (because when you scroll they do appear):
adapter.notifyDataSetChanged();
AsyncTask is asynchronous so the flow for your app is:
ListView Item needs to be displayed -> Calls Adapter.getView(...) for List item -> if image is not in cache, execute AsyncTask and return (not waiting for result)
So, when you scroll down and back up, the Adapter.get(...) method is called again, however this time the image is in cache so it returns the Drawable object which is displayed
One way to resolve this issue would be to have a callback to the Adapter from the AsyncTask that will update the image once it is retrieved calling notifyDataSetChanged on the Adapter, setting specific Drawable directly or something similar (display a loading gif for images in the meanwhile?)
Or
Call the AsyncTask get(long timeout, TimeUnit unit) method which will block the man thread and wait for the AsyncTask to finish. After it is finished then it will return the result (your Drawable in this case). This will cause the main UI thread to hang while fetching images, so not optimal way to go about this.
The issue is that your view loads and populates your list OnCreate, but at that time your Async task hasn't returned your list yet so when getView calls your cache it's empty, due to android View Recycling when you scroll it calls getView again, this time your cache has been populated.
I recommend that onPostExecute you call NotifyDataSetChanged on your ListView adapter, this will force a redraw once your have your images.

Android LoadScreen

When my app launches a file scraper that iterates recursively through the sdcard is kicked off. As you can imagine, this takes a fair amount of time. On my epad transformer it can take 30 to 40 seconds to blitz through the 32gb of storage.
This activity is done in two fragments that live on the main layout, launched from the main activity.
Obviously with load times like that I need to do something to alert the user. However, from what I've read I can't launch the main activity in the background behind a load screen. I've tried to drop a progress bar on and also failed.
So, any suggestions?
EDIT
I'm just reading up on using an aSyncTask within a fragment. I would love to use a progressbar, either within the slow list fragment, or on a splashscreen, or just over the main view
Do something like this
LoadTask load;
public class LoadTask extends AsyncTask<Void, Integer, Boolean> {
int prog = 0;
#Override
protected Boolean doInBackground(Void... params) {
//All your loading code goes in here
publishProgress(prog++); //Use this to update the progress bar
publishProgress(-1) //Use this to open other app
return false;
}
protected void onProgressUpdate(Integer... progress) {
if(progress[i] == -1)
//open other app here
for(int i = 0; i < progress; i++){
//set progress here with progress[i];
}
}
}
then to start the async task use
load = new LoadTask();
load.execute();

Categories

Resources