GridView scrollBar: erratic behaviour when dynamically adding images - java

I am trying to make some kind of image gallery where images are loaded in the background and are dynamically added to a gridView when they have finished loading. The image loading works quite well, but the gridView's scrolling behaviour won't work as expected if the images inside the gridView exceed the screen's height.
For testing purposes I am loading 15 dummy images, aligend in two columns. After all images are loaded it seems that the gridView's height fits its content height (8 images or rows on the left column) according to the scrollBar on the right. But if I try to scroll past the 4th row item to reach the bottom of the view (row 5/6/7/8), the scrollBar indicates that the gridView's height has changed and the bottom of the view is reached. Scrolling past the 4th line is not possible. If I scroll up again, the gridView seems to contain 8 rows again.
Left view: gridView seems to contain 15 images.
Right view: gridView suddenly seems to contain only 8 images
I have already tried using different approaches like the ExpandableHeightViewGrid mentioned here, but the scrolling behaviour was the same. I would choose using a gridView having multiple columns of images over a single row (like using a listView) because if I there are more than 15 images to load, scrolling to the bottom would be very annoying.
Here is my code:
photo_gallery.xml
<?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">
<!-- This is basically a HorizontalScrollView where i add some buttons -->
<com.my.HorizontalButtonScrollList
android:id="#+id/horizontalButtonScrollList"
android:layout_width="match_parent"
android:layout_height="50dip">
</com.my.HorizontalButtonScrollList>
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<GridView
android:id="#+id/gridView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnWidth="100dp"
android:numColumns="2"
android:verticalSpacing="0dp"
android:horizontalSpacing="0dp"
android:stretchMode="columnWidth"
android:gravity="center"
android:scrollbars="vertical">
</GridView>
</android.support.v4.widget.SwipeRefreshLayout>
</LinearLayout>
PhotoGalleryActivity.java (I simplified the code for better readability)
public class PhotoGalleryActivity extends myBaseView {
private GridView gridView;
private PhotoGalleryImageAdapter imageAdapter;
private PhotoGalleryModel[] photoGalleryModels;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.photo_gallery);
gridView = (GridView) findViewById(R.id.gridView);
loadImages();
}
void loadImages() {
photoGalleryModels = PhotoGalleryModel.getFakeData();
imageAdapter = new PhotoGalleryImageAdapter(this, photoGalleryModels);
gridView.setAdapter(imageAdapter);
}
}
PhotoGalleryImageAdapter (also simplified)
public class PhotoGalleryImageAdapter extends BaseAdapter {
private Context mContext;
private PhotoGalleryModel[] photoGalleryModels;
public PhotoGalleryImageAdapter(Context c, PhotoGalleryModel[] models){
mContext = c;
photoGalleryModels = models;
}
#Override
public int getCount() { return photoGalleryModels.length; }
#Override
public Object getItem(int position) { return null; }
#Override
public long getItemId(int position) { return 0; }
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final ImageView imageView = new ImageView(mContext);
DownloadImageWithURL(photoGalleryModels[position].thumb_image_url, new MyHttpCallback() {
#Override
public void MyHttpCallback_OnSuccess(Object data, String responseString)
{
if(data instanceof Bitmap) {
imageView.setImageBitmap((Bitmap)data);
}
}
#Override
public void MyHttpCallback_OnError(String responseString, ErrorDataModel error)
{}
});
convertView = imageView;
return convertView;
}
}
I would be really glad if someone could help me out here and help me fix my gridView so that I can scroll through all of the loaded images as intended.

