In a few words, I want to scale view - in the same way that Android Market does it, when you click the "More" button, on the for examplae 'Description'.
I figure it out, that the Android Market has the layout of following structure :
<FrameLayout
android:id="#+id/artists_frame"
android:layout_width="fill_parent"
android:layout_height="64dip"
android:layout_below="#id/something1"
android:layout_above="#id/something2"
>
<!-- Some view there, which height >> 64dip -->
</FrameLayout>
So, I've tried various Animations / LayoutAnimations on the FrameLayout (via view.setAnimation(), view.setLayoutAnimation() and ScaleAnimation), but the effect is always the same : the view animates, but it's real layout_height after scaling is still the same (so the position of the other views, that depend on the given FrameLayout, remain the same).
After that, I've thought - I change in the loop the layout_height of the given FrameLayout:
layoutParams = view.getLayoutParams()
layoutParams.height = scaleTo;
layout.setLayoutParams(layoutParams);
I've it got animating, market-like view, but the cost (performance!!!) is way to high...
So, the question is : Is there any other, proper way to scale (change e.g. height from 50dip to 200dip) the given View / ViewGroup so that the position of the views below the animating view also changes?
As Chet Haase says in this blog post: http://android-developers.blogspot.com/2011/02/animation-in-honeycomb.html
"...Finally, the previous animations changed the visual appearance of the target objects... but they didn't actually change the objects themselves..."
Since what your need is only visual, instead of changing the view's LayoutParameters I would animate the view in the bottom as well with a translate using android:fillAfter for both.
Related
I have a fairly complicated situation where I need to either process events in a custom view, which is added via WindowManager, or pass them to the underlying window if it is outside of the wanted area. The wanted area is the containerView where it can be smaller from the root view itself, or may have equal width / height.
The view has a size of 28x28, but it can grow up until 60x60. The growing part is done with ValueAnimator where current width and target width is determined by the ValueAnimator.getAnimatedValue() (in this case, between 28 and 60). The window needs to consume the event if it has been clicked on, or the target view which may be smaller than the window itself, is clicked.
An example of the layout looks like this:
<FrameLayout android:layout_width="wrap_content"
android:layout_height="wrap_content">
<FrameLayout android:id="#+id/containerView"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_gravity="center">
<!-- rest of the view, not important -->
<!-- the containerView can have 28x28 size or
60x60 size -->
</FrameLayout>
</FrameLayout>
The animated view is the one that is defined with android:id="#+id/containerView".
I've tried to attach the view using regular layout params, like this, to make the window layout dynamic:
WindowManager manager = context.getSystemService(WindowManager.class);
View rootView = LayoutInflater.from(context).inflate(resId, null, false);
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
params.flags = FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH;
manager.addView(rootView, params);
And this similar code block adds the view with 28x28 size, that's not a problem. However, while animating to 60x60 size depending on a state change (on containerView), the animation flickers a lot. I guess it happens because both view itself and the window needs to be re-sized. I've tried to use setLayerType(HARDWARE, null) but that didn't seem to work. Then I've found another workaround, which is increasing the size of the window directly, before starting the animation, by giving it fixed width-height values, like this:
params.width = dpToPx(60);
params.height = dpToPx(60);
manager.updateViewLayout(rootView, params);
And after this, I start the growing animation, which changes the containerView width and height gradually. With this way, animation is smooth, even on lower-end devices so I think it's a good optimization.
The problem begins with the window size change. You see, containerView has to have the attribute android:layout_gravity="center" to position the view to window's center. But, increasing the window width and height changes the view's position. To overcome that, I've decided to write another approach by doing something like this:
// This method is inside the root view, which contains
// the WindowManager.LayoutParams as its layout params.
private void setWindowSize(int widthPx, int heightPx)
{
WindowManager.LayoutParams params = getLayoutParams(); // ignore cast
int oldWidth = params.width;
int oldHeight = params.height;
int differenceWidth = widthPx - oldWidth;
int differenceHeight = heightPx - oldHeight;
// Position the view relatively to the window so
// it should look like its position is not changed
// due to containerView's center layout_gravity.
params.x -= differenceWidth / 2;
params.y -= differenceHeight / 2;
params.width = widthPx;
params.height = heightPx;
// Update itself since this is already the root view.
manager.updateViewLayout(this, params);
}
The code above was causing the position change happening with animation. Hence, I've searched if this animation can be disabled, and found an answer here which seems to be working with Android 10 emulator. However, I don't think this is a reliable approach, as most manufacturers change source codes of framework classes to implement their own themes etc. so I'm looking for a more reliable approach. The change also cause a flicker due to the containerView.onLayout() operation, presumably happening after manager.updateViewLayout() is executed, where it appears on top-left for one frame and on center on the 2nd frame, visible to the eyes.
At this point, I can only think of some ways to prevent these bugs:
1) Process touch events only on certain states (such as the coordinates intercepting the containerView)
2) Make the view non-touchable after receiving MotionEvent.ACTION_OUTSIDE which will indicate a touch event happened outside of the view's boundaries.
1st one has a flaw: If the view is clickable in all cases, it becomes clickable starting from the root view, and once the touch event is received from that view, it is not transferred to other windows (a.k.a underlying applications) which cause an issue.
2nd one seemed a good approach for me, but the event MotionEvent.ACTION_OUTSIDE does not contain any specific x or y coordinates so it is impossible to tell if the event occurred in window's boundaries. If this was possible, I'd add FLAG_NOT_TOUCHABLE to layout params and updated the view, and removed that flag if the touch is to be processed.
So, my question is:
Can a custom view, that has been added with a WindowManager choose to deliver the events further based on, i don't know, returning false from dispatchTouchEvent() or something? Or, is there a way to receive all touch events even outside our application with the specific screen coordinates so I can change the window flags depending on it?
Any help is appreciated, thank you very much.
I was able to resolve the issue by applying an ugly hack. I've used a second window, where the window itself is full screen and contains flags FLAG_NOT_FOCUSABLE and FLAG_NOT_TOUCHABLE and since the touch is disabled the events are passed below window.
The window-resize flickering depending on the animation was the cause, so I've thought about using a temporary view, added the view to a second window, by getting the cache of the view itself using a bitmap and a canvas (the states are cached and recycled by the way), and making the image view visible, setting the view on the original window as INVISIBLE and after making sure it became invisible (by using ViewTreeObserver.addOnDrawListener because the draw function is called) changing window size.
With this approach, the view becomes already invisible while the window size is changed, and translated accordingly, which eliminated the possibility of the buggy view.
Then, after the layout is complete (I've also made sure by using ViewTreeObserver.addOnGlobalLayoutListener() and waiting for the view to be placed on the target coordinates relative to parent), switched the views. Extra memory is used because of the extra added window and image view and bitmap, but the issue seems to be resolved.
The only remaining thing is how to disable window animations with the call windowManager.updateViewLayout() because the flag that the other question mentioned is apparently added in API 18, whereas this app targets to API 16. On the rest of the emulators and the devices that I've tested on seem to have this flag consistently, and the window translate animations seem to be disabled successfully.
I am new to Android and currently having some issues with layout. Here is my problem:
View view = (View)findViewById(R.id.MiddleMan);
view.layout(240,358,240+view.getWidth(),358+view.getHeight());
width is 2 dp, height is match_parent. This view is inside a relative_layout, which is inside a linear_layout. This coordinates are relative to linear_layout
When I run, instead of being exactly at this coordinate, it keeps sticking to the right edge of the parent. Can anyone show me how I can deal with this problem, pls ? Appreciate a lot
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.
Can I Scale A Layout(e.g LinearLayout) from 0.5 to 1.0 while Keeping the size And position of the TextView inside the layout to 1.0 During the whole transformation (the overflow parts should hidden)?
I see That The Scale Animation in IOS Keep the scale of the Children And How Can I Achieve this in Android ?
Plus,On Starting Animation, The Parent Layout of the animated block has already make space for it,But in IOS ,the parent makes room gradually during the animation, does it have an option for me to do the same thing in Android ?
This can be done pretty easily with the Property Animation API.
havent tried it on LinearLayout but if the view children are ordered correctly with their "layout_width" & "layout_height" you can use the ValueAnimator class to scale the parent view via LayoutParams.width & height.
Only setback is API 11 and above.
How can I change the position of view through code? Like changing its X, Y position. Is it possible?
For anything below Honeycomb (API Level 11) you'll have to use setLayoutParams(...).
If you can limit your support to Honeycomb and up you can use the setX(...), setY(...), setLeft(...), setTop(...), etc.
Yes, you can dynamically set the position of the view in Android. Likewise, you have an ImageView in LinearLayout of your XML file. So you can set its position through LayoutParams.But make sure to take LayoutParams according to the layout taken in your XML file. There are different LayoutParams according to the layout taken.
Here is the code to set:
LayoutParams layoutParams=new LayoutParams(int width, int height);
layoutParams.setMargins(int left, int top, int right, int bottom);
imageView.setLayoutParams(layoutParams);
There are different valid answers already, but none seems to properly suggest which method(s) to use in which case, except for the corresponding API level restrictions:
If you can wait for a layout cycle and the parent view group supports MarginLayoutParams (or a subclass), set marginLeft / marginTop accordingly.
If you need to change the position immediately and persistently (e.g. for a PopupMenu anchor), additionally call layout(l, t, r, b) with the same coordinates. This preempts what the layout system will confirm later.
For immediate (temporary) changes (such as animations), use setX() / setY() instead. In cases where the parent size doesn't depend on WRAP_CHILDREN, it might be fine to use setX() / setY() exclusively.
Never use setLeft() / setRight() / setBottom() / setTop(), see below.
Background:
The mLeft / mTop / mBottom / mRight fields get filled from the corresponding LayoutParams in layout(). Layout is called implicitly and asynchronously by the Android view layout system. Thus, setting the MarginLayoutParams seems to be the safest and cleanest way to set the position permanently. However, the asynchronous layout lag might be a problem in some cases, e.g. when using a View to render a cursor, and it's supposed to be re-positioned and serve as a PopupMenu anchor at the same time. In this case, calling layout() worked fine for me.
The problems with setLeft() and setTop() are:
Calling them alone is not sufficient -- you also need to call setRight() and setBottom() to avoid stretching or shrinking the view.
The implementation of these methods looks relatively complex (= doing some work to account for the view size changes caused by each of them)
They seem to cause strange issues with input fields: EditText soft numeric keyboard sometimes does not allow digits
setX() and setY() work outside of the layout system, and the corresponding values are treated as an additional offset to the left / top / bottom / right values determined by the layout system, shifting the view accordingly. They seem to have been added for animations (where an immediate effect without going through a layout cycle is required).
There is a library called NineOldAndroids, which allows you to use the Honeycomb animation library all the way down to version one.
This means you can define left, right, translationX/Y with a slightly different interface.
Here is how it works:
ViewHelper.setTranslationX(view, 50f);
You just use the static methods from the ViewHelper class, pass the view and which ever value you want to set it to.
I would recommend using setTranslationX and setTranslationY. I'm only just getting started on this myself, but these seem to be the safest and preferred way of moving a view. I guess it depends a lot on what exactly you're trying to do, but this is working well for me for 2D animation.
You can try to use the following methods, if you're using HoneyComb Sdk(API Level 11).
view.setX(float x);
Parameter x is the visual x position of this view.
view.setY(float y);
Parameter y is the visual y position of this view.
I hope it will be helpful to you. :)
For support to all API levels you can use it like this:
ViewPropertyAnimator.animate(view).translationYBy(-yourY).translationXBy(-yourX).setDuration(0);
Set the left position of this view relative to its parent:
view.setLeft(int leftPosition);
Set the right position of this view relative to its parent:
view.setRight(int rightPosition);
Set the top position of this view relative to its parent:
view.setTop(int topPosition);
Set the bottom position of this view relative to its parent:
view.setBottom(int bottomPositon);
The above methods are used to set the position the view related to its parent.
Use LayoutParams.
If you are using a LinearLayout you have to import android.widget.LinearLayout.LayoutParams, else import the proper version of LayoutParams for the layout you're using, or it will cause a ClassCastException, then:
LayoutParams layoutParams = new LayoutParams(int width, int height);
layoutParams.setMargins(int left, int top, int right, int bottom);
imageView.setLayoutParams(layoutParams);
NB: Note that you can use also imageView.setLeft(int dim), BUT THIS WON'T set the position of the component, it will set only the position of the left border of the component, the rest will remain at the same position.
Use RelativeLayout, place your view in it, get RelativeLayout.LayoutParams object from your view and set margins as you need. Then call requestLayout() on your view. This is the only way I know.
In Kotlin you can do it as below;
view
.animate()
.x(50f)
.y(100f)
.duration = 500L
I found that #Stefan Haustein comes very close to my experience, but not sure 100%. My suggestion is:
setLeft() / setRight() / setBottom() / setTop() won't work sometimes.
If you want to set a position temporarily (e.g for doing animation, not affected a hierachy) when the view was added and shown, just use setX()/ setY() instead. (You might want search more in difference setLeft() and setX())
And note that X, Y seem to be absolute, and it was supported by AbsoluteLayout which now is deprecated. Thus, you feel X, Y is likely not supported any more. And yes, it is, but only partly. It means if your view is added, setX(), setY() will work perfectly; otherwise, when you try to add a view into view group layout (e.g FrameLayout, LinearLayout, RelativeLayout), you must set its LayoutParams with marginLeft, marginTop instead (setX(), setY() in this case won't work sometimes).
Set position of the view by marginLeft and marginTop is an unsynchronized process. So it needs a bit time to update hierarchy. If you use the view straight away after set margin for it, you might get a wrong value.
One thing to keep in mind with positioning is that each view has an index relative to its parent view. So if you have a linear layout with three subviews, the subviews will each have an index: 0, 1, 2 in the above case.
This allows you to add a view to the last position (or the end) in a parent view by doing something like this:
int childCount = parent.getChildCount();
parentView.addView(newView, childCount);
Alternatively you could replace a view using something like the following:
int childIndex = parentView.indexOfChild(childView);
childView.setVisibility(View.GONE);
parentView.addView(newView, childIndex);