Sample View
To set all the views to grayscale, for TextView we have to use setTextColor
to set a new color. For ImageView we can set a new resource which in itself is grayscale or use setColorFilter
. But every time we add/remove a new view in this layout, that will break the disabled state unless we handle grayscaling for this view as well.
And in case we want to “enable” the views once again based on another trigger, that’s a chore in itself, storing the actual color before setting it to gray and then restoring it when needed. Clearly, we can do better.
After lots of reading and mathematics later…
Based on my understanding of image processing, ideally we should be able to multiply any RGB color with a transformation matrix and get a grayscale color as a result. Then we tried to find a similar api in Android and ColorMatrix combined with some knowledge of custom views was what we’ve been looking for.
We decided to experiment with a custom matrix with equal amounts of Red, Green and Blue and use it a color filter for a CustomView that extends ConstraintLayout. You can pause and check the full code here.
private val paint = Paint() val cm = ColorMatrix() cm.set( floatArrayOf( 0.33f, 0.33f, 0.33f, 0f, 0f, 0.33f, 0.33f, 0.33f, 0f, 0f, 0.33f, 0.33f, 0.33f, 0f, 0f, 0f, 0f, 0f, 1f, 0f ) ) paint.colorFilter = ColorMatrixColorFilter(cm)
We’re using this paint on the canvas in the method dispatchDraw()
and draw()
. Why both? While rendering a layout, the background is drawn first in the draw()
method and then the child views are drawn in dispatchDraw()
method, so setting the paint here will make all the children use it as well. I want both the background and child views to appear disabled so I’ve used the paint in both places. Feel free to skip any of those based on your use case.
This approach is like masking the entire thing with a grayscale filter which will automatically take care of any changes to the view in future as well. Also, cherry on top of the cake, if we want to remove the grayscaling logic, setting disabled
to false would do the job, no extra logic required!
Final Result
Pro-tip
If you want a different shade of gray in the disabled state, you can change the multiplier parameters of the color matrix. Just remember to use the last parameter to add a constant and balance out the whites. If you look at the way new values of R,G and B are calculated based on the color matrix.
private val paint = Paint() init { val cm = ColorMatrix() cm.set( floatArrayOf( 0.149f, 0.149f, 0.149f, 0f, 141f, 0.149f, 0.149f, 0.149f, 0f, 141f, 0.149f, 0.149f, 0.149f, 0f, 141f, 0f, 0f, 0f, 1f, 0f ) ) paint.colorFilter = ColorMatrixColorFilter(cm) }
If the original color is white (255, 255, 255) and we pass it through the matrix,
R` = 255(0.149) + 255(0.149) + 255(0.149) + 141f = 255
If we don’t add the 141f as the last parameter, the whites will also become grayish and the disabled state will not look as good, especially if you have a white background.
There you go, you can check out the complete repository here and even try it out yourself. I just love situations like these which push us to learn something new and make thing more robust while making it easier as well. Have you had a similar experience while building your app? Do share that in the comments below.
Stay safe and stay at home, until the next one, Ciao! đ