Well, it seems that I've solved the problem myself by ignoring it. After I skipped fixing the gridView because I did not know what to do anymore I implemented caching the images with an LruCache (like shown in the Android developer's training page) to save some memory. And suddenly the gridView's scrolling behaviour was fixed, too.
Here are my changes:
PhotoGalleryImageAdapter (now with caching)
public class PhotoGalleryImageAdapter extends BaseAdapter {
private Context mContext;
private PhotoGalleryModel[] photoGalleryModels;
private LruCache<String, Bitmap> mMemoryCache;
public PhotoGalleryImageAdapter(Context c, PhotoGalleryModel[] models){
mContext = c;
photoGalleryModels = models;
final int maxMemory = (int)(Runtime.getRuntime().maxMemory() / 1024);
final int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
#Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount() / 1024;
}
};
}
#Override
public int getCount() { return photoGalleryModels.length; }
#Override
public Object getItem(int position) { return null; }
#Override
public long getItemId(int position) { return 0; }
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final ImageView imageView = new ImageView(mContext);
final String imageKey = photoGalleryModels[position].thumb_image_url;
final Bitmap bitmapImage = mMemoryCache.get(imageKey);
if (bitmapImage != null) {
imageView.setImageBitmap(bitmapImage);
}
else {
DownloadImageWithURL(photoGalleryModels[position].thumb_image_url, new MyHttpCallback() {
#Override
public void MyHttpCallback_OnSuccess(Object data, String responseString) {
if (data instanceof Bitmap) {
mMemoryCache.put(imageKey, (Bitmap)data);
imageView.setImageBitmap((Bitmap)data);
}
}
#Override
public void MyHttpCallback_OnError(String responseString, ErrorDataModel error)
{}
});
}
convertView = imageView;
return convertView;
}
}
I am happy that the gridView is finally working, but I'm not happy with the fact that it didn't work for me without caching the images. I should have set the imageView's bounds inside the imageAdapter's getView() method before the images were loaded, probably. I will try to fix the gridView without using caching and update my answer if I have found a solution to it in case someone has to face the same problems. Until then, I am glad that I managed to make it work :)
UPDATE:
I finally made it work with and without caching. Here is my updated PhotoGalleryImageAdapter:
public View getView(int position, View convertView, ViewGroup parent) {
final ImageView imageView;
// set the imagge's bounds if it is not loaded yet
if (convertView == null) {
imageView = new ImageView(mContext);
imageView.setLayoutParams(new ViewGroup.LayoutParams(imageSize, imageSize));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(0, 0, 0, 0);
}
else {
imageView = (ImageView) convertView;
}
final String imageKey = photoGalleryModels[position].thumb_image_url;
final Bitmap bitmapImage = mMemoryCache.get(imageKey);
if (bitmapImage != null) {
imageView.setImageBitmap(bitmapImage);
}
else {
imageView.setImageBitmap(emptyBitmap);
DownloadImageWithURL(photoGalleryModels[position].thumb_image_url, new MyHttpCallback() {
#Override
public void MyHttpCallback_OnSuccess(Object data, String responseString) {
if (data instanceof Bitmap) {
mMemoryCache.put(imageKey, (Bitmap)data);
imageView.setImageBitmap((Bitmap)data);
}
}
#Override
public void MyHttpCallback_OnError(String responseString, ErrorDataModel error)
{}
});
}
convertView = imageView;
return convertView;
}
As expected, I needed to set the images bounds before it was loaded.
Because I changed the gridView's numColumns parameter to 'auto_fit', the image's width/height (100dp + stretchMode columnWidth) is calculated as follows:
int imagesPerRow = screenSize.x / (int)(100 * mContext.getResources().getDisplayMetrics().density);
imageSize = screenSize.x / imagesPerRow;
Before loading the imageView's bitmapImage, I create an empty bitmap image and assign it to the imageView (found the code here):
emptyBitmap = Bitmap.createBitmap(imageSize, imageSize, Bitmap.Config.ARGB_8888);
The gridView now works as expected no matter if the LruCache is used or not. I don't know if it's common practice to anser one's own question but in doing so I thought it could help others who facing a similar problem.

Related

Android GridView not showing images

