Nov 19, 2011

Android :: wrong image / data in ListView rows

Android's ListView reuses list items when they aren't need anymore. For this reason, you need to make sure all views that should change, will actually get changed. Your problem is that if you don't find a drawable for the current list item, you don't empty nor hide the ImageView. You should do thumb.setImageDrawable(null) in that case, or thumb.setVisibility(View.GONE).
- From stackoverflow

Example code:


 @Override
     public View getView(int pos, View v, ViewGroup parent) {
      TimetableViewHolder timetableHolder;
         if (v == null) {
             LayoutInflater vi = ((Activity)context).getLayoutInflater();
             v = vi.inflate(R.layout.timetable_item, parent, false);
             timetableHolder = new TimetableViewHolder();
             timetableHolder.rel = (TextView) v.findViewById(R.id.rel);
             timetableHolder.time = (TextView) v.findViewById(R.id.time);
             timetableHolder.icon = (ImageView) v.findViewById(R.id.icon);
             v.setTag(timetableHolder);
         } else {
          timetableHolder = (TimetableViewHolder) v.getTag(); 
         }

         TimetableItem item = items.get(pos);

         if(item != null) {
          timetableHolder.rel.setText(item.rel);
          timetableHolder.time.setText(item.time);
          if(item.url.equals("ERROR")) {
           timetableHolder.icon.setImageResource(android.R.drawable.ic_menu_close_clear_cancel);
          } else if(item.url.equals("PRESTOP")) {
           timetableHolder.icon.setImageResource(android.R.drawable.ic_menu_directions);
          } else { // THIS IS IMPORTANT
           timetableHolder.icon.setImageResource(android.R.drawable.ic_dialog_info);
          }
         }

         return v;
     }

Android search dialog doesn't appear

Have a problem?
The search dialog doesn't show up?

I just thought you should know, I found this interesting comment on stackoverflow:

oh my god. this is f*ing stupid. i had to use android:label="@string/xyz" instead of android:label="lol" and no single word about this in the documentation. unbelievable

 This is my manifest:

<activity android:name=".SearchableActivity" >
 <intent-filter >
  <action android:name="android.intent.action.SEARCH" />
 </intent-filter>

 <meta-data
  android:name="android.app.searchable"
  android:resource="@xml/searchable" />
</activity>
<activity
 android:label="Preferred stations"
 android:name="PreferredStation"
 android:theme="@android:style/Theme.NoTitleBar" >
 <meta-data
  android:name="android.app.default_searchable"
  android:value=".SearchableActivity" />
</activity>
And searchable
<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
    android:label="@string/search_label"
    android:hint="@string/search_hint" >
</searchable>


Android :: google maps (MapView) hacks, tricks, workarounds

MapView isn't very developer friendly now is it?

Here are some hacks I've had to work with:

1) When tapping on the overlay, the app crashes if you're trying to display the dialog.
Unable to add window -- token null is not for an application
Yes, this is due to context. Especially if you're trying to do this in a seperate thread or AsyncTask.
Turns out the context you need to pass to your dialog is mapView.getContext();
AlertDialog.Builder dialog = new AlertDialog.Builder(mapView.getContext());




2) Displaying only the overlays which are within map bounds
Oh yeah, several problems here. You've probably come across at least one of these:

  • Wrong map bounds in onCreate (0, 360000000),
    Yeah, in onCreate those haven't been calculated yet. onStart doesn't help either. Try this:
Runnable waitForMapTimeTask = new Runnable() {
  public void run() {
    if(mapView.getLatitudeSpan()==0||mapView.getLongitudeSpan()== 360000000) {
      mapView.postDelayed(this, 100);
    } else {
      redrawMarkers(); // draw here
    }
  }
};
mapView.postDelayed(waitForMapTimeTask, 100);

You create a new thread and wait until you get the right bounds. Recursively call it again.
  • Map bounds ???
    Don't worry, it's simple.
public Rect getMapBounds() {
return new Rect(
mapView.getMapCenter().getLongitudeE6() - mapView.getLongitudeSpan()/2,
mapView.getMapCenter().getLatitudeE6() - mapView.getLatitudeSpan()/2,
mapView.getMapCenter().getLongitudeE6() + mapView.getLongitudeSpan()/2,
mapView.getMapCenter().getLatitudeE6() + mapView.getLatitudeSpan()/2
);
}

