First of all I apologize for not posting any code on what I have done.But I am not getting the point from where and how to start customize listview.
Question :
I have to create a listview in which based on the selection adjacent listitems shall merge.
I googled but couldn't find any relevant information.
Please help me.
Please see the attached image.
See this image..it shows rounded corner background of listitems
Default list is the list with some custom adapter and second image shows what is expected.
It is bit tricky but i think the best possible solution for this problem is listView inside a listView.
Now let me explain,
There should be two list view
LV1 (ExtendedHeightListView) : It will have height equal to the height of all items (obviously view will not recycle in this listview). We will use it as a item for main list view.
public class ExtendedHeightListView extends ListView {
boolean expanded = false;
public ExtendedHeightListView(Context context) {
super(context);
}
public ExtendedHeightListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ExtendedHeightListView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}
public boolean isExpanded() {
return expanded;
}
public void setExpanded(boolean expanded) {
this.expanded = expanded;
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (isExpanded()) {
// Calculate entire height by providing a very large height hint.
// But do not use the highest 2 bits of this integer; those are
// reserved for the MeasureSpec mode.
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
android.view.ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}
LV2 (Default ListView) : Main ListView have items as extended height list view. (It is normal list view so your small list views will recycle).
Now considering you have data in grouped manner. Say
G1 - 2 items,
G2 - 5 items,
G3 - 1 item
BaseAdapter(BA1) for ExtendedHeightListView (LV1) :
public void BA1(Context context, Grp g) {
this.context = context;
this.grp = g;
}
public int getCount() {
return grp.getNumberOfItems();
}
public View getView(int position, ... ) {
// write here your normal getView;
}
BaseAdapter(BA2) for Main ListView (LV2) :
pass G1, G2 and G3 to the adapter.
public int getCount() {
return 3; //Number of groups
}
public View getView (int position, ... ) {
if (convertView == null) {
convertView = inflate LV1;
}
ExtendedHeightListView lv1 = convertView.findViewById(R.id.lv1);
Grp g = getGroup(position);
BA1 adapter = new BA1 (context, g);
//If background of each group is different
lv1.setBackgroundResource(R.drawable.bg);
lv1.setAdapter (adapter);
return convertView;
}
And set your OnItemClickListener on Main ListView (LV2).
I think it will help...
Related
Write a Custom ListView like:
public class MyListView extends ListView {
public MyListView(Context context) {
super(context);
}
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
Log.d("onLayout","onLayout=====");
}
}
As I know, when the layout attribute of view has changed, in order to apply the change ( invalide() or requestLayout() ),its parent's onLayout method should be called and layout its children.
So when I scroll the ListView, the layout attribute of its child view has changed, but onLayout doesn't called at all. Why?
Finally I realized relayout a ViewGroup don't need always call onLayout()/layout() method.There are many ways to change views position in ViewGroup,but each way must call onDraw() to write the changed position in FrameBuffer in order to show it in Screen.(Please tell whether I'm wrong with this)
In ListView,I had debug the source code,and when scroll ListView, the stack trace is:
`
trackMotionScroll:5023, AbsListView (android.widget)
scrollIfNeeded:3424, AbsListView (android.widget)
startScrollIfNeeded:3352, AbsListView (android.widget)
onTouchMove:3793, AbsListView (android.widget)
onTouchEvent:3651, AbsListView (android.widget)
dispatchTouchEvent:9294, View (android.view)
in trackMotionScroll,it will call ViewGroup#offsetChildrenTopAndBottom(incrementalDeltaY)
public void offsetChildrenTopAndBottom(int offset) {
final int count = mChildrenCount;
final View[] children = mChildren;
boolean invalidate = false;
for (int i = 0; i < count; i++) {
final View v = children[i];
v.mTop += offset;
v.mBottom += offset;
if (v.mRenderNode != null) {
invalidate = true;
v.mRenderNode.offsetTopAndBottom(offset);
}
}
if (invalidate) {
invalidateViewProperty(false, false);
}
notifySubtreeAccessibilityStateChangedIfNeeded();
}
This will cause the reLayout of ListView.
So I got a conclusion,you don't need obey the framework's measure()–layout()–draw() procedure,but only change view's layout attribute and invalidate,it will also change view's layout.
And I guess ListView dispense with layout() when scroll will improve its efficiency
I have a GridView with variable height cells. I want the row to be as high as all the largest cell in the row. I am able to adjust the cell heights to be consistent on a row, but I cannot set the Height of the GridView and have it actually change.
Another problem is that this GridView is in a ScrollView, so having a scroll bar is out of the question.
This is a problem because the way the GridView determines the height of the entire Grid is to take the first cell and multiply it by the number of rows. This is an obvious problem if the rows can have different heights. For example:
I have tried numerous ways to update it, but I am sure I am missing something simple. I am trying to do the update in a ViewTreeObserver so I know that the GridView has rendered so my calcs are correct (and they are). The code:
ViewTreeObserver treeListener = mGridView.getViewTreeObserver();
treeListener.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#SuppressLint("NewApi")
#SuppressWarnings("deprecation")
#Override
public void onGlobalLayout() {
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
mGridView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
else {
mGridView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
// Calculate the new height we want for the GridView
int newHeight = determineCellHeight(mGridView, mNumberOfColumns, mRows);
ViewGroup.LayoutParams params = mGridView.getLayoutParams();
params.height = newHeight;
mGridView.setLayoutParams(params);
// Have tried all of these too!!!
// mAdapter.notifyDataSetChanged();
// mGridView.requestLayout();
// mGridView.invalidateViews();
// mGridView.refreshDrawableState();
// mGridView.setLayoutParams(new LinearLayout.LayoutParams( LayoutParams.MATCH_PARENT, newHeight + 10));
// mGridView.setLayoutParams(new LinearLayout.LayoutParams( LayoutParams.MATCH_PARENT, newHeight + 10));
// View lastChild = mGridView.getChildAt( mGridView.getChildCount() - 1 );
// mGridView.setLayoutParams( new LinearLayout.LayoutParams( LayoutParams.MATCH_PARENT, lastChild.getBottom() ) );
// mAdapter.notifyDataSetChanged();
// mGridView.invalidateViews();
// mGridView.setMinimumHeight(newHeight);
// mGridView.requestLayout();
// mGridView.refreshDrawableState();
}
});
I am beginning to wonder if this is even possible, though the numerous Stackflows seem to suggest it is...
I did come across this problem too several months ago, so there's an easy solution. You need to subclass your own GridView, and override the "onMeasure()" method so as to calculate the actual height of your needs. Here is the implementation.
public class ExpandableHeightGridView extends GridView {
boolean expanded = false;
public ExpandableHeightGridView(Context context) {
super(context);
}
public ExpandableHeightGridView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ExpandableHeightGridView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}
public boolean isExpanded() {
return expanded;
}
public void setExpanded(boolean expanded) {
this.expanded = expanded;
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (isExpanded()) {
int expandSpec = MeasureSpec.makeMeasureSpec(MEASURED_SIZE_MASK,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}
Hope it helps.
I'd like to use listview with custom adapter and it dynamically changing.
There is that i want (element above ListView must be scrolled):
I found two main ways to do it:
use ScrollView and code with listView.measure(0,0); to dynamically set up the listview height (but it doesn't work, listview is cropped);
For example: listView have 3 items, but it height is for 2 items (1 item is hidden);
don't use ScrollView, use a setHeaderView (but it doesn't work too, ListView don't have a scrolling)
Any idea?
Use custom ListView ExpandableHeightListView here
import android.content.Context;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.ListView;
public class ExpandableHeightListView extends ListView
{
boolean expanded = false;
public ExpandableHeightListView(Context context)
{
super(context);
}
public ExpandableHeightListView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public ExpandableHeightListView(Context context, AttributeSet attrs,int defStyle)
{
super(context, attrs, defStyle);
}
public boolean isExpanded()
{
return expanded;
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// HACK! TAKE THAT ANDROID!
if (isExpanded())
{
// Calculate entire height by providing a very large height hint.
// But do not use the highest 2 bits of this integer; those are
// reserved for the MeasureSpec mode.
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
}
else
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
public void setExpanded(boolean expanded)
{
this.expanded = expanded;
}
}
use
list.setExpanded(true);
in onCreate() method.
i have a fragment in one of my apps who looks pretty much the same..
and I had the same problem as you, what I did in my case was set up the size for each of my list view cells at a fixed size. and measure the height by that logic.
not sure if that's considered the best way to achieve that, but it worked for me.
filterListView.getLayoutParams().height = (searchLabelsList.size() * (int) (43 * getScale() + 0.5f)) + (filterListView.getDividerHeight() * (searchLabelsList.size() - 1));
filterListView.setAdapter(searchLabelsAdapter);
and in the formula, 43 is the height for each cell, in dp's of course.
getScale is a method I wrote to get the scale of the screen size of the current user phone:
private float scale;
private float getScale() {
if (scale == 0) {
if (getActivity() != null) {
scale = getActivity().getResources().getDisplayMetrics().density;
}
}
return scale;
}
hope this will help, any question feel free to ask :)
good luck
You can try this:
First : In your xml put all other views inside ScrollView including ListView
<ScrollView
android:id="#+id/scrollViewId"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
// Add your other views over here....
<ListView
android:id="#+id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</ScrollView>
Second : In your java file,
Just use this custom method setListViewHeightBasedOnChildren(listview)
How ??
list = (ListView) findViewById(R.id.listview);
list.setAdapter(YOUR CUSTOM ADAPTER);
setListViewHeightBasedOnChildren(list);
Here is your custom method.
public static void setListViewHeightBasedOnChildren(ListView listView)
{
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null)
return;
int desiredWidth = MeasureSpec.makeMeasureSpec(listView.getWidth(), MeasureSpec.UNSPECIFIED);
int totalHeight=0;
View view = null;
for (int i = 0; i < listAdapter.getCount(); i++)
{
view = listAdapter.getView(i, view, listView);
if (i == 0)
view.setLayoutParams(new ViewGroup.LayoutParams(desiredWidth,
LayoutParams.MATCH_PARENT));
view.measure(desiredWidth, MeasureSpec.UNSPECIFIED);
totalHeight += view.getMeasuredHeight();
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight + ((listView.getDividerHeight()) * (listAdapter.getCount()));
listView.setLayoutParams(params);
listView.requestLayout();
}
Hope this helps you somehow.
I have layout having expandablelistview. and in that, each child item is having gridview. but gridview is having own scrolling and expandablelistview is having also. so I must have to set fix length for all gridview but there are dynamic number of gridviews.
I found solution for single gridview from here.
private OnGlobalLayoutListener mOnGlobalLayoutGridListener = new OnGlobalLayoutListener() {
#SuppressWarnings("deprecation")
#Override
public void onGlobalLayout() {
Debug.e("", "onGlobalLayout is called");
if (gridView != null && gridView.getChildCount() > 0) {
gridView.getViewTreeObserver().removeGlobalOnLayoutListener(
this);
// gridView.get
View lastChild = gridView
.getChildAt(gridView.getChildCount() - 1);
int rows = gridView.getAdapter().getCount() / numColumns;
int extra = gridView.getAdapter().getCount() % numColumns;
if (extra > 0) {
rows++;
}
int height = (int) (lastChild.getMeasuredHeight() * rows);
gridView.setLayoutParams(new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, height));
}
}
};
OnGlobalLayoutListener does not reference to particular Gridview. so that I can not use this listener for all gridviews because all gridviews are having different-different number of items.
see here
can anyone help me?
Thanks in advance.
You can use a custom GridView like this,then you don't have to set fix length for all gridview :
public class GridlistView extends GridView{
public GridlistView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void onMeasure(int widthMeasureSpec,int heightMeasureSpec) {
int expendSpec=MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE>>2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expendSpec);
}
}
use the Gridlistview in your xml;
I'm having a little difficulties while trying to get a certain layout to work: I want to have list. List does not have to be scrollable, but should be shown completely. But the page itself should be able to scroll (with the lists in it), if the total content ist higher than the screen.
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/linear_layout"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#ff181818"
>
<Textview android:id="#+id/my_text" text="header contents goes here" android:layout_width="fill_parent" android:layout_height="wrap_content"/>
<Textview android:id="#+id/headertext" text="header contents goes here" android:layout_width="fill_parent" android:layout_height="wrap_content"/>
<ListView
android:id="#+id/my_list1"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
/>
</LinearLayout>
</ScrollView>
it only uses a small part of the screen (about 2 lines per list), instead of filling the available height, and the lists themselves can be scrolled. How can I change the layout to always show the whole lists but have the screen be scrollalbe?
The solution I used is to replace ListView with LinearLayout. You can create all your items inside LinearLayout, they will all be displayed. So there's really no need to use ListView.
LinearLayout list = (LinearLayout)findViewById(R.id.list_recycled_parts);
for (int i=0; i<products.size(); i++) {
Product product = products.get(i);
View vi = inflater.inflate(R.layout.product_item, null);
list.addView(vi);
}
As #Alex noted in the accepted answer that LinearLayout is hardly a replacement. I had a problem where LinearLayout was not an option, that's when i came across this blog. I will put the code here for reference purposes. Hope it helps someone out there!
public class UIUtils {
/**
* Sets ListView height dynamically based on the height of the items.
*
* #param listView to be resized
* #return true if the listView is successfully resized, false otherwise
*/
public static boolean setListViewHeightBasedOnItems(ListView listView) {
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter != null) {
int numberOfItems = listAdapter.getCount();
// Get total height of all items.
int totalItemsHeight = 0;
for (int itemPos = 0; itemPos < numberOfItems; itemPos++) {
View item = listAdapter.getView(itemPos, null, listView);
item.measure(0, 0);
totalItemsHeight += item.getMeasuredHeight();
}
// Get total height of all item dividers.
int totalDividersHeight = listView.getDividerHeight() *
(numberOfItems - 1);
// Set list height.
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalItemsHeight + totalDividersHeight;
listView.setLayoutParams(params);
listView.requestLayout();
return true;
} else {
return false;
}
}
}
Usage:
//initializing the adapter
listView.setAdapter(adapter);
UIUtils.setListViewHeightBasedOnItems(listView);
//whenever the data changes
adapter.notifyDataSetChanged();
UIUtils.setListViewHeightBasedOnItems(listView);
You can make your own customlistview. (It can extends ListView/ExpandableListView/GridView) and override the onMeasure method with this. With this you'll never need to call a function or anything. Just use it in your xml.
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
I had a ListView in my layout and wanted to use a library which can't handle a ListView here because it wraps it into a ScrollView. The best solution for me is based on Fedor´s answer.
Since I already got an ArrayAdapter for the ListView I wanted to re-use it:
LinearLayout listViewReplacement = (LinearLayout) findViewById(R.id.listViewReplacement);
NamesRowItemAdapter adapter = new NamesRowItemAdapter(this, namesInList);
for (int i = 0; i < adapter.getCount(); i++) {
View view = adapter.getView(i, null, listViewReplacement);
listViewReplacement.addView(view);
}
For me this works fine because I just need to display dynamic data varying from 1 to 5 elements. I just had to add my own divider.
If someone still has the problem then you can make customList and add onMesure() method just like I implemented it:
public class ScrolleDisabledListView extends ListView {
private int mPosition;
public ScrolleDisabledListView(Context context) {
super(context);
}
public ScrolleDisabledListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ScrolleDisabledListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final int actionMasked = ev.getActionMasked() & MotionEvent.ACTION_MASK;
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Record the position the list the touch landed on
mPosition = pointToPosition((int) ev.getX(), (int) ev.getY());
return super.dispatchTouchEvent(ev);
}
if (actionMasked == MotionEvent.ACTION_MOVE) {
// Ignore move events
return true;
}
if (actionMasked == MotionEvent.ACTION_UP) {
// Check if we are still within the same view
if (pointToPosition((int) ev.getX(), (int) ev.getY()) == mPosition) {
super.dispatchTouchEvent(ev);
} else {
// Clear pressed state, cancel the action
setPressed(false);
invalidate();
return true;
}
}
return super.dispatchTouchEvent(ev);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}
Check this out:
ListView ignoring wrap_content
Using android:layout_height and android:layout_weight solved it for me:
<ListView
android:layout_height="0dp"
android:layout_weight="1"
/>
I just did it using setting params of ListView
public static void setListViewHeightBasedOnChildren(ListView listView) {
//this comes from value from xml tag of each item
final int HEIGHT_LARGE=75;
final int HEIGHT_LARGE=50;
final int HEIGHT_LARGE=35;
ViewGroup.LayoutParams params = listView.getLayoutParams();
int screenSize = getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;
switch(screenSize) {
case Configuration.SCREENLAYOUT_SIZE_LARGE:
params.height =(int) (HEIGHT_LARGE*size);
break;
case Configuration.SCREENLAYOUT_SIZE_NORMAL:
params.height =(int) (HEIGHT_NORMAL*size);
break;
case Configuration.SCREENLAYOUT_SIZE_SMALL:
params.height =(int) (HEIGHT_SMALL*size);
break;
}
listView.setLayoutParams(params);
}
I don't have a static header, but using HussoM's post as a clue, here is what I was able to get to work. In my scenario, the height of the items in the list was non-uniform, due to variable text sentences in each of the items, and I am using wrap_content for the height and match_parent for the width.
public class NonScrollableListView extends ListView {
public NonScrollableListView(Context context) {
super(context);
}
public NonScrollableListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NonScrollableListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public NonScrollableListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
/**
* Measure the height of all the items in the list and set that to be the height of this
* view, so it appears as full size and doesn't need to scroll.
* #param widthMeasureSpec
* #param heightMeasureSpec
*/
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
ListAdapter adapter = this.getAdapter();
if (adapter == null) {
// we don't have an adapter yet, so probably initializing.
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
int totalHeight = 0;
// compute the height of all the items
int itemCount = adapter.getCount();
for (int index=0; index<itemCount; index++) {
View item = adapter.getView(index, null, this);
// set the width so it can figure out the height
item.measure(widthMeasureSpec, 0);
totalHeight += item.getMeasuredHeight();
}
// add any dividers to the height
if (this.getDividerHeight() > 0) {
totalHeight += this.getDividerHeight() * Math.max(0, itemCount - 1);
}
// make it so
this.setMeasuredDimension(widthMeasureSpec,
MeasureSpec.makeMeasureSpec(totalHeight, MeasureSpec.EXACTLY));
}
}
If all items has the same height
int totalItemsHeight = baseDictionaries.size() * item.getMeasuredHeight();
int totalDividersHeight = listView.getDividerHeight() * (baseDictionaries.size() - 1);
int totalPadding = listView.getPaddingBottom() + listView.getPaddingTop();
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) listTranslationWords.getLayoutParams();
lp.height = totalItemsHeight + totalDividersHeight + totalPadding;
listTranslationWords.setLayoutParams(lp);
Iam supprised no one see this.U cant have two scrolls on the same layout. 1st u have a scrollview and then u have a list, i bet u are killing some android good practices there.
If you want a simple solution to this problem without extending ListView class, this is a solution for you.
mListView.post(new Runnable() {
#Override
public void run() {
int height = 0;
for(int i = 0; i < mListView.getChildCount();i++)
height += mListView.getChildAt(i).getHeight();
ViewGroup.LayoutParams lParams = mListView.getLayoutParams();
lParams.height = height;
mListView.setLayoutParams(lParams);
}
});
In my case, I had ListView inside ScrollView and scrollview was shrinking listview by default. So I just add this in my ScrollView and it worked for me
android:fillViewport="true"
Set android:layout_height="fill_parent" in your LinearLayout