I'm building a custom view, sort of like a custom bar chart. I'm extending the LinearLayout for this one. The custom view then populates the views from data. Problem is, whenever I want the views to be 'refreshed', I am calling removeAllViews() and similar methods, so the custom view layout is in clean slate, then to repopulate the data, I call addView(), but child views don't show up. Reason why I need to call removeAllViews is for the child views to not duplicate in the custom views.
These are some of the snippets from my custom view, I also implemented onLayout() so whenever I display the custom views I get proper heights for layouting purposes. BarChartData is just a model class for the data that should be displayed in this custom view:
public void setChartData(BarChartData data) {
this.chartData = data;
addBarDataToUi();
}
void addBarDataToUi() {
Log.d(TAG, "Add bar data to UI called");
if (chartData != null) {
//this.removeAllViews(); -> first one I tried, no luck, not displaying views after `addView`
//this.removeAllViewsInLayout(); -> tried this too but no luck
this.removeViewsInLayout(0, this.getChildCount()); // again, to no avail :(
for (int i = 0, count = chartData.getItemCount(); i < count; i++) {
addBarItemDataUi(chartData.getItemByPos(i));
}
Log.d(TAG, "Child count: " + this.getChildCount());
}
}
void addBarItemDataUi(BarItemData data) {
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LinearLayout layout = (LinearLayout) inflater.inflate(R.layout.bar_chart_item, this, false);
FrameLayout mainLayout = (FrameLayout) layout.findViewById(R.id.bar_chart_item_main_layout);
//TextView topText = new TextView(getContext());
TextView topText = (TextView) layout.findViewById(R.id.bar_chart_item_top_text);
TextView bottomText = (TextView) layout.findViewById(R.id.bar_chart_item_bottom_text);
topText.setText(String.valueOf(data.percentage));
bottomText.setText(data.title);
mainLayout.setBackgroundColor(data.backgroundColor);
Log.d(TAG, "Height: " + this.getMeasuredHeight() + ", Top text height: " + topText.getMeasuredHeight());
int heightRel = (int) (data.getPercentageFractal() * (double) this.getMeasuredHeight());
mainLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, heightRel));
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f);
params.gravity = Gravity.BOTTOM;
layout.setLayoutParams(params);
this.addView(layout);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
Log.d(TAG, "On layout..");
if (chartData != null) {
addBarDataToUi();
}
}
Well, I have searched this problem, there are very few that came up, almost the same scenario and problem, but I think they have not resolved their problems about addView after removeAllViews.
I'm guessing that by calling the removeAllViews() function inside the addBarDataToUi() which is inside onLayout() when the function gets called by setChartData(BarChartData data) it adds the child views which triggers the onLayout() function which calls addBarDataToUi() and removes view and such in some kind of constant loop. The android documentation says to
avoid using removeAllViews() inside onDraw() or any related function
http://developer.android.com/reference/android/view/ViewGroup.html#removeAllViews()
Which I'm assuming might also include the onLayout() function as well.
My best suggestion is moving your removeAllViews() function call to inside your setChartData(BarChartData data) function just before you call addBarDataToUi()
Hope it helps
Related
I want to receive the height of the layout every time, but now I get the height of the layout when the for loop is done. Does anyone know how to receive the height every time in a for loop?
for (int i = 0; i < deelnemers.size(); i++) {
inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View informatie = inflater.inflate(R.layout.modeldeelnemer, null);
TextView volgNummer = (TextView) informatie.findViewById(R.id.volgnummer);
volgNummer.setText("Nummer: " + deelnemers.get(i).getVolgnummer() + " /");
lLayout.addView(informatie);
lLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
lLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
int height = lLayout.getMeasuredHeight();
int width = lLayout.getMeasuredWidth();
}
});
}
In general avoid to use the global layout listener that is bad practice. In the end you just want to know when the view is measured so hook that event. I wrote a small layout with a callback for observing OnMeasure calls. Check this layout: MeasureCallbackLayout
When you wrap that layout around your linear layout you can add the MeasureCallback and do what you want with the actual size:
measureCallbackLayout.addMeasureListener(new MeasureCallbackLayout.MeasureCallback() {
#Override
public void onMeasured() {
int height = lLayout.getMeasuredHeight();
int width = lLayout.getMeasuredWidth();
}
});
I am trying to create a custom View that would replace/inflate a certain layout which contains multiple linearlayout predefined and adding those views into each and every predefined layouts.
My predefined layout contains 4 linearlayouts inside relativelayout as parent and inflating this xml in class where it extends Relativelayout and initialising views as below.
private void initView(Context context) {
//Inflate and attach your child XML
View.inflate(context, R.layout.layout_empty_views, this);
llTop = (LinearLayout) findViewById(R.id.linearLayout);
llLeft = (LinearLayout) findViewById(R.id.linearLayout2);
llBottom = (LinearLayout) findViewById(R.id.linearLayout3);
llRight = (LinearLayout) findViewById(R.id.linearLayout4);
}
Now ill accept 12 child views and need to add 3 child to each linearlayout is where im stuck now. In addView function not able to split the the views due to index is always -1.
#Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (llTop == null) {
super.addView(child, index, params);
} else {
//Forward these calls to the content view
llTop.addView(child, index, params);
}
}
In onLayout function, getChildCount is raised more than 12 due to inflating my predefined layout.
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//super.onLayout(changed, l, t, r, b);
// TODO Auto-generated method stub
Log.v("Count::", String.valueOf(getChildCount()));
/*for (int i = 0; i < getChildCount(); i++) {
View v = getChildAt(i);
removeViewAt(i);
//llTop.addView(v);
if (i < 3) {
llTop.addView(v);
} else if (i > 2 && i < 6) {
llRight.addView(v);
} else if (i > 5 && i < 9) {
llBottom.addView(v);
} else if (i > 9) {
llLeft.addView(v);
}
}*/
}
When an index of -1 is received in addView(View child, int index), this means it should be added to the end of the ViewGroup.
I'm not sure I exactly understand your intent with this View, you may not need a custom View here. However, I can tell you that you should not be adding views in your onLayout function. This function is where a View should size and position its children.
Check out the Android Developer's guide on creating custom views.
I am trying to set a margin in a text view and padding in a linear layout in a list view adapter. Basically, if the text view has 2 or less lines of text I am creating a margin/padding for that list item.
Here is the code:
public class StockCountListAdapter extends ArrayAdapter<StockCountItem> {
private TextView txtProduct;
private LinearLayout llStockCountItem;
public StockCountListAdapter(Context context, int textViewResourceId, List<StockCountItem> objects) {
super(context, textViewResourceId, objects);
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.stock_count_list_item, parent, false);
txtProduct = (TextView) rowView.findViewById(R.id.stock_count_item_product);
llStockCountItem = (LinearLayout) rowView.findViewById(R.id.ll_stock_count_item);
StockCountItem item = getItem(position);
txtProduct.setText(item.Product);
llStockCountItem.post(new Runnable() {
#Override
public void run() {
if (txtProduct.getLineCount() <= 2) {
llStockCountItem.setPadding(0, 0, 0, 10);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 7);
params.setMargins(1, 1, 1, 10);
txtProduct.setLayoutParams(params);
}
}
});
return rowView;
}
The margin/padding is applied when the list is scrolled, but not when the list is first displayed or on orientation change.
How can I get it to apply the margin/padding on activity load or orientation change?
Handling Runtime Changes
When Screen orientation such a change occurs, Android restarts the running Activity (onDestroy() is called, followed by onCreate()). The restart behavior is designed to help your application adapt to new configurations by automatically reloading your application with alternative resources that match the new device configuration.
To properly handle a restart, it is important that your activity restores its previous state through the normal Activity lifecycle, in which Android calls onSaveInstanceState() before it destroys your activity so that you can save data about the application state. You can then restore the state during onCreate() or onRestoreInstanceState().
http://developer.android.com/guide/topics/resources/runtime-changes.html
GRAVITY
in code
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0,
LinearLayout.LayoutParams.WRAP_CONTENT, 7);
params.setMargins(1, 1, 1, 10);
params.gravity = Gravity.FILL; // this is where i come in
txtProduct.setLayoutParams(params);
txtProduct.invalidate(); // edits
txtProduct.requestLayout();//edits
the type of Gravity is up to you now with rotation you need to override onOnrientationChanged() (something like that) and call that same code there
Edit
base on your post runnable code i am thinking of a workaround, i do not think it will work but worth it.. since txtProduct.getLineCount() returns 0 after layout pass, i am improvising
if(txtProduct.getLineCount()==0){ //if false you run your previous code without post
int textSize = txtProduct.getTextSize()/2,mw,lines;
// for the textsize i am thinking of a rectangle
if(txtProduct.getMeasuredWidth() > 0) //if this line checks out 0 the code is trash
//assume none where zero
int fillW = textSize * txtProduct.getText().toString().length();
if(fillW > mw){ //my logic here is we have more than one line
if(fillW / mw > -1){ //
lines = fillW/mv;
if(fillW%mv !=0){// it is not a factor which means there is another line trending
lines++;
// now add your normal code here without post to check if lines is what you want
}
}
}
}else{ //add your code without post
see if this works,
I've done all of the research on the matter. I know that Google thinks it's pointless and that the developers, know that it's not. I also know that there is no known workaround, but I know that I am close to making one. The user DougW posted this code:
public class Utility {
public static void setListViewHeightBasedOnChildren(ListView listView) {
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null) {
// pre-condition
return;
}
int totalHeight = 0;
for (int i = 0; i < listAdapter.getCount(); i++) {
View listItem = listAdapter.getView(i, null, listView);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
listView.setLayoutParams(params);
}
}
Which almost gets the job done for me. But when I try it, I get a NullPointer exception at the listItem.measure(0, 0) line. The listItem itself is initialized, but the method throws the exception anyway. Please tell me how I can fix this.
Here is my code:
public class ExpenseReportsActivity extends Activity {
private ListView lvReports;
private ExpenseReportListAdapter adapter;
private Button btnSend;
private Button btnCancel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.expensereports);
lvReports = (ListView)findViewById(R.id.lv_reports);
lvReports.setBackgroundResource(R.drawable.shape_expense_report_list);
ColorDrawable cd = new ColorDrawable(0xFFffffff);
lvReports.setDivider(cd);
lvReports.setDividerHeight(1);
adapter = new ExpenseReportListAdapter(this);
lvReports.setAdapter(adapter);
int totalHeight = 0;
for (int i = 0; i < adapter.getCount(); i++) {
View listItem = adapter.getView(i, null, lvReports);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams params = lvReports.getLayoutParams();
params.height = totalHeight + (lvReports.getDividerHeight() * (adapter.getCount() - 1));
lvReports.setLayoutParams(params);
}
}
Another workaround I am working on is using my custom view's onWindowFocusChanged method. It tells the exacts height of the view. The problem is that the event isn't fired while I am still in my Activiy's onCreate method, nor in my Activity's onWindowFocusChanged method. I tried a custom event, but it never fired (it was placed inside my custom view's onWindowFocusChanged method and the listener was in my Activity's onWindowFocusChanged method).
Ok, as far as I got your needs I think you may just use the ListView.addFooterView(View v) method:
http://developer.android.com/reference/android/widget/ListView.html#addFooterView(android.view.View)
It will allow you to have all your list items + "a few buttons" footer to be scrolled as a single block.
So the code should be smth like that:
import android.os.Bundle;
import android.view.LayoutInflater;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
public class YourActivity extends ListActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LayoutInflater factory = getLayoutInflater();
LinearLayout footer =
(LinearLayout) factory.inflate(R.layout.your_a_few_buttons_footer, null);
getListView().addFooterView(footer);
String[] array = new String[50];
for (int i = 0; i < 50;) { array[i] = "LoremIpsum " + (++i); }
setListAdapter(
new ArrayAdapter<String>(this, R.layout.list_item, array)
);
}
}
Note, the doc says addFooterView() should be called BEFORE the setListAdapter().
UPDATE: to add a View at the top of the list use ListView.addHeaderView(View v). Note that, for instance, LinearLayout is also a View. So you can put anything you want as a header or a footer and it'll be scrolled with the list as an indivisible block.
Out of curiosity, is your layout using RelativeLayout? If so, calling measure(0,0) will always throw an NPE, but a LinearLayout will not.
http://groups.google.com/group/android-developers/browse_thread/thread/5a947482d7dcb605
Change it to Linear and you can make that call. I hope that helps!
I have a situation in my app where I have paragraphs of text, imageviews, all sorts of information on a given subject...and then, depending on the item, there is possibly a ListView of comparison data in the middle of all of that info. About one in every 10 items has it, nestled between all the text. The comparison data is never more than 4 items at max, so I don't want the ListView to scroll, ever. I just want the ListView to appear in its entirety at the exact point I specify.
Adding them all as nested Linear Layouts is insane, so is using MergeAdapter to put all of that together when I may not even have a ListView on screen. And using complex ListView headers & footers is out of the question as well.
I'm not the first person to want that kind of functionality, and I won't be the last. The above solution is nearly perfect, it sizes my ListView so that it's full on screen, and all the scrolling comes from the ScrollView parent. (It's easy as sin to do on the iOS SDK, btw., and a lot of apps over there do similar things; we'll need a good solution for this.)
I am creating a scrolling panel with many child views (e.g. buttons). Each child view has a fixed location based on their row and column index. I cannot create them at the beginning since there are tons of them and I will run out of memory, so I'd like to only add a child view when it intersects with the screen view port (when users scrolls to that area). I override the onLayout() method with something like this:
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
for (int row = 0; row < ROW_NUM; row++) {
ColumnAdapter columnAdapter = mRowAdapter.getItem(row);
for (int col = 0; col < COLUMN_NUM; col++) {
ItemView itemView = columnAdapter.getView(col, null, this);
if (isOnScreen(row, col)) {
itemView.layout(col * 100, row * 100, (col + 1) * 100, (row + 1) * 100);
addViewInLayout(itemView, row + col, null, true);
}
}
}
scrollTo(getScrollX(), getScrollY());
}
(ColumnAdapter is an Adapter extension and ItemView is button extension). This wouldn't work because onLayout() is not called during scrolling. What should I do to add ItemViews dynamically as user scrolls?
Never mind I figured that you can use requestLayout()...
You may also want to consider using a ListView. It does exactly what you want, that is a lazy load of its elements. See the Google IO session on ListView for details.
In short, the Adapter class you are extending has a View getView(int position, View convertView, ViewGroup parent) function. This function can be overriden and doing a check to see if convertView is not null and loading that since it is actually a previously created View of that child element.
There are also additional details on avoiding the findViewById() function call since it is expensive.