Skip to content

Instantly share code, notes, and snippets.

@Ahmed-Abdelmeged
Last active July 13, 2023 05:46
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save Ahmed-Abdelmeged/662007e7af74301c6758634041082676 to your computer and use it in GitHub Desktop.
Save Ahmed-Abdelmeged/662007e7af74301c6758634041082676 to your computer and use it in GitHub Desktop.
Rounded Layout with specific corners rounded

To make a rounded corner layout or image there is plenty of ways and libraries for this even you can use the CardView attributes to create that

So what is the problem :D . what if you want to make the layout rounded in specific corner or all corners expect one for example . actually there are familiar solution for this to make a XML file like this

<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#fff" />
    <corners
        android:bottomLeftRadius="0dp"
        android:bottomRightRadius="0dp"
        android:topLeftRadius="12dp"
        android:topRightRadius="12dp" />
</shape>

and make it background to this layout and you are done. this solution will work for the most cases but what if you try to use Glide or Picasso to upload the image in imageView in that layout. the rounded corner is gone because the image override the background so know you have to solutions

1- To make a custom transformation and pass the image to it but this solution if your images are have different sizes the rounded corner won't be equal in each view.

2-You can make a custom view as wrapper around your views and give it the exactly corners you want to round . the only problem with that solutions that your memory will increase a little bit.

So that is the code for the custom view that I created you can customize it in other way depend on your use case. you can specify the corner radius for specific corner in xml and in runtime

1- Declare a styleable in your attrs.xml file and it's name match the view name

2- Decalre the custom view as the code snippet

3- Decalre your xml file

5- but the drawable file also as backgroud also

4- load the images into the view

This the result of snippet code Sample

I hope that will be helpfully :D

<com.abdelmeged.ahmed.roundedlayout.RoundedView 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="wrap_content"
app:topLeftCornerRadius="12dp"
app:topRightCornerRadius="12dp"
android:background="@drawable/up"
tools:context="com.abdelmeged.ahmed.roundedlayout.MainActivity">
<ImageView
android:layout_width="match_parent"
android:id="@+id/image_view"
android:layout_gravity="center"
android:scaleType="centerCrop"
android:layout_height="wrap_content" />
</com.abdelmeged.ahmed.roundedlayout.RoundedView>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="RoundedView">
<attr name="topLeftCornerRadius" format="dimension" />
<attr name="topRightCornerRadius" format="dimension" />
<attr name="bottomLeftCornerRadius" format="dimension" />
<attr name="bottomRightCornerRadius" format="dimension" />
</declare-styleable>
</resources>
package com.abdelmeged.ahmed.roundedlayout;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ImageView;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
public class MainActivity extends AppCompatActivity {
ImageView image;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
image = findViewById(R.id.image_view);
GlideApp.with(this)
.load(url)
.into(image);
}
}
package com.abdelmeged.ahmed.roundedlayout;
/**
* Created by ahmed on 9/17/2017.
*/
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.Region;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
/**
* Custom wrapper view to get round corner round view
*/
public class RoundedView extends FrameLayout {
/**
* The corners than can be changed
*/
private float topLeftCornerRadius;
private float topRightCornerRadius;
private float bottomLeftCornerRadius;
private float bottomRightCornerRadius;
public RoundedView(@NonNull Context context) {
super(context);
init(context, null, 0);
}
public RoundedView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0);
}
public RoundedView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
private void init(Context context, AttributeSet attrs, int defStyle) {
TypedArray typedArray = context.obtainStyledAttributes(attrs,
R.styleable.RoundedView, 0, 0);
//get the default value form the attrs
topLeftCornerRadius = typedArray.getDimension(R.styleable.
RoundedView_topLeftCornerRadius, 0);
topRightCornerRadius = typedArray.getDimension(R.styleable.
RoundedView_topRightCornerRadius, 0);
bottomLeftCornerRadius = typedArray.getDimension(R.styleable.
RoundedView_bottomLeftCornerRadius, 0);
bottomRightCornerRadius = typedArray.getDimension(R.styleable.
RoundedView_bottomRightCornerRadius, 0);
typedArray.recycle();
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
@Override
protected void dispatchDraw(Canvas canvas) {
int count = canvas.save();
final Path path = new Path();
float[] cornerDimensions = {
topLeftCornerRadius, topLeftCornerRadius,
topRightCornerRadius, topRightCornerRadius,
bottomRightCornerRadius, bottomRightCornerRadius,
bottomLeftCornerRadius, bottomLeftCornerRadius};
path.addRoundRect(new RectF(0, 0, canvas.getWidth(), canvas.getHeight())
, cornerDimensions, Path.Direction.CW);
canvas.clipPath(path, Region.Op.REPLACE);
canvas.clipPath(path);
super.dispatchDraw(canvas);
canvas.restoreToCount(count);
}
public void setTopLeftCornerRadius(float topLeftCornerRadius) {
this.topLeftCornerRadius = topLeftCornerRadius;
invalidate();
}
public void setTopRightCornerRadius(float topRightCornerRadius) {
this.topRightCornerRadius = topRightCornerRadius;
invalidate();
}
public void setBottomLeftCornerRadius(float bottomLeftCornerRadius) {
this.bottomLeftCornerRadius = bottomLeftCornerRadius;
invalidate();
}
public void setBottomRightCornerRadius(float bottomRightCornerRadius) {
this.bottomRightCornerRadius = bottomRightCornerRadius;
invalidate();
}
}
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#59dfff" />
<corners
android:bottomLeftRadius="0dp"
android:bottomRightRadius="0dp"
android:topLeftRadius="12dp"
android:topRightRadius="12dp" />
</shape>
@Zhuinden
Copy link

Zhuinden commented Feb 20, 2019

canvas.clipPath(path, Region.Op.REPLACE); crashes if targetSdkVersion 28.

java.lang.IllegalArgumentException: Invalid Region.Op - only INTERSECT and DIFFERENCE are allowed

Also see https://stackoverflow.com/questions/50231950/what-is-the-best-alternative-to-canvas-cliprect-with-region-op-replace

@Zhuinden
Copy link

According to Reddit, if you replace REPLACE with INTERSECT, then it should still work.

@hemantbeast
Copy link

How do I set CardView like shadows to this?

@lopspower
Copy link

lopspower commented Jun 5, 2019

Add this after clipPath to manage elevation

    @Override
    protected void dispatchDraw(Canvas canvas) {
        // ...

        canvas.clipPath(path);

        setOutlineProvider(new ViewOutlineProvider() {
            @Override
            public void getOutline(View view, Outline outline) {
                outline.setConvexPath(path);
            }
        });

        // ...
    }

@minchaej
Copy link

Thank you so much for this! You are a genius! haha :)

@gohlinka2
Copy link

Hi, thanks very much for this gist!

The only thing that's bad about it is that elevation (shadows) doesn't work on child views.
However, that seems to be resolved when removing the line setLayerType(View.LAYER_TYPE_SOFTWARE, null);, without any visible drawbacks. So I'd like to ask, what is the purpose of that line, which disables hardware acceleration for this view?

Also, it seems to work without this line: canvas.clipPath(path, Region.Op.REPLACE); which uses the now deprecated function. I'm not sure, but doesn't the line below: canvas.clipPath(path); do the same as the one above it? If that's the case, why there are both?

Thanks again, if anyone answers my questions :)

@AbdelQuinonez
Copy link

I tried this code using a FragmentContainerView (Google Maps), the only problems is that it only shows "Google" logo and the background is invisible, any suggest?
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment