Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to get the Position #56

Closed
D33pTh0ught opened this issue Dec 16, 2013 · 16 comments
Closed

How to get the Position #56

D33pTh0ught opened this issue Dec 16, 2013 · 16 comments

Comments

@D33pTh0ught
Copy link

Is there a way to get the position of a Tileview so i can set another Tileview to the same position with moveTo() ?

@moagrius
Copy link
Owner

Sure, it's just the scroll positions, so the native View methods of getScrollX and getScrollY would do it.

@D33pTh0ught
Copy link
Author

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.

@moagrius
Copy link
Owner

Try this:

tileview2.scrollTo(tileview1.getScrollX(), tileview1.getScrollY());
tileview2.setScale(tileview1.getScale());

@D33pTh0ught
Copy link
Author

Thanks, now it works :)

@D33pTh0ught
Copy link
Author

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?

@moagrius
Copy link
Owner

try calling requestRender on the tileview that has not loaded the tiles.

@D33pTh0ught
Copy link
Author

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.

@moagrius
Copy link
Owner

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.

@D33pTh0ught
Copy link
Author

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.

@moagrius
Copy link
Owner

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.

@D33pTh0ught
Copy link
Author

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.

@moagrius
Copy link
Owner

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();
    }
    // ...

@D33pTh0ught
Copy link
Author

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);
    }

@moagrius
Copy link
Owner

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!

@D33pTh0ught
Copy link
Author

Nice, I haven't tried this particular solution of yours since I'm using the jar.
Thanks again for your help!

@moagrius
Copy link
Owner

NP! And good job getting it to work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants