Obtain width and height of a Layout - android

I have a FrameLayout, when I try to get its params like this:
ViewGroup.LayoutParams params = cameraPreviewFrameLayout.getLayoutParams();
int layoutHeight = params.height;
int layoutWidth = params.width;
Here, layoutHeight is -2 and layoutWidth is -1. This is my XML:
<FrameLayout
android:id="#+id/liveActivity_cameraPreviewFrameLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"/>
I am performing this action from onCreate, but also I tried to to it through onStart(), with the same result. Obviously the height and width of this layout is not these values.
How can I retrieve the size of the layout effectively?

The values returned are correct, because:
-2 stands for LayoutParams.WRAP_CONENT
-1 stands for LayoutParams.MATCH_PARENT
If you want to get exact values, you'll need to use the methods getHeight and getWidth. However those methods can only be used once the layout has been measured, so delay your call like this:
cameraPreviewFrameLayout.post(new Runnable() {
#Override
public void run() {
View v = cameraPreviewFrameLayout;
Log.e("TAG", v.getWidth() + ":" + v.getHeight());
}
});

Proper way to getting size of layout after or inside onLayoutChange method call
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final FrameLayout layout = (FrameLayout) findViewById(R.id.liveActivity_cameraPreviewFrameLayout);
layout.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
int width = right - left;
int height = bottom - top;
Log.v("TAG", String.format("%d - %d", width, height));
//Or
Log.v("TAG", String.format("%d - %d", layout.getWidth(), layout.getHeight()));
//And after this method call, calling layout.getWidth() and layout.getHeight() will give right values
}
});
}

Related

get ConstraintLayout height and width

I want to make a method that computes the margin values based on the parent's height and width. The code below outputs -1 for both height and width. How can I get the parent's height and width properly?
#RequiresApi(api = Build.VERSION_CODES.R)
public void setMarginsPercentages(Context context, #NotNull ConstraintLayout parent, #NotNull Object object, double leftPercentage, double topPercentage, double rightPercentage, double bottomPercentage) {
FrontEndObject item = getObjectType(object);
ConstraintLayout.LayoutParams params = new ConstraintLayout.LayoutParams(parent.getLayoutParams());
int height = params.height; //outputs -1
int width = params.width; //outputs -1
int left = (int) (leftPercentage * width);
int right = (int) (rightPercentage * width);
int top = (int) (topPercentage * height);
int bottom = (int) (bottomPercentage * height);
item.setMargins(parent, object, left, top, right, bottom);
}
There are couple problems here, the parms.height will give you this value (https://developer.android.com/reference/android/view/ViewGroup.LayoutParams#MATCH_PARENT), which is -1. Not the constraint layout's height in pixel you are looking for.
Also, You cannot use the width/height/getMeasuredWidth/getMeasuredHeight on a View before the system renders it (typically from onCreate/onResume). But you could set a listener, to get the value after the system calculate the pixel for the MATCH_PARENT.
Try this at your fragment's onViewCreated()
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
myView = view.findViewById(R.id.rtt); # your constraint layout
view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
int height = bottom - top;
int width = right - left;
Log.i("LOG", "height is" + height);
Log.i("LOG", "width is " + width);
}
});
}
Anyway, notice you only get this value after the view is rendered. At this point the view is already rendered, you might not be able to edit the margin. You might want to rethink your approach.

Creating a locked ScrollView in Android

