Search code examples
androidandroid-drawable

How to create Arc with solid colour on Android background in Portrait and landscape


My current Android Application UI requires a two tone background with a curved arc as border as shown in the following image

enter image description here

I can achieve this effect in portrait (although I do not like using negative margin values) as follows...

    <ImageView
        android:id="@+id/backgroud_arc"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="-150dp"
        android:layout_marginEnd="-150dp"
        android:adjustViewBounds="true"
        android:scaleType="centerCrop"
        android:src="@drawable/background_circle" />

where background circle is this drawable shape

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="@color/background" />
    <size
        android:width="120dp"
        android:height="60dp" />
    <corners
        android:bottomLeftRadius="60dp"
        android:bottomRightRadius="60dp" />
</shape>

Is there an easier method I can employ to achieve the desired effect?

In landscape this drawable is very badly "clipped".


Solution

  • Instead of using an inflexible drawable background, we should rather draw the colours along with the curve on the canvas.

    For this we create a custom View ArcBackgroundView as below. The code is self explanatory:

    import android.content.Context
    import android.content.res.Configuration
    import android.graphics.Canvas
    import android.graphics.Paint
    import android.graphics.Path
    import android.util.AttributeSet
    import android.view.View
    import androidx.core.content.ContextCompat
    
    class ArcBackgroundView(context: Context, attrs: AttributeSet) : View(context, attrs) {
        private val paint = Paint()
        private val path = Path()
    
        init {
            paint.isAntiAlias = true
        }
    
        override fun onDraw(canvas: Canvas?) {
            super.onDraw(canvas)
            if (canvas == null) {
                return
            }
            // Draw background color
            canvas.drawColor(ContextCompat.getColor(context, R.color.blue))
    
            // Draw curve portion of background
            // Setup color
            paint.color = ContextCompat.getColor(context, R.color.white)
    
            val curveStartAndEndY = 0.6f * measuredHeight // <------ You can change this value based on your requirement
            // Set curve's control point's Y position (downwards bulge of curve) based on orientation
            var curveControlPointY = measuredHeight / 1.4f // <------ You can change this value based on your requirement
            if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
                // Since in landscape mode, the curve will have greater arc length,
                // we need to give the curve more downwards bulge as compared to that in portrait mode.
                curveControlPointY = measuredHeight / 1.2f // <------ You can change this value based on your requirement
            }
            // Now we draw the entire path for curve
            path.moveTo(0f, curveStartAndEndY)
            path.quadTo(
                measuredWidth / 2f,
                curveControlPointY,
                measuredWidth.toFloat(),
                curveStartAndEndY
            )
            path.lineTo(measuredWidth.toFloat(), 0f)
            path.lineTo(0f, 0f)
            path.lineTo(0f, curveStartAndEndY)
            canvas.drawPath(path, paint)
        }
    }
    

    The ArcBackgroundView can now be used in an xml as shown below:

    <FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <com.example.myapp.ArcBackgroundView
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>
    

    The resulting output for Portrait and landscape is shown below:

    enter image description here

    enter image description here

    Apart from creating a custom view as shown, if you require, you can also create a say custom FrameLayout called ArcBackgroundFrameLayout. Copy paste the code of onDraw() into your ArcBackgroundFrameLayout's onDraw(). Now you can use your custom container directly like below:

    <?xml version="1.0" encoding="utf-8"?>
    <com.example.myapp.ArcBackgroundFrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/black">
    
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:background="@color/teal_200"
            android:gravity="center"
            android:text="THIS IS A TEXT VIEW ON TOP OF THE DUAL TONE BACKGROUND"
            android:textSize="28sp"
            android:textStyle="bold" />
    </com.example.myapp.ArcBackgroundFrameLayout>
    

    Note the android:background="@color/black" added with the ArcBackgroundFrameLayout. Some background (not necessarily black color) must be added otherwise for containers, #onDraw() would not be called. You should not worry about this black color as it would not show. We are changing background colours from inside our overridden onDraw().

    The resulting output for Portrait and landscape is shown below:

    enter image description here

    enter image description here

    If you have any other requirement, please let me know. I will edit this answer.

    The code is in Kotlin. If your project is in Java, please let me know again. I can edit and give a Java version of the same.

    Thanks.