I figured out a solution for this using ansible.cfg.
First, create an ansible.cfg in the playbook directory (if using Vagrant, create it in the Vagrantfile directory).
Set roles_path to something like ./data/ansible/roles. Optionally gitignore it.
Run `ansible-galaxy install -r ansible.yml` to install! Make sure you run this from the directory that has ansible.cfg, OR run `ANSIBLE_CONFIG=/path/to/ansible.cfg ansible-galaxy install -r ansible.yml`
Replace ansible.yml with the name of your roles file.
I still kind of recommend not gitignoring the roles just because there is no good way to manage updates; you would have to tell your team manually that there was a new version. ansible-galaxy isn't smart enough to realize that the code you want to install isn't the same as what it already has, and it won't do it when it sees the directory name is the same. If you are using fully-versioned roles, though (none running off master branches that might change), you could tell your developers always to run `ansible-galaxy install --force -r ansible.yml`. This will replace the roles every time it's run. So you can see why I don't encourage using it on roles that might arbitrarily be updated without a clear new version being specified.
Simply committing the roles into the repo and keeping the ansible.yml file as a handy reference for when you want to update them is, in my mind, a reasonable approach. Then you guarantee everyone's running the same stuff.
One could get more complex with this setup, e.g. hosting tarballs of specific role versions on an internal server and pointing ansible.yml at that. But now we're getting into edge cases.
Hope this helps!