...
if(!s.drawn && rect.contains(point.getLongitudeE6(), point.getLatitudeE6())) {


  • Yeah, okay, but what about panning / zooming?
    Well, there are no methods, like onPan or onZoom, but some people found their way around this problem. There is no perfect solution, you'll see.
    Check these links out:
  1. http://stackoverflow.com/questions/2328650/how-can-i-detect-if-an-android-mapview-has-been-panned-or-zoomed
  2. http://bricolsoftconsulting.com/2011/10/31/extending-mapview-to-add-a-change-event/
  3. http://stackoverflow.com/questions/3567420/how-to-catch-that-map-panning-and-zoom-are-really-finished
  • Zoom in on double tap?
    Click here: http://dev.kafol.net/2011/11/how-hard-is-it-to-make-simple-zoom-in.html.
  • Overlays don't get drawn immediately!
    Try this:
    mapView.postInvalidate();
    or this:
    mapView.invalidate();

    But keep in mind, that invalidate() needs to be called from an UI! If you're trying to get it working from a thread, use postInvalidate()!
  • MapView java.util.ConcurrentModificationException when adding new overlays
    Not sure if I solved this one, but it seems to work now. I read somewhere that this could happen if you add overlays in a non UI thread. I moved the 
    Nope, sorry, this one was my bad. I was doing some crazy async sorting and all hell broke loose.
itemizedOverlay.populateNow();
mapOverlays.add(itemizedOverlay);
mapView.postInvalidate();

From doInBackground to  onPostExecute in  AsyncTask.


You could also run something in a UI thread like this:

runOnUiThread(new Runnable() {
      @Override
       public void run() {
           //do stuff here
       }
});



Nov 18, 2011

Android :: google maps on double tap zoom in

How hard is it to make a simple zoom in call on double tap in MapView in Android?

Not very.

How hard is it to get the information on how to do it?

Very.

Here's what you probably didn't know:
You need to extend the MapView and use this extended class in the Android XML layout file.
In the extended class you instantiate the gesture detector and set on double tap listener.
In the Map Activity you implement OnGestureListener and OnDoubleTapListener.

Example:
<?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_horizontal" >
    
 <net.kafol.vlaki.ExtMapView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/mapview"
     android:layout_width="fill_parent"
     android:layout_height="fill_parent"
     android:clickable="true"
     android:enabled="true"
     android:apiKey=""
 />

</RelativeLayout>
package net.kafol.vlaki;

import android.content.Context;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.GestureDetector.OnDoubleTapListener;
import android.view.MotionEvent;
import android.view.GestureDetector.OnGestureListener;
import com.google.android.maps.MapView;

public class ExtMapView extends MapView {
 private Context context;
 private GestureDetector gestureDetector;

 public ExtMapView(Context c, AttributeSet attrs) {
  super(c, attrs);
  context = c;

  gestureDetector = new GestureDetector((OnGestureListener) context);
  gestureDetector.setOnDoubleTapListener((OnDoubleTapListener) context);
 }

 public boolean onTouchEvent(MotionEvent ev) {
  if (this.gestureDetector.onTouchEvent(ev))
   return true;
  else
   return super.onTouchEvent(ev);
 }
}
public class Map extends MapActivity implements OnGestureListener, OnDoubleTapListener {
...
 @Override
 public boolean onDoubleTap(MotionEvent e) {
     int x = (int)e.getX(), y = (int)e.getY();;
     Projection p = mapView.getProjection();
     mapView.getController().animateTo(p.fromPixels(x, y)); // zoom in to a point you tapped 
     mapView.getController().zoomIn();
  return true;
 }

Android : Checkbox ListView - (un) check all

This recycling of views in android is pretty insane. Pain to work with.

So the problem is how to check all check boxes in a list view, if a list view only contains the visible items.

Iterating through the adapter or array of holders was pretty unreliable, some checkboxes weren't affected.

What I did was actually add an attribute to the data object and iterate through this object array (the same way it get's added in the adapter)-

Here are some functions:
 public void toggleCheck(Boolean val) {
  
  for(Station s : stations.list) {
   s.checked = val;
   editor.putBoolean("PF_"+s.getID(), val);
  }
  
  for(int i=0 ; i < lv.getChildCount() ; i++) {
   CheckBox cb = (CheckBox) lv.getChildAt(i).findViewById(R.id.cb);
   cb.setChecked(val);
  }

  editor.apply();
 }
 
    private class StationCBViewHolder {
        public CheckBox cb;
        public Station s;
    }
 
 private class StationListAdapter extends ArrayAdapter {
     private ArrayList items;
     private Context context;
     
     public StationListAdapter(Context context, int tvResId, ArrayList items) {
         super(context, tvResId, items);
         this.items = items;
         this.context = context;
     }

     @Override
     public View getView(int pos, View v, ViewGroup parent) {
      final StationCBViewHolder holder;
      final Station item = items.get(pos);
      
         if (v == null) {
             LayoutInflater vi = ((Activity)context).getLayoutInflater();
             v = vi.inflate(R.layout.stationcheckboxitem, parent, false);
             
             holder = new StationCBViewHolder();
             holder.cb = (CheckBox) v.findViewById(R.id.cb);
             holder.s = item;
             
             item.checked = prefs.getBoolean("PF_"+item.getID(),true);
             
             //holder.cb.setTag(holder);
             holder.cb.setTag(item);
             holder.cb.setChecked(item.checked);
             holder.cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
     @Override
     public void onCheckedChanged(CompoundButton v, boolean isChecked) {
      Station s = (Station) v.getTag();
      editor.putBoolean("PF_"+s.getID(), isChecked);
      editor.apply();
     }
    });
                        
             v.setTag(holder);
         } else {
          holder = (StationCBViewHolder) v.getTag(); 
         }

         holder.cb.setText(item.toString());
         holder.cb.setChecked(item.checked);

         return v;
     }

 }


Full code here: http://pastebin.com/8NMbHqRV


The code still has some bugs related to SharedPreferences and SharedPreferences.Editor, but at least it checks and unchecks all checkboxes.