Maxime Michel
Maxime Michel

Reputation: 615

Custom button with gradient outline and icon with transparent background

I am trying to create custom buttons for my android application. These buttons are simply to add/edit/delete data on the screen. However, I would like to do some nice styling to the buttons but I am having some difficulties with the way android programming works.

The idea is to have a border of a given thickness (let's say 2dp) whose color is a gradient. This implies the center being transparent however and I consider this to be one of the main difficulties. Along with this, I would like the inside of the button to have an icon representing its function.

Currently, I have the gradient and its rounded edges like this:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:gravity="top">
        <shape android:shape="rectangle">
            <gradient
                android:startColor="@color/colorPrimaryDark"
                android:centerColor="@color/colorAccent"
                android:endColor="@android:color/white"
                android:angle="270" />
            <corners android:radius="20dp"/>
        </shape>
    </item>
</layer-list>

This corresponds to what I want color-wise. However, having a transparent center seems impossible to do in a drawable resource file.

Along with this issue, in my code, I have a ListView above a LinearLayout containing three buttons which this styling should eventually be applied to. However, I am unable to even change the background color of the buttons.

Here is the code representing the activity:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    tools:context=".AddPlayersActivity"
    android:orientation="vertical"
    android:paddingRight="10dp"
    android:paddingLeft="10dp"
    android:background="@drawable/background_gradient">

    <TextView
        android:id="@+id/add_players_activity_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="20dp"
        android:fontFamily="@font/lato_bold"
        android:text="@string/app_name"
        android:textAlignment="center"
        android:textColor="@android:color/white"
        android:textSize="36sp" />

    <TextView
        android:layout_below="@+id/add_players_activity_title"
        android:id="@+id/add_players_activity_current_players"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fontFamily="@font/lato"
        android:text="@string/current_players"
        android:textColor="@android:color/white"
        android:textSize="20sp" />

    <ListView
        android:id="@+id/add_players_activity_player_list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/add_players_activity_current_players"
        android:layout_above="@id/add_players_activity_button_layout" />

    <LinearLayout
        android:id="@+id/add_players_activity_button_layout"
        android:layout_alignParentBottom="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center">

        <Button
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_marginHorizontal="10dp"
            android:text="Add"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_marginHorizontal="10dp"
            android:text="Edit"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_marginHorizontal="10dp"
            android:text="Delete" />
    </LinearLayout>
</RelativeLayout>

None of the other StackOverflow questions have solved either of my issues. Even the most common "Set the style attribute of the buttons" one.

I'm very open to solutions that could require external tools or other things so don't feel limited to internal functions. I do however feel that there has to be a way around this within the Android Studio.

Thanks in advance to anyone that takes time to look into this!

Here is an example as requested (Only difference is I will be using a drawable/image instead of text)

Example

Upvotes: 3

Views: 4337

Answers (2)

Xid
Xid

Reputation: 4951

I recommend creating a custom view for this. Creating a custom view would be highly beneficial to you as have mentioned you have many buttons. With a custom view, you can reuse your button with varying height, width, border width, colors, and corner radius.

Read more custom views here. You could also go through a series of codelabs starting this one.

attrs.xml (values/attrs.xml):

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- Custom attributes for the button -->
    <declare-styleable name="StyledButton">
        <attr name="cornerRadius" format="dimension" />
        <attr name="borderWidth" format="dimension" />
        <attr name="startColor" format="color" />
        <attr name="centerColor" format="color" />
        <attr name="endColor" format="color" />
    </declare-styleable>
</resources>

StyledButton.kt:

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.LinearGradient
import android.graphics.Paint
import android.graphics.Path
import android.graphics.Shader
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatImageButton
import androidx.core.content.withStyledAttributes

class StyledButton @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : AppCompatImageButton(context, attrs, defStyleAttr) {

    private var cornerRadius = 0f
    private var borderWidth = 0f
    private var startColor = 0
    private var centerColor = 0
    private var endColor = 0

    private val path = Path()
    private val borderPaint = Paint().apply {
        style = Paint.Style.FILL
    }

    init {
        //Get the values you set in xml
        context.withStyledAttributes(attrs, R.styleable.StyledButton) {
            borderWidth = getDimension(R.styleable.StyledButton_borderWidth, 10f)
            cornerRadius = getDimension(R.styleable.StyledButton_cornerRadius, 10f)
            startColor = getColor(R.styleable.StyledButton_startColor, Color.WHITE)
            centerColor = getColor(R.styleable.StyledButton_centerColor, Color.RED)
            endColor = getColor(R.styleable.StyledButton_endColor, Color.BLACK)
        }
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)

        // Create and set your gradient here so that the gradient size is always correct
        borderPaint.shader = LinearGradient(
            0f,
            0f,
            width.toFloat(),
            height.toFloat(),
            intArrayOf(startColor, centerColor, endColor),
            null,
            Shader.TileMode.CLAMP
        )
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        //Remove inner section (that you require to be transparent) from canvas
        path.rewind()
        path.addRoundRect(
            borderWidth,
            borderWidth,
            width.toFloat() - borderWidth,
            height.toFloat() - borderWidth,
            cornerRadius - borderWidth / 2, 
            cornerRadius - borderWidth / 2,
            Path.Direction.CCW
        )
        canvas.clipOutPath(path)

        //Draw gradient on the outer section
        path.rewind()
        path.addRoundRect(
            0f,
            0f,
            width.toFloat(),
            height.toFloat(),
            cornerRadius,
            cornerRadius,
            Path.Direction.CCW
        )
        canvas.drawPath(path, borderPaint)
    }
}

Usage:

<com.example.andriod.myapplication.StyledButton
    android:layout_width="100dp"
    android:layout_height="40dp"
    android:src="@drawable/ic_android"
    android:scaleType="center"
    android:layout_gravity="center"
    app:borderWidth="2dp"
    app:cornerRadius="20dp"
    app:startColor="@color/colorPrimaryDark"
    app:centerColor="@android:color/holo_red_dark"
    app:endColor="@color/colorPrimary"/>

Result

Styled button result

Upvotes: 4

iknow
iknow

Reputation: 9872

I don't know if it is the best solution, but You can do it easy in this way:

  1. Create gradient_back.xml:
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <corners android:radius="10dp" />
    <gradient
        android:endColor="@color/colorPrimary"
        android:startColor="@color/colorAccent" />
</shape>
  1. Create shape_front.xml:
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <corners android:radius="9dp" />
    <solid android:color="@android:color/white" />
</shape>
  1. Now in Your layout create a button like this:
<FrameLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/gradient_back">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="3dp"
        android:background="@drawable/shape_front"
        android:text="WOW" />

</FrameLayout>

Result:

enter image description here


Version with the transparent body but not with gradient:

  1. To Your build.gradle (Module: app) add:
implementation "com.google.android.material:material:1.2.1"
  1. in res/values/styles change parent style to:
parent="Theme.MaterialComponents.Light.DarkActionBar"
  1. Now in layout You can add a button:
<com.google.android.material.button.MaterialButton
    style="@style/Widget.MaterialComponents.Button.OutlinedButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="WOW"
    android:textColor="@color/textColor"
    app:cornerRadius="10dp"
    app:strokeWidth="2dp"
    app:strokeColor="@color/colorAccent" />

Result:

enter image description here

Upvotes: 1

Related Questions