I have created a sample code to load all the images from the gallery and display them in a GridView.
I am using picasso open source library for displaying the images.
The GridView scroll is very slow, I have more than 1000 images that I want to display at once.
Here is my code for getting the images, which is a basic cursor.
paths = new ArrayList<String>();
cursor = getActivity().getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, null);
if (cursor != null) {
for (int i = 0; i < cursor.getCount(); i++) {
cursor.moveToPosition(i);
paths.add(cursor.getString(1));
}
}
And the following code is the getview() implementation in the BaseAdpter where I display the images.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
SquaredImageView iv = (SquaredImageView) convertView;
if (iv == null) {
iv = new SquaredImageView(inflater.getContext());
}
String url = data.get(position);
Picasso.with(inflater.getContext()).load(new File(url)).into(iv);
return iv;
}
/** An image view which always remains square with respect to its width. */
final public class SquaredImageView extends ImageView {
public SquaredImageView(Context context) {
super(context);
}
public SquaredImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth());
}
}
You should just display the photos that are currently visible on the screen, as well as perhaps preloading the ones just above and below them. When they move off screen, you should get rid of them to clear memory.
Use gridView.setOnScrollListener to listen for scrolling. In the onScroll() event, use the parameters firstVisibleItem and visibleItemCount to calculate which views to show and hide.
Related
I'm trying to create a custom view, inherit from view group, and layout custom sub-views inside this view group in a customized way. Basically I'm trying to create a calendar view similar to the one in outlook, where each event takes up screen height relative to its length.
I initialize an ArrayList of View in the ViewGroup's constructor, override onMeasure, onLayout and onDraw, and everything works well, except... the rendered views all render starting at (0,0), even though I set their left and right properties to other values. Their width and height come out ok, only their top and left are wrong.
This is the code, which I abbreviated for clarity and simplicity:
public class CalendarDayViewGroup extends ViewGroup {
private Context mContext;
private int mScreenWidth = 0;
private ArrayList<Event> mEvents;
private ArrayList<View> mEventViews;
// CalendarGridPainter is a class that draws the background grid.
// this one works fine so I didn't write its actual code here.
// it just takes a Canvas and draws lines on it.
// I also tried commenting out this class and got the same result,
// so this is DEFINITELY not the problem.
private CalendarGridPainter mCalendarGridPainter;
public CalendarDayViewGroup(Context context, Date date) {
super(context);
init(date, context);
}
//... other viewGroup constructors go here...
private void init(Date date, Context context) {
mContext = context;
// the following line loads events from a database
mEvents = AppointmentsRepository.getByDateRange(date, date);
// inflate all event views
mEventViews = new ArrayList<>();
LayoutInflater inflater = LayoutInflater.from(mContext);
for (int i = 0; i < mEvents.size(); i++) {
View view = getSingleEventView(mEvents.get(i), inflater);
mEventViews.add(view);
}
// set this flag so that the onDraw event is called
this.setWillNotDraw(false);
}
private View getSingleEventView(Event event, LayoutInflater inflater) {
View view = inflater.inflate(R.layout.single_event_view, null);
// [set some properties in the view's sub-views]
return view;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
// get screen width and create a new GridPainter if needed
int screenWidth = MeasureSpec.getSize(widthMeasureSpec);
if (mScreenWidth != screenWidth)
{
mScreenWidth = screenWidth;
mCalendarGridPainter = new CalendarGridPainter(screenWidth);
}
int numChildren = mEvents.size();
for (int i = 0; i < numChildren; i++) {
View child = mEventViews.get(i);
Event event = mEvents.get(i);
// event width is the same as screen width
int specWidth = MeasureSpec.makeMeasureSpec(mScreenWidth, MeasureSpec.EXACTLY);
// event height is calculated by its length, the calculation was ommited here for simplicity
int eventHeight = 350; // actual calculation goes here...
int specHeight = MeasureSpec.makeMeasureSpec(eventHeight, MeasureSpec.EXACTLY);
child.measure(specWidth, specHeight);
}
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int numChildren = mEvents.size();
for (int i = 0; i < numChildren; i++) {
View child = mEventViews.get(i);
Event event = mEvents.get(i);
int eventLeft = 0;
int eventTop = (i + 1) * 200; // test code, make each event start 200 pixels after the previous one
int eventWidth = eventLeft + child.getMeasuredWidth();
int eventHeight = eventTop + child.getMeasuredHeight();
child.layout(eventLeft, eventTop, eventWidth, eventHeight);
}
}
#Override
protected void onDraw(Canvas canvas) {
// draw background grid
mCalendarGridPainter.paint(canvas);
// draw events
for (View view : mEventViews) {
view.draw(canvas);
}
super.onDraw(canvas);
}
}
For some reason, it seems like the way children are drawn with ViewGroups is that the ViewGroup translates the canvas to child's position then draws the child at 0,0.
But as it turns out, ViewGroup will handle all the drawing of children for you. I think if you simplify your onDraw() method you should be all set:
#Override
protected void onDraw(Canvas canvas) {
// draw background grid
mCalendarGridPainter.paint(canvas);
// draw events
super.onDraw(canvas);
}
Now that I'm looking at your code further, I noticed you are inflating your child views within the code for your ViewGroup. It would be best to do all that outside your ViewGroup, add those views using addView(), then use getChildCount() and getChildAt() to access the child views during onLayout().
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...
I am using a grid view to show my data [i.e. A image and its tiltle]
And my image is loaded dynamically with my imageloader library. The issue is some of my images are in landscape or portrait. And due to this my grid element dont maintain exact square ratio. Some are vertically stretch or compressed other wise.
<GridView
android:id="#+id/fcpl_grid_listview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:numColumns="2"
android:scrollingCache="false"/>
Here is my getView method:
public View getView(final int position, View convertView, final ViewGroup parent) {
final ViewHolder viewholder;
Log.e("Calling...","getView");
if (convertView == null) {
convertView = inflater.inflate(R.layout.inflater_product_list, parent, false);
viewholder = new ViewHolder();
viewholder.productImage = (ImageView) convertView.findViewById(R.id.ipl_imageview);
viewholder.backGroundLayout = (RelativeLayout) convertView.findViewById(R.id.ipl_bk_layout);
viewholder.productName = (TextView) convertView.findViewById(R.id.ipl_productname);
convertView.setTag(viewholder);
} else {
viewholder = (ViewHolder) convertView.getTag();
}
viewholder.productName.setText("SOME TEXT");
String imageUrl = "SOME URL";
imageLoader.get(imageUrl, VALUES_FOR_MY_IMAGE_LOADER_LIBRARY));
return convertView;
}
I tried different suggested method online but none work as i have dynamic image loading and the imageView layoutParams are not known when the getView is called.
I suggest you to make class that extends ImageView (or GridView) and measure bounds of root or any other parent view. Then just use it in your item xml.
public class SquareImageView extends ImageView {
public SquareImageView(Context context) {
super(context);
}
public SquareImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SquareImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
View rootView = this.getRootView();
if (rootView != null) {
width = rootView.getWidth();
height = rootView.getHeight();
}
int size = width > height ? height : width;
super.onMeasure(size, size);
setMeasuredDimension(size, size);
}
}
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 trying to place a listview inside a listviewitem. the inner listview should not be scrollable but take all size it needs to display all it's rows. is there a better way to to this? table, grid, ...? the problem i'm facing right now is that the inner listview doesn't take the space it needs, so it's cut at about the end of the first listitem. if i try to scroll, just the outer listview is scrolling which is exactly what i want.
thanks, my final solution is
LinearLayout layout = (LinearLayout) row.findViewById(R.id.LLBroadcasts);
layout.removeAllViews();
for (Item b : bs.getItems()) {
View child = _inflater.inflate(R.layout.item_row, null);
TextView tvTitle = (TextView) child.findViewById(R.id.TVItemTitle);
tvTitle.setText(b.getTitle());
TextView tvDesc = (TextView) child.findViewById(R.id.TVItemDescription);
tvDesc.setText(b.getDescription());
layout.addView(child);
}
From the Android documentation - Listview: ListView is a view group that displays a list of scrollable items
You do not really want to scroll that inner list view, you want to scroll the outer listview. However I asume that the inner listview may vary on the amount of elements it contains.
Instead of the inner list view you could use a
linear layout, see this tutorial or look at Adding content to a linear layout dynamically?
table layout
For the linear layout (some sample code):
// access your linear layout
LinearLayout layout = (LinearLayout)findViewById(R.id.layout);
// load the xml structure of your row
View child = getLayoutInflater().inflate(R.layout.row);
// now fill the row as you would do with listview
//e.g. (TextView) child.findViewById(...
...
// and than add it
layout.addView(child);
You should save the linear layout in a view holder (see View Holder pattern). I think the removeAllViews() is only necessary when the current row has lesser inner rows than the reused one, so I would also save the number of rows in the view holder.
If the maximum number of inner rows is not to high you could also think about caching them in the view holder to avoid the inflate and findByViewId (lets say in an ArrayList).
I have the same problem in my App but I needed to use a ListView cause it was a shared item and I didn't want to replicate equal components. So.. I just fixed the size of inner ListView programatically to show all rows and.. voila! Problem solved:
ViewGroup.LayoutParams layoutParams = innerListView.getLayoutParams();
layoutParams.height = (int) context.getResources().getDimension(R.dimen.rowheight) * innerListView.getCount();
innerListView.setLayoutParams(layoutParams);
CustomAdapter adapter = new CustomAdapter(context, blabla..);
innerListView.setAdapter(adapter);
rowListView.invalidate();
Maybe somebody will find my solution useful.
It is based on #ChrLipp answer and uses LinearLayout.
public class NotScrollableListView extends LinearLayout {
private ListAdapter adapter;
private DataChangeObserver dataChangeObserver;
private Drawable divider;
private int dividerHeight;
private List<View> reusableViews = new ArrayList<>();
public NotScrollableListView(Context context) {
super(context);
}
public NotScrollableListView(Context context, AttributeSet attrs) {
super(context, attrs);
setAttributes(attrs);
}
public NotScrollableListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setAttributes(attrs);
}
public ListAdapter getAdapter() {
return adapter;
}
public void setAdapter(ListAdapter adapter) {
if (this.adapter != null && dataChangeObserver != null) {
this.adapter.unregisterDataSetObserver(dataChangeObserver);
}
this.adapter = adapter;
}
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (adapter != null) {
dataChangeObserver = new DataChangeObserver();
adapter.registerDataSetObserver(dataChangeObserver);
fillContents();
}
}
#Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (adapter != null) {
adapter.unregisterDataSetObserver(dataChangeObserver);
dataChangeObserver = null;
}
}
private void fillContents() {
// clearing contents
this.removeAllViews();
final int count = adapter.getCount(); // item count
final int reusableCount = reusableViews.size(); // count of cached reusable views
// calculating of divider properties
ViewGroup.LayoutParams dividerLayoutParams = null;
if (divider != null && dividerHeight > 0) {
dividerLayoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dividerHeight);
}
// adding items
for (int i = 0; i < count; i++) {
// adding item
View converView = null;
if (i < reusableCount) { // we have cached view
converView = reusableViews.get(i);
}
View view = adapter.getView(i, converView, this);
if (i >= reusableCount) { // caching view
reusableViews.add(view);
}
addView(view);
// adding divider
if (divider != null && dividerHeight > 0) {
if (i < count - 1) {
ImageView dividerView = new ImageView(getContext());
dividerView.setImageDrawable(divider);
dividerView.setLayoutParams(dividerLayoutParams);
addView(dividerView);
}
}
}
}
private void setAttributes(AttributeSet attributes) {
int[] dividerAttrs = new int[]{android.R.attr.divider, android.R.attr.dividerHeight};
TypedArray a = getContext().obtainStyledAttributes(attributes, dividerAttrs);
try {
divider = a.getDrawable(0);
dividerHeight = a.getDimensionPixelSize(1, 0);
} finally {
a.recycle();
}
setOrientation(VERTICAL);
}
private class DataChangeObserver extends DataSetObserver {
#Override
public void onChanged() {
super.onChanged();
fillContents();
}
#Override
public void onInvalidated() {
super.onInvalidated();
fillContents();
}
}
}
<com.sample.ui.view.NotScrollableListView
android:id="#+id/internalList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="#color/list_divider_color"
android:dividerHeight="#dimen/list_divider_width"
/>
I tried making this exact structure (a ListView inside of a ListView) and had the same problem of it only showing the first item of the inner ListView. I fixed it by changing the layout_height of the inner list from match_parent to a set dp.
It seemed to work exactly as I wanted it to.
#Try this nested class
this works for scroll listView inside listView Or 2 listviews in same activity
<com.example.taskgrptaskslistview.NestedListView
android:id="#+id/listviewTasks"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:layout_weight="1"
android:cacheColorHint="#00000000" >
</com.example.taskgrptaskslistview.NestedListView>
</LinearLayout>
NestedListView :
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ListAdapter;
import android.widget.ListView;
public class NestedListView extends ListView implements OnTouchListener, OnScrollListener {
private int listViewTouchAction;
private static final int MAXIMUM_LIST_ITEMS_VIEWABLE = 99;
public NestedListView(Context context, AttributeSet attrs) {
super(context, attrs);
listViewTouchAction = -1;
setOnScrollListener(this);
setOnTouchListener(this);
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (getAdapter() != null && getAdapter().getCount() > MAXIMUM_LIST_ITEMS_VIEWABLE) {
if (listViewTouchAction == MotionEvent.ACTION_MOVE) {
scrollBy(0, -1);
}
}
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int newHeight = 0;
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (heightMode != MeasureSpec.EXACTLY) {
ListAdapter listAdapter = getAdapter();
if (listAdapter != null && !listAdapter.isEmpty()) {
int listPosition = 0;
for (listPosition = 0; listPosition < listAdapter.getCount()
&& listPosition < MAXIMUM_LIST_ITEMS_VIEWABLE; listPosition++) {
View listItem = listAdapter.getView(listPosition, null, this);
//now it will not throw a NPE if listItem is a ViewGroup instance
if (listItem instanceof ViewGroup) {
listItem.setLayoutParams(new LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}
listItem.measure(widthMeasureSpec, heightMeasureSpec);
newHeight += listItem.getMeasuredHeight();
}
newHeight += getDividerHeight() * listPosition;
}
if ((heightMode == MeasureSpec.AT_MOST) && (newHeight > heightSize)) {
if (newHeight > heightSize) {
newHeight = heightSize;
}
}
} else {
newHeight = getMeasuredHeight();
}
setMeasuredDimension(getMeasuredWidth(), newHeight);
}
#Override
public boolean onTouch(View v, MotionEvent event) {
if (getAdapter() != null && getAdapter().getCount() > MAXIMUM_LIST_ITEMS_VIEWABLE) {
if (listViewTouchAction == MotionEvent.ACTION_MOVE) {
scrollBy(0, 1);
}
}
return false;
}
}