Background
To learn animations, I'm creating a Towers of Hanoi type of game. My main goal is to animate the movement of the block from one tower to another. I've got the following layout
|------RelativeLayout------|
|-Linear-|-Linear-|-Linear-|
|-Block1-|-----------------|
|-Block2-|-----------------|
|-Block3-|-----------------|
|--------------------------|
ID| Tower1 | Tower2 | Tower3 |
I've set the XML attribute android:clipChildren="false" on every ViewGroup.
For example, if I tried to animate the movement of Block1 from Tower1 to Tower2 using Block1.animate().setDuration(3000).translationX(1000). As the layout is right now, Block1 will animate within Tower1, but Block1 gets clipped the second it leaves Tower1.
I've played with changing the z-order by adding the blocks last in the XML file. It doesn't reliably work, though.
To ensure animations don't get clipped, I've decided to add a copy of the Block1 (named copyBlock) to the root RelativeLayout, position copyBlock on top of Block1, and animate it to the destination Tower2 (defined in coordinates)
To get the destination coordinates, I was planning on adding an invisible copyBlock to Tower2, then get the coordinates. This way, I can take advantage of the LinearLayout's layout functions to account for matters such as padding, gravity, etc. Otherwise, I'll have to get the position of Tower2, calculate the topmost block, adjust the coordinates of Block1 so that it'll be on top of the topmost block and centered.
But, I'm pretty sure this way is hacky, and there's a better way
Questions
How can I get the above destination coordinates without having to add an invisible view to the destination tower? Is there a way to ask for a "prelayout" without having to add View to the layout?
Do you have any explanations for why the animation gets clipped? Is there a better way to approach this rather than adding a new view to the root RelativeLayout ViewGroup?
Ok,
1- You can't overcome the clipping issue, because you are trying to move the child beyond the parent dimensions, unless you are moving within the same parent.
2- Use FrameLayout instead of RelativeLayout so you can control views margins correctly;
3- You can get the destination coordinates by knowing the height of the root layout and width, its seems the towers width are equal, so the (total width/3) will give the cell width then get the X coordinate, and the (total height - cell height ) will give the Y coordinate of the tower ( assuming you have the tower hight)
Related
In Android, one used ScrollView as the root in an XML file, then include many other different kinds of views inside it, that allowed the scroll behavior.
How does Swift 2.1 and Xcode 7.1.1 do this, since the storyboard is not long enough to insert all the different kinds of views I want to put in, like I did in the XML code for Android?
I am thinking iOS with an Android brain :(
0 lines of code
Storyboard is long enough:
All you need to do is to create a freeform view, put all your content is that freeform view using top-to-bottom Autolayout constraints, and use that view as the content of your UIScrollView.
Tutorial
View Controller > Show the Attributes inspector > Size > Freeform
View Controller > Show the Size Inspector > Simulated Size > Freeform > width & height
Add a UIScrollView
Add 4 AutoLayout constraints, with top/left relative to superview, and bottom/right of superview relative to scrollview
Scroll.Top = Superview.Top Margin
Scroll.Leading = Superview.Leading
Bottom Layout Guide.Top = Scroll.Bottom
Scroll.Trailing = Superview.Trailing
Add a UIView as a subview to UIScrollView
Repeat 4 AutoLayout constraints, same rule: anchored top, superview relative to subview width/height
Content.Top = Scroll.Top
Content.Leading = Scroll.Leading
Scroll.Bottom = Content.Bottom
Content.Trailing = Scroll.Trailing
Add all subviews to that UIView. Ensure you can trace an AutoLayout chain of constraints all the way from top to bottom (and left to right) with again the same rule as 4. and 6. anchored top & superview.width and superview.height relative to content.
Vertical height of your content view is handled by 7. For the width, and in this example, I decided to go full width. Notice how the Content view's width is relative to the root view width:
Content.width = View.width
Methodology
Create a hierarchy of views using the following structure:
root UIView (the one owned by the UIViewController
UIScrollView scroller (must stretch with relationship to the root)
content UIView (which will dictate the scroller scroll bars & area)
everything else goes into the content view
Understand what dictates the size of the content
either hard sizes
either relationship to contained views (continuous constraints)
either relativity to superviews
Understand the the edge-to-edge AutoLayout constraint continuity rule
You should be able to follow a continuous set of constraints top-to-bottom or left-to-right for the directions that will define the size of en enclosing view
You do not need such continuity for merely locating elements ; in fact, continuity where you do not need it may create conflicts
to make the enclosure size relative to the enclosed views (what you are trying to achieve vertically in your example):
attach the fist top view to a rigid location
attach each view underneath to the object above it
attach the bottom of the enclosure to the bottom of the last object
► Find this solution on GitHub and additional details on Swift Recipes.
There is a scroll view for iOS also in the interface builder object library:
Start to finish here is how to make it work in storyboard.
1: go to you view controller and click on Attribute Inspector.
2: change Size to Freeform instead of Inferred.
3: Go to the main view on that storyboard, not your scrollview but
rather the top level view.
4: Click Size Inspector and set this view to your desired size. I
changed my height to 1000.
Now you will see that you storyboard has your view setup so you can
see the entire height of your scroll for easy design.
5: Drop on a scrollview and stretch it so it takes up the whole view.
You should now have a scrollview with size of 320,1000 sitting on a
view in your view controller.
Now we need to make it scroll and need to make it show content
correctly.
6: Click on your scrollview and click on Identity Inspector.
7: Add a User Defined runtime attribute with KeyPath of contentSize
then type of SIZE and put in your content size. For me it is (320,
1000).
Since we want to see our whole scroll view on the storyboard we
stretched it and it has a frame of 320,1000 but in order for this to
work in our app we need to change the frame down to what the visible
scrollview will be.
8: Add a runtime attribute with KeyPath frame with Type RECT and
0,0,320,416.
Now when we run our app we will have a visible scrollview has a frame
of 0,0,320, 416 and can scroll down to 1000. We are able to layout our
subviews and images and whatnot in Storyboard just the way we want
them to appear. Then our runtime attributes make sure to display it
properly. All of this without 1 line of code.
Is this what you were thinking of?
If you want the scroll view to change size I would recommend trying this:
You want to do is drop the scroll view onto the view controller and and add constraints.
I have never used a scroll view before, so this might not work.
Within Android, I'm trying to move a TextView from outside the parents bounds into view, but the contents never shows up, or remains clipped if it was partially within the bounds already.
Initial situation at start
Situation after animation
(Below this is another view, that was completely out of bounds and isn't drawn either)
I have 4 TextViews below each other in a custom Object extending RelativeLayout. Based on a percentage the top 2 should move outside it's bounds and the bottom 2 should move in (from the bottom).
I use the following code to update the properties of each TextView. In this class each variable **positionY* is filled with their initial position from the layout-xml. effect is percentage between 0 & 1. The animation works, but the views aren't drawn again.
public class ActionBarTitleView extends RelativeLayout {
public void updateTransition(float effect) {
float height = getHeight();
titleView1.setY(title1positionY - height*effect);
detailView1.setY(detail1positionY - height*effect);
titleView2.setY(title2positionY - height*effect);
detailView2.setY(detail2positionY - height*effect);
invalidate();
}
}
What I tried
After some researching I found a few hints what the issue might be, but so far none of the tried options had any effect. Below is a list of things I've found on SO and tried.
Calling invalidate() on the RelativeLayout - No effect.
Invalditing the TextViews - No effect.
clipChildren = false for the RelativeLayout - No effect.
setWillNotDraw = false for the RelativeLayout - No effect. (onDraw is being called)
I haven't tried to solve this with a ScrollView, but I don't want to really, cause that adds another layer in the hierachy for something pretty small.
I thought I understood the drawing logic, but perhaps I'm missing something, so I hope someone might be able to point me in the right direction.
What I ended up doing (September 3rd)
Since no real solution was offered, I tried again and came to the following "fix". I set both second labels to Visibility.GONE, but within the original bounds of the container view. Then when I start the animation, I set their correct values, then move them outside the bounds and finally setting Visiblity.VISIBLE. When the animation progresses the labels roll into view as supposed to. So a fix to the issue, but no real explanation why the TextViews aren't drawn again...
I have a TextView that can have a few different values, and is updated runtime (in Java code).
However, I need this TextView to retain its center point, so that when the text in that TextView is updated, it is always center justified. It should be centered around a point which is not the center of the screen or anything else, so setting gravity only will not help.
As the values that it may contain are already defined, I could try with the longest one first, position it to the correct top left position and set its gravity to center. In this case every shorter in length text should fit correctly.
However, I would like to know if there is better approach, for cases when the values are not known beforehand.
This TextView is placed below an ImageView and it could take the whole screen width (nothing else is placed left or right to it).
Note: I guess it could be also possible to position it every time to a new X axis position, whenever the text is changed, but I don't think it is a nice solution at all.
Set the android:layout_width to match_parent and set android:gravity to center_horizontal.
This way the View is stretched all the way horizontally, and the content (the text) will be centered. If the content changes, it will still be centered.
One thing that many people don't realize is the difference between android:gravity and android:layout_gravity. The first one defines the alignment of the content inside itself, the other one defines alignment of itself relative to its parent.
According to the docs for the View class:
The geometry of a view is that of a rectangle. A view has a location, expressed as a pair of left and top coordinates, and two dimensions, expressed as a width and a height. The unit for location and dimensions is the pixel.
It is possible to retrieve the location of a view by invoking the methods getLeft() and getTop(). The former returns the left, or X, coordinate of the rectangle representing the view. The latter returns the top, or Y, coordinate of the rectangle representing the view.
In addition, several convenience methods are offered to avoid unnecessary computations, namely getRight() and getBottom(). These methods return the coordinates of the right and bottom edges of the rectangle representing the view. For instance, calling getRight() is similar to the following computation: getLeft() + getWidth().
My interpretation of the above is that the View's position is controlled by its "Left" and "Top" values, while its width and height are controlled by its "Width" and "Height" values. This seems especially clear considering that last sentence, where "Right" is derived by adding Left and Width.
Despite this, when I use setLeft() and/or setTop() to change the position of the View, the SIZE of the View changes on screen! Meanwhile, the lower right corner of the View stays anchored to its original spot. This behavior implies that "Right" and "Bottom" are actual values, not derived as described in the docs.
So what is really going on here? The docs say one thing, but the behavior says the opposite. What is the proper way to reposition a View?
EDIT: I added a RelativeLayout:
myParams = new RelativeLayout.LayoutParams(300,300);
myParams.addRule(RelativeLayout.CENTER_HORIZONTAL);
myParams.addRule(RelativeLayout.CENTER_VERTICAL);
myView.setLayoutParams(myParams);
...to create a View 300x300 centered on the screen. Works perfectly. But examining that RelativeLayout, the location seems to be controlled by leftMargin and topMargin - yet both are zero! That raises the questions of 1) how can you examine the LayoutParams to know where the View is right now, and 2) how can you alter the LayoutParams to move it to a different location?
EDIT: As an experiment, I added an onTouch method to the View and did this within it (excerpt):
if (MotionEvent.ACTION_MOVE == iAction) {
myParams = (RelativeLayout.LayoutParams) v.getLayoutParams();
myParams.leftMargin = 0;
myParams.topMargin = 0;
v.setLayoutParams(myParams);
}
...on the theory that my vertically and horizontally centered View would then move to the upper left corner of the screen. Result: It didn't move at all. Not exactly surprising, since .leftMargin and .topMargin were already zero, but I wanted to try it just in case there was some magic hiding here.
Other suggestions?
In How Android Draws Views:
"Because the tree is traversed in-order, this means that parents will be drawn before (i.e., behind) their children, with siblings drawn in the order they appear in the tree."
Does the author actually mean "pre-order", instead of "in-order"?
Thank you for your help.
It's more like "depth-first order" for measuring the children and "pre-order" for the actual drawing.
The parent will be drawn first and the children stacked on top of the parent.
That is a bug in the documentation. It should be pre-order traversal where the parent draws before children.
Most UI toolkit libraries (including android) do this:
render the view hierarchy top down in pre-order - first draw self,
then children
Measure (size calculation) happens bottom up in post-order - first
measure children, then measure self
Layout (positioning) happens top down in pre-order - first my parent
positions me, then i position my children