What i've tried to far:
Calling measure()
tv.measure(0, 0);
int height = tv.getMeasuredHeight();
Calling measure() with specified sizes/modes
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(99999, View.MeasureSpec.AT_MOST);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
tv.measure(widthMeasureSpec, heightMeasureSpec);
int height = tv.getMeasuredHeight();
Calling getTextBounds()
Rect bounds = new Rect();
tv.getPaint().getTextBounds(tv.getText().toString(), 0, tv.getText().length(), bounds);
int height = bounds.height();
Calling measure() and then calling getTextBounds()
Calling getLineCount() * getLineHeight()
None seem to work. They all return incorrect values (container view gets incorrect height - it's either too small or too large)
Ideas on how to calculate this simple thing??
You need to specify the available width so the height can be properly calculated.
Note: In cases where you just need to get the height of a view that is already drawn, use ViewTreeObserver. See this question for a thing to consider in that case.
This is why I do, in a piece of code where I want to scale a view from hidden to its full necessary height:
int availableWidth = getParentWidth(viewToScale);
int widthSpec = View.MeasureSpec.makeMeasureSpec(availableWidth, View.MeasureSpec.AT_MOST);
int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
viewToScale.measure(widthSpec, heightSpec);
int measuredHeight = viewToScale.getMeasuredHeight();
// Offtopic: Now I animate the height from 1 to measuredHeight (0 doesn't work)
You may pass the availableWidth yourself, but I calculate it from the parent:
private int getParentWidth(View viewToScale)
{
final ViewParent parent = viewToScale.getParent();
if (parent instanceof View) {
final int parentWidth = ((View) parent).getWidth();
if (parentWidth > 0) {
return parentWidth;
}
}
throw new IllegalStateException("View to scale must have parent with measured width");
}
Where are you calling those methods?
The best way to do what you are trying to do is to use ViewTreeObserver and add onPreDrawListener.
Take a look at this i think it will help
What you can do here is get ViewTreeObserver associated with this TextView and add OnGlobalLayoutListener to it:
final ViewTreeObserver vto = textView.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// textView dimensions are calculated at this stage, but textView
// isn't rendered yet. Do what you need to do and remove OnGlobalLayoutListener
// after
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
textView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
} else {
textView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
}
}
I had a similar problem recently, trying to measure the height of a ViewGroup containing several multiline TextView.
What worked for me was to measure() the TextView, then add (lineCount-1)*lineHeight to its measuredHeight.
Here is the code to measure only one TextView :
private int getMeasuredHeight(TextView textView) {
textView.measure(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
int height = textView.getMeasuredHeight();
height += (textView.getLineCount()-1) * textView.getLineHeight();
return height;
}
And in the case of a ViewGroup with many TextViews, here is my code :
private int getMeasuredHeight(ViewGroup viewGroup) {
viewGroup.measure(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
int height = viewGroup.getMeasuredHeight();
for(TextView textView : findAllTextView(viewGroup)) {
height += (textView.getLineCount()-1) * textView.getLineHeight();
}
return height;
}
private List<TextView> findAllTextView(ViewGroup v) {
List<TextView> result = new ArrayList<>();
for (int i = 0; i < v.getChildCount(); i++) {
Object child = v.getChildAt(i);
if (child instanceof TextView)
result.add((TextView) child);
else if (child instanceof ViewGroup)
for(TextView tv: findAllTextView((ViewGroup) child))
result.add(tv);
}
return result;
}
Related
I have an imageView and a textView inside a cardView.
The alpha of the cardView is set to .5f.
The cardView is used in a vertical recyclerView.
What I'm trying to do here is as the user scrolls through the reyclerView the alpha of the completely visible cardView should always change to 1f and for the non-completely visible cardViews alpha stays .5f.
There is only one completely visible cardView at a time.
Here is what I tried but it doesn't work.
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int center = recyclerView.getHeight() / 2;
View centerView = recyclerView.findChildViewUnder( recyclerView.getTop(), center);
int centerPos = recyclerView.getChildAdapterPosition(centerView);
if (prevCenterPos != centerPos) {
// dehighlight the previously highlighted view
View prevView =
recyclerView.getLayoutManager().findViewByPosition(prevCenterPos);
if (prevView != null) {
prevView.setAlpha(.5f);
}
// highlight view in the middle
if (centerView != null) {
prevView.setAlpha(1f);
}
prevCenterPos = centerPos;
}
}
As you are finding center point with this
int center = recyclerView.getHeight() / 2;
which is not proper way you should use:
mLayoutManager.findFirstCompletelyVisibleItemPosition()
try this:
val firstCompelteVisible = mLayoutManager.findFirstCompletelyVisibleItemPosition()
val centerView =
recyclerView.layoutManager!!.findViewByPosition(firstCompelteVisible)
if (prevCenterPos != centerPos) {
// dehighlight the previously highlighted view
val prevView =
recyclerView.layoutManager!!.findViewByPosition(prevCenterPos)
if (prevView != null) {
prevView.alpha = .5f
}
// highlight view in the middle
if (centerView != null) {
prevView!!.alpha = 1f
}
prevCenterPos = centerPos
}
I hope this will work.
I have a LinearLayout inside a LinearLayout and child linearlayout have its layout params where height and width are defined as
LinearLayout linearLayout=new LinearLayout(context);
LinearLayout.LayoutParams lp=new LinearLayout.LayoutParams(50,50);
linearLayout.setLayoutParams(lp);
now after view created i am trying to get height of a view from getHeight() and getMeasuredHeight() but both methods returning 0 . ?
int heightofView=linearLayout.getHeight();
int heightofview=linearLayout.getMeasuredHeight();
why both these methods are returning 0 height of a view ? when these methods return the actual result ? how to get height of my created view ?
Try this code.
imageView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
int width = imageView.getWidth();
int height = imageView.getHeight();
//you can add your code here on what you want to do to the height and width you can pass it as parameter or make width and height a global variable
imageView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
Do as below -
linearLayout.post(new Runnable() {
#Override
public void run() {
int linearLayoutHeight = linearLayout.getHeight();
Log.e("MS", "linearLayout Ht = " + linearLayoutHeight);
}
});
I'd like to do something like the following:
rootLayout.getLayoutParams().height = 100;
Currently I have this line in my 'loadData' method. The problem is - 'layoutParams' seems to be null until sometime after 'loadData' has been called (but before it is displayed obviously).
Is there somewhere I can place this line where the layoutParams will have been instantiated, but still be before the view is shown for the first time?
rootLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
rootLayout.getViewTreeObserver()
.removeOnGlobalLayoutListener(this);
} else {
rootLayout.getViewTreeObserver()
.removeGlobalOnLayoutListener(this);
}
rootLayout.getLayoutParams().height = 100;
rootLayout.requestLayout();
}
});
You should consider to set layoutParams of this view as
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, 100));
rootLayout.setLayoutParams(lp);
So You can set layoutParams height as 100 and assuming that your parent view as linear layout,if it's not, then you should use its layoutparams
on the docs you can check all the callbacks the view have that you can override https://developer.android.com/reference/android/view/View.html
if your view is inside an XML layout with the LayoutParams specified in there, you can/should put your code inside onFinishInflate
#Override
public void onFinishInflate() {
}
if you're doing all this programmatically, you can override the layout pass
#Override
public void onLayout (boolean changed, int left, int top, int right, int bottom){
// be carefull that this get's called several times
}
alternatively (a nicer approach in my opinion), you can trick the onMeasure of your view to have the size you want. For example, to have a view with a maxWidth
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// apply max width
int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
if (maxWidth > 0 && maxWidth < measuredWidth) {
int measureMode = MeasureSpec.getMode(widthMeasureSpec);
widthMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidth, measureMode);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
I have a RecyclerView with Expandable Child Views, when the child ViewGroup is clicked it inflates an amount of views animating the ViewGroup height from 0 to the measured viewgroup height, like the following gif:
The problem is: I'm calling smoothScrollToPosition on recyclerView, it smooth scroll to the view position, but it considers the current view height, which is still not expanded, in the above gif i'm touching on the under view of the recyclerview, which dont scroll to position because the view is already visible, but when i touch again (calling the smoothscrolltoposition again) it scroll the view to the correct position, because the view is already expanded.
Is there any approach to scroll the view to the top of screen or just scroll to make content visible?
For references:
This is the method called to inflate the views:
collapsible_content.removeAllViews();
for(int i = 0; i < 5; i++) {
View link_view = getLayoutInflater().inflate(R.layout.list_item_timeline_step_link, collapsible_content, false);
TextView text = (TextView) link_view.findViewById(R.id.step_link_text);
text.setText("Test");
collapsible_content.addView(link_view);
}
And this is my method to expand:
public void toggle() {
collapsible_content.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
Animation a;
if (mExpanded) {
a = new ExpandAnimation(collapsible_content.getLayoutParams().height, 0);
} else {
a = new ExpandAnimation(collapsible_content.getLayoutParams().height, getMeasuredHeight());
}
a.setDuration(mAnimationDuration);
collapsible_content.startAnimation(a);
mExpanded = !mExpanded;
}
And the animation:
private class ExpandAnimation extends Animation {
private final int mStartHeight;
private final int mDeltaHeight;
public ExpandAnimation(int startHeight, int endHeight) {
mStartHeight = startHeight;
mDeltaHeight = endHeight - startHeight;
}
#Override
protected void applyTransformation(float interpolatedTime,
Transformation t) {
final int newHeight = (int) (mStartHeight + mDeltaHeight *
interpolatedTime);
collapsible_content getLayoutParams().height = newHeight;
if (newHeight <= 0) {
collapsible_content setVisibility(View.GONE);
} else {
collapsible_content setVisibility(View.VISIBLE);
}
collapsible_content requestLayout();
}
#Override
public boolean willChangeBounds() {
return true;
}
}
My solution was to constant check for view bottom within applyTransformation method, and compare it with RecyclerView height, if the bottom get higher than the RV height, i scroll by the diff values:
final int bottom = collapsible_content.getBottom();
final int listViewHeight = mRecyclerView.getHeight();
if (bottom > listViewHeight) {
final int top = collapsible_content.getTop();
if (top > 0) {
mRecyclerView.smoothScrollBy(0, Math.min(bottom - listViewHeight + mRecyclerView.getPaddingBottom(), top));
}
}
The trick was to use Math.min to get the view top, so it don't scroll up making the top not visible.
Solution based on ListViewAnimations
Add an animationlistener and start the scrolling of the recyclerview after the expanding animation is finished.
I have a LinearLayout and ImageView inside this LinearLayout.
There is a translation effect for ImageView.
// v = ImageView
ObjectAnimator animation2 = ObjectAnimator.ofFloat(v, "translationY", 200);
animation2.setDuration(3000);
animation2.setTarget(v);
animation2.start();
Animation working but it's disappearing when ImageView go outside of LinearLayout.
How can i fix it without modify LinearLayout's height.
Find the ViewGroup that the ImageView belongs to and apply ViewGroup.setClipChildren(false).
By default, the drawing of the children is limited to the bounds of the parent ViewGroup.
Two attributes exist that may cause this to happen: clipChildren and clipToPadding. You'll need to set clipChildren to false for each parent ViewGroup whose bounds the object will animate out of. You also need to set clipToPadding to the immediate parent (and maybe more, but I haven't seen a case for it yet).
You can set both attributes in the XML
android:clipChildren="false"
android:clipToPadding="false"
or in code
viewGroup.setClipChildren(false);
viewGroup.setClipToPadding(false);
My implementation. It can probably help somebody:
Java version:
public static void setAllParentsClip(View v, boolean enabled) {
while (v.getParent() != null && v.getParent() instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) v.getParent();
viewGroup.setClipChildren(enabled);
viewGroup.setClipToPadding(enabled);
v = viewGroup;
}
}
call
setAllParentsClip(yourView, false); to disable the clipping in all the parents.
Edited:
Kotlin's version as an extension function:
fun View.setAllParentsClip(enabled: Boolean) {
var parent = parent
while (parent is ViewGroup) {
parent.clipChildren = enabled
parent.clipToPadding = enabled
parent = parent.parent
}
}
Call: yourView.setAllParentsClip(false)
In my case clipChildren did nothing but clipToPadding="false" fixed the problem. Go figure.
Get the view height, then add a percentage of the height to where it will slide to
public void SlideUp(View view){
float height = view.getHeight();
TranslateAnimation animate = new TranslateAnimation(0,0,0,0);
animate.setDuration(500);
animate.setFillAfter(true);
view.animate().translationY((float)(0-0.62*height)).start();
view.startAnimation(animate);
}
try to update camera position as in my case below:
ValueAnimator lockAnimator = ValueAnimator.ofFloat(1, 0); // value from 0 to 1
lockAnimator.setDuration(500);
lockAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator pAnimation) {
float value = (Float) (pAnimation.getAnimatedValue());
if (value < .6 && flipped) {
if (preview != null)
mCanvasImage.setImageBitmap(preview);
else
mCanvasImage.setImageBitmap(imageBitmap);
flipped = false;
}
if (value > .3 && value < .7) {
lyt_rlt_container.setCameraDistance(lyt_rlt_container.getCameraDistance() - 100);
} else {
lyt_rlt_container.setCameraDistance(lyt_rlt_container.getCameraDistance() + 100);
}
lyt_rlt_container.setRotationY(180 * value);
}
});
lockAnimator.start();