I am a starter in android programming. I am wondering is it possible for me to customize the size of a customized layout in program?
Here is the solution I am trying:
1. I created a customized Layout Class called MyLayout and write the onMeasure and onScale method as
MyLayout extends ViewGroup {
public double childWidth, childHeight;
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (getChildCount() > 0) {
//In the current version, we should only have one child view
View childView = getChildAt(0);
measureChild(childView, (int)(childWidth), (int)(childHeight));
}
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (getChildCount() > 0) {
View childView = getChildAt(0);
childView.layout(0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight());
}
}
}
create the XML file of this layout activity_mylayout, inside the layout I included a imageview as the child layout
<com.example.MyLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:id="#+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/android" />
</com.example.MyLayout>
Write the code for drawing the view and set the size of the child view it contains
setContentView(R.layout.activity_mylayout);
MyLayout usl = (MyLayout)findViewById(R.layout.mylayout);
if(usl == null) System.out.println("SSSS");
usl.childWidth = 200;
usl.childHeight = 200;
Now I am having the problem of having MyLayout as null and throws a null pointer exception. I am probably doing wrong in many places I guess, but any suggestions on the reason why the MyLayout is null?
Well the thing that immediately pops into my mind is that onMeasure must call setMeasuredDimension(int, int) before it exits or later calls to measure will fail. Even if the width and height are measured to 0 you need to do that.
The second thing is- if there's only one child, why does this view exist at all? All you're doing is adding overhead. Its probably the wrong design to have this layout at all, rather than just using the child view directly.
The third is that you're probably hitting the usl equals null case causing the null pointer exception, but its impossible to tell without the stack trace and xml being used.
Related
I am a new developer in Android. I am trying to develop an application in Android that displays data in a table format. My data scrolls both horizontally and vertically. My parent class contains FrameLayouts which acts as rows. Inside each framelayout the CustomViews(extended from viewGroup) are populated which act as columns. Am maintaining the FrameLayouts in the parent panel in a list FrameLayoutList and the customViews inside that FrameLayout in a List CustomViewsList in the child class. Initially I have created the views in each FrameLayoutClass(rows) that will fit into the view. All works fine. But Upon scrolling horizontally I am creating a new CustomView and storing it in a list. Then adding the newly created items from the list into the FrameLayout in OnMeasure method. But the problem is the OnMeasure is called for the parent, but is not called for the child FrameLayouts. But I have called view.Measure(int,int) in my parent's OnMeasure. I tried all possible Invalidate(), postInvalidate(), InvalidateChild(view,Rect) and requestLayout. Nothing solved this. Can anyone help me on this?
This the code in my Parent's OnMeasure:
protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int totalwidth = {some calculation...};
int totalheight = {some calculation...};
foreach (CustomFrameLayout row in this.FrameLayoutList) {
if (this.IndexOfChild(row) == -1)
this.AddView(row);
MeasureChild (row, 450, 750);
//row.Measure(450,750);
}
SetMeasuredDimension(totalwidth,totalheight);
}
When the MeasureChild is hit I expect the ChildLayout's OnMesure to be called. It is called for the first time when loading. But when scrolling horizontally the code is hit but the ChildLayout's OnMeasure is not called.
This is the code in my child's OnMeasure:
protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int totalwidth = {some calculation...};
int totalheight = {some calculation...};
foreach (CustomView column in this.CustomViewsList) {
if (column.IsNew) {
if (this.IndexOfChild(row) == -1)
this.AddView(column);
column.Measure(50,50);
}
}
SetMeasuredDimension(totalwidth,50);
}
Can anyone help me in this? how can get the OnMeasure called for the child class upon scrolling horizontally.
I have spent the whole day debugging various ways to add custom ViewGroup into another custom ViewGroup and nearly went crazy because none of them works, and there is no official documentation or sample that shows how it can be done...
Basically, I have 2 custom ViewGroup:
HorizontalDockView extends ViewGroup
GameEntryView extends FrameLayout
HorizontalDockView overrides onDraw, onMeasure, etc and everything is called normally and works perfectly.
However, when I create GameEntryView from inside HorizontalDockView's constructor and call addView(gameEntryView), the gameEntryView will never ever show regardless of the layoutParams, addView called from whatever thread, or however I call, load, and setContentView on the parent HorizontalDockView. If I list through the horizontalDockView.getChildAt(); all the gameEntryView objects are still there.
Hopeless, I try to debug through GameEntryView's onDraw, onMeasure, dispatchDraw methods and realized none of them actually get called! No.. not even once!
Do I need to iterate through all the child view in the parent (HorizontalDockView's) on* call and call the children's on* explicitly? I was just calling super.on*() on the parent.
I did call setWillNotDraw( false ); on both the parent and the child class.
How do I get the child to show up inside the parent's view? simple sample or existing small open source project is highly appreciated!
Thank you very much!
Did you overwrite onLayout? When Android lays out your ViewGroup, your ViewGroup is responsible for laying out the children.
This code is from a custom ViewGroup that lays out all children on top of each other:
#Override
protected void onLayout(final boolean changed, final int l, final int t, final int r, final int b) {
int count = this.getChildCount();
for (int i = 0; i < count; i++) {
View child = this.getChildAt(i);
child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
}
}
For completeness, the onMeasure override:
#Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(parentWidth, parentHeight);
int count = this.getChildCount();
for (int i = 0; i < count; i++) {
View child = this.getChildAt(i);
this.measureChild(
child,
MeasureSpec.makeMeasureSpec(parentWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(parentHeight, MeasureSpec.EXACTLY));
}
}
I have a Custom ViewGroup with some views inside.
In response to an event, a requestLayout is called and in OnLayout some of the views will
get a new layout position.
One of them maintain its position but needs to be rearranged inside. However this view's onlayout method will not be called since its layout position doesn't change. Trying to call requestLayout, forceLayout or invalidate in this view doesn't work.
The only dirty trick that I have found is to change some of its value position to force the layout change, but I think there should be a better way. So, by now I do something (horrible) like:
int patch = 0;
#Override protected void onLayout(boolean changed, int l, int t, int r, int b) {
...
_myView.patchedLayout(patch);
_myview.layout(l1, t1 , r1, t1+patch);
patch = (patch+1) % 2;
...
}
Any way to get the same result in a better way?
I finally got the solution: I need to override onMeasure and be sure to call mesure in my view:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
...
_myview.measure(widthMeasureSpec, heightMeasureSpec);
...
It will set the LAYOUT_REQUIRED flag in the view's private field mPrivateFlags so it will force to call onLayout
I wanted to create a custom LinearLayout (and later a custom ImageButton) that could take percentage values for both dimensions of size based on its parent's size regardless of the parent type (Relative or Linear). I was following this post: How to size an Android view based on its parent's dimensions, and it was very helpful, but I have a problem that those answers don't address.
When I place my Custom LinearLayout inside another LinearLayout, everything works as expected. My Custom LinearLayout covers the expected space (80% of the parent's width in the example below).
However if I place it inside a RelativeLayout, my screen always shows empty, I am not sure why this happens.
Here is my class:
public class ButtonPanel extends LinearLayout {
public ButtonPanel(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int newWidth = (int) Math.ceil(parentWidth * 0.8);
this.setMeasuredDimension(newWidth, parentHeight);
this.setLayoutParams(new RelativeLayout.LayoutParams(newWidth,parentHeight));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
And here is my testing layout for the activity.
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<com.android.tests.views.ButtonPanel
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#drawable/inner_panel"
android:gravity="center_horizontal"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true">
</com.android.tests.views.ButtonPanel>
</RelativeLayout>
In my activity all I do is set the Content View to the above layout.
(Incidentally, does anybody now how I could get the type of the parent dynamically for setting the new LayoutParameters? Above you'll see the parent type (RelativeLayout) hard-coded into the Custom View onMeasure function)
Thanks in advance for any help!
Is this exposed to be a problem?
this.setLayoutParams(new RelativeLayout.LayoutParams(newWidth,parentHeight)); // <-- a RelativeLayout params?
In the onMeasure function you could use something like this to know what class is the parent of the view.
this.getParent().getClass().getName()
This should also work
a instanceof B
or
B.class.isAssignableFrom(a.getClass())
When using "instanceof", you need to know the class of "B" at compile time. When using "isAssignableFrom" it can be dynamic and change during runtime.
If you are not compfortable with string comparison, you could also use enums.
Turns out my two inquiries in this post were more related than expected.
I realized that by setting my view's LayoutParams to a completely new instance, I was overwriting the layout positioning information needed by the Relative Layout to position my view.
By 'zeroing out' that information, my view has the right dimensions, but the layout doesn't know where to place it, so it simply doesn't.
The following code for the new onMeasure shows how just directly modifying the height and width of the LayoutParams already attached to my view I avoid both overwriting the layout position information and having to create new LayoutParams based on the parent's type.
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int specWidth = MeasureSpec.getSize(widthMeasureSpec);
int specHeight = MeasureSpec.getSize(heightMeasureSpec);
int newWidth = (int) Math.ceil(parentWidth * 0.8);
int newHeight = (int) Math.ceil(parentHeight * 0.8);
this.setMeasuredDimension(newWidth, newHeight);
this.getLayoutParams().height = newHeight;
this.getLayoutParams().width = newWidth;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
Now, I'll be honest and say that this code is still not bug-free. Bringing the activity to the foreground and background multiple times constantly reduces the size of this custom view. The 0.8 reduction factor gets applied over and over each time the activity is brought up (I suspect the setting of the LayoutParams has to do with it, it might actually be unnecessary, but I haven't has time to test).
BUT, this still answered the question concerning this post, namely, why was my view not appearing at all despite having the right dimensions.
Answered
I have a RelativeLayout where I am adding views dynamically as the user scrolls vertically or horizontally. I have rolled my own ViewRecycler since there is potentially thousands of views that could compose the whole of what can be scrolled, but I only show 30 or so at any time. Think a zoomed in view of a calendar.
I am running into performance problems when I add the views that are about to be seen, onMeasure is called on the RelativeLayout cascading down to onMeasure getting called on all of it's child views. I already have the calculated size of how big the RelativeLayout will ever be and have set that on it's LayoutParameters, so measuring the ViewGroup isn't necessary, nor is re-measuring the Views that have already been added with their final size and the newly added view has no bearing on those view.
The simple example to demonstrate the problem is adding/removing a View to a RelativeLayout and watching the onMeasure get called despite the fact that it doesn't affect the RelativeLayout's size or the position of other Views.
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/shell"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</LinearLayout>
MyActivity.java
public class MyActivity extends Activity
{
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ViewGroup shell = (ViewGroup) findViewById(R.id.shell);
final RelativeLayout container = new RelativeLayout(this) {
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.d("MyActvity", "onMeasure called on map");
}
};
container.setBackgroundColor(Color.rgb(255, 0, 0));
ViewGroup.LayoutParams containerParams = new ViewGroup.LayoutParams(300, 300);
final TextView childView = new TextView(this);
childView.setBackgroundColor(Color.rgb(0, 255, 0));
childView.setText("Child View");
Button viewToggle = (Button) findViewById(R.id.button);
viewToggle.setText("Add/Remove Child View");
viewToggle.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
if (childView.getParent() == null) {
container.addView(childView, 400, 30);
} else {
container.removeView(childView);
}
}
});
shell.addView(container, containerParams);
}
}
Running this, you would see 2 initial (an expected) calls to onMeasure, then one for each time that you add/remove the view by clicking the button. This obviously runs fine, but you can see where constant calls to onMeasure when you have a complex layout of nested views can get problematic.
Is there a recommended way to bypass these onMeasure calls or at least onMeasure calling measureChildren?
Instead of rolling my own Layout Manager (which I may still do in the future), I changed the onMeasure to:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
for (int i = 0; count > i; i++) {
View v = getChildAt(i);
if (v.getVisibility() != GONE) {
if (v.getMeasuredWidth() <= 0 || v.getMeasuredHeight() <= 0) {
measureChild(v,
MeasureSpec.makeMeasureSpec(v.getLayoutParams().width,
MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(v.getLayoutParams().height,
MeasureSpec.AT_MOST));
}
}
}
setMeasuredDimension(resolveSize(staticContainerWidth, widthMeasureSpec),
resolveSize(staticContainerHeight, heightMeasureSpec));
}
... and added a sudo-hard coded height and width for the container as a variable. Setting these to what you expect is out of the scope of this solution.
int staticContainerHeight = 300;
int staticContainerWidth = 300;
I ran into a similar problem when animation occurs on the size of viewgroup, whose onMeasure() gets called very frequently. Because parent view contains numerous child views, the frequent cascaded onMeasure() calls caused animation performance hiccups. I have another dirty solution but much simpler than rolling out my own layoutManager.
long mLastOnMeasurTimestamp;
...
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
...
long currentTimestamp = System.currentTimeMillis();
if(currentTimestamp - mLastOnMeasureTimestamp < SKIP_PERIOD_IN_MILL){
return;
}
mLastOnMeasureTimestamp = currentTimestamp;
...
I have encountered a similar problem and my solution was to check if the dimensions have changed:
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(parentWidth, parentHeight);
if ( mClientWidth == parentWidth && mClientHeight == parentHeight ) {
return;
}
mClientWidth = parentWidth;
mClientHeight = parentHeight;
So, if the parent's dimensions don't really change, it won't be cascaded down to its children.