Esta é a melhor solução que encontrei seguindo as dicas da resposta de @Vinayak. Todas as outras soluções têm desvantagens diferentes.
Em primeiro lugar, crie uma função como esta.
void addClickEffect(View view)
{
Drawable drawableNormal = view.getBackground();
Drawable drawablePressed = view.getBackground().getConstantState().newDrawable();
drawablePressed.mutate();
drawablePressed.setColorFilter(Color.argb(50, 0, 0, 0), PorterDuff.Mode.SRC_ATOP);
StateListDrawable listDrawable = new StateListDrawable();
listDrawable.addState(new int[] {android.R.attr.state_pressed}, drawablePressed);
listDrawable.addState(new int[] {}, drawableNormal);
view.setBackground(listDrawable);
}
Explicação:
getConstantState (). newDrawable () é usado para clonar o Drawable existente, caso contrário, o mesmo drawable será usado. Leia mais aqui:
Android: clonando um drawable para criar um StateListDrawable com filtros
mutate () é usado para fazer com que o clone do Drawable não compartilhe seu estado com outras instâncias do Drawable. Leia mais sobre isso aqui:
https://developer.android.com/reference/android/graphics/drawable/Drawable.html#mutate ()
Uso:
Você pode passar qualquer tipo de View (Button, ImageButton, View etc) como parâmetro para a função e eles obterão o efeito de clique aplicado a eles.
addClickEffect(myButton);
addClickEffect(myImageButton);