Android customlayout children not gettting measured height and width - android

I have implemented and activity (called HomeScreenActivity) that consists of two fragments. One of them displays a list of items, and the other displays the details of the selected item. The details fragment (ResumeFragment in my code) consists of a series of components, which can be configured in height and width by the user by specifying it as a number of cells in a configurations file. These components are added to a custom layout called ComponentsLayout.
When opening the HomeScreenActivity i display each fragment in the onCreate method, and they are shown as intended.
But when i click and item in the items list fragment, and i replace the resumeFragment with a new instance of the same class, nothing is displayed.
I have spend a lot of time debugging and i see that the resumeFragment, does fill out the height and width it is given, but the componentsLayout only has a height of 1. This i have found out is because the height and width of the components contained in the componentsLayout are not getting set, even though i set their layoutParams in the componentsLayout onMeasure method.
Below is the code of the HomeScreenActivity, the ResumeFragment and the ComponentsLayout:
public class HomeScreenActivity extends FragmentActivity implements OnItemSelectedListener {
.
.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.home_screen);
.
.
//Check if device is in landscape orientation
if(width > height && isTablet) {
//The patient context fragment must maintain the same width as when in portrait orientation.
findViewById(R.id.patient_resume_fragment_container).setLayoutParams(new LinearLayout.LayoutParams(height, height));
findViewById(R.id.screen_pager).setLayoutParams(new LinearLayout.LayoutParams(width-height, height));
fragmentManager = getSupportFragmentManager();
patientResumeFragment = new ResumeFragment();
testFragment = new NysomTestFragment();
fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.patient_resume_fragment_container, patientResumeFragment);
fragmentTransaction.commit();
}
}
.
.
#Override
public void onItemSelected(Item item) {
itemResumeFragment.getView().findViewById(R.id.componentsContainer);
itemResumeFragment = new ResumeFragment(item);
fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.item_resume_fragment_container, itemResumeFragment);
fragmentTransaction.commit();
}
The ResumeFragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View fragmentView = inflater.inflate(R.layout.resume_fragment,container, false);
ComponentsLayout componentsContainer = (ComponentsLayout) fragmentView.findViewById(R.id.componentsContainer);
placeComponents(componentsContainer);
return fragmentView;
}
private void placeComponents(ComponentsLayout layout) {
List<ComponentConfig> configs = new ArrayList<ComponentConfig>(testMobile.getModel().getComponentConfiguration());
int viewId = 1;
for (ComponentConfig currConfig : configs) {
ComponentsLayout.LayoutParams params = new ComponentsLayout.LayoutParams(0, 0);
params.setWidthInCells(currConfig.getWidth());
params.setHeightInCells(currConfig.getHeight());
if (viewId == 1) {
params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
} else {
if (currConfig.getPositionX() > 0) {
// looking for rightOf viewId
params.addRule(RelativeLayout.RIGHT_OF, getRightOfComponentId(configs, currConfig, viewId - 1));
}
if (currConfig.getPositionY() > 0) {
// looking for below viewId
params.addRule(RelativeLayout.BELOW, getBelowComponentId(configs, currConfig, viewId - 1));
}
}
try {
// this code will be changed in story 9
View view = currConfig.getClassType().newInstance().getView(getActivity());
view.setId(viewId);
view.setBackgroundColor(colors[(viewId - 1) % 6]);
layout.addView(view, params);
} catch (Exception e) {
// we should never get to this point as configuration is validated
Log.e(TAG, "getView: unable to create component for position " + viewId, e);
throw new RuntimeException(e);
}
viewId++;
}
}
And the ComponentLayout
public class ComponentsLayout extends RelativeLayout {
public ComponentsLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int horizontalCellsNum = getResources().getInteger(R.integer.resume_horizontal_cells_num);
int cellHeight = (int) (getResources().getDimension(R.dimen.resume_cell_height));
int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
LayoutParams params = (LayoutParams) child.getLayoutParams();
params.width = parentWidth * params.getWidthInCells() / horizontalCellsNum;
params.height = cellHeight * params.getHeightInCells();
child.setLayoutParams(params);
}
}
And once again, this works when called from the HomeScreenActivity onCreate method, but not when done from the onItemSelected method.
Can anyone help??

I have created a work around by adding all layout params to the components before onMeasure is called on the componentLayout. It was not the solution i was looking for, but it was quite simple, which is possibly why i overlooked it.

Related

