I'm trying to do "Minesweeper". Basically I have GridLayout inside ScrollView and HorizontalScrollView. Everything is OK if all fields are on screen (in my case ImageButtons). But if I zoom in (increase size of ImageButtons), HorizontalScrollView works just partly.
Take a look at this pics:
More I zoom in more fields are hidden on the left side and more free space is on right side.
XML layout:
<ScrollView
android:id="#+id/scrollView1"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<HorizontalScrollView
android:id="#+id/horizontalScrollView1"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<GridLayout
android:id="#+id/grid"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center" >
</GridLayout>
</HorizontalScrollView>
</ScrollView>
And how I add buttons dynamically to GridLayout:
field = (GridLayout) findViewById(R.id.grid);
field.setColumnCount(N);
field.setRowCount(N);
for(int i = 0; i < N; i++){
for(int j = 0; j < N; j++){
ImageButton b = new ImageButton(this);
b.setId(j*10+i);
b.setImageResource(R.drawable.unknowen);
b.setTag(R.drawable.unknowen);
b.setScaleType(ImageView.ScaleType.CENTER_CROP);
b.setPadding(5,5,5,5);
GridLayout.LayoutParams params = new GridLayout.LayoutParams();
params.setMargins(-7,-7,-7,-7);
params.width = fieldSize;
params.height = fieldSize;
b.setOnClickListener(this);
b.setOnLongClickListener(this);
field.addView(b, params);
}
}
I apologize for bad language, and thanks for help!
First, I'm pretty sure nesting a GridView (which implements scroll listening) inside a HorizontalScrollView (which also implements scroll listening) isn't likely to work as expected.
But, if you're doing horizontal scrolling only, you may want to just add android:scrollbars="horizontal" to the GridView and get rid of the HorizontalScrollView entirely (but I'm not sure if you also require vertical scrolling, which would cause this answer to give more problems).
As for adding data to the GridView, you should be changing the data in the LIST of objects the GridView's Adapter is backed by; not the actual View itself. The Views inside the GridView are generated as-needed by the Adapter and are populated in the Adapter's override-able getView() method by mapping the position in the View with the position in your dataset.
Also note, after changing the dataset, you MUST ALWAYS call notifyDataSetChanged() on the Adapter in order for it to refresh its View's with the updated result.
Related
So basically you can't place a ListView in a ScrollView because the Scrolling ability clashes in both layouts. When I tried to do it, the ListView becomes completely useless and many other problems arise.
How has Facebook done it?
As you can see, the work section is a ListView and it's also a Scrollable layout so that the user can scroll down to view the Education section, which is also a ListView.
My code:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#fff"
android:layout_marginBottom="40dp">
<!-- More layouts -->
<ListView
android:id="#+id/work_list"
android:layout_below="#+id/recentpic"
android:layout_marginTop="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</ListView>
</ScrollView >
I do not want a ListView Scroll bar
Therefore the scrolling dilemma is completely removed from the equation. Even when I disable the scroll bars, the problem persists.
Solution that I have in mind:
Generating the XML rows (Each Workplace of the ListView) and injecting it to the layout and avoiding the use of ListViews, similar to HTML Code Generation using Javascript.
What method do you think Facebook has used in their android app to get this done and what changes should I make to my code? :)
Have you tried using NestedScrollView? I think it's a NestedScrollView which contains a ListView and the whole thing is enclosed in a ScrollView. This link might help:
http://ivankocijan.xyz/android-nestedscrollview/
Okay so I managed to code my own idea I mentioned. It's a very 'sexy' code and it gets the job done :D
Here you go, guys. I hope it helps someone :)
So basically I'm inflating a Parent Layout with multiple Child Layouts dynamically and completely getting rid of ListViews in the view. Which makes is very simple to use it with a ScrollView and forget about that dilemma.
Parent Layout:
<RelativeLayout
android:id="#+id/work_list"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</RelativeLayout>
Child Layout - work_single_item.xml
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#ffffff">
<ImageView
android:id="#+id/work_pic"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:src="#mipmap/image_placeholder"/>
</RelativeLayout>
Coded the following lines in the OnCreate function of the Parent Layout.
RelativeLayout parent = (RelativeLayout) findViewById(R.id.work_list);
//array containing all children ids
ArrayList<Integer> children = new ArrayList<>();
//adding 10 children to the parent
for(int i=0;i<10;i++) {
RelativeLayout child = new RelativeLayout(this);
View tempchild = getLayoutInflater().inflate(R.layout.work_single_item, null);
child.addView(tempchild);
child.setId(i); //setting an id for the child
children.add(i); //adding the child's id to the list
if(i!=0) //if it isn't the 1st child, stack them below one another, since the 1st child does not have a child to stack below
{
RelativeLayout.LayoutParams params
= new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.BELOW, children.get(i - 1)); //stack it below the previous child
child.setLayoutParams(params);
}
parent.addView(child); //add the new child
}
I am currently working on an application in which I have to taken into account two ListViews one above another and that too with a common scroll. I know this is not the best practice, instead I should have taken just a single ListView but the problem is with the layouts that doesn't seem to be possible with a sinlge ListView. Actually my first ListView will contain list of friend requests that I can accept or ignore and that are not static and can vary with no limit and second list will contain my friends which may vary too i.e not static. If i have to only show these two things, then I should have preferred single ListView and can be easily done but the actual problem arises when i have to add an Alphabetical sorting of my second list i.e my friends list using side bar and that is applicable only for my second list not for first one. This one is the screenshot without side bar
but alphabetical side bar will be there for new connections. So I don't think, this can be possible with a single list since alphabetical side bar will be added to the whole list not the part of a list. so right now I am trying to save myself from using ScrollView for my two ListViews since that will be very expensive. so I need the best way as how to go with this type of structure. Should take two ListView inside ScrollView or it can be done with a single ListView that will be more preferred.
Please pour your suggestions. Thanks in advance.
What about putting your two ListViews one after the other inside a LinearLayout container and you put that container inside a ScrollView? Then (programatically) you calculate the total height that the ListView needs to be in order to wrap all it's content, so your ListViews will never scroll but your ScrollView will. Here is the idea:
Layout
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<!-- Container -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<!-- Your list views -->
<ListView
android:id="#+id/listView1"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</ListView>
<ListView
android:id="#+id/listView2"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</ListView>
</LinearLayout>
</ScrollView>
Code
private void resizeListView(ListView listView) {
ListAdapter adapter = listView.getAdapter();
int count = adapter.getCount();
int itemsHeight = 0;
// Your views have the same layout, so all of them have
// the same height
View oneChild = listView.getChildAt(0);
if( oneChild == null)
return;
itemsHeight = oneChild.getHeight();
// Resize your list view
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)listView.getLayoutParams();
params.height = itemsHeight * count;
listView.setLayoutParams(params);
}
You may want to (actually you need to) wait for the ListView to get drawn so getChildAt(int index) can actually return a View and avoid getting a null. You can achieve this adding this to your onCreate:
ViewTreeObserver listVTO = listView.getViewTreeObserver();
listVTO.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
listView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
resizeListView(listView);
}
});
Just a suggestion, you may want to try the new RecyclerView because it can handle recycling with different types of Views
I have a FrameLayout that loads Fragments by tapping on tabs in a TabWidget. I can't figure out how to make the height of the FrameLayout as tall as its content, so that the whole containing ScrollView will scroll together as one instead of a separate scrolling view.
Here's a visual example of this Fragment's structure:
As you can see, the Frame Layout Visible Height only reveals one row of the Fragment, when in fact, there are a few. I can scroll within the FrameLayout to see the other rows as it is now, but that's not what I'm going for. The FrameLayout is made up of a LinearLayout containing a GridView with their layout_heights set to wrap_content.
I tried hardcoding the height of the FrameLayout to something like 500dp and it works great except for the fact that it's no longer dynamically sized. Would I need to resize the FrameLayout programmatically each time a new image is loaded into the inner content? Is there a layout attribute I can set so it'll stretch its height to match its inner content?
Here's my layout xml file:
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="200dp">
<!-- CONTAINS USER INFO AND STATS -->
</RelativeLayout>
<android.support.v4.app.FragmentTabHost
android:id="#android:id/tabhost"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#ffffff">
<TabWidget
android:id="#android:id/tabs"
android:layout_width="fill_parent"
android:layout_height="60dp"
android:weightSum="2">
</TabWidget>
<FrameLayout
android:id="#android:id/tabcontent"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
</FrameLayout>
</LinearLayout>
</android.support.v4.app.FragmentTabHost>
</LinearLayout>
</ScrollView>
Thank you!
Since I'm going to set a bounty on this, I thought I'd share what I've figured out so far.
In the thumbnails, onSuccess when each image is loaded, I'm calling a function in the GridLayout that holds the images that counts the images and sets the height of the GridLayout. This works fine, although it seems like it'd be a bit inefficient.
What I'm doing is setting the GridLayout height and then calling requestLayout and invalidate on it and it's parent(s). This works, but not as the images loading. It'll work if I go to a different tab and return to the thumbnails, oddly enough. Which makes me think I'm not updating at the right time or on the right object.
Anyway, that said. Does anyone know how to make the height of a GridLayout expand to hold its contents (instead of scrolling) so I can scroll the entire page (including the top section)?
I should also add the GridView layout:
<GridView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/grid"
android:stretchMode="columnWidth"
android:gravity="center"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:drawSelectorOnTop="false"
android:fastScrollAlwaysVisible="false"
android:fastScrollEnabled="false"
android:numColumns="3"
android:choiceMode="none">
</GridView>
I was in a similar situation but I had a ListView instead of a GridView. You are right in the part when you have to set the height dynamically each time you add an item or if you call notifyDataSetChanged().
THIS CODE IS FOR LISTVIEW WITH DIFFERENT HEIGHT FOR EACH ROW
private void setListViewHeightBasedOnChildren(MyQueueAdapter listAdapter) {
int desiredWidth = MeasureSpec.makeMeasureSpec(
mListView.getWidth(), MeasureSpec.AT_MOST);
int totalHeight = 0;
View view = null;
int i;
for (i = 0; i < listAdapter.getCount(); i++) {
view = listAdapter.getView(i, view, mListView);
view.measure(desiredWidth, MeasureSpec.UNSPECIFIED);
totalHeight += view.getMeasuredHeight();
}
ViewGroup.LayoutParams params = mListView.getLayoutParams();
params.height = heightList
+ (mListView.getDividerHeight() * (listAdapter
.getCount() + 3));
heightListComplete = params.height;
mListView.setLayoutParams(params);
}
You need to modify this code according to your needs, you don't need the loop as the height of each row is static in your case. If you need more help let me know.
ALTERNATIVE
If you know the height of the view in dp you can easily convert the dp in px and set the height of your gridview according to number of rows.
When using dynamic sizes you'll run into problems once you put match_parent inside a wrap_content thing. One tries to get a small as it's content and the content tries to be as big as it's parent. Neither side will know how to scale properly in the end.
ScrollView is such a thing that falls in this category. It's a scalable window to it's content so it can't be wrap_content and the content can't be match_parent because it's parent is a virtual infinite space.
Change <ScrollView android:layout_height="wrap_content" to match_parent (or a fixed size).
To solve the size of the content
set the root layout (LinearLayout in your case) of your ScrollView to be a fixed size so it's content can be match_parent again.
use wrap_content all the way.
combine the two: wrap_content until a child defines an absolute size, then match_parent inside there.
The wrap_content route will only work if all the elements in the layout from inner to outer most expand properly based on their content. Nothing can rely on parent bounds unless you add some.
Your content looks rather dynamic in size. So it is likely that you need to use some code to manually set sizes based on content. E.g. if those images inside your tab frame are a GridView (essentially ScrollView with grid content again) you'll need to set it's size manually. More than 1 degree of freedom in wrapping dynamically sizing containers isn't solvable automatically.
Parent of your frame layout is linear layout whose height is wrap_content. also, your framelayout's height is wrap_content. change both of them to fill_parent. using match_parent is more preferred now a days insted of fill_parent
I am loading some newsitems from a webservice I want to add these to a HorizontalScrollView.
On iOS I would achieve by looping my news items and then add text labels as subviews to a ScrollView. I'am trying to do something similar in android but I can't seem to get it to work properly. No matter what I do, the items are listed below each other.
Heres my code to add the views:
public void setupViews(ArrayList<News> news)
{
HorizontalScrollView scrollView = (HorizontalScrollView) getView().findViewById(R.id.horizontalScrollView);
LinearLayout layout = (LinearLayout) getView().findViewById(R.id.linearLayout);
for(int i = 0; i < news.size(); i ++)
{
News newsItem = news.get(i);
TextView head = new TextView(this.getActivity());
head.setText(newsItem.getHead());
head.setId(100+i);
layout.addView(head);
}
}
The XML looks like this:
<HorizontalScrollView
android:layout_width="fill_parent"
android:layout_height="150dp"
android:id="#+id/horizontalScrollView" android:layout_gravity="center"
>
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" android:id="#+id/linearLayout">
</LinearLayout>
</HorizontalScrollView>
It might be worth noting that I am doing this inside an fragment..
you can only add a single child layout to a ScrollView
in your case :
add a horizontal LinearLayout (set orientation as horizontal via adding this to your LinearLayout android:orientation="horizontal")
add stuff inside the horizontal LinearLayout
and things will fall right into place, horizontally :-)
currently i have the following layout
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_marginTop="9px"
android:layout_below="#+id/desc"
android:id="#+id/ll_item"
android:orientation="vertical"
android:paddingRight="3px"
android:paddingLeft="3px"
android:paddingBottom="5px"
android:paddingTop="5px"
android:background="#drawable/rounded_corner_lists" >
<!--
<ListView android:drawSelectorOnTop="false" android:id="#+id/lv" android:layout_height="fill_parent" android:layout_width="wrap_content" android:divider="#ddd" android:dividerHeight="1px" android:background="#drawable/white" />
-->
</LinearLayout>
the listview that i have commented out, i have tried to make this in the xml, with the height set to wrap_content, fill_parent, currently i am doing this programatically with the following code
LinearLayout ll_item = (LinearLayout) this.findViewById(R.id.ll_item);
if(list.length() > 0)
{
ll_item.setVisibility(View.VISIBLE);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,calcListHeight(list));
listview = new ListView(this);
listview.setBackgroundResource(R.drawable.white);
listview.setDivider( new ColorDrawable(this.getResources().getColor(R.drawable.dividercolor)) );
listview.setDividerHeight(1);
listview.setCacheColorHint(0);
mAdapter = new JSONAdapter( list, this );
listview.setAdapter(mAdapter);
mAdapter.notifyDataSetChanged();
ll_item.addView(listview, lp);
}
this is the result
so you can see in this image, that since i'm containing the listview in a linearlayout to get the rounded corner look, it doesn't just automatically stretch to contain the entire listview, is there any way to have the two elements just wrap the content vertically so there is no scrolling without me programatically setting the height ? ? ?
i guess one other thing i should mention is that i have all this layout in a scrollview, because i want this listview to be a tiny subsection of the entire layout, so it would be something like
-scrollview
-textview
-textview
-linearlayout
-listview
- button
here is a simpler layout of what i have
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:orientation="vertical" android:layout_height="fill_parent" android:layout_width="fill_parent"
android:background="#drawable/bg" xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/titlebar">
<ScrollView android:id="#+id/sv" android:layout_width="fill_parent" android:background="#drawable/bg"
android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout android:id="#+id/widget28"
android:layout_width="fill_parent" android:layout_height="fill_parent" android:padding="4dip"
>
<LinearLayout android:orientation="vertical" style="#style/rounded_corner_full_width_button"
android:id="#+id/editfields">
<ListView android:drawSelectorOnTop="false" android:id="#+id/lv" android:layout_height="fill_parent"
android:layout_width="wrap_content" android:divider="#ddd" android:dividerHeight="1px"
android:background="#drawable/white"/>
</LinearLayout>
</RelativeLayout>
</ScrollView>
</LinearLayout>
ListViews do not go in ScrollViews.
ListView is for displaying a limited window into unbounded content efficiently. If you were to "disable scrolling" on a ListView to put it within a ScrollView you lose all practical reason for using a ListView in the first place.
If you want to use a ListView to show lots of content or unbounded content but also have content above and below that scrolls with it, add header or footer views to the ListView using addHeaderView or addFooterView. If the list content is going to be a small portion of your overall layout as you describe, this probably isn't the best approach for you.
If you have a small, bounded set of content to present, go ahead and use a ScrollView and programmatically generate child views for your "list items" where appropriate.
A common pattern used in the framework to mix inflated XML content with programmatically generated content is to add a placeholder view in the layout XML, usually a LinearLayout or FrameLayout. Use findViewById to locate it at runtime and add generated child views to it.
You could even still use a ListAdapter with this approach if you have one written already, just call content.addView(adapter.getView(position, null, content)) in a loop for all adapter positions (where content is the placeholder view you located with findViewById). Note that this is only practical if you know that you have a small number of list items in the adapter!
Add a empty item on list end
Example:
ArrayList<String> options = new ArrayList<String>();
String lastItem = "";
int lastPosition;
options.add(lastItem);
public function addItem() {
lastPosition = options.size() - 1;
lastItem = options.get(lastPosition);
options.remove(lastPosition);
//add new items dynamically
for (int i = 0; i < 5; i++)
options.add("new item: "+i);
//add empty item
options.add(lastItem);
}