Getting the width/height of a layout in Android - android

I'm wondering how to measure the dimensions of a view. In my case it is aan Absolute Layout. I've read the answers concerning those questions but I still don't get it.
This is my code:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
AbsoluteLayout layoutbase = (AbsoluteLayout) findViewById(R.id.layoutbase);
drawOval();
}
public void drawOval(){ //, int screenWidth, int screenHeight){
AbsoluteLayout layoutbase = (AbsoluteLayout) findViewById(R.id.layoutbase);
int screenWidth = layoutbase.getWidth();
int screenHeight = layoutbase.getHeight();
Log.i("MyActivity", "screenWidth: " + screenWidth + ", screenHeight: " +screenHeight);
Coordinates c = new Coordinates(BUTTONSIZE,screenWidth,screenHeight);
...some code ...
((ViewGroup) layoutbase ).addView(mybutton, new AbsoluteLayout.LayoutParams(BUTTONSIZE, BUTTONSIZE, c.mX, c.mY));
mybutton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
showText(mybutton);
}
});
}
public void showText(View button){
int x = findViewById(LAYOUT).getWidth();
int y = findViewById(LAYOUT).getHeight();
Toast message = Toast.makeText(this, "x: " + x , Toast.LENGTH_SHORT);
message.show();
}
The getWidth() command works great in showText() but it does not in drawOval(). I know it looks a bit different there but I also used the int x = findViewById(LAYOUT).getWidth(); version in drawOval(), and x/y are always 0. I don't really understand why there seems to be no width/height at that earlier point. Even if I actually draw a Button on the Absolute Layout, getWidth() returns 0. Oviously I want to measure the sizes in drawOval().

I think will help you.
LinearLayout headerLayout = (LinearLayout)findviewbyid(R.id.headerLayout);
ViewTreeObserver observer = headerLayout .getViewTreeObserver();
observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// TODO Auto-generated method stub
int headerLayoutHeight= headerLayout.getHeight();
int headerLayoutWidth = headerLayout.getWidth();
headerLayout .getViewTreeObserver().removeGlobalOnLayoutListener(
this);
}
});
}

getWidth() is giving you 0 because onCreate is called before layout actually happens. Due to views being able to have dynamic positions and sizes based on attributes or other elements (fill_parent for example) there's not a fixed size for any given view or layout. At runtime there is a point in time (actually it can happen repeatedly depending on many factors) where everything is actually measured and laid out. If you really need the height and width, you'll have to get them later as you've discovered.

This specially deal with Dimensions so
Display display = getWindowManager().getDefaultDisplay();
int width = display.getWidth();
int height = display.getHeight();
This may help you in managing dimensions.
Note: This returns the display dimensions in pixels - as expected. But the getWidth() and getHeight() methods are deprecated. Instead you can use:
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int width = size.x;
int height = size.y;
as also Martin Koubek suggested.

If your goal is to simply draw an oval on the screen, then consider creating your own custom View rather than messing around with AbsoluteLayout. Your custom View must override onDraw(android.graphics.Canvas), which will be called when the view should render its content.
Here is some extremely simple sample code that might help get you started:
public class MainActivity extends Activity {
private final Paint mPaint = new Paint();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new SampleView(this));
}
// create a nested custom view class that can draw an oval. if the
// "SampleView" is not specific to the Activity, put the class in
// a new file called "SampleView.java" and make the class public
// and non-static so that other Activities can use it.
private static class SampleView extends View {
public SampleView(Context context) {
super(context);
setFocusable(true);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.CYAN);
// smoothen edges
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(4.5f);
// set alpha value (opacity)
mPaint.setAlpha(0x80);
// draw oval on canvas
canvas.drawOval(new RectF(50, 50, 20, 40), mPaint);
}
}
}