Alternative approach for ExpandableListView

I am designing a form which has part expandable groups and part generic form elements. When we uncollapse the group, an individual form is displayed.
I have tried with ExpandableListView but there are too many files that needs to be created. We have four tabs and each should have its own expandable list. What could be the alternative way to handle this?
The view I am expecting looks something like this: (ofcourse we have four tabs and each should have this view).
That's how I solved my problem. You have to change the static values. I hope it works.
#Override
public void onClick(View view) {
ViewHolder holder = (ViewHolder) recyclerView.findViewHolderForAdapterPosition(selectedItem);
if (holder != null) {
holder.expandButton.setSelected(false);
holder.expandableLayout.collapse();
}
int position = getAdapterPosition();
//dynamic Listview Height calculate start
float scale = view.getContext().getResources().getConfiguration().fontScale;
int hh = 0;
try {
WindowManager wm = (WindowManager) view.getContext().getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
int width = display.getWidth(); // deprecated
int height = display.getHeight(); // deprecated
View childView = listdetail.getAdapter().getView(0, null, listdetail);
childView.measure(UNBOUNDED, UNBOUNDED);
hh=childView.getMeasuredHeight();
} catch (Exception e) {
e.printStackTrace();
}
int scl = (int)(scale * (12f));
scl = scl<=0?1:scl;
ViewGroup.LayoutParams params = listdetail.getLayoutParams();
if(hh<=0) {
params.height = 10 * (listdetail.getAdapter().getCount()) * scl;
}
else {
params.height = (int) (((scale<0f?1f:1f) *hh * (listdetail.getAdapter().getCount()))+10);
}
listdetail.setLayoutParams(params);
//dynamic Listview Height calculate stop
if (position == selectedItem) {
selectedItem = UNSELECTED;
} else {
expandButton.setSelected(true);
expandableLayout.expand();
selectedItem = position;
}
}

ScrollView with multiple LinearLayouts doesn't size correctly

