layout function doesn't change View dimension. - android

I am trying to create a view that slides up from the bottom of the screen. I tried setting the initial position of the view (which should be offscreen) in xml, but instead of placing the imageview where I specified, it truncated it. My second thought was to set the position of the view programatically inside the onWindowFocusChanged method. Here's my code
#Override
public void onWindowFocusChanged(boolean hasFocus) {
if(hasFocus) {
slide_dock.layout(slide_dock.getLeft(), phone_height - 70, slide_dock.getRight(), phone_height + 230);
}
}
The problem is that this only works SOME of the time. I've been debugging it, and I believe the issue is that the layout values of slide_dock get altered after my onWindowFocusChanged function completes, I'm just not sure where. Can anyone here help me out? Or link me to somewhere that explains the layout process in-depth? I've been searching around to no avail.

Have you tried using the Animation framework? Use a RelativeLayout and align your child view to the bottom of the parent. Then use the following animation, maybe showing and hiding your view appropriately with View.setVisibility(int)
View myView = View(this);
TranslateAnimation slideUp = new TranslateAnimation(myView.getHeight(), 0, 0, 0);
slideUp.setDuration(250); // millis
slideUp.setFillAfter(true); // Required for animation to "stick" when done
myView.startAnimation(slideUp);
You might have to play with the TranslateAnimation constructor parameters to get it to work right (this is from the top of my head).

So I figured out the cause for the issue above, and I'm posting it here in case anyone ever runs into the same problem.
The reason why the ImageView was resizing was because in ImageView's onMeasure function, it resizes itself if it doesn't think that it will fit onto the screen. You can view the ImageView source here and see how it works: http://www.google.com/codesearch/p?hl=en#uX1GffpyOZk/core/java/android/widget/ImageView.java&d=3
To work around this, I created a custom view that extended ImageView and overrode the onMeasure method. In my new onMeasure method, I simply called setDimension to give my new view the dimensions that I wanted it to have, this effectively stopped the view from resizing as it was doing earlier
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec), 300);
}

Related

Android equivalent to View.setTranslationX, but to adjust width?

I have been spending hours unsuccessfully trying to adjust the width, height, and offset of a simple view in Android as the result of a button press. I have discovered that setTranslationX and setTranslationY always work; the legacy method of setLayoutParams never works once the view is laid out initially. Calls to requestLayout() and invalidate() similarly produce no results.
I have tried to setLayoutParams within the context of posting a runnable, but this does nothing.
Because setTranslationX always works, I would just use that, but unfortunately there is no equivalent method like setWidth or setHeight.
As you can see in the AOSP, setTranslationX makes a call to invalidateViewProperty, which is a private method of View.
Is there an equivalent method to setTranslationX to adjust a view width or view margin, that presumably triggers invalidateViewProperty, and, by extension, works reliably?
EDIT
While in some situations, setLayoutParams may be expected to work after the initial layout, I am in a situation where setLayoutParams has no effect after the initial layout, but setTranslationX does. My setup is as follows:
Running Android KitKat 4.4
The view in question is MATCH_PARENT for both width, height
The view in question is a child of a RelativeLayout
The view in question is a View class with a simple solid-color background drawable
Here is the view:
<View
android:id="#+id/border"
android:background="#drawable/match_background_border_transparent"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
And here is the (non-working) code meant to dynamically alter its margins, but has no effect. Again, if I call setTranslationX, that always works.
holder.toggleButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
imageBorder.post(new Runnable() {
#Override
public void run() {
RelativeLayout.LayoutParams p = (LayoutParams) imageBorder.getLayoutParams();
p.leftMargin = 20;
p.rightMargin = 20;
p.topMargin = 20;
p.bottomMargin = 20;
imageBorder.setLayoutParams(p);
// imageBorder.setTranslationX does have an effect if I included it here
}
});
}
});
I have determined why setTranslationX was working, but setLayoutParams was not. My views were ultimately descendents of an AdapterView. I was able to programmatically manipulate LayoutParams of the AdapterView and his siblings, but none of the AdapterView's descendents.
Additional research showed that this was a common Android question:
Margin on ListView items in android
Why LinearLayout's margin is being ignored if used as ListView row view
What was confusing was that this view was several levels deep; i.e., it went:
AdapterView -> FrameLayout -> RelativeLayout -> View
Anyhow, I was able to accomplish my programmatic layout goals by wrapping view in another view, and using setPadding.

Layout a view after resizing

