I have a layout displaying some images and text, with buttons at the bottom.
When a button is pressed, I need to display some new information below the buttons.
So the initial content needs to remain at the same height (height of the device screen), and the new content needs to be added beneath it, allowing the user to scroll down.
When a button is pressed it will ideally need to show the new content like a page anchor, but the part I'm having difficulty is getting the initial content to be fullscreened, and maintain that size when new content is added whilst also making the whole thing scroll-able.
I have been playing with different layouts, different height parameters, android:fillViewport="true" or not etc.
I can provide some XML / further explanation if necessary. But I'm not sure whether what I am aiming to achieve is possible or not. At least I'd like to get a scrollable overall view, with the top layout as fullscreen and some layouts underneath which the user can scroll to.
image:
Try this:
Make ScrollView container and add your layout #1 into it
Set height of layout #1 into the code according per screen height
After button click add layout #2 into ScrollView
UPDATED:
Ok, I can suggest you only this solution (it worked for me in emulator).
XML:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:id="#+id/layout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ff0000">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
android:onClick="btnClick"/>
</RelativeLayout>
</LinearLayout>
</ScrollView>
Code:
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void btnClick(View v){
LinearLayout container = (LinearLayout)findViewById(R.id.container);
ScrollView scroll = (ScrollView)findViewById(R.id.scroll);
RelativeLayout layout1 = (RelativeLayout)findViewById(R.id.layout1);
RelativeLayout layout2 = new RelativeLayout(this);
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, 300);
layout2.setBackgroundColor(Color.YELLOW);
layout1.getLayoutParams().height = scroll.getHeight();
scroll.setFillViewport(false);
container.addView(layout2, params);
}
}
I would create a custom ScrollView which sets the height of its first child to be its own height. This will simulate a fullscreen view while still being able to add content below it.
To handle screen size changes properly, the best is to override onSizeChanged() :
public class CustomScrollView extends ScrollView {
// ...
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
View view = getChildAt(0);
if (view != null)
view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, h)));
}
}
Related
I have a listview below a viewpager and in the initial state (when nothing has been scrolled), the viewpager shows only one item with a 10dp "preview" of the next and previous items (I have achieved this by setting a negative page margin:viewPager.setPageMargin(-48);). What I am trying to do is, on scrolling down the listview:
1) the listview should "push" the viewpager up, decreasing its height up to a certain point. On reaching that point (some minHeight for the viewpager), the listview should scroll normally with the smaller sized viewpager above it.
2) The next and the previous items in the viewpager should pull inside (towards the central item) and in the final state, three items of the viewpager should be fully displayed. (Images below to illustrate this)
Scrolling up the listview should do the opposite.
I have managed to do part (1) of my task. Here's the code
My viewpager and listview are inside a FrameLayout like this:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:showIn="#layout/activity_main" tools:context=".MainActivity">
<ListView
android:id="#+id/listView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:dividerHeight="1dp"
android:divider="#000000"
android:scrollbars="none" />
<android.support.v4.view.ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:background="#FFFFFF"/>
</FrameLayout>
I "fake" the listview to be below the viewpager by adding a transaprent header view to the listview and making the heights of both the headeview and the viewpager same. Here's a snippet of the code:
screenWidth = // Screen width of the phone
headerHeight = // Required height of the viewpager and the headerview
headerView = inflater.inflate(R.layout.fake_list_header, listView, false);
headerView.getLayoutParams().height = headerHeight;
headerView.getLayoutParams().width = screenWidth;
viewPager.getLayoutParams().height = headerHeight;
viewPager.getLayoutParams().width = screenWidth;
viewPager.setPageMargin(negativeMargin);
listView.addHeaderView(headerView, null, false);
// Other initializations and stuff
fake_list_header layout file:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Finally, my listview OnScrollListener that takes care of adjusting the viewpager height depending on the amount scrolled by the listview and stopping when we reach the minimum height for the viewpager:
OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView absListView, int i) {
}
#Override
public void onScroll(AbsListView absListView, int i, int i1, int i2) {
if (listview.getFirstVisiblePosition() == 0) {
View firstChild = listview.getChildAt(1); // 0th element is the fake headerview itself
int topY = 0;
if (firstChild != null) {
topY = firstChild.getTop();
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
layoutParams.width = screenWidth;
layoutParams.height = topY;
if (topY < headerHeight && topY >= minHeight) {
// minHeight is the minimum height the viewpager takes, after this point it stops getting smaller
//And vice-versa with headerHeight taking care of the maximum height the viewpager can take
viewpager.setLayoutParams(layoutParams);
}
}
}
}
}
Part (2) of my task is where I am stuck (and running out of ideas), I have tried changing pageMargin of the viewpager with the scroll but the results aren't good (also don't think it is the right approach for achieving something like this). Setting X position of the next(or previous) view in the pager by calling setTranslationX with scroll also isn't working.
Here are some mocks of what I am trying to achieve:
Initial state (nothing scrolled)
Final state (minHeight of viewpager achieved)
Is using viewpager and a listview right way of achieving something like this? I thought of using a horizontal recyclerview instead of a viewpager, but I need the "page by page" scroll behavior of a viewpager for the horizontal scroll/swipe of items. Any suggestions welcome
Try this in your main layout
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:showIn="#layout/activity_main" tools:context=".MainActivity">
<ListView
android:id="#+id/listView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:dividerHeight="1dp"
android:layout_below="#+id/pager"
android:divider="#000000"
android:scrollbars="none" />
<android.support.v4.view.ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="150dp"
android:clipToPadding="false"
android:background="#FFFFFF"/>
</RelativeLayout>
I have a 9 patch image of a shadow that I want to add to the bottom of a RelativeLayout. The layout fills the screen, but slides up then the user taps a button. So, I would like to have the shadow image be below the RelativeLayout (pulled down with a negative bottom margin) so that when the layout sides up, the shadow is on the bottom edge of the layout, giving it a layered effect.
<ImageView
android:id="#+id/shadow"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginBottom="-10dp"
android:src="#drawable/shadow" />
I am sliding up the frame using:
ObjectAnimator mover = ObjectAnimator.ofFloat(mainView, "translationY", !historyShown ? -ph : 0);
mover.setDuration(300);
mover.start();
Strangely, when I add the image to the layout and it give it a negative margin, it just isn't shown, like it is cutting off anything outside the bounds of the layout.
Is there a way around this?
I can think of one way of doing it, but I'm sure there must be a cleaner way.
Make the layout in which the mainView resides a FrameLayout (or RelativeLayout), and include the ImageView in that, making it a sibling of mainView, but list it before mainView. Set it to be at the bottom using layout_gravity="bottom" (or layout_alignParentBottom="true" if using a RelativeLayout).
Now change the target in the ObjectAnimator to something above the mainView (so either its container View or the Activity/Fragment), and change the property to something like "scrollUp", then create a method named setScrollUp(float) in the target. In this method set the translation of the mainView and shadow using setTranslationY(). Offset the shadow by its height.
Works for me here in a simple app using solid colours:
XML:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#f00">
<View
android:id="#+id/shadow"
android:layout_width="match_parent"
android:layout_height="20px"
android:layout_gravity="bottom"
android:background="#00f"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0f0"
android:id="#+id/container"/>
</FrameLayout>
Activity code:
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
container = findViewById(R.id.container);
shadow = findViewById(R.id.shadow);
container.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(final View v)
{
final float translationTo = (open) ? 0 : -300;
final float translationFrom = (open) ? -300 : 0;
open = !open;
ObjectAnimator anim = ObjectAnimator.ofFloat(MyActivity.this, "scrollUp", translationFrom, translationTo);
anim.setDuration(500);
anim.start();
}
});
}
public void setScrollUp(final float position)
{
container.setTranslationY(position);
shadow.setTranslationY(position + shadow.getHeight());
}
I need to move the content of the screen to the left, so I can make room for the slide menu on the right
Here is the code:
// modify content layout params
try {
content = ((LinearLayout) act.findViewById(android.R.id.content)
.getParent());
} catch (ClassCastException e) {
/*
* When there is no title bar
* (android:theme="#android:style/Theme.NoTitleBar"), the
* android.R.id.content FrameLayout is directly attached to the
* DecorView, without the intermediate LinearLayout that holds the
* titlebar plus content.
*/
content = (FrameLayout) act.findViewById(android.R.id.content);
}
FrameLayout.LayoutParams pr = (android.widget.FrameLayout.LayoutParams) content
.getLayoutParams();
pr.rightMargin = menuSize;
content.setLayoutParams(pr);
// add the slide menu to parent
parent = (FrameLayout) content.getParent();
try {
parent = (FrameLayout) content.getParent();
} catch (ClassCastException e) {
/*
* Most probably a LinearLayout, at least on Galaxy S3.
*/
LinearLayout realParent = (LinearLayout) content.getParent();
parent = new FrameLayout(act);
realParent.addView(parent, 0); // add FrameLayout to real parent of
// content
realParent.removeView(content); // remove content from real parent
parent.addView(content); // add content to FrameLayout
}
LayoutInflater inflater = (LayoutInflater) act
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
menu = inflater.inflate(R.layout.slidemenu, null);
FrameLayout.LayoutParams lays = new FrameLayout.LayoutParams(menuSize,
FrameLayout.LayoutParams.MATCH_PARENT, Gravity.RIGHT);
lays.setMargins(0, statusHeight, 0, 0);
menu.setLayoutParams(lays);
parent.addView(menu);
but what I get is:
the content is just resized to fit the screen, how can I make this work?
Btw I can't modify the content layout its a relative layout with a surfaceview, but it should work with any layouts because I modify the DecorView which is the top view container
To move the 'Screen' you'll need to call getLayoutParams() on the View, modify it as necessary and then call setLayoutParams() on the View.
But for a great tutorial to how to implement a slide in menu see here:-
http://android.cyrilmottier.com/?p=658
To add further help, here's a layout that achieve's what I think you're after:-
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/blue" >
</LinearLayout>
<LinearLayout
android:layout_width="200dip"
android:layout_height="match_parent"
android:layout_marginLeft="-100dip"
android:background="#color/grey_divider"
android:orientation="vertical" >
<Button
android:id="#+id/button1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Button" />
</LinearLayout>
This will result in the second LinearLayout appearing 50% off the left hand side of the screen. FYI the LinearLayout you're moving will need an absolute width. But you could define it to FILL_PARENT in your xml, and then get the width and set it to this as an absolute value in code the first time you set the margin to a negative value.
Hope this helps.
Use parent as liner layout, set weight for both left and right layouts view.
put your left view in horizontalScrollview which need to be scrolled out of screen.
What I want is to have a layout with 2 views arranged vertically. Let's call the top view A and the bottom one B. I want the amount of height given to B to be its normal height (i.e. wrap content) except that I don't want it to be given more than half of the available space. A gets what is left.
Another way to word it is that A should always get at least 50% of the available height and B should get at most 50%.
I can't seem to find an easy way to achieve that. I can set both layout heights to 0 and give them equal weights which makes them both 50% always, but if B is smaller than 50% it should be given only what it needs.
The only way I can see to do it is use a custom class for A or B and override onMeasure to constrain the height to 50% of the parent, but it seems there should be an easier way.
Ok, I got it now. If I understood correctly you want to have it like this:
if A > B -> do nothing
if B > A & B > parent layout -> 50% to both of them
if B > A & B < parent layout -> A = parent layout - B
I had to do it all in onWindowFocusChanged because otherwise in onCreate the height of the Views would return 0. I did it with 2 LinearLayouts as child layouts, but you can take what ever you want.
My XML:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:id="#+id/parent_lay"
android:orientation="vertical"
android:layout_height="match_parent" >
//Layout A:
<LinearLayout
android:id="#+id/lay_1"
android:layout_width="match_parent"
android:background="#android:color/background_dark"
android:layout_height="10dp" >
</LinearLayout>
//Layout B:
<LinearLayout
android:id="#+id/lay_2"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="#123456" >
</LinearLayout>
</LinearLayout>
MainActivity:
public class MainActivity extends Activity {
LinearLayout parent_lay;
LinearLayout lay_1;
LinearLayout lay_2;
int parent_height;
int lay_1_height;
int lay_2_heigth;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
#Override
public void onWindowFocusChanged(boolean hasFocus) {
// TODO Auto-generated method stub
super.onWindowFocusChanged(hasFocus);
parent_lay = (LinearLayout) findViewById(R.id.parent_lay);
lay_1 = (LinearLayout) findViewById(R.id.lay_1);
lay_2 = (LinearLayout) findViewById(R.id.lay_2);
lay_1_height = lay_1.getHeight();
lay_2_heigth = lay_2.getHeight();
parent_height = parent_lay.getHeight();
if (lay_2.getHeight() > lay_1.getHeight()
&& lay_2.getHeight() > (parent_lay.getHeight() / 2)) {
lay_1.setLayoutParams(new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, 0, 1));
lay_2.setLayoutParams(new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, 0, 1));
} else if (lay_2.getHeight() < (parent_lay.getHeight() / 2)) {
lay_1.setLayoutParams(new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, (parent_height - lay_2_heigth)));
}
}
}
Example:
If A is 60dp and B is 40dp:
If A is 60dp and B is 400dp:
You must write your own component to achieve this.
For example, if you use LinearLayout here, you can extends a LinearLayout with overdid onMeasure method. You can implement onMeasure like this:
#Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int width = getMeasuredWidth();
final int height = getMeasuredHeight();
setMeasuredDimension(width, height / 2);
}
This code is not elegant enough. If you really want to do it well, copy the original onMeasure method from Android source code (http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/1.5_r4/android/widget/LinearLayout.java#LinearLayout.onMeasure%28int%2Cint%29), and in measureVertical(int widthMeasureSpec, int heightMeasureSpec), set mTotalLength = mTotalLength / 2.
For detailed information of onMeasure, visit http://developer.android.com/guide/topics/ui/custom-components.html and http://developer.android.com/reference/android/view/View.html#onMeasure(int, int).
Now the desired effect can be achieved with the ConstraintLayout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="#+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#+id/containerFrameLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<android.support.constraint.Guideline
android:id="#+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.5"/>
<FrameLayout
android:id="#+id/containerFrameLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constrainedHeight="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/guideline"
app:layout_constraintVertical_bias="1">
<TextView
android:id="#+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</FrameLayout>
</android.support.constraint.ConstraintLayout>
Create a linear layout with two inner frames, each with .5 weight. Inside those frames, place your views, setting them to wrap_content or match_parent as appropriate.
I want to resize my LinearLayout (or a view) to a dimension which is relative to the parent or itself. For example, I want the width to be 1/3 of the parent's width. Or, the height should be same as its own width. I don't want to use any constants , so that it works for all devices.
adding code:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<LinearLayout android:id="#+id/ll_board"
android:gravity="center_horizontal"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</LinearLayout>
...
</LinearLayout>
code:
public class GMActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
LinearLayout board_layout = (LinearLayout)findViewById(R.id.ll_board);
// I wanted to resize board_layout here ..
// getParent().getWidth() returns 0
Log.d("gm", "layout: " + ((LinearLayout) board_layout.getParent()).getWidth());
// ..
}
}
getWidth() is giving 0. Is it too early to call this? If yes, what is the correct place to call this?
Basically my intention is to make the width of the layout a fraction of the screen size width, and, height same as its own width.
Considering layout your LinearLayout and that its parent it's another LinearLayout:
Get the parent's width:
int parentWidth = ((LinearLayout) layout.getParent()).getWidth();
Get the view's width:
int viewWidth = ((LinearLayout) layout).getWidth();
set the
view.setHeight(viewWidth );
view.setWidth(parentWidth / 3);
I found height=width solution (square shaped layout) in LinearLayout in Square Form