I had a similar issue and never was really satisfied  with the performance. The tip of it all was when I expanded my playfield to 30*30 so I got 900 ImageViews. I tried many optimizations, but the performance and memory was high and unpredictable across devices.
So what I did is to create a custom View. Just one. This view then paints squares in a canvas. I was amazed that now I can have literally tens of thousands of squares (100x100) and performance is ultra-smooth.
I post here the skeleton of my view with all the crap removed for an inspiration, I strongly recommend you to follow this approach.
    /**
     * ChequeredView is a view that displays a 2D square matrix, where each square can be individually selected.
     * For high performance, It only uses one view regardless of the matrix size (everything is drawn in a canvas)
     * @author rodo 13 march 2014 <rlp@nebular.tv>
     */
    public class ChequeredView extends View {
        private final String TAG="ChequeredView";
        private static final int 
            DEFAULT_MATRIX_SIZE_COLS=20, 
            DEFAULT_MATRIX_SIZE_COLS=20, 
            DEFAULT_SQUARE_SIZE=100;
        private int mCols=DEFAULT_MATRIX_SIZE_COLS, 
                    mRows=DEFAULT_MATRIX_SIZE_ROWS;
        /* Save touch press */
        private int mTouchX=0, mTouchY=0;
        ///////////////// VIEW CODE
        public ChequeredView(Context context, AttributeSet attrs) { super(context, attrs); }
        public ChequeredView(Context context) { super(context); }
        /**
         * Report a size of your view that is: SquareSize * NUM_COLS x SquareSize * NUM_ROWS. You will paint it later.
         */
        @Override
        protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            // calculate optimum square size
            mStyleSquareSize=MeasureSpec.getSize(widthMeasureSpec) / mSquaresPerCanvas;
            // report total size
            setMeasuredDimension(DEFAULT_MATRIX_SIZE_COLS * mStyleSquareSize, DEFAULT_MATRIX_SIZE_ROWS * mStyleSquareSize);
        }
        @Override
        public void onDraw(Canvas canvas)  {
            render(canvas);
        }
        @Override 
        public boolean onTouchEvent(android.view.MotionEvent event) {
            switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // I execute the action in ACTION_UP so I can put this inside a scrollview and touch doesn't interferre the scroll.
                mTouchX=(int) event.getX();
                mTouchY=(int) event.getY();
                return true;
            case MotionEvent.ACTION_UP:
                // only process touch if finger has not moved very much (if it has, it's a fling on parent)
                if ( isApprox(event.getX(), mTouchX, 5) && (isApprox(event.getY(), mTouchY, 5)) ) 
                        processTouch((int)event.getX(), (int)event.getY());
                break;
            }
            return false;
        };
        /**
         * Check if a value is close to another one
         * @param value Value to check
         * @param ref Reference value
         * @param threshold Threshold
         * @return true if |val-ref|<threshold
         */
        private boolean isApprox(float value, int ref, int threshold) {
            float result=Math.abs(value-ref);
            return (result<threshold);
        }
        ///////////////// VIEW METHODS
        public void setMatrixSize(int numx, int numy) {
            mRows=numx;
            mCols=numy;
            invalidate();
        }
        ///////////////// VIEW INTERNALS
        /**
         * Renders the whole squaremap
         * @param canvas
         */
        private void render(Canvas canvas) {
            if (canvas==null) return;
            for (int x=0; x<mCols; x++) {
                for (int y=0; y<mRows; y++) {
                    render_square(canvas, x, y);
                }
            }
        }
        /**
         * Renders one of the squares
         * @param canvas Canvas where to draw
         * @param nCol The column
         * @param nRow The row
         */
        private void render_square(Canvas canvas, int nCol, int nRow) {
            String text=null, transition=null;
            int delay=0;
            Paint paint=null;
            int cx=nCol*mStyleSquareSize, cy=nRow*mStyleSquareSize;
            canvas.save();
            canvas.translate(cx, cy);
            canvas.drawRect(mStyleSquareMargin, mStyleSquareMargin, mStyleSquareSize-2*mStyleSquareMargin, mStyleSquareSize-2*mStyleSquareMargin, paint);
            // this draws an square (I use vectorial squares with text rather than images, but just change drawRect to drawBitmap)
            // just change it for drawBitmap() to draw one bitmap
            canvas.restore();
        }
        /**
         * Process a touch on the map area
         * @param x raw x coordinate
         * @param y raw y coordinate
         */ 
        private void processTouch(int x, int y) {
            int nx=x/mStyleSquareSize, ny=y/mStyleSquareSize;
            mSelectedX=nx;
            mSelectedY=ny;
            if (mSquareListener!=null) {
                mSquareListener.onSquareSelected(nx, ny, data);
            } 
            invalidate();
        }
    }