This give you screen resolution:
WindowManager wm = (WindowManager)context.getSystemService(context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point outSize = new Point();
display.getSize(outSize);

kabuko's answer is correct, but could be a little more clear, so let me clarify.
getWidth() and getHeight() are (correctly) giving you 0 because they have not been drawn in the layout when you call them. try calling the two methods on the button after addView() (after the view has been drawn and is present in the layout) and see if that gives you the expected result.
See this post for more information.

Related

In which state activity measure it's view

I have created a custom ViewGroup ReadPage, and in activity I use it
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
pager=(ReadPage)findViewById(R.id.readpage);
pager.addArticle("...");
}
While the addArticle need the view's width and height
public void addArticle(String s){
articles.add(new Article(s,getMeasuredWidth(),getMeasuredHeight()));
}
But the measurewidth and measureheight is 0 at that time.
So I want to know at which state the view will be measured so I can get the right value it show in screen.
This answer probably gives you what you need: https://stackoverflow.com/a/1016941/213528
You would maybe use it like this:
private int WIDTH;
private int HEIGHT;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
WIDTH = size.x;
HEIGHT = size.y;
pager = (ReadPage)findViewById(R.id.readpage);
pager.addArticle("...");
}
// ...
public void addArticle(String s){
articles.add(new Article(s, WIDTH, HEIGHT));
}
Use ViewTreeObserver
viewToMeasure.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
viewToMeasure.getViewTreeObserver().removeGlobalOnLayoutListener(this);
/* you can get the view's height and width here
using viewToMeasure.getWidth() and viewToMeasure.getHeight()
*/
}
});
Views are measured sometimes later, during a "measure pass". When View structure changes (due to adding , removing, updating a view), a measure and then a layout pass runs that re-calculates the View sizes and locations.
For example, you can set text data to a TextView any time, but the View itself decides how to display it, when it is ready to display it. The text warp etc is calculated then, and not while setting the text.
You should design the View displaying the Article to be similar. You can provide the data, but let View process and display it further when its onSizeChanged() is called. Views can also employ addOnLayoutChangeListener() to know when layout has been done.

Imageview parallax as you scroll

I saw many examples demonstrative parallax background as you scroll or listview parallax but I cannot find a clear example how to implement a parallax effect on images as you scroll in the activity.
An example implementation can be found in Airbnb app. As you scroll down you can see more of the image's bottom and as you scroll up you can see more of the image's top.
Any hints and tips on how to create such effect?
There are a few libraries that to a parallax effect, it depends on your app if they are useful for your particular case, for example:
ParallaxScroll
Paralloid
Google is your friend pal ;) if none of these suits your needs then you have to create a custom ScrollView but that's a longer story, first give them a try and post your results.
Edit
If none of these fit your requeriments then this is what you have to do:
First, create a custom ScrollView so you can listen to scroll changes.
public class ObservableScrollView extends ScrollView {
public interface OnScrollChangedListener {
public void onScrollChanged(int deltaX, int deltaY);
}
private OnScrollChangedListener mOnScrollChangedListener;
public ObservableScrollView(Context context) {
super(context);
}
public ObservableScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ObservableScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if(mOnScrollChangedListener != null) {
mOnScrollChangedListener.onScrollChanged(l - oldl, t - oldt);
}
}
public void setOnScrollChangedListener(OnScrollChangedListener listener) {
mOnScrollChangedListener = listener;
}
}
Obviously you need to use this in your layout instead of the default ScrollView:
<your.app.package.ObservableScrollView
android:id="#+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
Also you need to wrap your ImageView inside a container to make the parallax work:
<FrameLayout
android:id="#+id/img_container"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<ImageView
android:id="#+id/img"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />
</FrameLayout>
Finally set your Activity as a listener for your brand new ObservableScrollView and let the parallax begin:
public class MyActivity extends Activity implements ObservableScrollView.OnScrollChangedListener {
private ObservableScrollView mScrollView;
private View imgContainer;
#Override
public void onCreate(Bundle savedInstanceState) {
// Init your layout and set your listener
mScrollView = (ObservableScrollView)findViewById(R.id.scroll_view);
mScrollView.setOnScrollChangedListener(this);
// Store the reference of your image container
imgContainer = findViewById(R.id.img_container);
}
#Override
public void onScrollChanged(int deltaX, int deltaY) {
int scrollY = mScrollView.getScrollY();
// Add parallax effect
imgContainer.setTranslationY(scrollY * 0.5f);
}
}
You can modify the 0.5 value depending on how much parallax you want.
Edit
The above answer works fine if your ImageView is in the top of the activity. I am posting some code below to add the functionality to have the ImageView anywhere in the activity layout which I successfully made to work. These are generic calculations (might have some mistakes) and with a little tweak you can have it working for your own case.
For this example I have a fix height for the image container 200dp and for the image 240dp. The main purpose is when the image container is in the middle of the screen have no parallax effect and as the user scroll up or down to apply the effect. So as the image container get closer to the top of the screen or closer to the bottom of the screen the more of the parallax effect will be applied. The following calculations are a little hard to understand by reading them so try to make an example with real numbers in paper.
public class MyActivity extends Activity implements ObservableScrollView.OnScrollChangedListener {
private ObservableScrollView mScrollView;
private View imgContainer;
private ImageView mImageView;
#Override
public void onCreate(Bundle savedInstanceState) {
// Init your layout and set your listener
mScrollView = (ObservableScrollView)findViewById(R.id.scroll_view);
mScrollView.setOnScrollChangedListener(this);
// Store the reference of your image container
imgContainer = findViewById(R.id.img_container);
// Store the reference of your image
mImageView = findViewById(R.id.img);
}
#Override
public void onScrollChanged(int deltaX, int deltaY) {
// Get scroll view screen bound
Rect scrollBounds = new Rect();
mScrollView.getHitRect(scrollBounds);
// Check if image container is visible in the screen
// so to apply the translation only when the container is visible to the user
if (imgContainer.getLocalVisibleRect(scrollBounds)) {
Display display = getWindowManager().getDefaultDisplay();
DisplayMetrics outMetrics = new DisplayMetrics ();
display.getMetrics(outMetrics);
// Get screen density
float density = getResources().getDisplayMetrics().density;
// Get screen height in pixels
float dpHeight = outMetrics.heightPixels / density;
int screen_height_pixels = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpHeight, getResources().getDisplayMetrics());
int half_screen_height = screen_height_pixels/2;
// Get image container height in pixels
int container_height_pixels = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200, getResources().getDisplayMetrics());
// Get the location that consider a vertical center for the image (where the translation should be zero)
int center = half_screen_height - (container_height_pixels/2);
// get the location (x,y) of the image container in pixels
int[] loc_screen = {0,0};
imgContainer.getLocationOnScreen(loc_screen);
// trying to transform the current image container location into percentage
// so when the image container is exaclty in the middle of the screen percentage should be zero
// and as the image container getting closer to the edges of the screen should increase to 100%
int final_loc = ((loc_screen[1]-center)*100)/half_screen_height;
// translate the inner image taking consideration also the density of the screen
mImageView.setTranslationY(-final_loc * 0.4f * density);
}
}
}
I hope it can help someone that is looking for similar functionality.
scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
int top = scrollView.getScrollY(); // Increases when scrolling up ^
if(top != 0) {
int newTop = (int) (top * .5f);
imageFrame.setTop(newTop < 0 ? 0 : newTop);
}
}
});