I'm trying to implement a ScrollView in Android that doesn't scroll when adding an item above the current scroll position.
The default implementation of ScrollView behaves as following:
Adding an item above the current scroll position:
Adding an item below the current scroll position:
How can I "lock" the ScrollView prior to adding an item above the current scroll position?
This is my layout file, I've currently overridden both the ScrollView and LinearLayout, but haven't made any alterations yet.
<LinearLayout
android:layout_height="fill_parent"
android:orientation="vertical"
android:layout_width="fill_parent">
<LinearLayout
android:id="#+id/LinearLayout02"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_alignParentBottom="true">
<Button
android:id="#+id/Button02"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" android:text="Add To Top"
android:onClick="addToStart">
</Button>
<Button
android:id="#+id/Button03"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Add to End"
android:onClick="addToEnd">
</Button>
</LinearLayout>
<com.poc.scroller.locable.lockablescrollerpoc.LockedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:fillViewport="true"
android:scrollbarAlwaysDrawVerticalTrack="true"
android:verticalScrollbarPosition="right"
android:fadeScrollbars="false"
android:background="#color/scrollColor">
<com.poc.scroller.locable.lockablescrollerpoc.LockedLinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:id="#+id/Container">
</com.poc.scroller.locable.lockablescrollerpoc.LockedLinearLayout>
</com.poc.scroller.locable.lockablescrollerpoc.LockedScrollView>
</LinearLayout>
</android.support.constraint.ConstraintLayout>
Example Source Code:
https://github.com/Amaros90/android-lockable-scroller-poc
Thank you!
You can easily get the opposite behavior by using addOnLayoutChangeListener of your LinearLayout and reset the ScrollView's ScrollY. Here is the implementation in your ScrollViewActivity.onCreate
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.scrollview);
_linearLayout = (LockedLinearLayout)findViewById(R.id.Container);
_scrollView = (LockedScrollView)findViewById(R.id.ScrollView);
_layoutInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
_linearLayout.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
_scrollView.scrollTo(0, _scrollView.getScrollY() + (bottom - oldBottom ));
}
});
addExampleImage(10, _linearLayout);
}
Then you can easily flag the addToEnd function or check where the child was added to avoid changing scroll when some child is added to the bottom.
You can try to use RecyclerView and implement onDataSetChanged(). Then detect whether add to TOP or add to END button was pressed. Then use scrollToPositionWithOffset(int, int) to manage the scroll.
For example:
//Scroll item 3 to 20 pixels from the top
linearLayoutManager.scrollToPositionWithOffset(3, 20);
For restoring the scroll position of a RecyclerView, this is how to save the scroll positions (the two arguments for the method scrollToPositionWithOffset(int, int)):
int index = linearLayoutManager.findFirstVisibleItemPosition();
View v = linearLayoutManager.getChildAt(0);
int top = (v == null) ? 0 : (v.getTop() - linearLayoutManager.getPaddingTop())
Implementing this worked for me. You can check out the example app I added in the original question.
public class LockableScrollView extends ScrollView {
private boolean _enabled = true;
public LockableScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
super.setFillViewport(true);
}
#Override
public void addView(View layout, ViewGroup.LayoutParams params) {
super.addView(layout, params);
((ViewGroup)layout).setOnHierarchyChangeListener(new OnHierarchyChangeListener() {
#Override
public void onChildViewAdded(View layout, View item) {
if (_enabled) {
item.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View item, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
item.removeOnLayoutChangeListener(this);
int scrollViewY = ((LockableScrollView)item.getParent().getParent()).getScrollY();
int layoutPosition = ((View)item.getParent()).getTop();
boolean shouldScroll = item.getTop() + layoutPosition <= scrollViewY || item.getBottom() + getTop() <= scrollViewY;
if (shouldScroll) {
final int childViewHeight = item.getHeight();
((View)item.getParent()).addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View layout, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
layout.removeOnLayoutChangeListener(this);
LockableScrollView scrollView = ((LockableScrollView)layout.getParent());
scrollView.scrollTo(scrollView.getScrollX(), scrollView.getScrollY() + childViewHeight);
}
});
}
}
});
}
}
#Override
public void onChildViewRemoved(View layout, View item) {
if (_enabled) {
int scrollViewY = ((LockableScrollView)layout.getParent()).getScrollY();
int layoutPosition = layout.getTop();
boolean shouldScroll = item.getTop() + layoutPosition <= scrollViewY || item.getBottom() + getTop() <= scrollViewY;
if (shouldScroll) {
final int childViewHeight = item.getHeight();
layout.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View layout, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
layout.removeOnLayoutChangeListener(this);
LockableScrollView scrollView = ((LockableScrollView)layout.getParent());
scrollView.scrollTo(scrollView.getScrollX(), scrollView.getScrollY() - childViewHeight);
}
});
}
}
}
});
}
}
you can do it easily just get it..
first of all in onclick event store the int value by
using getFirstVisiblePosition()
example.
in onclick of add element button do this---
int currentpos = recycleview.getFirstVisiblePosition();
now after you insert elment to list/recyclerview---
recyclerview.scrolltoposition(currentpos);
thats it try if you want to do it without error..
Good Luck :)