I'm creating a grid that displays values that change very often. Because of this, I'm using a TextView that autoresizes when its content changes (Auto Scale TextView Text to Fit within Bounds). The resize takes place, but the view doesn't layout properly
The thing is, when I examine the activity with HierarchyViewer the layout displays as I want.
My guess is that HierarchyViewer invokes requestLayout() or invalidate() on the view, but I've tried that with no success. This code is invoked in the main activity with no effect.
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
getWindow().getDecorView().requestLayout();
getWindow().getDecorView().invalidate();
}
}, 5000);
I've also tried invalidating the view after resizing.
The TextView has gravity set to Center, and if no resize takes place, it looks ok.
Any hint will be welcome, thanks in advance!
I solved it by overriding onLayout in one of the TextView's parent and using a Handler created in the constructor
public class CellView extends LinearLayout{
public CellView(Context context) {
super(context);
mHandler = new Handler();
View.inflate(context, R.layout.cellview, this);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if(changed){
mHandler.post(new Runnable() {
#Override
public void run() {
requestLayout();
}
});
}
super.onLayout(changed, left, top, right, bottom);
}
I had tried calling requestLayout inside TextView's onLayout, tho it didn't work, I'm not sure why. It might be because the value was updated through an Observer, but the onTextChanged listener should happen in the UI Thread. I hope it serves someone else
The way requestLayout() works is that when called on a view, it will schedule a layout pass on itself and all of it's children. This is desirable whenever a view has shifted or resized do to margin, padding, or content changes.
The documentation on the method getDecorView() isn't very clear on what exactly it gives you. However, per the documentation on the website:
Note that calling this function for the first time "locks in" various window characteristics as described in setContentView(View, android.view.ViewGroup.LayoutParams).
This leaves me to believe that there's something special about the view that getDecorView() retrieves. What you are probably doing is making the layout of the view permanent thus never changing when you make a requestLayout() pass.
This is apparently the proper way to get the root view of your entire activity.
However, for efficiency reasons, I recommend calling requestLayout() on the lowest child you possibly can. Like I said before, it schedules a layout pass on a view and it's children. If you do a layout pass on the top-most view, you're essientially rebuilding everything which includes the views that stay in place.
You probably need to run the timer code in the UI thread using runOnUiThread as explained here.

How to bring Animation to front: bringToFront and zAdjustment/Z-ordering

I have a LinearLayout with two views in it, next to each other. When a view is tapped upon, it animates to full screen. This goes fine for the view on the right, but it fails for the left view:
I am unable to animate the left view on top of the right view
Without corrupting my layout with bringToFront(). Adjusting the Z-order of my animation does not seem to work.
Not a solution: The problem is gone when I use "brintToFront()" on the left view, but this causes my layout to be completely corrupted afterwards, and there is no brintToBack() function or whatsoever. => brintToFront = not a good solution?
Adjusting the Z-order of my animation does not seem to work (does not change anything).
scaleAnimation.setZAdjustment(Animation.ZORDER_TOP);
translateAnimation.setZAdjustment(Animation.ZORDER_TOP);
AnimationSet set = new AnimationSet(true);
set.addAnimation(scaleAnimation);
set.addAnimation(translateAnimation);
set.setZAdjustment(AnimationSet.ZORDER_TOP);
myFrameLayout.startAnimation(set);
Why does Z-ordering not work as expected?
This should be possible if you extend LinearLayout and override the following method:
getChildDrawingOrder (int childCount, int i)
To make sure layout uses your function you need to call setChildrenDrawingOrderEnabled(true)
See ViewGroup javadoc
For your z reordering will apply you gonna need to request a new layout on the view, but try doing it before starting your animation:
AnimationSet set = new AnimationSet(true);
// init animation ...
scaledView.bringToFront();
scaledView.requestLayout();
myFrameLayout.startAnimation(set);
I guess there is no good answer to this.
I am now animating everything else on the screen too, so if view 1 has to grow larger on top of view 2 than I animate view 2 also, in such a way that it looks like view 1 is growing on top of view 2, while actually view 2 is getting smaller while view 1 is getting larger.
Quite a lot of work and a bad solution, but I guess the only solution.

How to properly move a view?

Can someone please explain to a noob the correct way to animate a View so its touch area and image actually move together?!
I have read lots of posts and questions and tutorials, but none explains what moves the layout and what moves the image such that I can animate a view and then leave it at its new position.
This is an example method I'm working with, trying lots of different combinations to no success. The view is in the parent RelativeLayout. It's a touchable menu of icons, and is animated with an xml resource on a click to slide off screen leaving just a little tab showing, where it needs to stay until clicked again.
public void RetractTools (View v){
final ImageView finalImage1 = (ImageView) findViewById(R.id.paintsView);
Animation slideout = AnimationUtils.loadAnimation(this, R.anim.slideout_tools);
slideout.setFillAfter(true);
slideout.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationEnd(Animation animation) {
finalImage1.setEnabled(true);
optionMenu.showing = false;
optionMenu.inMotion = false;
finalImage1.layout(1258, 668, 1697, 752);
finalImage1.setRight(1280);
finalImage1.invalidate();
}
#Override
public void onAnimationRepeat(Animation arg0) {
}
#Override
public void onAnimationStart(Animation arg0) {
finalImage1.setEnabled(false);
}
});
optionMenu.inMotion = true;
v.startAnimation(slideout);
}// End RetractMenu
No matter what I try, I encounter problems. setFillAfter does nothing when set in the xml file. Set programmatically, it leaves the image in the right place but the touch controls remain where the menu was. I have tried setLeft and setRight which apparently only move the image, not the view position, and all sorts of different layout options, and fill and no fill and invalidating and not, but can't solve it. I clearly don't undersatnd the underlying mechanics needed to position and render a view! :D
Thanks.
EDIT : Solution
For anyone having similar issues, this is how I have found to work with relative layouts. You create a LayoutParams object with the specified size, and then you can assign it positions. eg.
final RelativeLayout.LayoutParams position = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
position.leftMargin = 440;
Then assign that to your view
myView.setLayoutParams(position);
So in summary, you use a LayoutParams object as an interface to your view's position, rather than accessing the view's coordinates directly as I assumed.
What you have is basically fine, with two flaws:
You are using setFillAfter(), which is not especially useful
You are calling layout() and setRight() and stuff, which is not especially effective
Instead, in onAnimationEnd(), you need to modify the LayoutParams of the View to reflect the new position you want the widget to be in. The size and position of a widget is dictated by the layout rules it negotiates with its container. Initially, those are set via your layout XML resource. By modifying the LayoutParams at runtime, you are changing what those rules are.
What those LayoutParams are (LinearLayout.LayoutParams, RelativeLayout.LayoutParams, etc.) and what values you should specify in them, we cannot tell you, because we don't know what you are doing.