How to show PopupWindow at special location?

I need to show PopupWindow under one Views shown on the screen.
How can I calculate coordinates of needed View and place PopupWindow under it? Code example are more than welcome. Thanks.
Locating an already displayed view is fairly easy - here's what I use in my code:
public static Rect locateView(View v)
{
int[] loc_int = new int[2];
if (v == null) return null;
try
{
v.getLocationOnScreen(loc_int);
} catch (NullPointerException npe)
{
//Happens when the view doesn't exist on screen anymore.
return null;
}
Rect location = new Rect();
location.left = loc_int[0];
location.top = loc_int[1];
location.right = location.left + v.getWidth();
location.bottom = location.top + v.getHeight();
return location;
}
You could then use code similar to what Ernesta suggested to stick the popup in the relevant location:
popup.showAtLocation(parent, Gravity.TOP|Gravity.LEFT, location.left, location.bottom);
This would show the popup directly under the original view - no guarantee that there would be enough room to display the view though.
you have getLeft() and getBottom() to get the exact position of the view in the layout. You also have getWidth() and getHeight() to know the exact space occupied by the view. If you want to position your popup window below a view.
You setLeft() and setTop() methods of the view to position the new popup Window.
To get size of the main application screen without stuff like title and notification bars, override the following method in the class generating the screen in question (sizes are measured in pixels):
#Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
}
To get the bottom coordinate of the view under which you want to show the popup:
View upperView = ...
int coordinate = upperView.getBottom();
Now as long as height - coordinate is large enough for your popup view, you can simply place the popup like this:
PopupWindow popup = new PopupWindow();
Button button = new Button(this);
button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
popup.showAtLocation(parent, Gravity.CENTER, 0, coordinate);
}
});
Here, showAtLocation() takes the parent view as an argument together with gravity and location offsets.