Correctly layout children with onLayout in Custom ViewGroup

I am creating a custom ViewGroup. I does need to have 2 FrameLayout one above the other; the one that stays to the bottom must be 20dp, while the other one must cover the rest of the view.
onMeasure
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(widthSize, heightSize);
final View content = getChildAt(CONTENT_INDEX);
final View bar = getChildAt(BAR_INDEX);
content.measure(
MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(heightSize - getPixels(BAR_HEIGHT), MeasureSpec.EXACTLY)
);
bar.measure(
MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getPixels(BAR_HEIGHT), MeasureSpec.EXACTLY)
);
onLayout
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mInLayout = true;
final View content = getChildAt(CONTENT_INDEX);
final View bar = getChildAt(BAR_INDEX);
if (content.getVisibility() != GONE) {
content.layout(0, 0, content.getMeasuredWidth(), content.getMeasuredHeight());
}
if (bar.getVisibility() != GONE) {
bar.layout(0, content.getMeasuredHeight(), bar.getMeasuredWidth(), 0);
}
mInLayout = false;
mFirstLayout = false;
}
}
The views I am adding to this custom ViewGroup
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
mContentContainer = new FrameLayout(getContext());
mContentContainer.setLayoutParams(lp);
mBarContainer = new FrameLayout(getContext());
mBarContainer.setLayoutParams(lp);
// ... adding stuff to both containers ....
addView(mContentContainer, 0);
addView(mBarContainer, 1);
The issue
mContentContainer gets rendered correctly (from top=0 to bottom=(totalHeight - bar height) and match parent for the width), while the bar is not rendered.
The last parameter in the View#layout() method is the bottom of the View. For your bar, you're passing 0, but it should be the height of your custom View, which you can figure from the t and b values passed into onLayout().
bar.layout(0, content.getMeasuredHeight(), bar.getMeasuredWidth(), b - t);

on layout Change Lisnter error

What is the wrong with this code?
row_height gives a value outside onLayoutChange* but gives zero inside CreateEvent
public class MainActivity extends AppCompatActivity {
private int row_height;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView textView = (TextView) findViewById(R.id.textView33);
textView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
row_height = bottom - top;
textView.setText("" + row_height); //this show a correct value
}
});
float Start_time = (float) 6;
int Event_duration_in_min = 120;
int day_number = 3;
CreateEvent(Event_duration_in_min, Start_time, day_number - 1);
}
private void CreateEvent(final int event_duration_in_min, final float start_time, final int day) {
TextView textView = (TextView) findViewById(R.id.textView27);
textView.setText("" + row_height); // this show 0 !!
}
}
how to fix this ?
private void CreateEvent(final int event_duration_in_min, final float start_time, final int day) {
TextView textView = (TextView) findViewById(R.id.textView27);//any textview from the horizontal grid
textView.setText("" + row_height);
//Now creating events here
textView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
RelativeLayout RL = (RelativeLayout) findViewById(R.id.RL);
final int width = right - left;
float scale = getApplicationContext().getResources().getDisplayMetrics().density;//const code line
int pixels_h = (int) (event_duration_in_min * scale + 0.5f);//const code line
TextView tv = new TextView(getApplication().getApplicationContext());
if(row_height == 120) {
RelativeLayout.LayoutParams Params1 = new RelativeLayout.LayoutParams(width, pixels_h * 2);//const code line
tv.setLayoutParams(Params1);
}
else {
RelativeLayout.LayoutParams Params1 = new RelativeLayout.LayoutParams(width, pixels_h);//const code line
tv.setLayoutParams(Params1);
}
tv.setBackgroundResource(R.color.Red);
tv.setText("attttttttef");
tv.setX(day * width);
tv.setY((float) (start_time * row_height));
RL.addView(tv);
}
});
}
The layout for your view has not occurred by the time you call CreateEvent(), so the height will be zero. If you want to call CreateEvent() once your row_height is determined, put it inside your OnLayoutChangeListener, and check that your row_height is greater than zero.
Based on your result, CreateEvent is called before the OnLyoutChangeListener is triggered. Therefore, textView27's text is set before the row_height is assigned in the onLayoutChange (default 0).
Change the signature of your CreateEvent method to receive the row_height as a parameter and change this line to:
CreateEvent(Event_duration_in_min, Start_time, day_number - 1, row_height);

