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.
Related
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.
EDIT: After more investigation, it turns out that this has nothing to do with the ViewPager. The issue is with setting a visibility attribute on a ViewGroup in XML, then attempting to change it at runtime. I'm leaving the original question as it is. See my answer below for more information.
I have a layout with a ViewPager and a custom PagerIndicator class. On the 0th page of the ViewPager, I want the indicator to be View.GONE. On other pages, I want it to be View.VISIBLE. Here's my code, which is called during onCreate:
void setupPager() {
mPager.setAdapter(new TutorialPagerAdapter());
mPager.setOnPageChangeListener(new OnPageChangeListener() {
#Override
public void onPageSelected(int currentPage) {
Log.d(TAG, String.valueOf(currentPage));
if (currentPage == 0) {
mPagerIndicator.setVisibility(View.GONE);
} else {
mPagerIndicator.setVisibility(View.VISIBLE);
}
mPagerIndicator.setCurrentPage(currentPage);
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
#Override
public void onPageScrollStateChanged(int arg0) {
}
});
}
The mPagerIndicator is set with android:visibility="invisible" in the XML layout. When I scroll between pages, I can see that the callback is being called, and the page number is correct. However, the pager doesn't appear.
This is where it gets bizarre: I loaded up the Android Hierarchy Viewer that comes with the SDK. When it loaded the view hierarchy from the emulator, POOF, the indicator appeared. It also doesn't seem to be a problem on a physical device. (EDIT: After some more testing, it appears to be an issue with 2.3, as it doesn't happen on higher versioned devices, but does happen on a 2.3.6 phone.)
Any idea why this is happening? Is it reasonable to assume that this is just a quirk of the emulator, or should I be worried that it won't work on some devices? Any hacks to get it to show up? What does the hierarchy viewer do that might be forcing it to refresh itself?
What do you mean by "it doesn't seem to be a problem on a physical device"? Do you mean you see the indicator OK, or that the indicator is visible in the HierarchyViewer?
Everything looks OK to me in your code. Sounds like your indicator is simply covered by another view - maybe the ViewPager - but it exists just fine, therefore you can see it in the HierarchyViewer.
Try removing the code above to toggle visibility, then set your PagerIndicator to View.VISIBLE in the xml. If you still can't see it, then there's your problem.
It appears that there is an issue in Android 2.3.3+ involving visibility of ViewGroups. Setting the ViewGroup with android:visibility=X in the XML seems to set the visibility of all of the subviews. However, when changing the visibility at runtime, it does not apply it to the subviews. Because of this, starting with a visibility of gone or invisibile will cause setting the visibility later to fail.
The solution is to override setVisibility in my custom view, to make it set the visibility on all of its subviews as well:
#Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
for (ImageView image : mImages) {
image.setVisibility(visibility);
}
}
Thanks to http://www.kittehface.com/2011/03/view-visibility-bug-on-android-233.html and android setVisibility does not display if initially set to invisble for getting me pointed in the right direction.
I have seen some widgets using addView and sometimes addViewInLayout.
What is the difference between them?
What will happen if I replace one with the other?
Should I keep a flag during layout and use "addViewInLayout" or "addView" accordingly?
Thanks.
BR,
Henry
ps. add more tags: removeview, removeviewinlayout
Its generally a bad idea to call addView inside onLayout because addView internally triggers a requestLayout which eventually will call onLayout. So you end up triggering a layout while you are in the middle of a layout.
addViewInLayout is a "safer" version of the addView in the case you really have to add a new view in onLayout. It basically doesn't trigger a layout pass (doesn't call requestLayout internally).
See this video (by android engineer) for more detail: http://www.youtube.com/watch?v=HbAeTGoKG6k
addViewInLayout
Adds a view during layout. This is useful if in your onLayout() method, you need to add more views (as does the list view for example). If index is negative, it means put it at the end of the list.
addView
Assign the passed LayoutParams to the passed View and add the view to the window.
*Note that addView is implemented by ViewManager, an Interface to let you add and remove child views to an Activity, so you will be able to add views to ViewGroup at run time (DYNAMICALLY). Also note that addViewInLayout is a protected method of ViewGroup so if you are doing to create a custom view group you can call addViewInLayout() in onLayout() method.
For more see this
for example: we have a Layout (mLayout) and you want to add 2 views (view1, view2) into this layout.so there are 2 ways (the same)
case1: simply you use following command
mLayout.addView(view1); //onLayout() will be called first time
mLayout.addView(view2); //onLayout() will be called second time after the first time.
in this case, you don't care function onLayout(). it is simple source code.
case2: complicate but better performance
//do something to <global variable>
bCheck = true; //check it in fuction onLayout()
requestLayout(); //use this function to call onLayout() function for only one time
//in onLayout() function of mLayout, you use addViewInLayout()
//addViewInLayout() dont call onLayout() function, so you can add 2 views with only one time to call onLayout()
//onLayout() is abstract function, so mLayout is a instant of subclass of ViewGroup (ex: RelativeLayout, LinearLayout....)
#Override
onLayout(boolean changed, int l, int t, int r, int b)
if(bCheck == true){
v.addViewInLayout(view1); //add view1 to mLayout
v.addViewInLayout(view1); //add view2 to mLayout
bCheck = false;
}
}
});
I have no time to test it. anyone can help me make it more clear.
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.
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);
}