Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Force gridview to draw all tiles

I have an android gridview which i'm using some custom scrolling going on in, to let it scroll in two dimensions - this means that the default scrolling isn't called.

I suspect this may be the reason that the rows that are off-screen are invisible. I know they're there, they affect the layout and everything, but they never draw.

So my question is this - is there any way to force the gridview to draw all of its tiles when it's loaded, and not just the visible ones?

Thanks.

Edit: To clarify - In my tileadapter, i set the child count to exactly 225. In my gridview, a call to getChildCount() returns 165.

Edit again: This only happens when the height of the gridview is greater than that of the screen - the children that are off-screen on the y axis are simply subtracted from the childcount - setting the size of the children to a number where they all fit snugly on screen removes the problem, but kills the purpose of scrolling.

Code!

XML Layout of activity:

  <LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:theme="@style/Theme.Custom"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <TextView android:id="@+id/logmessage"
  android:theme="@style/Theme.Custom"
  android:layout_width="fill_parent"
  android:layout_height="25dip"
  android:text="LogMessage"/>

  <RelativeLayout android:id="@+id/boardwrap"
  android:layout_weight="1"
  android:layout_height="fill_parent"
  android:layout_width="fill_parent"
  android:gravity="center_vertical">
  <com.MyProject.GameGrid 
    android:id="@+id/board"
    android:theme="@style/Theme.Custom"
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content"
    android:numColumns="15"
    android:stretchMode="none"
    android:verticalSpacing="0dip"
    android:horizontalSpacing="0dip"
    android:padding="0dip"
    android:columnWidth="20dip"
    android:scrollbars="none"/>
</RelativeLayout>
<RelativeLayout 
    android:id="@+id/toolbar"
    android:layout_width="fill_parent"
    android:layout_height="60dip"
    android:background="#FFFFFFFF"/>
</LinearLayout>

Activity:

public class GameBoardActivity extends Activity {

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.gameboard);

        GameGrid Board = (GameGrid)findViewById(R.id.board);
        Board.setAdapter(new TileAdapter(this));
    }
}

GameGrid:

