getViewTreeObserver in for loop not working, why? - android

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();
}
});

Related

How to check programatically if all the Views fit inside the screen

I have a layout which is something like this:
LinearLayout (linearLayout)
'--TextView (textView1)
'--ImageView (imageView)
'--TextView (textView2)
textView1 changes its text sometimes and it can be long, so it leaves part of textView2 out of the screen. I want to prevent that, so I want to remove imageView from the layout whenever this happens. imageView may or may not be visible at the time when this is computed (maybe it was removed before when textView1 was edited previously).
This is what I have coded:
void changeText(String veryLongString){
textView1.setText(veryLongString);
int [] loc = new int [2];
textView2.getLocationOnScreen(loc);
int bottom = textView2.getMeasuredHeight() + loc[1];
if (imageView.getVisibility() == View.GONE)
bottom += imageView.getHeight();
if (bottom > linearLayout.getMeasuredHeight()){
imageView.setVisibility(View.GONE);
} else {
imageView.setVisibility(View.VISIBLE);
}
}
But for some reason this doesn't work as expected, because it seems as if changes in the position and height of the Views don't take place immediately. When I call getMeasuredHeight() and getLocationOnScreen() I get the values BEFORE the changes I have just made. The result that I get is that if I set a very large text imageView is not removed, but if I then set a short text, it is removed.
If there any other way to face this problem?
Even though I think that this is not the right approach (you can do all kinds of stuff in your XML so you don't have to meddle with Java code), here is a quick example of what you can do from Java (for example, in your onStart() method)
ViewGroup group = (ViewGroup) findViewById(R.id.myLayout);
int groupHeight = group.getHeight();
for (int i = 0; i < group.getChildCount(); i++) {
groupHeight -= group.getChildAt(i).getHeight();
if (groupHeight < 0) {
// they don't fit in the layout
myImageView.setVisibility(View.GONE);
}
}

Saving and retrieving inflated View's size in RecyclerView

I have a RecyclerView which has a staggeredGridLayoutManager as layout manager. My layout stands as having 2 spans(cols), which items inside may have different heights.
Inflated items has a ImageView and some other views inside a LinearLayout container.
I want to save Inflated(or should I say binded?) View's size(height and width) after the view's image is fully loaded. Because this operation makes me know how much width and height the LinearLayout occupy at final-after the image is placed in the layout-.
After scrolling, this container may be recycled and binded again. What I want to achieve is to savebinded layout's size immediately after it is binded, according to the height and width values previously calculated because this makes recyclerView's item positions more stable. They are less likely move around.
I have mWidth and mHeight members in my ViewHolder, which basically store these values. However, I lost syncronisation between item position in adapter and corresponding ViewHolder. For example I calculate height of 8th item as 380px when it first become visible, which is correct. After recycling and binding 8th position again, my view's height retrieved as 300 px, which is incorrect.
Code:
BasicActivity is derived from Activity..
public ItemsRVAdapter(BasicActivity activity, JSONArray items){
this.items = items;
this.activity = activity;
this.itemControl = new Items(activity);
}
OnCreate:
#Override
public ItemListViewHolders onCreateViewHolder(ViewGroup viewGroup, int i) {
View layoutView =activity.getLayoutInflater().inflate(R.layout.list_element_items, viewGroup, false);
ItemListViewHolders rcv = new ItemListViewHolders(layoutView);
return rcv;
}
OnViewAttachedToWindow (I tried the same code here in different places, like onViewRecycled but I don't know this method is the most right place to calculete the size)
#Override
public void onViewAttachedToWindow(ItemListViewHolders holder)
{
holder.layoutCapsule.measure(LinearLayout.MeasureSpec.makeMeasureSpec(0, LinearLayout.MeasureSpec.UNSPECIFIED), LinearLayout.MeasureSpec.makeMeasureSpec(0, LinearLayout.MeasureSpec.UNSPECIFIED));
if(holder.image.getDrawable() != null){
holder.height = holder.layoutCapsule.getHeight();
holder.width = holder.layoutCapsule.getWidth();
}else{
holder.height = 0;
holder.width = 0;
}
}
onBindViewHolder: Only relevant part. Here I paired position value and my array's member index
#Override
public void onBindViewHolder(ItemListViewHolders holder, int position) {
try {
//JSONObject item = items.getJSONObject(holder.getAdapterPosition());
JSONObject item = items.getJSONObject(position);
holder.image.setImageDrawable(null);
ViewGroup viewGroup = holder.layoutCapsule; //Main Container
...
}
}
I recommend looking for a different approach to resolve your problem with the items moving around not depending on View sizes, but if you want to proceed this way this is my proposed solution:
Don't depend or save the size values on the holder as this gets recycled, you will need to create an object "descriptor" with the values (width and height) for each position and save them on a HashMap or something like that, save the values as you are doing it already, i understand on "onViewAttachedToWindow".
class Descriptor(){
int width;
int height;
void setWidth(int width){
this.width = width;
}
int getWidth(){
return width;
}
void setHeight(int height){
this.height = height;
}
int getHeight(){
return height;
}
Initialize array on constructor:
descriptors = new HashMap<Integer, Descriptor>();
in onBindViewHolder save the position on a view tag to use it on OnViewAttachedToWindow
public void onBindViewHolder(ItemListViewHolders holder, int position) {
....
holder.image.setTag(position);
...
}
populate values on onViewAttachedToWindow
public void onViewAttachedToWindow(ItemListViewHolders holder){
...
int position = (Integer)holder.image.getTag();
Descriptor d = descriptors.get(position);
if(d == null){
d = new Descriptor();
descriptors.put(position, d);
}
d.setWidth(holder.layoutCapsule.getWidth());
d.setHeight(holder.layoutCapsule.getHeight());
...
}
Then use the size data on the descriptor on the method you need getting it by position, you will be creating descriptors as the user is scrolling down, also this works on the asumption that the data maintains the same position during the life of the adapter.

addView not displaying after removeAllViews on custom view

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

Auto Scroll to HorizontalScrollView

I am doing auto-horizontal scrolling. So i have 15 items. Now i want to access at 12 item so my index is 11. But i am unable to scroll it auto when a index occur.
horizontalScrollView.scrollTo(12, 0);
#Override
public void onPageSelected(int page) {
for(int i = 0; i < holeTitle.length; i++) {
if(i == page) {
title[i].setTextColor(0xffffffff);
horizontalScrollView.scrollTo(12, 0);
}
else {
title[i].setTextColor(0xffe0e0e0);
}
}
}
please expert make a look.
DmRomantsov's answer is the right way to scroll to the 12th button. However, getLeft() and getRight() methods return 0 because the layout is not displayed yet on the screen. It is too early to calculate the width of the layout parent and children. To achieve it, you need to do your auto-scroll inside onWindowFocusChanged.
#Override
public void onWindowFocusChanged(boolean hasFocus){
super.onWindowFocusChanged(hasFocus);
if(hasFocus){
// do smoothScrollTo(...);
}
}
However, inside a Fragment, this method above will not work. I just wrote it to give a clue, to understand the concept. To have the same behaviour in Fragment, you just need to do a Runnable which lets the time to your UI to be displayed. Then, do this with a LinearLayout oriented to horizontal:
// Init variables
HorizontalScrollView mHS;
LinearLayout mLL;
// onCreateView method
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.layout_container, container, false);
// Find your views
mHS = (HorizontalScrollView)view.findViewById(R.id.hscrollview);
mLL = (LinearLayout)view.findViewById(R.id.hscrollview_container);
// Do a Runnable on the inflated view
view.post(new Runnable() {
#Override
public void run() {
Log.v("","Left position of 12th child = "+mLL.getChildAt(11).getLeft());
mHS.smoothScrollTo(mLL.getChildAt(11).getLeft(), 0);
}
});
return view;
}
Middle HorizontalScrollView:
Your question was to auto-scroll until your 12th child. However, in the comments below, you ask me to auto-scroll at the middle of the HorizontalScrollView, I assume on every device. You need to calculate the width of the screen, the total width of the container and how many children are displayed inside the device width. Here is a simple code:
// Auto scroll to the middle (regardless of the width screen)
view.post(new Runnable() {
#Override
public void run() {
// Width of the screen
DisplayMetrics metrics = getActivity().getResources()
.getDisplayMetrics();
int widthScreen = metrics.widthPixels;
Log.v("","Width screen total = " + widthScreen);
// Width of the container (LinearLayout)
int widthContainer = mLL.getWidth();
Log.v("","Width container total = " + widthContainer );
// Width of one child (Button)
int widthChild = mLL.getChildAt(0).getWidth();
Log.v("","Width child = " + widthChild);
// Nb children in screen
int nbChildInScreen = widthScreen / widthChild;
Log.v("","Width screen total / Width child = " + nbChildInScreen);
// Width total of the space outside the screen / 2 (= left position)
int positionLeftWidth = (widthContainer
- (widthChild * nbChildInScreen))/2;
Log.v("","Position left to the middle = " + positionLeftWidth);
// Auto scroll to the middle
mHS.smoothScrollTo(positionLeftWidth, 0);
}
});
/**
* Your value might be resumed by:
*
* int positionLeftWidth =
* ( mLL.getWidth() - ( mLL.getChildAt(0).getWidth() *
* ( metrics.widthPixels / mLL.getChildAt(0).getWidth() ) ) ) / 2;
*
**/
Middle HorizontalScrollView with chosen Value:
I have a bit misunderstand the real request. Actually, you wanted to auto-scroll until a chosen child view, and display this view at the middle of the screen.
Then, I changed the last int positionLeftWidth which refers now to the left position of the chosen view relative to its parent, the number of children contained in one screen, and the half width of the chosen view. So, the code is the same as above, except positionLeftWidth:
// For example the chosen value is 7
// 7th Child position left
int positionChildAt = mLL.getChildAt(6).getLeft();
// Width total of the auto-scroll (positionLeftWidth)
int positionLeftWidth = positionChildAt - // position 7th child from left less
( ( nbChildInScreen // ( how many child contained in screen
* widthChild ) / 2 ) // multiplied by their width ) divide by 2
+ ( widthChild / 2 ); // plus ( the child view divide by 2 )
// Auto-scroll to the 7th child
mHS.smoothScrollTo(positionLeftWidth, 0);
Then, whatever the value in getChildAt() method, and whatever the width screen, you will always have the chosen (in your case) button at the middle of the screen.
Try
horizontalScrollView.smoothScrollTo(horizontalScrollView.getChildAt(11).getRight(),0);
first patameter - X coord, second - Y.
Offset:
public final void smoothScrollBy (int dx, int dy)
Absolute:
public final void smoothScrollTo (int x, int y)
Try horizontalScrollView.smoothScrollBy(12, 0);
Try this is working code. position will be where you want to scroll
final HorizontalScrollView mHorizontalScrollView = (HorizontalScrollView) .findViewById(R.id.horizontalScrollView);
mHorizontalScrollView.postDelayed(new Runnable() {
#Override
public void run() {
mHorizontalScrollView.scrollTo(position, 0);
mHorizontalScrollView.smoothScrollBy(1200, 0);
}
},100);

Detect if some part of the view is NOT visible?

Is it possible to detect if some part of the view is not visible on the screen?
This is used in a situation that view's width/height is bigger that its parent's width/height.
EDIT
I get that the height of a view is 0. Does anyone knows why? I fetch the height in onCreate.
LinearLayout lin = (LinearLayout) findViewById(R.id.linear_layout);
final int layoutHeight = lin.getHeight();
Toast.makeText(this,"LinLay height: "+layoutHeight,Toast.LENGTH_SHORT).show();
...
button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
int displayTextWidth = textView.getWidth();
if (displayTextWidth <= layoutHeight) {
textView.setTextSize(textView.getTextSize() + 1);
}
}
});
You could get the view's width and height by calling View.getWidth() and View.getHeight(), then get the device dimensions by these means: How do I get a device's maximal width and height in android
Then compare the two, and if the view's bounds are larger than your device's bounds, then some parts of the view are not visible.
In response to comments:
textView.post( new Runnable() {
#Override
public void run() {
int displayTextWidth = textView.getWidth();
// Code that uses width here...
}
});

Categories

Resources