I have made a GridView with an ImageAdapter but it does not show the images inside.
I tried to change numColumns, columnWidth and other attributes but it didn't work.
In Android Studio xml Design panel i can see my Gridview.
This is my gridview inside my xml layout file:
<GridView
android:id="#+id/gridview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/linear_buttons"
android:layout_above="#+id/btnSearch"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:background="#null"
android:columnWidth="120dp"
android:gravity="center"
android:numColumns="auto_fit"
android:stretchMode="columnWidth" />
This is my Adapter:
public class CustomGridViewAdapter extends BaseAdapter {
private final Context mContext;
public CustomGridViewAdapter(Context c) {
mContext = c;
}
public int getCount() {
return mThumbIds.length;
}
public Object getItem(int position) {
return null;
}
public long getItemId(int position) {
return 0;
}
// create a new ImageView for each item referenced by the Adapter
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) {
imageView = new ImageView(mContext);
imageView.setLayoutParams(new GridView.LayoutParams(R.dimen.grid_dimens_width, R.dimen.grid_dimens_height));
imageView.setScaleType(ImageView.ScaleType.CENTER);
imageView.setPadding(1, 5, 1, 1);
} else {
imageView = (ImageView) convertView;
}
imageView.setImageResource(mThumbIds[position]);
return imageView;
}
private final Integer[] mThumbIds = {
R.drawable.grid_agapis, R.drawable.grid_asteies, R.drawable.grid_auto,
R.drawable.grid_gamos, R.drawable.grid_goneis,
};
I set the adapter with the following code:
GridView gridview = (GridView) findViewById(R.id.gridview);
gridview.setAdapter(new CustomGridViewAdapter(this));
gridview.setOnItemClickListener(this);
Can you explain me where is the problem?
Thank you.
imageView.setLayoutParams(new GridView.LayoutParams(R.dimen.grid_dimens_width,
R.dimen.grid_dimens_height));
With this line of code you are trying to limit the size of the grabbed drawable to a fixed width & height that are equal to grid_dimens_width & grid_dimens_height respectively.
But actually using R.dimen.foo won't return the value of foo, instead it returns the generated integer value of the resource itself which can be something like a big number (e.g. -21893103 or 33238590) .. this will make you see nothing on the screen because the image is either:
Too big (in case of a positive resource value 33238590) so you are seeing the tiny pixels of it
or too small (in case of a negative resource value -21893103) because its size is zero.
What you need to do instead is to get the dimen resource using getDimention() and pass the resource id to it.
To apply that to your code:
Replace:
imageView.setLayoutParams(new GridView.LayoutParams(R.dimen.grid_dimens_width,
R.dimen.grid_dimens_height));
With:
imageView.setLayoutParams(new GridView.LayoutParams(
(int) mContext.getResources().getDimension(R.dimen.grid_dimens_width),
(int) mContext.getResources().getDimension(R.dimen.grid_dimens_height)));
Result:

Image won't center in ImageView

I have a grid containing seven rows of five ImageViews. I want to center the images in the views. I've tried setting every layout, gravity, and foregroundGravity property in the xml to centered, but nothing seems to center it. I read a few stackoverflow posts and they all suggested doing things I'd already tried.
The grid is closer to the left side than the right, and when I touch an image, the highlighted area has more space on the right side of the image than the left.
Any suggestions on how to get them to center?
Here's my layout file:
<ImageView
android:id="#+id/grid_image"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_gravity="center"
android:foregroundGravity="center"
>
</ImageView>
</LinearLayout>
Here's the class that I'm using it in:
class CustomGrid extends BaseAdapter {
private Context context;
private final List<Button> buttons;
CustomGrid(Context c, List<Button> buttons) {
context = c;
this.buttons = buttons;
}
#Override
public int getCount() { return buttons.size(); }
#Override
public Object getItem(int position) { return null; }
#Override
public long getItemId(int position) { return 0; }
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View grid;
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (convertView == null) {
grid = inflater.inflate(R.layout.grid_single, null);
ImageView imageView = (ImageView) grid.findViewById(R.id.grid_image);
imageView.setImageResource(buttons.get(position).getBasicImageId());
buttons.get(position).setImageView(imageView);
}
else {
grid = convertView;
}
return grid;
}
}
And here's the image. For now I'm just using an Android stock image.
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M6,18c0,0.55 0.45,1 1,1h1v3.5c0,0.83 0.67,1.5 1.5,1.5s1.5,-0.67 1.5,-1.5L11,19h2v3.5c0,0.83 0.67,1.5 1.5,1.5s1.5,-0.67 1.5,-1.5L16,19h1c0.55,0 1,-0.45 1,-1L18,8L6,8v10zM3.5,8C2.67,8 2,8.67 2,9.5v7c0,0.83 0.67,1.5 1.5,1.5S5,17.33 5,16.5v-7C5,8.67 4.33,8 3.5,8zM20.5,8c-0.83,0 -1.5,0.67 -1.5,1.5v7c0,0.83 0.67,1.5 1.5,1.5s1.5,-0.67 1.5,-1.5v-7c0,-0.83 -0.67,-1.5 -1.5,-1.5zM15.53,2.16l1.3,-1.3c0.2,-0.2 0.2,-0.51 0,-0.71 -0.2,-0.2 -0.51,-0.2 -0.71,0l-1.48,1.48C13.85,1.23 12.95,1 12,1c-0.96,0 -1.86,0.23 -2.66,0.63L7.85,0.15c-0.2,-0.2 -0.51,-0.2 -0.71,0 -0.2,0.2 -0.2,0.51 0,0.71l1.31,1.31C6.97,3.26 6,5.01 6,7h12c0,-1.99 -0.97,-3.75 -2.47,-4.84zM10,5L9,5L9,4h1v1zM15,5h-1L14,4h1v1z"/>
</vector>

