Difference in RelativeLayout and other in onMeasure - android

So, basically i have a custom view :
public class CustomView extends PercentFrameLayout
As you can see i've inherited it from PercentFrameLayout(might be the reason for this issue, but i'm not sure)
Inside of this view i've inflated layout .xml file:
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center">
<View
style="#style/SomeStyle"
app:layout_widthPercent="98%"
app:layout_heightPercent="98%"/>
....
</merge>
Also, in this custom view, i've overriden onMeasure method to multiply it's size by 2(just for test):
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(MeasureSpec.getSize(widthMeasureSpec) * 2|MeasureSpec.EXACTLY, MeasureSpec.getSize(heightMeasureSpec) * 2|MeasureSpec.EXACTLY);
}
Currently this custom view is in RelativeLayout:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center">
<com.example.CustomView
android:id="#+id/progress_new_collagen"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_centerHorizontal="true"/>
</RelativeLayout>
The issue: Currently, its size not multiplied by two, its multiplied by, like 4 for width and 3 for height, and i don't know why.
The strange thing which made me want to ask a question: If it's not RelativeLayout as parent to this view, but any other ViewGroup class, like FrameLayout, LinearLayout and so on - code is working. It's also working if i just remove onMeasure. Is there any difference in this ViewGroups in case of onMeasure ?

RelativeLayout will call onMeasure() twice each time it is rendered. I think that this is why you are getting your strange results. If the second onMeasure() uses the results from the first call, that you will not double, but quadruple your dimensions.

Related

Android: clipChildren=false and onClickListner for clipped children

I have a problem.
We have layout custom frame layout with scaling and translating functionality
public class MyFrameLayout extends FrameLayout {
...
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
getChildAt(0).setTranslationX(vTranslate.x);
getChildAt(0).setTranslationY(vTranslate.y);
getChildAt(0).setScaleX(mScale.scaleFactor);
getChildAt(0).setScaleY(mScale.scaleFactor);
}
The class listens for touch gestures and translations/scale its child.
I use it as parent viewgroup for my ViewGroup with content.
<MyFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/gray"
android:clipChildren="false">
<android.support.constraint.ConstraintLayout
android:id="#+id/calc_constraint_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clipChildren="false"/>
Content for constraint layout adds programmaticaly and it is bigger then screen or parent group. Because of it we set clipChildren=false.
But the children which was out of bounds before transation don't response on onClick events.
Some pictures:
Start screen
After translation:
The 6% node just doesn't answer to clicks.
Please, help me.
Add:
Well. I couldn't find the way to make constraint layout(white one) to fit the content with wrap_content value. My workaround was to dynamicaly increase width and height of the constraint layout accordingly width and height of my node tree.
When you add additional node - layout measures grows and you see how it tweaks abit. Then to fix it i had to override onLayout() method in parent layout (It's green on the screenshots, but it was custom ZoomableLayout).
And now everything works fine. But i'm not sure if this implementation ugly or not.

fake a View to portrait inside landscape-mode

I have an Activity in Landscape-Mode. Inside there is a Custom-Title-View aligned like this:
Is it possible to keep the landscape mode and 'fake' this one View into portrait-mode like this:
I have tried to overwrite my custom TitleView and put something like this to draw(Canvas)
public class VerticalTitle extends Title{
public draw(Canvas){
canvas.save();
canvas.rotate(getWidth() / 2, getHeight() / 2);
// i tryed many translations, but get none to work
canvas.translate(0, getHeight());
super.draw(canvas);
canvas.restore()
}
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
}
}
If it is on any interest, The TitleBar extends from RelativeLayout and has fixed height and fill_parent width
The setRotate parameter from View is not an option, because the app should stay compatible to 2.2.
you have to extend Text view And build your custom vertical TextView first see these two links for more details
Vertical TextView
Vertical rotated label
and after that in your Layout-land you can use this Xml file for your layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<com.yourpackage.CustomVerticalTextView
android:layout_width="42dp"
android:layout_height="fill_parent"
android:text="#string/hello"
android:background="#0000FF" />
</LinearLayout>
If you want to rotate the layout instead of rotating all of its children by 90 degrees use
LayoutAnimationController
see this SO thread for more details
I have never done this but I found two other answers here on SO that show ways you might accomplish this.
The first one involves extending the TextView class, and is here. Since you want to do a whole view though, I think the next answer is better suited to your situation.
The other answer is to create an animation using rotation to deal with it. That answer is here.

Android get Fragment width