How to bring a view to front without calling bringToFront()?

There is apparently a bug in Android which breaks View.bringToFront.
If I have Views arranged in a GridView and want to bring a View to front by calling bringToFront(), it may get moved to the bottom right position in the GridView.
Something shady is going on there, so I can't use bringToFront(). When I don't call it, everything works fine. But the Views overlap - if I use scale animation to scale up a View, it's partially obscured by the Views to the bottom and to the right.
I've checked out bringToFront's source and it calls parent.bringChildToFront(...)
it's this method
public void bringChildToFront(View child) {
int index = indexOfChild(child);
if (index >= 0) {
removeFromArray(index);
addInArray(child, mChildrenCount);
child.mParent = this;
}
}
it apparently removes the View from itself! Without mercy! Is a ViewGroup so dumb that it can't manage Z-indexes in any other way that shuffling controls around? Obviously when GridView removes its child and then adds it back again, it gets placed at the end of the Grid!
Is there anything I can do to show a View on top of others without resorting to some hacking? One thing that comes to my mind is to create another View above the GridView, which will appear to be above the one I'm trying to bringToFront(), but I don't like this solution.
apparently I missed the android:clipChildren="false" property in GridView. It solves my problem.
Have you tried making the View focusable and just calling requestFocus()?
Calling bringToFront() changes the ordering in the GridView. Try creating an ImageView with an image of the view you want to animate and animate that instead.
Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
final ImageView imageView = new ImageView(getActivity());
imageView.setImageBitmap(bitmap);
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(view.getWidth() , view.getHeight());
imageView.setLayoutParams(params);
rootview.addView(imageView);
Add an animation listener to your animation and remove the ImageView at the end of the animation.
From API 21 you can call:
view.setZ(float)
If you target API above 21, you can simply add the attribute android:translationZ="xxx dp" to your XML.
Please note that if you add elevation in views like cardview, you go into the Z axis. And if you want a view come to foreground using this way, you just have to make it higher than the elevation you set.
Example : 6 dp elevation in cardview will require a 7dp in the attribute translationZ of the view you want foreground.

Categories

Resources