I have a list of items with a custom background. The background is a simple shape with rounded-rect drawable filled with white color. So my view is similar to a Card. I can set an elevation on it and it works. There is a shadow beneath it.
But I want to optimize it. My window's background is white, so I thought that I can remove View's background color to reduce overdraw. But it's not that simple. As soon as I set color to transparent in my view's background drawable, elevation stops working and shadow is not drawn anymore. I've tried to use stroke instead of solid color inside my view's bakcground drawable but it doesn't work too. It's probably because of a ViewOutline. So I've created a custom OutlineProvider that returns something like this:
outline.drawRoundRect(0,0,view.getWidth(), view.getHeight(), someRadius);
Now my View has transparent background and shadow is also visible but there is something wrong with it. It does not look good. At the top of the View there is some sort of a background visible beneath it. Below is a picture of my output. On the left is a view with transparent background and a custom OutlineProvider. On the right is a view with opaque white background.
Is it a bug? Or is there another way to achieve this? It seems like it's a very simple performance optimization but it turns out that it's much more complicated than it should be.
I believe this to be related to a known issue in Lollipop https://code.google.com/p/android/issues/detail?id=78248
Thanks for the report and repro steps - this is indeed a bug, and it
has just been fixed in an internal branch. Will be released externally
in a future release.
As a workaround, you can either set an alpha on the GradientDrawable,
or set a custom outline provider on the view casting the shadow (via
View#setOutlineProvider) to query the outline from the background and
override the alpha provided by the drawable.
The issue was that GradientDrawable was being too conservative in
reporting a 0 alpha in cases where it had a transparent fill. (See
GradientDrawable#getOutline(), for the curious)
Related
I have an ImageView and a TextView. They both use ?attr/somecolor to see what color they have to use depending on what theme is selected. This somecolor is either black or white depending on theme.
Now when I say they both need to be red using R.color.red, the TextView has a more brighter red then the ImageView.
sometext.setTextColor(ContextCompat.getColor(this,R.color.green))
someimage.setColorFilter(ContextCompat.getColor(this, R.color.green))
Is there any reason why these colors don't match up?
For what it's worth, here's what I get for an ImageView with app:tint="#color/black and a TextView with android:textColor="#colorBlack, and then
someText.setTextColor(Color.RED)
someImage.setColorFilter(Color.RED)
It's hard to say exactly what you need to do, but there are two different things going on here. For the TextView, you're literally just telling it what colour to draw with. That should always come out looking how you expect.
But for images, it's a little more complicated. You're applying a tint over existing image data, which interacts with the original colours and alpha to produce a final mixed image. For a bitmap image, that colour is in the individual pixels. For a vector drawable, it's in attributes like the fillColor.
For example, that fill colour on the arrow is #color/white, but here's what happens if I knock the alpha down by setting android:fillColor="#AAFFFFFF":
You can see the colour is different - it's actually partially transparent so it's blending with the white background. Applying full-alpha red over it didn't change the overall alpha - and it can't really, it's the alpha that says which parts of that square image are "empty" and which are "the arrow". (Well, you can do that with a different PorterDuff mode, but by default it doesn't!)
The alpha of your tint matters too - here's a partially transparent tint applied to the #color/white arrow (and the text):
someText.setTextColor(Color.parseColor("#99FF0000"))
someImage.setColorFilter(Color.parseColor("#99FF0000"))
And here's the same tint applied when the arrow's fill colour is #color/black
So the colour tint, when it's partially transparent, only really shifts the overall colour appearance, rather than fully replacing it. So check your R.color.green value and make sure it's actually fully opaque. If it's not, and that's how you want it, then pay attention to the fillColor you're using - you can see there that I'm applying the exact same colour to the text, but it matches more closely when the arrow is white
The other thing is the different PorterDuff tinting modes you can apply with setColorFilter - I can't really go into them right now (some info here and here) but you can control how the tint colour and the image are composited, how they're blended together.
Ideally you'd just make your image's fillColor white and apply your tint to it and call it a day, but if you can't do that (because it's in use elsewhere) or you need to tint bitmaps, you can look into that stuff!
Instead of using the ?astr which is for the main portion of the app user interface example navbar and things like that. You can create your color as Third Primary Color and just call it from there. I only use the ?astr for main color of the title bar or nav bar.
When you define the color as a third primary define the same color in both themes. You should get the same color for both. I do this a lot on all of my apps.
Also Android Studio is yelling at me saying that the ?astr is no longer needed. So I have a feeling that it will be done away with in the future.
Also defining your own colors will ensure the colors are the same. Be sure to set the same #13345fg(not actual color) with both themes that are pre-built for the app.
A somewhat well-known issue in Android, since Lollipop and the introduction of Material Design, is that elevated Views with see-through backgrounds reveal ugly artifacts from their shadow draws behind them.
Those visual glitches are all caused by the shadows being adjusted for increasing elevation by kind of "zooming out" on the gradients, which causes their inner borders to shrink inward within the View's bounds, normally unseen in ones with opaque backgrounds. No clipping is done there, and every relevant check I've come across in the source turns the shadow off completely when that artifact would be seen, so that clipping is seemingly omitted intentionally, likely for efficiency.
As you might expect, many questions have been posted about it here over the years, but turning it off or somehow avoiding it altogether seem to be about the only generally effective solutions other users have found, as well:
How to set semi transparent background color for Android CardView?
CardView background alpha colour not working correctly
Cardview - rounded corners with transparent background
Elevation + transparency bug on Android Lollipop
How to create a shape with a transparent background and a shadow, but the shadow should not be visible behind the shape outline?
CardView with transparent background issue on > API 21
Weird view behavior when an elevation and a transparent background color are used on API 21
Remove background colour from FloatingActionButton
Android Floating Action Button Semi Transparent Background Color
How to set border and background color of Floating Action Button with transparency through xml?
How to center image in FloatingActionButton behind transparent background?
How to remove those dark circular background from floating action button?
Android transparency and Shadows
a shadow appear below material button after setting background color tint in android
Multiple layers of shadows in android recyclerview elevation
How to adjust shadow in translucent navbar?
Transparent white button looks bad in Android Material Design
Various bespoke workarounds are available for a few common setups, some users ignore it or don't realize what it is, and some have even integrated the effect into their designs, but I still have not found a single example where it's been truly fixed. I haven't gone digging into the native graphics code yet, but I also can't find any instance in the SDK source where anything is done about it other than disabling the shadow when that might be visible, and if we're not able to do it at the app level it doesn't really matter what the low-level graphics stuff can do.
There doesn't seem to be much out there about the general problem, but recently I'd shared some information about it on this old question concerning CardView, including a couple of basic techniques for creating clipped shadow replicas as replacements. However, the examples in that answer are quite specific, and adjusting them for multiple different Views would be tedious and error-prone, and would likely require some not-insignificant modifications to an existing setup in order to add them.
Is there any way to apply those workarounds generally, and with minimal changes?
In my app, I have ConstraitLayout with a gradient background. In a runtime, this ConstraitLayout can be resized. The problem I faced is that gradient drawable "isn't scaled". I will show an image to explain it better:
In the green circles, You can see that the color before and after resizing at the and of ConstraitLayout is different. Is this possible to force an app to set a gradient in the full range of colors?
The only way I have found to do it is to set ConstraitLayout background every time its changes. I think it is not the best way to do it, especially in my case when ConstraitLayout size was changing in on onTouch event, so my App has to change it so many times. But I didn't see any problems while using this solution.
I would have thought that there would be a variety of options in connection with a button's background from an image, for example the image could be tiled, or stretched or centred etc etc, but when I list a button's methods I can't see anything. Now I'm suspecting that it could be a two stage process, perhaps getting some kind of view first and then using a method of that view. Or maybe there is simply no control whatsoever concerning a button background. Please advise.
Note that any View's background is something that fills the area covered by that View, so you can't have it centered.
Stretching the the default behaviour, that's why a state list of 9-patches the the best thing to use for Button's background.
If you want tiled background, you may use XML Bitmap with tileMode="repeat". See also other kinds of Drawables on this site. You can for example make something that feels like centered background image using Inset Drawable.
And finally the functions are there: setBackgroundDrawable and setBackgroundResource.
I've noticed that changing the background color of an android widget (ex. Button or TextView) by program:
myButton.setBackgroundColor(Color.BLUE);
makes it to loose its 3D shape, border, and shadows effects, and then appears like a ugly flat square. What am I missing?
Sorry for the very naive question but I couldn't get it right although tried for a time.
Rounded corners, shadow effects, etc are often accomplished in Android by using images. See this developer documentation for an explanation of how that works.
A widget can have either an image background, or a solid color background. So, by setting the background color you are override the background image. If you want to change the color without losing everything else, you need to edit the image files.
See:
How to set background color of a View
Is it possible to have a Button with a background image with text on top?
How to gradient fill a button's background?
Problem with EditText background (android)
Since background is a drawable, you can modify the drawable to change the color:
myButton.getBackground().setColorFilter(Color.BLUE, PorterDuff.Mode.SRC_ATOP);