Closed D33pTh0ught closed 10 years ago
Sure, it's just the scroll positions, so the native View methods of getScrollX
and getScrollY
would do it.
okay, thank you for your quick answer, however this did only work for me when the view was not scaled. I managed to set the scale of a TileView to the scale of another , but then the position was not correct anymore:
tileview2.moveTo(tileview1.getScrollX, tileview1.getScrollXY); tileview2.setScale(tileview1.getScale());
Can you tell me whats wrong with this peace of code? Thanks in advance.
Try this:
tileview2.scrollTo(tileview1.getScrollX(), tileview1.getScrollY());
tileview2.setScale(tileview1.getScale());
Thanks, now it works :)
With that working now, i ran into another minor issue:
When I manualy zoom in and scroll to another position in the second view and then change back to the first one (where I again getScroll and setScale to the new values from the second view) the image i get is not sharp. Only when i move the map a little, then the tiles for the needed level of detail are being loaded.
hope my description is not to confusing ... any ideas what I could do to resolve this problem?
try calling requestRender
on the tileview that has not loaded the tiles.
unfortunately I get the same result with calling requestRender. It is only sharp when i switch to a view I haven't loaded before, or one where the area i move the view to has been loaded before.
This actually comes up a lot - I strongly suspect it's a validation issue. First thing to do is try refresh
on the instance not loading tiles. Hopefully that works - if not, it's most definitely a validation issue, which is a bear with the Android framework - I've tried to catch any potential validation failure everywhere I can, but as I said it happens a lot. If refresh
doesn't work for you, try calling _both_ invalidate
and postInvalidate
(yes, they should be redundant, but they really aren't) on as many layers as your can when that happens (again, should not be necessary but sometimes is - both should cascade but don't always do so reliably) - maybe start on the tile view itself, but to be sure it's happening you may need to recurse down children as well...
Hopefully refresh
will do it and you won't have to get into the validation quagmire - post back and LMK how it goes.
refresh and also invalidating didn't work for me. but maybe I'm doing it wrong: I have a Fragment which returns different instances of a class Map that extends TileView. I get those instances by calling getMap(x) where I first scrollTo and setScale to the position of the Map loaded before and then return it. In this method(getMap) I tried calling requestRender() refresh() and both invalidate() and postInvalidate() on the Map instance before returning it but that didn't work. Have i got something wrong("as many layers as your can" "recurse down children")? - because I don't know where else I should call this methods.
requestRender
and refresh
are TileView methods, and can only be called by TileView instances, so that's pretty straightforward. They should both be called only when the TileView instance is in a viewable state (added to the display list, etc). They should both be called only from the UI thread (if you're calling either method from an AsyncTask, for example, you'd want to post
a Runnable
to the UI thread to call either method).
Separate from that, the Android framework tries to save resources by only drawing if the last image on the screen is invalid. If there is no change, there's no sense wasting time and resources drawing the same thing again. In general, it does an OK job of determining what is valid and what is not, but can get confused when doing certain things programmatically, like scrolling - thus they (Android) offers two methods: invalidate
and postInvalidate
. They do the same thing - they mark the View as being _invalid_ and it should be drawn again the next time the UI does a drawing pass. The only difference between the two methods is that postInvalidate
automatically sends the invalidate invocation through the UI thread (otherwise you'd have to do it yourself, using the same post
and Runnable
technique I described earlier).
In theory, if you invalidate a parent, all it's children get redrawn as well, but in practice calling invalidate on a parent does not always work - sometimes you have to target the child that is responsible for the change in drawing state, and this isn't always obvious. These validation issues have come up a lot, and in general they're tough to track down.
If nothing I've suggested has worked, you can either post the relevant bits of your code, or I can take a look at your project if you have it available for download.
I'll post my code here. The interesting part is probably the MapManager.
I have a Fragment which returns the View instances:
public class MapFragment extends Fragment {
public static final String ARG_SERVICE_NUMBER = "service_number";
int i;
public MapFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
i = getArguments().getInt(ARG_SERVICE_NUMBER);
return MainActivity.maps.getMap(i);
}
@Override
public void onPause() {
super.onPause();
MainActivity.maps.clear(i);
}
@Override
public void onResume() {
super.onResume();
MainActivity.maps.resume(i);
}
@Override
public void onDestroy() {
super.onDestroy();
((ViewGroup) MainActivity.maps.getMap(i).getParent()).removeView(MainActivity.maps.getMap(i));
}
}
The variable maps in the Fragment above is an instance of my MapManager where the synchronisation of the view positions is done. This is also the class where i tried to apply your suggestions.
public class MapsManager {
private static Map[] map;
private static int currentFloor;
private static double[] position;
private static double scale;
private static MapsManager instance = new MapsManager();
public MapsManager() {
}
public static MapsManager getInstance(Activity activity) {
currentFloor = 0;
position = new double[2];
map = new Map[4];
map[0] = new MapLevel0(activity);
map[0].setPosition(700, 100);
map[0].setScale(0.3);
map[1] = new MapLevel1(activity);
map[2] = new MapLevel2(activity);
map[3] = new MapLevel3(activity);
return instance;
}
public TileView getMap(int i) {
position = map[currentFloor].getPosition();
map[i].setPosition((int) position[0], (int) position[1]);
scale = map[currentFloor].getScale();
map[i].setScale(scale);
currentFloor = i;
//at this point i tried requestRender() refresh() and both invalidate() and postInvalidate()
//map[i].refresh();
return map[i];
}
public void clear(int i) {
map[i].clear();
}
public void resume(int i) {
map[i].resume();
}
}
And here is the Map class:
public class Map extends TileView {
public Map(Context context) {
super(context);
this.setSize(6103, 3207);
defineRelativeBounds(42.379676, -71.094919, 42.346550, -71.040280);
setMarkerAnchorPoints(-0.5f, -1.0f);
setScaleLimits(0, 2);
setTransitionsEnabled(false);
}
public void frameTo(final double x, final double y) {
post(new Runnable() {
@Override
public void run() {
moveTo(x, y);
}
});
}
public double[] getPosition() {
double[] tmp = { getScrollX(), getScrollY() };
return tmp;
}
public void setPosition(int x, int y) {
scrollTo(x, y);
}
}
Any ideas what the problem could be? Feel free to point out any potential issues.
The good news is that I think I see where the problem is. The bad news is that I'm not sure how to fix it.
So the TileView determins what tiles need to be rendered by examining a Rect that represents the viewport - it's basically the scroll position + the width or height of the TileView itself: https://github.com/moagrius/TileView/blob/master/src/com/qozix/tileview/TileView.java#L742
If the width and height are 0 (e.g., it's not in the display list), then no tiles will ever intersect.
A view will commonly not have width or height until it is has undergone a layout pass. You'll notice if you make a custom view and Log the width / height in the constructor, it will almost always be 0.
A couple things to try.
First, I'd try to post out of onCreateView:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
i = getArguments().getInt(ARG_SERVICE_NUMBER);
final TileView map = MainActivity.maps.getMap(i);
map.post(new Runnable(){
@Override
public void run(){
map.refresh();
}
});
return map;
}
If that doesn't work, you can try to de-optimize the onLayout requestRender... Either hack the core of TileView.onLayout and remove the conditional:
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout( changed, l, t, r, b );
if ( changed ) {
updateViewport();
requestRender();
}
}
becomes:
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout( changed, l, t, r, b );
updateViewport();
requestRender();
}
Or if you don't want to hack the core (e.g., if you're using the jar), do the same thing in your subclass
public class Map extends TileView {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout( changed, l, t, r, b );
requestRender();
}
// ...
Thank you very much for your detailed answers. Even though i have no idea why your solutions did't do the trick, I now got it working by overriding the onLayout method in my Map class like this:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(true, l, t, r, b);
}
Awesome, glad you got it working. FWIW, your version (with the changed
variable hardcoded to true) should be identical to the core-hack mentioned above.
As a result, I'm going to remove that conditional check entirely, since it's caused other issues before that I've had to tweak other spots to accommodate - #61
Thanks for your feedback, and your determination to get it working!
Nice, I haven't tried this particular solution of yours since I'm using the jar. Thanks again for your help!
NP! And good job getting it to work!
Is there a way to get the position of a Tileview so i can set another Tileview to the same position with moveTo() ?