1

To make it short, this is what I would like to add a custom button that looks like this:

Button_On

IMG is a .png file in my mipmap folders and SOME TEXT is just a string value. What the dashed line is added just as a separator in the image, not in the button. The issue is that the rounded edges don't appear where the image is added. It looks like this:

Button_have

My questions are the following:

  1. Can this be achieved?
  2. Is there a way to override the <solid /> attribute in <shape />? I will have to create 10 of these buttons each with different colors and if I add android:color with a different value, the color does not change
  3. When adding the image, it makes me choose only one (e.g. the mdpi one). If this will be displayed on larger screens, will it take a different .png image based on the size?
  4. Is there a specific type of button I should use? I would like to revert the colors when the button is pressed and stay as pressed. I have a vague idea about how this can be achieved, but is there a way to do this for the .png files as well or do I need to import into the project others with the colors already inverted and just switch them?

custom_button.xml

<shape 
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">

<corners
    android:topLeftRadius="250px"
    android:bottomLeftRadius="250px"
    android:topRightRadius="50px"
    android:bottomRightRadius="50px" />
<solid
    android:color="@color/YellowPrimary"/>
</shape>

button_styles.xml

<resources>

<style name="CategoryToggle">
    <item name="android:background">@drawable/custom_button</item>
    <item name="android:textAllCaps">true</item>
</style>

<style name="CategoryToggle.First">
    <item name="android:color">@color/bluePrimary</item> // Does not override <solid>
    <item name="android:drawableLeft">@mipmap/icon_48mdpi</item>
    <item name="android:text">@string/first_cat</item>
</style>
</resources>

button_layout.xml

<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">

<Button
    style="@style/CategoryToggle.History"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="8dp"
    android:layout_marginEnd="8dp"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintTop_toTopOf="parent" />


</android.support.constraint.ConstraintLayout>

I have no java code at the moment as I just started and trying to implement this weird button format.

This is how it looks at my end: my_end

Gabriele Mariotti
  • 320,139
  • 94
  • 887
  • 841