Determining the size of an Android view at runtime

I am trying to apply an animation to a view in my Android app after my activity is created. To do this, I need to determine the current size of the view, and then set up an animation to scale from the current size to the new size. This part must be done at runtime, since the view scales to different sizes depending on input from the user. My layout is defined in XML.
This seems like an easy task, and there are lots of SO questions regarding this though none which solved my problem, obviously. So perhaps I am missing something obvious. I get a handle to my view by:
ImageView myView = (ImageView) getWindow().findViewById(R.id.MyViewID);
This works fine, but when calling getWidth(), getHeight(), getMeasuredWidth(), getLayoutParams().width, etc., they all return 0. I have also tried manually calling measure() on the view followed by a call to getMeasuredWidth(), but that has no effect.
I have tried calling these methods and inspecting the object in the debugger in my activity's onCreate() and in onPostCreate(). How can I figure out the exact dimensions of this view at runtime?
Use the ViewTreeObserver on the View to wait for the first layout. Only after the first layout will getWidth()/getHeight()/getMeasuredWidth()/getMeasuredHeight() work.
ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
viewTreeObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
viewWidth = view.getWidth();
viewHeight = view.getHeight();
}
});
}
There are actually multiple solutions, depending on the scenario:
The safe method, will work just before drawing the view, after the layout phase has finished:
public static void runJustBeforeBeingDrawn(final View view, final Runnable runnable) {
final OnPreDrawListener preDrawListener = new OnPreDrawListener() {
#Override
public boolean onPreDraw() {
view.getViewTreeObserver().removeOnPreDrawListener(this);
runnable.run();
return true;
}
};
view.getViewTreeObserver().addOnPreDrawListener(preDrawListener);
}
Sample usage:
ViewUtil.runJustBeforeBeingDrawn(yourView, new Runnable() {
#Override
public void run() {
//Here you can safely get the view size (use "getWidth" and "getHeight"), and do whatever you wish with it
}
});
On some cases, it's enough to measure the size of the view manually:
view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
int width=view.getMeasuredWidth();
int height=view.getMeasuredHeight();
If you know the size of the container:
val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(maxWidth, View.MeasureSpec.AT_MOST)
val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(maxHeight, View.MeasureSpec.AT_MOST)
view.measure(widthMeasureSpec, heightMeasureSpec)
val width=view.measuredWidth
val height=view.measuredHeight
if you have a custom view that you've extended, you can get its size on the "onMeasure" method, but I think it works well only on some cases :
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
final int newHeight= MeasureSpec.getSize(heightMeasureSpec);
final int newWidth= MeasureSpec.getSize(widthMeasureSpec);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
If you write in Kotlin, you can use the next function, which behind the scenes works exactly like runJustBeforeBeingDrawn that I've written:
view.doOnPreDraw { actionToBeTriggered() }
Note that you need to add this to gradle (found via here) :
android {
kotlinOptions {
jvmTarget = "1.8"
}
}
implementation 'androidx.core:core-ktx:#.#'
Are you calling getWidth() before the view is actually laid out on the screen?
A common mistake made by new Android developers is to use the width
and height of a view inside its constructor. When a view’s
constructor is called, Android doesn’t know yet how big the view will
be, so the sizes are set to zero. The real sizes are calculated during
the layout stage, which occurs after construction but before anything
is drawn. You can use the onSizeChanged() method to be notified of
the values when they are known, or you can use the getWidth() and
getHeight() methods later, such as in the onDraw() method.
Based on #mbaird's advice, I found a workable solution by subclassing the ImageView class and overriding onLayout(). I then created an observer interface which my activity implemented and passed a reference to itself to the class, which allowed it to tell the activity when it was actually finished sizing.
I'm not 100% convinced that this is the best solution (hence my not marking this answer as correct just yet), but it does work and according to the documentation is the first time when one can find the actual size of a view.
Here is the code for getting the layout via overriding a view if API < 11 (API 11 includes the View.OnLayoutChangedListener feature):
public class CustomListView extends ListView
{
private OnLayoutChangedListener layoutChangedListener;
public CustomListView(Context context)
{
super(context);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
if (layoutChangedListener != null)
{
layoutChangedListener.onLayout(changed, l, t, r, b);
}
super.onLayout(changed, l, t, r, b);
}
public void setLayoutChangedListener(
OnLayoutChangedListener layoutChangedListener)
{
this.layoutChangedListener = layoutChangedListener;
}
}
public interface OnLayoutChangedListener
{
void onLayout(boolean changed, int l, int t, int r, int b);
}
You can check this question. You can use the View's post() method.
Use below code, it is give the size of view.
#Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
Log.e("WIDTH",""+view.getWidth());
Log.e("HEIGHT",""+view.getHeight());
}
This works for me in my onClickListener:
yourView.postDelayed(new Runnable() {
#Override
public void run() {
yourView.invalidate();
System.out.println("Height yourView: " + yourView.getHeight());
System.out.println("Width yourView: " + yourView.getWidth());
}
}, 1);
I was also lost around getMeasuredWidth() and getMeasuredHeight() getHeight() and getWidth() for a long time.......... later i found that getting the view's width and height in onSizeChanged() is the best way to do this........ you can dynamically get your CURRENT width and CURRENT height of your view by overriding the onSizeChanged() method.
might wanna take a look at this which has an elaborate code snippet.
New Blog Post: how to get width and height dimensions of a customView (extends View) in Android http://syedrakibalhasan.blogspot.com/2011/02/how-to-get-width-and-height-dimensions.html
In Kotlin file, change accordingly
Handler().postDelayed({
Your Code
}, 1)
You can get both Position and Dimension of the view on screen
val viewTreeObserver: ViewTreeObserver = videoView.viewTreeObserver;
if (viewTreeObserver.isAlive) {
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
//Remove Listener
videoView.viewTreeObserver.removeOnGlobalLayoutListener(this);
//View Dimentions
viewWidth = videoView.width;
viewHeight = videoView.height;
//View Location
val point = IntArray(2)
videoView.post {
videoView.getLocationOnScreen(point) // or getLocationInWindow(point)
viewPositionX = point[0]
viewPositionY = point[1]
}
}
});
}
If you need to know the dimensions of a View right after it is drawn you can simply call post() on that given View and send there a Runnable that executes whatever you need.
It is a better solution than ViewTreeObserver and globalLayout since it gets called repeatedly not just once.
This Runnsble will execute only once and you will know the views size.
works perfekt for me:
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
CTEditor ctEdit = Element as CTEditor;
if (ctEdit == null) return;
if (e.PropertyName == "Text")
{
double xHeight = Element.Height;
double aHaight = Control.Height;
double height;
Control.Measure(LayoutParams.MatchParent,LayoutParams.WrapContent);
height = Control.MeasuredHeight;
height = xHeight / aHaight * height;
if (Element.HeightRequest != height)
Element.HeightRequest = height;
}
}

