I created a custom component called ScrollListView, which is basically a table, extending ListView. Using the function getView below, I fill this component with the data coming from the database. It works perfectly and the result is visually that:
Obviously I would like the cells were aligned, like this:
To achieve this, currently I perform the calculations to measure all the cell sizes, and then adjust the column based on the widest cell, but GC is called several times, causing lag during scrolling, as previously reported is this thread
My question is: how to automatically fix/measure the size of the columns, without crazy calculations called in every getView call, extinguishing all those GC occurrences? I've tried to extend the GridView instead ListView, but it did not work. I´m new on Android.
(I CAN NOT use a standard component, such as GridView or GridLayout, I MUST use my custom component ScrollListView due to other more complex functions that currently operate correctly)
Thanks in advance and sorry for my bad english
Edit: Here is my code (the getView code is in the thread already mentioned):
scroll_listview.xml
<?xml version="1.0" encoding="utf-8"?>
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:background="#color/BackGroundColor"
android:scrollbars="none" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal">
<TableLayout
android:id="#+id/header_lv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal">
</TableLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="horizontal" >
<android.ListView
android:id="#+id/rows_lv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="0dip"
android:divider="#null"
android:dividerHeight="0dp"
android:padding="0dip"
android:scrollbars="horizontal"
android:gravity="center_horizontal"
android:stretchColumns="*"
/>
</LinearLayout>
</LinearLayout>
</HorizontalScrollView>
And my ScrollListView.java
public class ScrollListView extends LinearLayout
{
TableLayout header;
ListView rows;
public ScrollListView(Context context, AttributeSet attrs)
{
super(context, attrs);
LayoutInflater layoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view=layoutInflater.inflate(R.layout.scroll_listview,this);
if (!this.isInEditMode())
{
rows = (ListView)this.findViewById(R.id.rows_lv);
header = (TableLayout)this.findViewById(R.id.header_lv);
rows.setOnRedrawListener(new OnListViewRedraw() {
#Override
public void onBeforeRedraw() {
}
#Override
public void onAfterRedraw() {
}
});
rows.setHeader(header);
rows.setMyParent(this);
}
// (and so on...)
Check out the TableLayout.onMeasure() source code. They find the widths of the cells for all children, then adjusts all children to match accordingly. You will need some modified version of this to fit your needs.
Related
I am extending a "MapView" that itself extends FrameLayout. But I can't access the ui elements I added: level_layout and *level_scroll. I have no trouble adding the code directly to my MainActivity, but I can't get it to work in a library.
<?xml version="1.0" encoding="utf-8"?>
<android.company.com.library.mapbox.MyMapView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/mapView">
<ScrollView
android:id="#+id/level_scroll"
android:layout_width="50dp"
android:layout_height="250dp"
android:layout_gravity="right"
android:layout_marginTop="100dp"
android:orientation="vertical">
<LinearLayout
android:id="#+id/level_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"></LinearLayout>
</ScrollView>
</android.company.com.library.mapbox.MyMapView>
In MyMapView.java I am getting a value from:
int level = R.id.level_layout;
But linear is null when trying to get the LinearLayout
LinearLayout linear = (LinearLayout) findViewById(R.id.level_layout);
I tried Adding
#Override
protected void onFinishInflate() {
super.onFinishInflate();
}
(I try to access my ui elements after this is called)
Calling the following in different places (e.g. constructors)
LayoutInflater inflater = (LayoutInflater)this.getContext().getSystemService
(Context.LAYOUT_INFLATER_SERVICE);
View v = inflater.inflate(R.layout.nameOfTheXmlFile, null);
I don't have multiple layout files with the same name.
I can't use setContentView(...) since this is a View and not an Activity?
Official documentation to MapView here explicitly says
Note: You are advised not to add children to this view.
This means that MapView custom inner views are supported only partially, or not supported at all. I would not recommend you to use something that is badly supported since it may produce weird bugs as the one you have. Especially in MapView with its complicated touch listener and extensive inner UI - you can easily break something(for example zoom or camera swiping with your ScrollView)
Frankly I'm not sure why are you doing this - you can easily place all the views you need in ordinary FrameLayout and everything will work fine(except maybe ScrollView swipe gesture processor may disrupt MapView gestures)
So it would be wiser do to it like this
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.company.com.library.mapbox.MyMapView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/mapView"/>
<FrameLayout
android:id="#+id/wrapper_layout"
android:layout_width="50dp"
android:layout_height="250dp"
android:layout_gravity="right"
android:layout_marginTop="100dp">
<ScrollView
android:id="#+id/level_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="#+id/level_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
</FrameLayout>
</FrameLayout>
This way you will be able to leverage all the possibilities of MapView and FrameLayout separately. Don't forget to handle lifecycle changes for the MapView in your fragment/activity as it is told here.
You may also notice that I wrapped ScrollView with FrameLayout and gave this wrapper layout wight and height, while ScrollView has match_parent. I've done that because ScrollView with defined height and width can be a real pain in the arse and is quite bug prone, thus wrapping it in some other layout is recommended workaround for such purposes.
While it is not the exact solution to your problem, I hope this answer will help you somehow.
In my project I have screens where the same pattern is repeated a lot - it's basically a container for views consisting of a linear layout with the heading, image and specific background. To avoid copying and pasting the same sequence multiple times I thought I could create a compound view, extend LinearLayout and define all the "styling" there, and then just use that component in my layouts.
I followed howto's and examples and got my compound view to work. However, all examples I've seen use the resulting view as follows:
<com.myproject.compound.myview
...some attrs...
/>
I.e. no children are added via XML. I need to use it like this:
<com.myproject.compound.myview
...some attrs...>
<TextView
..../>
...other views...
</com.myproject.compound.myview>
Since I'm extending LinearLayout I was expecting "myview" tag to work like LinearLayout too, but for some reason items I put inside do not get drawn. Is there something I need to do specially to get the inner views to draw?
My extended LinearLayout is very simple, I am not overriding any methods and just calling super in constructor and inflating the layout like this:
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.my_compound_view, this, true);
UPDATE: I thought I'd add an XML as a point of reference:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#drawable/bg"
android:padding="12dp">
<TextView
android:id="#+id/section_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#FF0000AA" />
<ImageView
android:layout_width="match_parent"
android:layout_height="2dp"
android:src="#drawable/line" />
</LinearLayout>
Actually found a more elegant solution. Just need to use merge tag instead of LinearLayout in the compound view. All boils down to:
<merge xmlns:android="http://schemas.android.com/apk/res/android"
<TextView
android:id="#+id/section_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="HEADING"
android:textColor="#FF0000AA" />
<ImageView
android:layout_width="match_parent"
android:layout_height="2dp"
android:src="#drawable/line" />
</merge>
and
public class CompoundLayout extends LinearLayout{
public CompoundLayout(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.compound_layout, this, true);
}
}
Main layout:
<com.testbench.CompoundLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FFFFDDEE"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Inner text"
android:layout_gravity="center_horizontal"/>
</com.testbench.CompoundLayout>
After reading through the Android source and examples I think I figured this one out. Basically in my case it's a hybrid of Compound View and Custom Layout. "Compound view" part takes care about laying out and drawing the content of the XML which specifies the "container". But items inside that container get inflated later on and in my implementation they didn't get laid out.
One way is to follow the Custom Layout path - I'd have to implement onLayout() and onMeasure() to properly calculate my children (and I did during my research, it worked). But since I really do not need anything different than what LinearLayout does already and I don't want to copy/paste it's code (those methods are huge there), I just decided to override the method onFinishInflate() and added my "container view" there. Here is the whole code, please comment is something can be improved.
public class CompoundLayout extends LinearLayout{
View mView;
public CompoundLayout(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mView = inflater.inflate(R.layout.compound_layout, this, false);
}
#Override
public void onFinishInflate(){
super.onFinishInflate();
addView(mView, 0);
}
}
Then in Activity's main layout I just use my custom layout the same way I would use LinearLayout. It renders the same, but always with those TextView and ImageView on top.
I have following code for Gridview (To make the Calendar).
<GridView
android:id="#+id/calendar"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_above="#+id/rl1"
android:layout_below="#+id/ll2"
android:columnWidth="250dp"
android:numColumns="7" >
</GridView>
Grid_Cell is as follows.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#drawable/calendar_button_selector" >
<Button
android:id="#+id/calendar_day_gridcell"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#drawable/calendar_button_selector"
android:gravity="center"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="#000"
android:textSize="14.47sp" >
</Button>
<TextView
android:id="#+id/num_events_per_day"
style="#style/calendar_event_style"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center" >
</TextView>
I want to set no space between the rows and columns.
Existing view is as following.
Please help me out... Thanks in Advance
.
I will try to give you a snippet, but to be honest, there is no point of using a GridView for your case since all you items are there on the screen anyway. You can create a couple of LinearLayouts in a small loop that will get the result.
I would advice you to set the columnWidth on Runtime according to the screen width.
And your adapter should be fed with the column width and height to set them when inflating child views. And in this case, you need to get rid of numColumns. Remember that using numColumns along with columnWidth makes no sense especially when you want to fill the whole space. If you want to set the numColumns, remove the columnWidth.
SOLUTION:
Here is the outcome:
First, we create our layout. In my case, it is the MainActivity's layout and is called activity_main.xml. (Notice there is no GridView because I'll add that later in code):
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<TextView
android:id="#+id/header"
android:background="#444"
android:gravity="center"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="#android:color/white"
android:text="Dynamic Static GridView" />
<TextView
android:id="#+id/footer"
android:gravity="center"
android:background="#444"
android:textColor="#android:color/white"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Shush" />
</RelativeLayout>
Our GridView element's layout is here in the item_grid.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="#fff"
android:layout_height="match_parent" >
<TextView
android:id="#+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textAppearance="?android:attr/textAppearanceLarge" />
</RelativeLayout>
Now the trick is in MainActivity (I have commented some of the code for you to understand):
package com.example.dynamicstaticgridview;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.GridView;
import android.widget.RelativeLayout;
import android.app.Activity;
import android.graphics.Color;
/**
* #author Sherif elKhatib
*
*/
public class MainActivity extends Activity {
private static String items[]; //these are temporary items :p do not use them
static {
items = new String[7*7];
for(int i=0;i<7*7;i++) {
items[i] = String.valueOf(i);
}
}
int numberOfColumns = 7; //defaulting to 7, you can change it when you know
int numberOfRows = 7; //defaulting to 7, you can change it when you know
GridView mGrid;
ArrayAdapter<String> mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
params.addRule(RelativeLayout.BELOW, R.id.header);
params.addRule(RelativeLayout.ABOVE, R.id.footer);
mGrid = new GridView(this) {
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if(!calculated)
getDimens();
}
};
mGrid.setVerticalSpacing(0);
mGrid.setHorizontalSpacing(0);
mGrid.setStretchMode(GridView.NO_STRETCH);
mGrid.setBackgroundColor(Color.rgb(100, 10, 10));
((RelativeLayout)findViewById(R.id.rootview)).addView(mGrid, params);
}
private int mCellWidth;
private int mCellHeight;
boolean calculated = false;
protected void getDimens() {
calculated = true;
//here you might have some rounding errors
//thats why you see some padding around the GridView
mCellWidth = mGrid.getWidth()/numberOfColumns;
mCellHeight = mGrid.getHeight()/numberOfRows;
mGrid.setColumnWidth(mCellWidth);
mGrid.setNumColumns(numberOfColumns);
mAdapter = new ArrayAdapter<String>(this, R.layout.item_grid, R.id.text, items) {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
GridView.LayoutParams params = null;
if(convertView == null) {
params = new GridView.LayoutParams(mCellWidth, mCellHeight);
}
convertView = super.getView(position, convertView, parent);
if(params != null) {
convertView.setLayoutParams(params);
}
return convertView;
}
};
mGrid.setAdapter(mAdapter);
}
}
COMMENTS:
You'd really better find a decent algorithm to choose mCellWidth, mCellHeight, numberOfColumns, and numberOfRows.
I might be wrong but as far as I know the GridView is an AdapterView, which in your case means that you can't dynamically, through xml, format the height of your items in a way so that they always fill the entire parent. That is not the way grid views (and list views and other adapter views) work.
These kind of "scrollable container views" could be seen as microfilm readers that used to exist in "modern libraries" a very long time ago. One needs to realize that the data has really nothing to do with the actual viewer, you just use your viewer (the GridView in your case) with a fix with and height to pan over the underlaying data items which also have their fix widths and heights.
As I see it you're trying to explicitly target the very corner-case where the data just happens to have the same geometric size as your viewer window.
A few tips, though:
You could instead have a look at GridLayout. The GridLayout is introduced in API level 14 (IceCream Sandwich) so you might have to rethink your version support strategy there (if I'm not completely misstaking it should also be included in the v7 support package for backwards compatibility on older platforms, in that case you could simply add the jar to your app to support older API levels).
Yet another tip would be to use a TableLayout with corresponding table rows and what not. The TableLayout has been around from day one, so there is no backward compataibility issues there, but they do tend to become very layout intensive, though. If you are planning to reload your view a lot or scroll between months smoothly, I'm not sure this is the solution for you (due to the amount of deep layout routes).
A third solution would be that you still use the GridView, but you measure the height of it and dynamically set a fix height to your inflated grid view items from your grid view adapter, based on the measured GridView height (In other words: you make sure you enter the above mentioned corner-case).
If you use the GridView you'd need to get rid of the vertical spacing as well. There is a android:stretchMode attribute on the GridView which you could play around with. You could also try different (negative?) dimensions on the android:verticalSpacing attribute.
Good luck with your custom calendar view!
If you want to give a height and width to your gridview put a relative layout outside of the gridview and give android:layout_width and android:layout_height to your relative layout. Then give your gridview's width and height to match_parent. After that if you want to give exact height and width to your gridview cell elements at first you have to know the exact width and height of all cells and you should define the columnWidth according to that, for example for this code
<GridView
android:id="#+id/calendar"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_above="#+id/rl1"
android:layout_below="#+id/ll2"
android:columnWidth="250dp"
android:numColumns="7" >
</GridView>
if you dont have 1000dp layout outside you never get 4 cells in one row in a good view but only gridview will try to determine its own cell width.
for your situation your calendar gridview should be something like this:
<GridView
android:id="#+id/calendar"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_above="#+id/rl1"
android:layout_below="#+id/ll2"
android:columnWidth="50dp"
android:numColumns="7" >
</GridView>
It seems to be a solution for you http://www.anddev.org/how_to_display_gridview_with_gridlines_and_borders-t11099.html
Try setting these two values in XML for your GridView:
android:horizontalSpacing="0dp"
android:verticalSpacing="0dp"
You'll probably want to remove the android:columnWidth field.
I've got three views in my activity in a linear vertical layout. The top and bottom views have fixed heights and the middle view takes whatever height is left available. This is how I set the sizes for the views:
void resize(int clientHeight)
{
int heightMiddle = clientHeight - heightTop - heightBottom;
topView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, heightTop));
middleView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, heightMiddle));
bottomView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.FILL_PARENT));
}
In order to obtain the clientHeight, I overrode the onMeasure() function and call resize() inside my overridden onMeasure() function. This works well in onCreate(). However when the phone orientation changes, it does not work. What I observed is that after onCreate(), onMeasure() is called twice. After onConfigurationChanged(), onMeasure() is only called once and my resizing code does not get a chance to take effect. My kluge solution is to setup a timer to call resize() 20ms later:
timer.schedule(new TimerTask()
{
#Override
public void run()
{
activity.runOnUiThread(new UiTask());
}
}, 20);
where UiTask will simply call resize(). This works for me, but I feel that there's got to be a better solution. Can someone shed some light on this?
Why not let LinearLayout do the layouting with the help of the android:layout_weight attribute? That way, you don't need any extra code at all, everything will just work. Here's what your res/layout/main.xml could look like:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="Top"
android:layout_weight="0"
android:background="#ff0000"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Middle"
android:layout_weight="1"
android:background="#00ff00"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="25dp"
android:text="Bottom"
android:layout_weight="0"
android:background="#0000ff"
/>
</LinearLayout>
And with no more code other than the regular
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
it would look like this in portrait:
and like this in landscape (automatically relayouted and redrawn):
This works both with and without android:configChanges="orientation" for the activity in the manifest. You'll also be able to setup the above layout using Java code if you need to.
I have a HorizontalScollView. It has a LinearLayout child. How do I populate the linear layout with children created based on content from a database since I can't bind a SimpleCursorAdapter to it (obviously)?
I've tried:
SimpleCursorAdapter summaryItems = new SimpleCursorAdapter(this, R.layout.summary_item, summaryItemsCursor, from, to);
int count = summaryItems.getCount();
View new_view;
for (int i = 0; i < count; i++) {
new_view = (summaryItems.newView(this, summaryItemsCursor, mNewsContainer));
mNewsContainer.addView(new_view);
}
I was hoping that the view returned by the SimpleCursorAdapter.newView() would be usable, but apparently not. I'm quite new to android and completely lost as to the right way to do this.
XML's for reference:
<HorizontalScrollView android:id="#+id/news_gallery"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout android:id="#+id/news_container"
android:orientation="horizontal"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
>
</LinearLayout>
</HorizontalScrollView>
and summary_item
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/news_gallery_item"
android:layout_marginRight="#dimen/news_gallery_item_spacing"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView android:id="#+id/news_gallery_item_image"
android:adjustViewBounds="true"
android:maxHeight="#dimen/news_gallery_image_height"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView android:id="#+id/news_gallery_item_tagline"
style="#style/news_gallery_text_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</FrameLayout>
This sounds (and somehow looks) like you'll want to use a ListView. A tutorial might be found here.
To populate the ListView, you can then use a SimpleCursorAdapter.
Since you need it horizontally, you can use the HorizonatalListView-class, which is a user-created extension to a normal ListView (so you can use all it's methods). The corresponding question can be found here.
A ViewBinder with an Inflater on your item view should do the trick