Andy
  • 87
  • 11
  • It's hard to figure out what's going wrong without some code. – Bryan Mar 06 '19 at 17:04
  • @Bryan I don't have much code to go with as I just started working. I only added the colors, strings, pngs. Nothing else at the moment. – Andy Mar 06 '19 at 17:40
  • Not much code is better than no code, post the XML you use to create the shape and any Java code you use to display it. – Bryan Mar 06 '19 at 17:49
  • @Bryan As I said, nothing much. Added what I have. No java for now as I am just trying to create that shape at the moment. – Andy Mar 06 '19 at 18:02
  • Does the `icon_48mdpi` mipmap have a yellow background? – Bryan Mar 06 '19 at 19:29
  • Yes, it does. I should have it transparent, right? – Andy Mar 06 '19 at 19:37
  • Probably. That's why you're getting the square shape on the left side of the button. There are [ways to mask the drawable](https://stackoverflow.com/questions/5574212/android-view-clipping), but it takes a bit of code. – Bryan Mar 06 '19 at 19:52
  • What do you mean by "is there a way to do this for the .png files as well"? You want to change the drawable based on the state of the button? – Bryan Mar 06 '19 at 19:55
  • Yes, give the PNG a transparent background and the rounded corner drawing should look correct (not be covered). – leorleor Mar 06 '19 at 19:55
  • @Bryan Yes, I want to change the colors of the png when the button is pressed. Something to mimic the fact that it's selected – Andy Mar 06 '19 at 19:57

4 Answers4

1
  1. Absolutely. As I mentioned in the comments, either make sure your drawable has a transparent background, or create a custom button to mask the drawable.
  2. You'll want to use a style attribute apply a different style to each button. By this I mean the color defined in your custom_button.xml should reference a color attribute (something like colorAccent should work in your case), instead of a static color.
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="?attr/colorAccent"/>
</shape>

Then change this color in your button styles instead of android:color.

<style name="CategoryToggle">
    <item name="colorAccent">@color/YellowPrimary</item>
</style>

Make sure you have the support library dependency added, or colorAccent will not be available.

Use the android:theme attribute, instead of the style attribute to apply the button theme.

<Button
    android:width="wrap_content"
    android:height="wrap_content"
    android:theme="@style/CategoryToggle"/>
  1. It looks like your drawables are not using resource qualifiers. You'll need to make sure each alternative resource has the exact same name as the original (i.e. your icon_48mdpi.png should instead be called icon_48dp.png for all configurations) and is placed in the corresponding drawable folder for its density. Your drawable resources should look like the following (in the Project view structure, not the Android view structure).
res/
|-- drawable/
|   +-- custom_button.xml
|-- drawable-hdpi/
|   +-- icon_48dp.png
|-- drawable-mdpi/
|   +-- icon_48dp.png
|-- drawable-xhdpi/
|   +-- icon_48dp.png
|-- drawable-xxhdpi/
|   +-- icon_48dp.png
|-- drawable-xxxhpdi/
|   +-- icon_48dp.png
~
  1. To change the color of a drawable based on state, you will need to abstract your color one step further and create a color state list.

res/color/button_color_selector.xml

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="@color/pressed_color" android:state_pressed="true"/>
    <item android:color="@color/focused_color" android:state_focused="true"/>
    <item android:color="@color/state_hovered" android:state_hovered="true"/>
    <item android:color="?attr/colorAccent"/>
</selector>

Then you can use this color resource in your shape drawable instead of colorAccent.

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="@color/button_color_selector"/>
</shape>

You can also make each of the colors in your color state list a styleable attribute by defining custom attributes and referencing those attributes in your styles. I won't go into that further for the sake of brevity though.

You can do this for drawables similarly by creating a state list drawable.


Lastly, you'll want to get into the habit of using dp instead of px unless you are absolutely certain you want to use px. This will prevent strange appearances at different screen densities.

Bryan
  • 14,756
  • 10
  • 70
  • 125
  • Good advice. I will try and let you know how it went. Some small questions and clarifications before: 1. I presume coding will take up less space than importing a new set of pngs? 2. Can I use any attribute? For example one less relevant here like colorEdgeEffect? 3. Remembered it from a previous project, but forgot to update the question. 4. I used px to test if it has any difference. Forgot to change it back before pasting the code. – Andy Mar 06 '19 at 21:14
  • @Andy 1. That's true. You can even use [vector assets](https://developer.android.com/guide/topics/graphics/vector-drawable-resources) instead of png files, and apply a color attribute to the color of the vector to take up even less space. 2. Yes, any attribute would work. I would try to stick to ones that make sense for the use case. – Bryan Mar 06 '19 at 21:27
  • After a lot of trial and error I managed to do number 1, but am pretty stuck at #2. The overriding of an ?attr color does not occur even though I did the exact steps. Could you pinpoint the library? I have added about 7 in gradle which seemed relevant, but cannot override the color. – Andy Mar 11 '19 at 12:40
  • @Andy Is `?attr/colorAccent` highlighted as an error? (It should appear red, and when you hover over it a dialog will appear stating something like "Cannot resolve symbol '?attr/colorAccent'") – Bryan Mar 11 '19 at 13:02
  • There is no error. In my shape I have `` which sets the correct color, and in my style applied to my button I have `@color/histYellowPrimary` which does not change it to yellow. I still get the one initially set for accent (a pink-ish color). I have the latest app compat dependency as well as others added from the list including `implementation 'com.android.support:palette-v7:28.0.0'` which is related to colors. – Andy Mar 11 '19 at 13:12
  • I also tried with the selector from #4 and it still does not override the `colorAccent` – Andy Mar 11 '19 at 13:37
  • @Andy Then you have the correct library (the Palette library, while useful, doesn't have anything to do with `colorAccent`). I apologize, I failed to mention in the answer; make sure you're setting the style using `android:theme="@style/CategoryToggle"` (and not `style="..."`). Also, the color change will not appear in the XML preview, so you will have to build the app to see any changes. – Bryan Mar 11 '19 at 13:49
  • If I set it up as `android:theme` then my shape disappears. What I get is a gray button with image and text. Nothing from my custom shape and colors. If I set it as `style=` then I get the shape but the color does not override – Andy Mar 11 '19 at 14:09
  • @Andy Hm, in my testing the shape was retained when using `android:theme`. Add your current code to the question so I can see what might be going wrong? – Bryan Mar 11 '19 at 14:19
  • Managed to solve the situation by creating a `` in the drawable folder and an item for each state which contains the shape and colors I need. This way I also get rid of a file for the shapes needed for each button. – Andy Mar 11 '19 at 15:08
  • @Andy Sounds like a lot of duplicate code, I would still try to see if I could separate it. But as long as it works. – Bryan Mar 12 '19 at 13:13
  • I know that it is a lot of duplicate code, but if I add the text color as well in the background section it just ignores the shape. For now I will use this since it works and maybe I will stumble upon something while implementing the app further. – Andy Mar 12 '19 at 16:27
0

On 4 "is there a way to (revert colors when pressed) for the .png files".. yes there are ways.

The by-the-book option is to use xml styles to specify different drawables for different button states. Consider ImageButton which has an example of this in its docs: https://developer.android.com/reference/android/widget/ImageButton.html

Another option is to apply tints or other filters to the drawable in your own code in button interaction callbacks. Compared to using different drawables, in this approach you add custom code instead of leveraging builtin resources, so you may wind up testing and debugging more on different devices, and you get full control so you can do custom animations and other elaborate things that may not fit well into Android resource tooling. Can checkout setColorFilter for starters: https://developer.android.com/reference/android/widget/ImageView.html#setColorFilter(int,%20android.graphics.PorterDuff.Mode)

Thanks for adding some code and images or your problem, helps in understanding it.

leorleor
  • 554
  • 5
  • 14
0

Big shout out to Bryan for his insight on this matter. It led me to the paths I needed to follow in order to solve this issue. I am posting this answer so that others with similar cases will know the steps. Although I have quite a number of files, this procedure did the trick perfectly.

  1. I have my custom button as described in the link provided by Bryan.

For the rest I have the following:

Change the text color: button_text_color.xml

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="@color/redPrimary" android:state_pressed="true"/>
    <item android:color="@color/redDark"/>
</selector>

Change the background: button_background.xml

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true">
        <shape android:shape="rectangle">
            <corners android:bottomLeftRadius="400dp" android:radius="100dp" android:topLeftRadius="400dp" />
            <solid android:color="@color/redDark" />
        </shape>
    </item>
    <item android:state_enabled="true">
        <shape android:shape="rectangle">
            <corners android:bottomLeftRadius="400dp" android:radius="100dp" android:topLeftRadius="400dp" />
            <solid android:color="@color/redPrimary" />
        </shape>
    </item>
</selector>

Change the drawable: button_drawable.xml

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/ic_button_inverted" android:state_pressed="true" />
    <item android:drawable="@drawable/ic_button"/>
</selector>

button_style.xml

<style name="MyButtonStyle">
        <item name="android:background">@drawable/button_background</item>
        <item name="android:drawableLeft">@drawable/button_drawable</item>
    <item name="android:text">@string/button_text</item>
    <item name="android:textColor">@color/button_text_color</item>
</style>

Implementing it in main_layout.xml

<packagename.MyCustomButton xmlns:app="http://schemas.android.com/apk/res-auto"
            android:id="@+id/myButton"
            style="@style/MyBUttonStyle"
            android:layout_marginBottom="8dp" />
Andy
  • 87
  • 11
0

Just use the official library.
Add the MaterialButton component using these attributes:

  • app:icon to add the icon
  • app:iconGravity to decide the position of the icon
  • app:iconPadding to increase/decrease the space between the icon and the text
  • android:paddingLeft the padding between the edge and the icon
<com.google.android.material.button.MaterialButton
    android:paddingLeft="2dp"
    app:icon="@drawable/ic_add_24px"
    app:iconGravity="start"
    app:iconPadding="4dp"
    app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Button.RoundedRight"
    .../>

With the app:shapeAppearanceOverlay you can customize the shape of the component.

  <style name="ShapeAppearanceOverlay.Button.RoundedRight" parent="">
    <item name="cornerFamily">rounded</item>
    <item name="cornerSizeTopRight">4dp</item>
    <item name="cornerSizeBottomRight">4dp</item>
    <item name="cornerSizeTopLeft">16dp</item>
    <item name="cornerSizeBottomLeft">16dp</item>
  </style>

You can obtain the same result using a style.

enter image description here

Gabriele Mariotti
  • 320,139
  • 94
  • 887
  • 841