I have created a GridView control, which inhertis from a ScrollView, the idea of this control, is that it will contain multiple Views arranged in a grid format with a given number of columns and rows.
When the view is first built, the GridView doesn't know the size of its container, so I wait until the onSizeChanged method is called, then I apply the relevant sizing.
When the below is run, it doesn't re-size the grid to show it correctly, each control is only as big as it needs to be to show the text.
When the `onSizeChanged' method is called, it has the correct size, and applies the correct size to each child view, but it doesn't affect the way the controls are drawn (i.e. they're still all bunched up on the top left of the screen).
Despite this, I have actually got it working, but it draws twice. I do this by creating a Runnable which calls ResizeList. Then calling new Handler().post(r) straight after I call BuildIt.
Although this is a solution, I just don't understand why it doesn't work in the below form.
Incidentally, if the GridView is the main View added to the Activity, it displays fine, it's only when it's subsequently added. Which is why I have the Button, which you have to press to show the grid.
Can anyone suggest why the below code doesn't work properly?
public class MainActivity extends Activity {
GridView sv;
FrameLayout flay;
#Override protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
flay=new FrameLayout(this);
this.setContentView(flay);
Button b=new Button(this);
b.setText("press me to show grid view");
b.setOnClickListener(ocl);
flay.addView(b);
}
OnClickListener ocl=new OnClickListener()
{
#Override public void onClick(View v)
{
BuildIt();
}};
private void BuildIt()
{
flay.removeAllViews(); // remove the button control
sv=new GridView(this);
for (int c1=0 ; c1<30 ; c1++)
{
TextView tv=new TextView(this);
tv.setText("Item "+c1);
tv.setGravity(android.view.Gravity.CENTER);
sv.addListItem(tv);
}
flay.addView(sv);
sv.ConstructList();
}
}
The GridView class
public class GridView extends ScrollView
{
final int rows=4;
final int cols=4;
private ArrayList<View> allViews=new ArrayList<View>();
private LinearLayout ll;
public GridView(Context context)
{
super(context);
ll=new LinearLayout(context);
ll.setOrientation(LinearLayout.VERTICAL);
this.addView(ll);
}
public void addListItem(View v)
{
allViews.add(v);
}
public void ConstructList()
{
int c1=0;
ll.removeAllViews(); // Just in case we're re-building
LinearLayout row=null;
for (View v : allViews)
{
if (c1%cols==0)
{
row=new LinearLayout(this.getContext());
ll.addView(row);
}
row.addView(v);
c1++;
}
}
private void ResizeList()
{
int useHeight=getHeight()/rows;
int useWidth=getWidth()/cols;
LinearLayout.LayoutParams lpCol=new LinearLayout.LayoutParams(useWidth, useHeight);
Log.i("log","About to set width/height="+useWidth+"/"+useHeight);
int numKids= ll.getChildCount();
for (int c1=0 ; c1<numKids ; c1++)
{
LinearLayout ll2=(LinearLayout)ll.getChildAt(c1);
for (int c2=0 ; c2<ll2.getChildCount() ; c2++) // use getChildCount rather than cols, just in case it's the last one
{
View v=ll2.getChildAt(c2);
v.setLayoutParams(lpCol);
}
}
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
super.onSizeChanged(w, h, oldw, oldh);
ResizeList();
}
}
I have a function which is used to resize the child's width and height in gridView.
May be this could help you :
public static void setGridChild_Height(GridView gridView, int columns) {
ListAdapter listAdapter = gridView.getAdapter();
if (listAdapter == null) {
// pre-condition
return;
}
int totalHeight = 0;
int items = listAdapter.getCount();
int rows = 0;
for (int j = 0; j < items; j++) {
View view = gridView.getChildAt(j);
if (view != null && view.getHeight() > totalHeight) {
totalHeight = view.getHeight();
}
}
System.out.println("totalHeight -> " + totalHeight);
if (totalHeight > 0) {
for (int j = 0; j < items; j++) {
View view = gridView.getChildAt(j);
if (view != null && view.getHeight() < totalHeight) {
view.setMinimumHeight(totalHeight);
}
}
}
// View listItem = listAdapter.getView(0, null, gridView);
// listItem.measure(0, 0);
// totalHeight = listItem.getMeasuredHeight();
//
// float x = 1;
// if (items > columns) {
// x = items / columns;
// rows = (int) (x + 1);
// totalHeight *= rows;
// }
//
// ViewGroup.LayoutParams params = gridView.getLayoutParams();
// params.height = totalHeight;
// gridView.setLayoutParams(params);
}
Try to change logic as per your requirement.
Code is not tested perfectly.
It's because onSizeChanged when newly added to the view hierarchy uses it's old sizes of "0" (according to the docs: http://developer.android.com/reference/android/view/View.html#onSizeChanged(int, int, int, int))
I think what you want is to a addOnLayoutChangedListener : http://developer.android.com/reference/android/view/View.html#addOnLayoutChangeListener(android.view.View.OnLayoutChangeListener)
Using the ViewTreeObserver might be another option that will work for you: How can you tell when a layout has been drawn?

Android: How to make a gridview of equal proportions within the Java code

I am working on an application which has a main screen with a gridview layout.
As it stands currently, I am hardcoding in DP numbers for the height and width of the boxes, which I obviously do not want to do. How to I create code that will determine the users' screen size and match the boxes accordingly so that someone with a large phone like a note will view the same thing as someone on a droid mini? IE, when hardcoding to 500dp on each side, it looks like this:
But my goal is to make it look like this on every screen size:
Currently, this is the Java code I have:
public class ActivityAdapter extends BaseAdapter {
//Array of Icons that will be used as menu choices
public static int[] imageOptions = {
R.drawable.vacation_quota, //Position 0
R.drawable.revenue_deficit, //Position 1
//+ many more options...
};
private Context context;
//Constructor to pass context back to Main class
public ActivityAdapter(Context applicationContext) {
context = applicationContext;
}
//Number of elements to be displayed on the grid
#Override
public int getCount() {
return imageOptions.length;
}
#Override
public View getView(int position, View convertView, ViewGroup arg2) {
ImageView iv;
if(convertView != null){
iv = (ImageView) convertView;
} else {
iv = new ImageView(context);
//Here I have it hard coded to match a 480px width. Here is where I need to change it, just not sure how
iv.setLayoutParams(new GridView.LayoutParams(240, 240));
iv.setScaleType(ScaleType.CENTER_CROP); //Center the cropping
}
//Sets the image to correspond to its position within the array.
iv.setImageResource(imageOptions[position]);
return iv;
}
Any ideas? I think logically I want to figure out the screen dimensions and use them to make the image dimensions match, just not sure how. Research from other threads has not helped a great deal so hoping I can get a simpler answer here with code posting.
You should get the screen width and height. Then use these values to dynamically set height and width for each cell
Try this code-
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle state) {
setContentView(new ViewGroup(this) {
private RelativeLayout[] items = new RelativeLayout[9];
private int width, height, itemWidth, itemHeight;
{
Random r = new Random();
for (int i = 0; i < 9; i++) {
items[i] = new RelativeLayout(getContext());
float[] hsv = new float[] {360 * r.nextFloat(), .50f, .75f};
items[i].setBackgroundColor(Color.HSVToColor(hsv));
addView(items[i]);
// UPDATE ////////////////////////////////////
ImageView image = new ImageView(getContext());
switch (i) {
case 0: // top left
case 1: // top center
case 2: // top right
case 3: // center left
case 4: // center center
case 5: // center right
case 6: // bottom left
case 7: // bottom center
case 8: // bottom right
image.setImageResource(R.drawable.ic_launcher);
break;
}
image.setScaleType(ScaleType.FIT_XY);
image.setLayoutParams(new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT,
RelativeLayout.LayoutParams.MATCH_PARENT
));
items[i].addView(image);
//////////////////////////////////////////////
}
}
#Override
protected void onMeasure(int wMS, int hMS) {
width = MeasureSpec.getSize(wMS);
height = MeasureSpec.getSize(hMS);
itemWidth = width / 3;
itemHeight = height / 3;
wMS = MeasureSpec.makeMeasureSpec(itemWidth, MeasureSpec.EXACTLY);
hMS = MeasureSpec.makeMeasureSpec(itemHeight, MeasureSpec.EXACTLY);
measureChildren(wMS, hMS);
setMeasuredDimension(width, height);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0; i < 9; i++) {
l = itemWidth * (i % 3);
t = itemHeight * (i / 3);
r = l + itemWidth;
b = t + itemHeight;
items[i].layout(l, t, r, b);
}
}
});
super.onCreate(state);
}
}
Output-
Edit-
For creating scrollable grid view see this http://www.androidhive.info/2012/02/android-gridview-layout-tutorial/
I ended up not able to use some of the recommendations here and had to use a deprecated method to retain my app structure. The code specifically that was added to mine was:
iv = new ImageView(context);
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
float width2 = display.getWidth();
int length = (int) (width2/2);
iv.setLayoutParams(new GridView.LayoutParams(length, length));
With this, the height and width would crop to the right size depending on the size of the screen.

Android: How to get size dimensions of a viewgroup?

I have a gridlayout who's size I want to check before displaying anything in the activity, because I want to check if an image needs to be resized. Can't do it OnStart() because apparently it hasn't been loaded yet. Adding a listener doesn't work due to synchronization issues with the rest of the code. It would be nice if I could just manually generate the value, by using the dimensions of the device, but I for the life of me can't find the exact value of those stupid margins Android places automatically. Any suggestions? Essentially in the code below I need to have height/width of the GridLayout BEFORE SplitImage() is called.
XML
<GridLayout
android:id="#+id/grid_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:useDefaultMargins="false" >
</GridLayout>
Relevant JAVA
public class GamePlay extends Activity {
int difficulty = 0;
int image = 0;
int widthView = 0;
int heightView = 0;
int boardW;
int boardH;
int nSqr;
Bitmap[][] gameBoard;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
Intent intent = getIntent();
difficulty = intent.getIntExtra("DIFFICULTY_KEY_DMIROU246", 0);
image = intent.getIntExtra("LEVEL_KEY_DMIROU246", 0);
setContentView(R.layout.activity_game_play);
Log.w("Derp", "1");
}
#Override
protected void onStart() {
super.onStart();
View view = (View) findViewById(R.id.grid_layout);
Log.w("Derp", "2");
heightView = view.getHeight();
widthView = view.getWidth();
Log.w("Derp", "3");
gameBoard = SplitImage();
Log.w("Derp", "9");
}
You won't get the height or width of the view until it is drawn on the screen.
For this purpose you'll have two ways to get the hight and width of a layout:-
1) ViewTreeObserver--Doc
View view = (View) findViewById(R.id.grid_layout);
ViewTreeObserver vto = view.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
this.layout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
gameBoard = SplitImage();
Log.w("Derp", "9");
}
});
2) Handler.postDelayed--Doc
View view = (View) findViewById(R.id.grid_layout);
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
gameBoard = SplitImage();
Log.w("Derp", "9");
}
}, 500);
The easiest solution is to do this asynchronously, use view.getViewTreeObserver then add a OnGlobalLayoutListener that will get the size of your view and call splitImage().
Take care that your listener is not called to often, the best is to remove it after it has been triggered once.
Try This One
Problem:I have some data show as list (Expandable view).
Main list view Show i am Using List View and show Child view Display another inner listview used.so child view can not calculate cell heigt.
1) i have both UI TABLAT and PHONE
2) if any user any problem implement time this code so contact me i will help you."rp1741995#gmail.com"
public static int getListViewHeightBasedOnChildren(ListView listView,Context context) {
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter != null)
{
int totalHeight = 0;
int size = listAdapter.getCount();
Log.e(TAG, "getListViewHeightBasedOnChildren: >>"+size);
for (int i = 0; i < size; i++)
{
View listItem = listAdapter.getView(i, null, listView);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
totalHeight = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount()-1));
if(listAdapter.getCount()!=0) {
if (MyApplication.isTablet(context)) {
Float height = convertDpToPixel(10);
totalHeight = (totalHeight / listAdapter.getCount()) + Math.round(height);
}else {
Float height = convertDpToPixel(80);
totalHeight = (totalHeight / listAdapter.getCount()) + Math.round(height);
}
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight;
//params.height;
Log.e(TAG, "getListViewSize: "+String.valueOf(totalHeight));
listView.setLayoutParams(params);
return totalHeight;
}
return 0;
}

Relative Layout get Width/Height return 0

I need to get width and height of Relative layout, however I do not know when the Relative Layout is ready to provide me these two attributes. My activity has several tabs and I call the getWidth() and getHeight() in onCreateView() after inflater.inflate(R.layout.fragment_pizza, container, false);, however it returns 0 for both of them, so they are not apparently ready. Is there some kind of event like afterCreateView() or something similar where I can call these two methods and get the actual width and height?
Code:
public class PizzaFragment extends Fragment {
private static final String TAG = "PizzaFragment";
private Controller controller;
private static final int BUTTON_WIDTH = 70;
private static final int BUTTON_HEIGHT = 20;
private static final int MARGIN_LEFT = 80;
private static final int MARGIN_BOTTOM = 30;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
controller = Controller.getInstance();
controller.loadProducts("pizza", getActivity().getApplicationContext());
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_pizza, container, false);
RelativeLayout layout = (RelativeLayout) view.findViewById(R.id.rl_order_pizza);
Log.d(TAG, "Width: " + layout.getWidth());
Log.d(TAG, "Height: " + layout.getHeight());
int currentX = 10;
int currentY = 10;
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(BUTTON_WIDTH, BUTTON_HEIGHT);
for (Product product: controller.getProducts("pizza")){
Button tempButton = new Button(getActivity());
tempButton.setId(product.getId());
tempButton.setText(product.getName());
if (layout.getWidth() < currentX + MARGIN_LEFT){
currentX = 10;
currentY = MARGIN_BOTTOM;
}
else{
currentX += MARGIN_LEFT;
}
Log.d(TAG, "CurrentY: " + currentY);
Log.d(TAG, "CurrentX: " + currentX);
layoutParams.leftMargin = currentX;
layoutParams.bottomMargin = currentY;
tempButton.setLayoutParams(layoutParams);
layout.addView(tempButton);
}
return view;
}
}
My suggestion is to use a global layout listener like this in onCreateView:
YOUR_LAYOUT_INSTANCE = view.findViewById(R.id.YOUR_LAYOUT_ID);
...
YOUR_LAYOUT_INSTANCE.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener()
{
#Override
public void onGlobalLayout()
{
// gets called after layout has been done but before display.
if (Build.VERSION.SDK_INT < 16) {
YOUR_LAYOUT_INSTANCE.getViewTreeObserver().removeGlobalOnLayoutListener(this);
} else {
YOUR_LAYOUT_INSTANCE.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
// get width and height
}
});
addOnGlobalLayoutListener is deprecated use removeOnGlobalLayoutListener .For better handling extending the solution provided above by #fasteque
YOUR_LAYOUT_INSTANCE.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener()
{
#Override
public void onGlobalLayout()
{
// gets called after layout has been done but before display.
if (Build.VERSION.SDK_INT < 16) {
YOUR_LAYOUT_INSTANCE.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
else {
YOUR_LAYOUT_INSTANCE.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
// get width and height
}
});

Categories

Resources