How-to create a clickable Button with a ShapeDrawable?

I'm trying to draw a shape on a Canvas and get information about the clicks that the user performs on that Canvas.
I created a class which extends the Button class.
class CustomDrawableButton extends Button {
private final ShapeDrawable mDrawable;
int x = 50;
int y = 50;
int width = 30;
int height = 30;
public CustomDrawableButton (Context context) {
super(context);
mDrawable = new ShapeDrawable (new OvalShape ());
mDrawable.getPaint().setColor(Color.GREEN);
mDrawable.setBounds(x, y, x + width, y + height);
}
protected void onDraw(Canvas canvas) {
mDrawable.draw(canvas);
}
}
Then, in a class which also extends View, I added the instantiation and a listener:
CustomDrawableButton mCustomDrawableButton = new CustomDrawableButton(getBaseContext());
layout.addView(mCustomDrawableButton);
mCustomDrawableButton.draw(canvas);
mCustomDrawableButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
System.out.println("Yey clicked");
Toast.makeText(view.getContext(), "Yey", Toast.LENGTH_LONG).show();
}});
When this is executed, it is not able to detect the clicks on the image / button. I have read that ShapeDrawable objects can't be "clickable", but I was expecting this to work since I'm extending the Button (which is a View and is "clickable").
Is this possible? If not this way, can you direct me to some way to get information about the screen coordinates that were pressed on a Canvas or a View ?
Thanks in advance.
After some searching here's a tutorial that shows how to do it using by getting the touched screen position.
http://marakana.com/tutorials/android/2d-graphics-example.html
Still don't know how to do it by automatically binding the touched event to a button. If anyone knows how to do it, please say so.
Thanks.

Categories

Resources