Elements inside viewpagers` fragment

I have a problem. I have a viewpager with 3 fragments inside. In first fragment i have some ImageViews.
First of all how make that imageviews visible with timer? I used thise code below but i have error which looks like: variable 'mImageView' is accessed from within inner class, needs to be declared class.
mImageView.setVisibility(View.INVISIBLE);
mImageView.postDelayed(new Runnable() {
public void run() {
mImageView.setVisibility(View.VISIBLE);
}
}, 5000);
How can i solve this problem?
Second I tried to move that elements (ImageViews) by X values when user start scrolling from first fragment to next fragment. It works but when i go to last 3-d fragment app crash. So why it happen?!
MainActivity.java
pager.setPageTransformer(false, new ViewPager.PageTransformer() {
#Override
public void transformPage(View page, float position) {
// transformation here
final float normalizedPosition = Math.abs(Math.abs(position) - 1);
page.setAlpha(normalizedPosition);
int pageWidth = page.getWidth();
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
page.setAlpha(0);
} else if (position <= 1) { // [-1,1]
ImageView mImageView = (ImageView)findViewById(R.id.imageView2);
mImageView.setTranslationX((float) (-(1 - position) * 1.7 * pageWidth));
mImageView.setVisibility(View.INVISIBLE);
mImageView.postDelayed(new Runnable() {
public void run() {
mImageView.setVisibility(View.VISIBLE);
}
}, 5000);
// The 0.5, 1.5 values you see here are what makes the view move in a different speed.
// The bigger the number, the faster the view will translate.
// The result float is preceded by a minus because the views travel in the opposite direction of the movement.
}
else{ // (1,+Infinity]
// This page is way off-screen to the right.
page.setAlpha(0);
}
}
});
Third: Is it possible to make move elements by circle when user scroll. Need any help!
For your first question, as I said in the comment, you need to make the mImageView variable final
final ImageView mImageView = (ImageView)findViewById(R.id.imageView2);
Then, the null pointer exception, is probably caused (as Blackbelt said), because you're using the activity's findViewById method, and probably the imageView you need is in the fragment view:
final ImageView mImageView = (ImageView) page.findViewById(R.id.imageView2);
And for your 3ยบ question, please explain what you mean by "move by circle", then I'll update my post(if I can) with an answer.
MainActivity.java
// Initialize the ViewPager and set an adapter
ViewPager pager = (ViewPager) findViewById(R.id.pager);
pager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager()));
ViewPagerAdapter.java
public class ViewPagerAdapter extends FragmentStatePagerAdapter {
private final int PAGES = 3;
private String[] titles={"News", "Organizations", "Map"};
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new TabFragment1();
case 1:
return new TabFragment2();
case 2:
return new TabFragment3();
default:
throw new IllegalArgumentException("The item position should be less or equal to:" + PAGES);
}
}
#Override
public CharSequence getPageTitle(int position) {
return titles[position];
}
#Override
public int getCount() {
return PAGES;
}
}
As you see for each page I have individual fragment documents. Here below one of them:
public class TabFragment1 extends Fragment{
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_tab_1, container, false);
}
}
fragment_tab_1.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="#drawable/city"
android:id="#+id/imageView2"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>

How to correctly implement a custom listview with images using Picasso library?

I created a custom listview layout with images which are loaded from web like this:
http://i.stack.imgur.com/l8ZOc.png
It works fine when scrolling down. However, when you scroll down, the previous items go out of screen then destroyed. When you try to scroll up again, it gets loaded again (from cache, faster but not instant) which causes a delay and it is not fluent as it should be.
1.Is there an example of how to do this properly?
2.Is there a way to prevent listview items being destroyed when they are out of screen?
3.If so, will it cause problems to keep too many items?
Bellow is my code:
MenuAdapter:
public class MenuAdapter extends BaseAdapter{
Context context;
List<MyMenuItem> menuItems;
MenuAdapter(Context context, List<MyMenuItem> menuItems) {
this.context = context;
this.menuItems = menuItems;
}
#Override
public int getCount() {
return menuItems.size();
}
#Override
public Object getItem(int position) {
return menuItems.get(position);
}
#Override
public long getItemId(int position) {
return menuItems.indexOf(getItem(position));
}
private class ViewHolder {
ImageView ivMenu;
TextView tvMenuHeader;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
LayoutInflater mInflater = (LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
if (convertView == null) {
convertView = mInflater.inflate(R.layout.menu_item, null);
holder = new ViewHolder();
holder.tvMenuHeader = (TextView) convertView.findViewById(R.id.tvMenuHeader);
holder.ivMenu = (ImageView) convertView.findViewById(R.id.ivMenuItem);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
MyMenuItem row_pos = menuItems.get(position);
Picasso.with(context)
.load(row_pos.getItem_image_url())
.into(holder.ivMenu);
holder.tvMenuHeader.setText(row_pos.getItem_header());
Log.e("Test", "headers:" + row_pos.getItem_header());
return convertView;
}
}
MyMenuItem:
public class MyMenuItem {
private String item_header;
private String item_image_url;
public MyMenuItem(String item_header, String item_image_url){
this.item_header=item_header;
this.item_image_url=item_image_url;
}
public String getItem_header(){
return item_header;
}
public void setItem_header(String item_header){
this.item_header=item_header;
}
public String getItem_image_url(){
return item_image_url;
}
public void setItem_image_url(String item_image_url){
this.item_image_url=item_image_url;
}
}
MainActivity:
public class MyActivity extends Activity implements AdapterView.OnItemClickListener {
List<MyMenuItem> menuItems;
ListView myListView;
JSONArray jsonArray;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
Bundle extras = getIntent().getExtras();
if(extras!=null){
try{
jsonArray = new JSONArray(extras.getString("Data"));
}catch (Exception e){
e.printStackTrace();
}
}
menuItems = new ArrayList<MyMenuItem>();
for (int i = 0; i < jsonArray.length(); i++) {
try {
MyMenuItem item = new MyMenuItem(jsonArray.getJSONObject(i).getString("title"), jsonArray.getJSONObject(i).getString("imageURL"));
menuItems.add(item);
}catch (Exception e){
e.printStackTrace();
}
}
myListView = (ListView) findViewById(R.id.list);
MenuAdapter adapter = new MenuAdapter(this, menuItems);
myListView.setAdapter(adapter);
myListView.setOnItemClickListener(this);
}
}
MenuItem.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/ivMenuItem"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="center"
android:src="#drawable/em" />
<TextView
android:id="#+id/tvMenuHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#55000000"
android:paddingBottom="15dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="15dp"
android:textColor="#android:color/white"
android:layout_gravity="left|top"
android:layout_alignBottom="#+id/ivMenuItem"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true" />
</RelativeLayout>
1. Is there an example of how to do this properly?
Your code looks pretty close to perfect. The Adapter's getView method is usually the critical path to optimize. Compare for example Picasso's own example SampleListDetailAdapter.java. The important points it (as well as your code) does
check for & re-use already inflated views, inflation is expensive.
use ViewHolder so you don't have to call findViewById every time. Not terribly expensive on simple views. Also cached afaik.
Picasso.with(context).load(url)... each time you need to display an image. This should finish instantly but still use caches and other magic.
There are some minor optimizations you can add, but I doubt that there are noticeable or even measurable changes:
pure style change: use BaseAdapter#getItem(position). This method
exists for you only. The framework doesn't use it.
#Override
public MyMenuItem getItem(int position) { // << subclasses can use subtypes in overridden methods!
return menuItems.get(position);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
...
MyMenuItem row_pos = getItem(position);
Use a sane id method
#Override
public long getItemId(int position) {
return menuItems.indexOf(getItem(position));
}
is equivalent to
#Override
public long getItemId(int position) {
return position;
}
but now infinitely faster. indexOf(Object) scales really badly with the number of objects.
Cache objects that don't change:
MenuAdapter(Context context, List<MyMenuItem> menuItems) {
this.mLayoutInflater = LayoutInflater.from(content);
this.mPicasso = Picasso.with(context);
}
..
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.menu_item, null);
...
mPicasso
.load(row_pos.getItem_image_url())
.into(holder.ivMenu);
2. Is there a way to prevent listview items being destroyed when they are out of screen?
No(*).
..(*) well you can essentially cache the result of getView e.g. in LruCache(position, View) or LruCache(MyMenuItem, View), then don't touch the convertView - they need to remain unconverted or you would kill those views in your cache. Also
#Override
public int getItemViewType(int position) {
return Adapter.IGNORE_ITEM_VIEW_TYPE;
}
seemed to be required because the standard adapter using code assumes that views it removes from visibility are gone. They are not and messing with them messes with your cache and caused weird display problems for me.
3. If so, will it cause problems to keep too many items?
Yes. This behavior is not intendend / expected. There is also more or less nothing you gain. You might be able to save you the call to holder.tvMenuHeader.setText(). Likewise the one to Picasso but both of them should complete instantly. Picasso should have your image cached already. By caching all Views you essentially add another cache that also contains all the images. I would rather check that the picasso cache works as intended and holds most items. The only reason you may want to do it with view caching is for cases that require complicated setup of the view, so it becomes worth caching the completely constructed view rather than just some content parts.
Profile
Profiling can actually tell you where you can / need / should improve. The first to look at IMO is traceview. You'll see if code blocks the main thread which results in choppy list scrolling. If you're doing complicated views and you see that the draw methods are executed most of the time, profile them as well.
http://www.curious-creature.org/docs/android-performance-case-study-1.html
http://blog.venmo.com/hf2t3h4x98p5e13z82pl8j66ngcmry/performance-tuning-on-android
http://www.vogella.com/tutorials/AndroidTools/article.html

Android: How to use extended layout class as ListView row?

I'm creating a simple chess clock -type timer app. I'm trying to show the players and the time they have left as rows in a ListView. I'm using a custom view that extends RelativeLayout for these rows, so that I can give it methods that highlight the player in turn, for example.
Row layout class:
public class GameTimerView extends RelativeLayout {
private TextView nameView;
private TextView timerView;
public GameTimerView(Context context) {
super(context);
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.timer_view, this);
loadViews();
}
...
private void loadViews() {
nameView = (TextView)findViewById(R.id.nameView);
timerView = (TextView)findViewById(R.id.timerView);
}
public void setName(String name) {
nameView.setText(name);
}
public void setTime(long timeInMillis) {
timerView.setText(String.format("%02d:%02d",
TimeUnit.MILLISECONDS.toMinutes(timeInMillis),
TimeUnit.MILLISECONDS.toSeconds(timeInMillis) -
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(timeInMillis))
));
}
public void setActive() {
this.nameView.setTextColor(Color.GREEN);
}
public void setInactive() {
nameView.setTextColor(Color.BLACK);
}
}
Row layout XML (timer_view.xml):
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="#+id/nameView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_toLeftOf="#+id/timerView"
android:text="#string/player_default_name" />
<TextView
android:id="#+id/timerView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="#string/zero_time" />
</RelativeLayout>
Adapter:
public class playerArrayAdapter extends ArrayAdapter<Player> {
private final Context context;
private final ArrayList<Player> players;
public playerArrayAdapter(Context context, ArrayList<Player> values) {
super(context, R.layout.timer_view, values);
this.context = context;
this.players = values;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
GameTimerView playerView = new GameTimerView(context);
players.get(position).setTimerView(playerView);
return playerView;
}
}
Player class setTimerView function:
public void setTimerView(GameTimerView timer) {
this.timerView = timer;
this.timerView.setName(this.name);
this.timerView.setTime(this.totalCountDown);
this.timerView.setInactive();
}
In the activity's onCreate method:
playerArrayAdapter playersAdapter = new playerArrayAdapter(
getApplicationContext(),
game.getPlayers()
);
ListView playersView = (ListView) findViewById(R.id.playerList);
playersView.setAdapter(playersAdapter);
At first this seems to work, and the desired player names and times are rendered to the list properly. However, if I later programmatically call for example Player.timerView.setActive(), nothing happens.
Having looked at dozens of examples of custom adapters for ListViews none of them seems to be using it this way - the view is always inflated directly in Apdater.getView(). I want the flexibility of an extended view class however, but apparently I'm doing something wrong.
So, what's the correct way to use custom view class for ListView rows?
First, when inflating your GameTimerView, you've got a RelativeLayoutinside another.
Second, to answer the question : it might be good not to re-create a view on each call to ArrayAdapter.getView(), but instead modify playerArrayAdapter by adding a cache like this :
private List<View> views;
public playerArrayAdapter(Context context, ArrayList<Player> values) {
super(context, R.layout.timer_view, values);
views = new ArrayList<View>(values.length);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View playerView;
if (position < views.size()) {
playerView = views.get(position);
if (playerView != null)
return playerView;
} else {
while (views.size() < position)
views.add(null);
}
playerView = new GameTimerView(context);
views.add(position, playerView);
players.get(position).setTimerView(playerView);
return playerView;
}
If this successfully corrects your problem, it means that previously, when setting a player as active the ListView was getting all views again for rendering, recreating them, and doing so, erasing any previous state.
As per your getView() method of Adapter, it will create a new view always that might have causing you issue.
If you really want to implement a CustomViewGroup then please refer this good implementation of the custom view here. Hope this will help you to start.

Categories