public GameGrid(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.setNumColumns(15);

        DisplayMetrics metrics = new DisplayMetrics();
        ((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(metrics);
        scale = metrics.density;
        smallSize = Math.round(20 * scale);
        largeSize = Math.round(40 * scale);

        columnwidth = largeSize;
        this.setColumnWidth(columnwidth);
        Common.DebugMessage(Float.toString(columnwidth));

    }

You may notice i'm defining a small and a large size here - double tapping the screen allows you to switch between the two.

Scrolling (what you helped me with earlier)

if (myState == TOUCH_STATE_SCROLLING) {
                    final int deltaX = (int) (mLastX - x);
                    final int deltaY = (int) (mLastY - y);
                    mLastX = x;
                    mLastY = y;

                    int xpos = this.getScrollX();
                    int ypos = this.getScrollY();

                    int maxX = (columnwidth * 15) - super.getWidth();
                    int maxY = (columnwidth * 15) - super.getHeight();

                    if (xpos + deltaX >= 0 && xpos + deltaX <= maxX && ypos + deltaY >= 0 && ypos + deltaY <= maxY )
                    {
                        this.scrollBy(deltaX, deltaY);
                    }
                    else {
                        this.scrollTo(xpos + deltaX <= 0 ? 0 : xpos + deltaX >= maxX ? maxX : xpos + deltaX,
                                      ypos + deltaY <= 0 ? 0 : ypos + deltaY >= maxY ? maxY : ypos + deltaY);
                    }
                    Common.DebugMessage(this.getChildCount());

                }

Common.DebugMessage is just a helper method for printing debug messages to LogCat

TileAdapter:

public TileAdapter(Context c) {
        mContext = c;
    }

    @Override
    public int getCount() {
        return 225;
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ImageView imageView;
        int colWidth = ((GameGrid)parent).getColumnWidth();
        if (convertView == null) {
            imageView = new ImageView(mContext);
            imageView.setLayoutParams(new GridView.LayoutParams(colWidth , colWidth));
            imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
            imageView.setPadding(0, 0, 0, 0);
        }
        else {
            imageView = (ImageView)convertView;
        }
        imageView.setImageResource(R.drawable.tile);
        return imageView;
    }
like image 879
Andreas Eriksson Avatar asked Oct 15 '11 06:10

Andreas Eriksson


2 Answers

Andreas,

If your issue is simply an onDraw() issue. You may do so quite easily with an overridden Draw(canvas) in your GridView. This has the side effect of increasing processor requirement while your Activity is loaded, but can create the desired effect. Such an override would be as follows:

 //This may be used in your GridView or Activity -- whichever provides the best result.
    public void draw(Canvas canvas)
    {   int _num = myGridView.getChildCount();
        for (int _i = _num; --_i >= 0; )
        {   View _child = (View)myGridView.getChildAt(_i);
            if (_child != null)
                _child.draw(canvas);
        }
    }

An Additional Technique (EDIT)

Sometimes overriding the draw() can have adverse effects. What we're really trying to do is trigger the objects to draw when they are available to. Overriding invalidate() in a similar manner can often have an effect depending on where the issue lies. Since we've established that we are getting strange results with overriding draw(), this seems to be the next course of action.

//This definitely goes in your GridView
public void invalidate()
{   int _num = myGridView.getChildCount();
    for (int _i = _num; --_i >= 0; )
    {   View _child = (View)myGridView.getChildAt(_i);
        if (_child != null)
            _child.invalidate();
    }
}

It has to be understood that this technique may not force a draw when you want, but merely lets the OS know that it is ready to be redrawn when it is available. Because invalidation does not automatically cascade down the View tree, if your ImageViews are nested deeper than the GridView you will have to adjust. This can be used in conjunction with the draw() or independantly. Additionally, when used with the appropriate placement of an invalidate() statement, may lower response but should help to keep your images drawn.

The issue may simply be a delayed layout or draw issue. If that is the case, a Lazy Loading solution might be best. A lazy loader is a way to load the content when it is needed, so that it typically uses less memory, and processing, and shows what is needed, when it is needed. Now I'm not awesome at Lazy Loading because I rarely have the need. But there is a GREAT example of code at this site. It is tailored for a GridView, as well.

Deemed Not Applicable (but may be useful for others)

The only other kind of issue that I think it may be is an Out of Memory issue that isn't causing a Force Close (and yes, they do exist). These are especially elusive and painful to take care of. The only way to spot these is in your LogCat during the load or scroll, with the error view selected. Or you can go through an entire LogCat dump (I recommend clearing the Log before you run your app first).

With this kind of issue, it becomes important to know how the lifecycle of an image works. If your images are being shrunk to thumbnail size in order to display, I would consider making real thumbnails alongside the fullsize images. Because shrinking an image in runtime temporarily requires more memory than keeping it the original size. Since this is all happening in one fell sweep, this may be a temporary problem, exhibiting itself permanently. This will dramatically lower memory requirements. (As a for instance, a 2x2 image requires 16 Bytes plus the Header, whereas a 4x4 image requires 64 Bytes plus the Header [400% for double the size!!].)

Additionally, adding System.gc() to critical places in your code will force a garbage collection, freeing up more memory, more often. (This is not a guarantee to free memory, but works more often than it doesn't).

The BEST solution would probably be a combination of all three, but would require a little more information than what we have with this question. For instance, we would need to see if you have overridden draw() and onMeasure() and onLayout() and maybe some other details.

like image 166
Fuzzical Logic Avatar answered Oct 07 '22 16:10

Fuzzical Logic


Honestly, my suggestion is to stop whacking on a platform API in a way that it is clearly not intended to be used. Even if through some contortions you managed to play enough games with the base GridView code to make it do what you want to do... how confident are you that your code will continue to work with the slightly changes to the GridView implementation as the platform evolves.

And really there is just no need to play these kinds of games. There is nothing special about GridView -- it is just an implementation of a view that puts things in a grid that you can scroll horizontally through?

If GridView's behavior is not what you want, the solution is to write your own view that does what you want. And with Android this is even easier because you can just go and take the GridView code from the open-source platform as a basis, get that compiling in your app (you will probably need to tweak a few things because the code as it stands takes doesn't need to be written purely against the SDK so probably isn't... but there is nothing it is doing that you can't do in a regular app build against the SDK), and then modify that code in your app to your heart's content, to make it do what want. Without fighting with a built-in widget that doesn't actually do what you want. And without fear of your carefully constructed house of cards collapsing on you if the underlying GridView implementation changes in the future.

like image 31
hackbod Avatar answered Oct 07 '22 15:10

hackbod