I have a layout with 3 fragments:
<LinearLayout android:id="#+id/acciones"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<LinearLayout
android:id="#+id/fragment1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1">
</LinearLayout>
<LinearLayout
android:id="#+id/fragment2"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1">
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:id="#+id/f3"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1">
</LinearLayout>
</LinearLayout>
In the first fragment I have a TableLayout in which I have one custom TextView in each row.
I want to know the width of the fragment because if the custom TextView is wider than the fragment, I'll set the number of lines necessary.
This is what I've done in my custom TextView:
#Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mMaxWidth = (float) (getMeasuredWidth());
}
With this line I got the width from the three Fragments, not only the one which contains the custom TextView.
Thanks.
You should be able to set the width of the TextView to be fill_parent, in which case it will do the wrapping for you. You should not set the widths of your layouts to be match_parent since it is inefficient when you're using layout weights.
Since android's layout system is occasionally mysterious with regards to view sizes, if setting the TextView width to be fill_parent actually makes it take up the whole screen (as your question appears to be implying) do the following:
Set your TextView width to 0 by default. In onCreate of your activity, after setting the content view:
findViewById(R.id.acciones).getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
final int fragmentWidth = findViewById(R.id.releventFragmentId).getWidth();
if (fragmentWidth != 0){
findViewById(R.id.yourTextViewId).getLayoutParams().width = fragmentWidth;
}
}
});
By setting the TextView's width to 0 initially, you prevent it from changing the widths of the fragments. Then you can use a view tree observer to get the width of whatever fragment you're interested in (by looking at its root view) after layout has occurred. Finally you can set your TextView to be that exact width, which in turn will do the wrapping for you automatically.
Note that onGlobalLayout can be called multiple times and is regularly called before all of the views have been completely laid out, hence the != 0 check. You will also probably want to do some kind of check to make sure that you only set the width of the text view once, or otherwise you can get into an infinite layout loop (not the end of the world, but not good for performance).

How to force a redraw of the views in Activity.onConfigurationChanged

I've got three views in my activity in a linear vertical layout. The top and bottom views have fixed heights and the middle view takes whatever height is left available. This is how I set the sizes for the views:
void resize(int clientHeight)
{
int heightMiddle = clientHeight - heightTop - heightBottom;
topView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, heightTop));
middleView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, heightMiddle));
bottomView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.FILL_PARENT));
}
In order to obtain the clientHeight, I overrode the onMeasure() function and call resize() inside my overridden onMeasure() function. This works well in onCreate(). However when the phone orientation changes, it does not work. What I observed is that after onCreate(), onMeasure() is called twice. After onConfigurationChanged(), onMeasure() is only called once and my resizing code does not get a chance to take effect. My kluge solution is to setup a timer to call resize() 20ms later:
timer.schedule(new TimerTask()
{
#Override
public void run()
{
activity.runOnUiThread(new UiTask());
}
}, 20);
where UiTask will simply call resize(). This works for me, but I feel that there's got to be a better solution. Can someone shed some light on this?
Why not let LinearLayout do the layouting with the help of the android:layout_weight attribute? That way, you don't need any extra code at all, everything will just work. Here's what your res/layout/main.xml could look like:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="Top"
android:layout_weight="0"
android:background="#ff0000"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Middle"
android:layout_weight="1"
android:background="#00ff00"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="25dp"
android:text="Bottom"
android:layout_weight="0"
android:background="#0000ff"
/>
</LinearLayout>
And with no more code other than the regular
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
it would look like this in portrait:
and like this in landscape (automatically relayouted and redrawn):
This works both with and without android:configChanges="orientation" for the activity in the manifest. You'll also be able to setup the above layout using Java code if you need to.

Fullscreen App, Layout shifted down by height of notification bar

I have an App that has a toolbar at the bottom of the screen and the rest is filled with a custom View (see xml below). Now when I make the App full screen (I tried all possibilities, programmatically and via Manifest.xml), when it's started the whole layout seems to be shifted down by about the height of the notification bar. The buttons in the toolbar are only visible half-way. Sometimes, all of it moves up after a few seconds, or when I click a button in the toolbar.
I'm pretty sure, that it's a problem with my custom view, because I do not get this effect if I replace it with a Button or the like. I guess it must have something to do with the onMeasure method. I don't really know how to implement it, my version is shown below. The custom view is used for drawing inside, so basically it wants to be as large as possible.
Any help would be much appreciated. I searched for several hours already, but no clue yet.
layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<com.example.MyCanvasView
android:id="#+id/canvas"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
/>
<!-- Buttonbar -->
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingTop="4dp"
android:orientation="horizontal"
android:background="#android:drawable/bottom_bar"
>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="2"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="3"
/>
</LinearLayout>
</LinearLayout>
And this is my onMeasure method:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(width, height);
}
You're not taking the mode into account when you're setting your measurement.
The mode of a MeasureSpec can be one of MeasureSpec.EXACTLY, MeasureSpec.AT_MOST, or MeasureSpec.UNSPECIFIED. Simply accepting the size component and setting your measured size to that is appropriate for EXACTLY, but it isn't often the right thing for the others.
Because you're trying to use layout_weight in addition to a height of wrap_content on this custom view, the following is happening:
Your custom view is the first child of the LinearLayout with a height of wrap_content so LinearLayout measures it. Since LinearLayout has been told by the LayoutParams that it should wrap_content, it measures your custom view with a MeasureSpec mode of AT_MOST and a size of the entire available space.
But your custom view is greedy. It decides to take all of the space available. In essence, you have implemented your measurement to treat wrap_content as match_parent.
Now there's no more space left. The lower button bar gets measured accordingly but it's not done yet, there's a child with weight. In a LinearLayout any space left over after all normal measurement is complete is divided among the weighted children according to their weight values. This isn't the behavior you want.
When you use weight to fill available vertical space like you're doing in this layout, you normally want to set the layout_height to 0dip. This will make LinearLayout skip the first measure pass on that child and only use the weighted measurement pass to measure your view, giving it the remaining available space.
I found the reason for the described behaviour. I had set the view to be focusable in touchmode via setFocusableInTouchMode(true) in the onCreate() method. As soon as I removed this, it works fine. Thanks to adamp though -- your description of what goes on during layout and measuring was very interesting.
But that leaves me with the problem that I do not receive any key/button clicks any more :-(

Categories

Resources