Gravity of a child TextView does not work with custom ViewGroup

I have the following classes:
public class MyGridView extends ViewGroup {
...
#Override
public void onSizeChanged(int xNew, int yNew, int xOld, int yOld) {
super.onSizeChanged(xNew, yNew, xOld, yOld);
// do calculations for drawing bounds of child views
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
View child;
Rect rect;
for (int i=0; i < getChildCount(); i++) {
child = getChildAt(i);
rect = getBoundsAtChildIndex(i)
child.layout(rect.left, rect.top, rect.right, rect.bottom);
}
}
...
}
AND
public class MyAdapter {
public void setMyGridView(MyGridView myGridView) {
// add a single TextView to my grid view
textView = createTextView(context);
addTextViewToGrid(textView);
container.add(textView);
}
private TextView createTextView(Context context) {
TextView textView = new TextView(context);
textView.setText("1");
textView.setTextColor(0xffffffff);
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f);
textView.setTypeface(typeface);
textView.setGravity(Gravity.CENTER | Gravity.FILL);
// textView.setGravity(Gravity.RIGHT); // other tries to see gravity
// textView.setGravity(Gravity.FILL);
return textView;
}
private void addTextViewToGrid(TextView textView) {
myGridViewPtr.addView(textView, ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
}
...
}
I have created a custom grid view and adapter, where its children draw to the screen to the correct size and location. However, their gravity, set in the createTextView() is not observed.
I can set a background image to the textView and see it fill its space in the grid.
In contrast, the text in textView always draws in its top left corner, and it always stays at the text size I set it at 12sp, rather than scale to fit using Gravity.FILL.
Preferably, the text would scale to fit and center within the textview.
EDIT
I have added the following method for onMeasure() in ViewGroup:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
doUpdateOnSizeChanged(width, height);
for (int i = 0; i < getChildCount(); i++)
getChildAt(i).measure((int) viewDim.x, (int) viewDim.y);
setMeasuredDimension(width, height);
}
The children are supposed to be the same height, width as each other. The android example code for ViewGroup is more sophisticated than I require. For instance, I am not getting the max of all children width, height.
For my onMeasure(), the child width, height in ViewDim is width, height integers that are computed in doUpdateOnSizeChanged to be less than getMeasuredWidth, getMeasuredHeight.
The result is now text aligns in the bottom-left corner of the TextView.
onMeasure needed minor corrections, basically needing to rely on MeasureSpec:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width, height;
int cWidth1, cHeight1;
width = MeasureSpec.getSize(widthMeasureSpec);
height = MeasureSpec.getSize(heightMeasureSpec);
doUpdateOnSizeChanged(width, height);
cWidth1 = (int)viewDim.x; cHeight1 = (int)viewDim.y;
measureChildren(MeasureSpec.makeMeasureSpec(cWidth1, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(cHeight1, MeasureSpec.EXACTLY));
setMeasuredDimension(width, height);
}

Categories

Resources