My activity shall have a screen divided into two sections. The first will hold custom view that occupies as much space as possible. The second section holds matrix of buttons. I do not their number at design time, I want to create them in Activity based on screen size and other conditions (easy level will have less buttons).
I currently use LinearLayout
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:id="#+id/puzzleScreen"
android:layout_width="match_parent" android:layout_height="match_parent">
<lelisoft.com.lelimath.view.TilesView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/tiles" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/choices">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="6"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="23"/>
</LinearLayout>
</LinearLayout>
TilesView had consumed complete screen until I overriden onMeasure(). To continue I have to calculate available space, number of buttons and divide space between both sections and pass this information to TilesView and LayoutManagers. Unfortunatelly onCreate() is not correct location for this calculation as I cannot figure out dimensions of root view (getters return 0).
Where is the correct place in Activity lifecycle where to get dimensions and create all buttons dynamically? It seems to me like chicken-egg problem.
UPDATE
I tried to debug other methods: onStart() and onResume() return 0, then measurement is called and finally onCreateOptionsMenu() receives coordinates. Too late.
UPDATE 2
The question is how to find dimensions of the contentView from Activity before measurement. I can get getWindowManager().getDefaultDisplay(), but it contains action bar etc.
protected void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_puzzle);
View puzzleView = findViewById(R.id.puzzleScreen);
// todo get maximum width and height for R.layout.activity_puzzle
Do you know exactly how many rows the bottom matrix has?
You could set the android:layout_weight="1" and android:layout_height="0px"
to tell the ViewGroup that your customView will occupy all the remaining space excluding the Button Matrix.
For your first question:
In onCreate() lifeCycle ,you can not get the dimensions.
Because your the all view have not measured()!
I always use view.post() to get the dimension() in onCreate() method;
view.post(new Runnable() {
#Override
public void run() {
view.getMeasuredHeight();
}
});
For your second question:
On the bottom section,I think you should use custom view extends ViewGroup.Override constructor、onMeasure()、onLayout():
public class TextBlocksView extends ViewGroup {}
In constructor, you add LinearLayout(orientation:horizontal) as much as Line Number,and add each item(addView() method and layoutParams weight 1!!) ....
public TextBlocksView(Context context, AttributeSet attrs) {
super(context, attrs);
this.removeAllViews();
.....
this.addView(linearLayoutView);
}
In onMeasure(), you calculate your dimensions in this place.(Tips:pay attention to dipToPx method!)
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
View childAt = this.getChildAt(0);
int measuredHeight = childAt.getMeasuredHeight();
int measuredWidth = childAt.getMeasuredWidth();
childAt.layout(left, top, right, bottom);
.......
}
in onLayout(),you place your Line item(LinearLayout orientation:horizontal)!
On the top section. you can use the same way!
My mother tongue is chinese,i am not good at english. Hope some guys edit my answer correctly! Thanks a lot.
Related
I have two layouts (green on top, red on bottom) in a vertical LinearLayout (parent) looking similar to this:
.
When focus goes from the green to red, I would like the green to slide up off the screen and have the red simultaneously slide up with it and fill the whole screen. And when focus moves from red back up I want the green to slide back into the screen and return to the original configuration. I have tried looking at many other questions but none have had the solution I need. I tried just changing visibility between gone and visible but I want it to be a smooth animation. I've tried using parentLayout.animate().translationY(greenLayout.getHeight()) on the outer LinearLayout and that does give the animation I want but then the red does not expand to fill the screen, like this:
.
I know this question is similar to this one but that question is really old and only had one answer which didn't work for me.
My solution has a lot of different pieces, so I'll start with the full XML and java code, and then talk about the important bits:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:id="#+id/green"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#0f0" />
<View
android:id="#+id/red"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#f00"/>
</LinearLayout>
In the XML, the only really important part is that the red view uses a height of 0dp and weight of 1. This means it takes up all extra vertical space, which will be important when we get rid of the green view.
public class MainActivity extends AppCompatActivity {
private int originalHeight;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final View green = findViewById(R.id.green);
final View red = findViewById(R.id.red);
green.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
green.getViewTreeObserver().removeGlobalOnLayoutListener(this);
originalHeight = green.getHeight();
}
});
green.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
animateHeightOfView(green, originalHeight, 0);
}
});
red.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
animateHeightOfView(green, 0, originalHeight);
}
});
}
private void animateHeightOfView(final View view, int start, int end) {
ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int height = (int) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams params = view.getLayoutParams();
params.height = height;
view.setLayoutParams(params);
}
});
animator.start();
}
}
In the Java, the two main parts are the ViewTreeObserver.OnGlobalLayoutListener and the animateHeightOfView() method.
The OnGlobalLayoutListener exists to capture the green view's original height. We have to use a listener to do this instead of just writing originalHeight = green.getHeight() inside onCreate() because the green view isn't actually laid out at that point, so getHeight() would return 0 if we tried that.
The animateHeightOfView() method leverages the ValueAnimator class to animate the height of whatever view you pass to it. Since there's no direct setter for a view's height, we can't use simpler methods like .animate(). We set up the ValueAnimator to produce int values on every frame, and then we use a ValueAnimator.AnimatorUpdateListener to modify the view's LayoutParams to set the height.
Feel free to play with it. I'm using click listeners to trigger the animation, and you mentioned focus, but you should be able to call animateHeightOfView() in a different way if it suits you.
For a dropdown animation, I need to get the actual dp height of a view that has been set to wrap_content, thus depending on the number and sizes of its contained views.
The code sample shows the LinearLayout that I need to know the height of, inside a relative layout with a height of 0. The animation is supposed to increase the RelativeLayout's height to the height value of the inner LinearLayout:
<RelativeLayout
android:id="#+id/new_device_wrpr"
android:layout_width="wrap_content"
android:layout_height="#dimen/zero_size"
android:layout_alignLeft="#id/new_device_dd_button_knob"
android:layout_below="#id/new_device_dd_button_knob" >
<LinearLayout
android:id="#+id/new_device_spawn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#android:color/holo_orange_light"
android:orientation="vertical" >
</LinearLayout>
</RelativeLayout>
An implementation like the following only returns 0:
LinearLayout lL = (LinearLayout) findViewById(R.id.new_device_knob_spawn);
lL.getHeight();
As Christian stated you need to wait for layout to occur before you can retrieve the size of the view. If you're attempting to do this in onCreate I would recommend using a global layout listener to execute a callback once layout is complete.
final LinearLayout lL = (LinearLayout) findViewById(R.id.new_device_knob_spawn);
lL.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
int h = lL.getHeight();
// Do whatever you want with h
// Remove the listener so it is not called repeatedly
ViewHelper.removeOnGlobalLayoutListener(lL, this);
}
});
In order not to use a deprecated method I have this static helper method to remove global layout listeners. This is due to a renamed method in Jellybean.
public static void removeOnGlobalLayoutListener(View v, ViewTreeObserver.OnGlobalLayoutListener victim) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
removeLayoutListenerJB(v, victim);
} else removeLayoutListener(v, victim);
}
#SuppressWarnings("deprecation")
private static void removeLayoutListenerJB(View v, ViewTreeObserver.OnGlobalLayoutListener victim) {
v.getViewTreeObserver().removeGlobalOnLayoutListener(victim);
}
#TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private static void removeLayoutListener(View v, ViewTreeObserver.OnGlobalLayoutListener victim) {
v.getViewTreeObserver().removeOnGlobalLayoutListener(victim);
}
getWidth() or getHeight() will work , but it depends "WHEN" you call it!
It always return 0 if you're calling it in "onCreate" event (that's because the view was not measured yet).
I am creating my own custom viewgroup in android. I have two children. One is a linearLayout (it's the first child and covers half of the screen) with some background image and buttons over it and other is a extension of View (it's the second child and covers whole screen) where I draw something using my finger.
I want the (first child) Linear Layout to be hidden under the (second child) extension of view so that I can use some gesture to swipe the second child to the right hand side (kind of like slide of google,youtube) and see the first child (LinearLayout). The problem is inside onLayout I place the children in certain order but the first child (LinearLayout) always comes in front no matter what I do.
secondchild.layout(0,0,top,bottom);
firstchild.layout(0,0,top/2,bottom);
I also tried
firstchild.layout(0,0,top/2,bottom);
secondchild.layout(0,0,top,bottom);
But the first child always comes on top.
Code for Costume ViewGroup:
public class RootViewLayout extends ViewGroup {
private View mDrawView;
private View mSlideView;
private int mTop;
private int mDragRange;
public RootViewLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onFinishInflate() {
mDrawView = findViewById(R.id.content_frame_white);
mSlideView = findViewById(R.id.slide_frame);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(widthSize, heightSize);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom){
bringChildToFront(mDrawView);
mDrawView.layout(0, 0, right, bottom);
mSlideView.layout(0, 0, right/2, bottom);
}
}
XML Code :
<com.example.drawapp.RootViewLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/Root_View_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white">
<com.example.drawapp.DrawView
android:id="#+id/content_frame_white"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/whitepaperwithcoffeestain">
</com.example.drawapp.DrawView>
<LinearLayout
android:id="#+id/slide_frame"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#drawable/slidebackgrd"
android:orientation="vertical">
<Button
android:id="#+id/pen"
android:layout_width="100dip"
android:layout_height="wrap_content"
android:background="#drawable/pic"/>
</LinearLayout>
</com.example.drawapp.RootViewLayout>
When I don't put
bringChildToFront(mDrawView);
the blank is placed with proper black background, but that's not what I actually want. I want whole screen to be covered with DrawView (whose background is white with coffee stain over it).
Is there any specific way to tell the children to be placed one on top of other?
You need to change the z-order of the child views. You should probably use bringChildToFront(), i.e.
parentLayout.bringChildToFront(secondChild);
However, the effect depends on the type of the parent layout (e.g. if it's a LinearLayout then the views would be swapped). Since you're overlaying I guess it means it's a RelativeLayout, and then it should work as you want.
I see that in your case you're using a custom ViewGroup. If it's only to achieve the "full width/half width" children, then I would suggest swapping it for a RelativeLayout. Add secondchild with match_parent and firstchild as right of a centered 0dp view as in Adjust width to half screen
Or another option, possibly simpler, is to just change the visibility on the child that goes on top (VISIBLE or GONE).
I have an Activity with a view hierarchy as follows:
<RelativeLayout>
<LinearLayout>
<GridLayout />
</LinearLayout>
</RelativeLayout>
The top RelativeLayout takes up the full screen while the LinearLayout takes up a subset of the screen. I'm trying to set the size of the children views of the GridLayout in such a way that with n rows and m columns the size of cells is directly related to the n,m values and the width and height of the LinearLayout.
So for example, I'm trying to get the cell width and height as follows:
int linearLayoutWidth = mLinearLayout.getMeasuredWidth();
int linearLayoutHeight = mLinearLayout.getMeasuredHeight();
...
int rows = mGridModel.getRows() + 1;
int columns = mGridModel.getColumns() + 1;
int cellWidth = (int) (linearLayoutWidth / columns);
int cellHeight = (int) (linearLayoutHeight / rows);
Unfortunately, mLinearLayout.getMeasuredWidth() does not return the correct size when the view is created. It always returns the actual device's screen width until the views have been fully laid out - in which case it is too late. I have already given my children cell views the size based on the initially completely incorrect values.
My question is - how do I know when the views have been FULLY laid out. I need to query for their correct or actual measured width/height.
setContentView() creates the views and theoretically lays them out. But they views are not guaranteed to be in their final correct locations/sizes yet.
You can add ViewObserver to any view, in this observer there is a callback method onGlobalLayout, invoked when Layout has been laid;
ViewTreeObserver observer = view.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
//in here, place the code that requires you to know the dimensions.
//this will be called as the layout is finished, prior to displaying.
}
}
Extending jeet's answer, you can have your activity implement the listener, which leads to slightly nicer code.
public class MyActivity extends Activity implements ViewTreeObserver.OnGlobalLayoutListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
...
view.getViewTreeObserver().addOnGlobalLayoutListener(this);
}
#Override
public void onGlobalLayout() {
...
}
}
How can I construct this scenario?
I want to build a game where the game board is a canvas controlled by a custom View but within a ScrollView. The game board is potentially much bigger than the screen and I want to scale, scroll around the game. However, I want all the advantages of the UI outside the game board to include Buttons, TextViews, ImageViews, etc.
Here's the outline code of what I imagine should work. Am I wrong in thinking I can use both
my own onDraw and expect buttons and other widgets to be managed by Android? Must I implement
all my own UI ? Just trying to deal with a Button (see below) fails me Here's the XML where I want my custom view surrounded by other UI elements.
I've looked at a lot of other examples and tutorials but have yet to see a custom view placed with an activity with UI elements around it.
My main activity
// Activity
public class gameStartActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.game);
}
}
My custom view
// gameView.java
public class gameView extends View {
public gameView(Context context) {
super(context);
setFocusable(true); //necessary for getting the touch events
InitBoardView();
}
private void InitBoardView(){
Button myButton = (Button)findViewById(R.id.editname);
-----> fails at this point as myButton is null
myButton.setOnClickListener(doSomething);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(face, getPaddingLeft(), getPaddingTop(),paint);
}
#Override
protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
setMeasuredDimension(xSize, ySize);
}
game.xml
<ScrollView
android:layout_width="240dp"
android:layout_height="600dp"
<com.android.example.game.gameView
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</ScrollView>
<TextView
android:id="#+id/sometext
android:layout_width="120dp"
android:layout_height= "wrap_content"
android:layout_weight="1"
android:visibility="gone"/>
<Button
android:id="#+id/editname"
android:text="DoIt"
android:layout_height="wrap_content"
android:layout_width="wrap_content">
</Button>
findViewById() would work in a custom view only if:
- You extend from ViewGroup (or another subclass of ViewGroup)
- The Button is a child of your custom view
A better way of doing this would be to find the button in the Activity and set its listener there. You could also have a setButton(Button view) method in your custom view and have the activity do the following:
((MyCustomView) findViewById(R.id.myCustomView)).setButton((Button) findViewById(R.id.myButton));