public class

ImageFilterButton

extends AppCompatImageButton

 java.lang.Object

↳ImageButton

androidx.appcompat.widget.AppCompatImageButton

↳androidx.constraintlayout.utils.widget.ImageFilterButton

Gradle dependencies

compile group: 'androidx.constraintlayout', name: 'constraintlayout', version: '2.2.0-beta01'

  • groupId: androidx.constraintlayout
  • artifactId: constraintlayout
  • version: 2.2.0-beta01

Artifact androidx.constraintlayout:constraintlayout:2.2.0-beta01 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.constraintlayout:constraintlayout com.android.support.constraint:constraint-layout

Overview

An AppCompatImageButton that can display, combine and filter images. Added in 2.0

Subclass of AppCompatImageButton to handle various common filtering operations.

ImageFilterButton attributes

altSrc Provide and alternative image to the src image to allow cross fading
saturation Sets the saturation of the image.
0 = grayscale, 1 = original, 2 = hyper saturated
brightness Sets the brightness of the image.
0 = black, 1 = original, 2 = twice as bright
warmth This adjust the apparent color temperature of the image.
1=neutral, 2=warm, .5=cold
contrast This sets the contrast. 1 = unchanged, 0 = gray, 2 = high contrast
crossfade Set the current mix between the two images.
0=src 1= altSrc image
round (id) call the TransitionListener with this trigger id
roundPercent Set the corner radius of curvature as a fraction of the smaller side. For squares 1 will result in a circle
overlay Defines whether the alt image will be faded in on top of the original image or if it will be crossfaded with it. Default is true. Set to false for semitransparent objects

Summary

Constructors
publicImageFilterButton(Context context)

publicImageFilterButton(Context context, AttributeSet attrs)

publicImageFilterButton(Context context, AttributeSet attrs, int defStyleAttr)

Methods
public voiddraw(Canvas canvas)

public floatgetContrast()

Returns the currently applied contrast

public floatgetCrossfade()

Returns the currently applied crossfade.

public floatgetImagePanX()

gts the pan from the center pan of 1 the image is "all the way to the right" if the images width is greater than the screen width, pan = 1 results in the left edge lining up if the images width is less than the screen width, pan = 1 results in the right edges lining up if image width == screen width it does nothing

public floatgetImagePanY()

gets the pan from the center pan of 1 the image is "all the way to the bottom" if the images width is greater than the screen height, pan = 1 results in the bottom edge lining up if the images width is less than the screen height, pan = 1 results in the top edges lining up if image height == screen height it does nothing

public floatgetImageRotate()

gets the rotation

public floatgetImageZoom()

sets the zoom where 1 scales the image just enough to fill the view

public floatgetRound()

Get the corner radius of curvature NaN = RoundPercent in effect.

public floatgetRoundPercent()

Get the fractional corner radius of curvature.

public floatgetSaturation()

Returns the currently applied saturation

public floatgetWarmth()

Returns the currently applied warmth

public voidlayout(int l, int t, int r, int b)

public voidsetAltImageResource(int resId)

Set the alternative image used to cross fade to.

public voidsetBrightness(float brightness)

sets the brightness of the image; 0 = black, 1 = original, 2 = twice as bright

public voidsetContrast(float contrast)

This sets the contrast.

public voidsetCrossfade(float crossfade)

Set the current mix between the two images that can be set on this view.

public voidsetImageDrawable(Drawable drawable)

public voidsetImagePanX(float pan)

sets the pan from the center pan of 1 the image is "all the way to the right" if the images width is greater than the screen width, pan = 1 results in the left edge lining up if the images width is less than the screen width, pan = 1 results in the right edges lining up if image width == screen width it does nothing

public voidsetImagePanY(float pan)

sets the pan from the center pan of 1 the image is "all the way to the bottom" if the images width is greater than the screen height, pan = 1 results in the bottom edge lining up if the images width is less than the screen height, pan = 1 results in the top edges lining up if image height == screen height it does nothing

public voidsetImageResource(int resId)

public voidsetImageRotate(float rotation)

sets the rotation angle of the image in degrees

public voidsetImageZoom(float zoom)

sets the zoom where 1 scales the image just enough to fill the view

public voidsetRound(float round)

Set the corner radius of curvature

public voidsetRoundPercent(float round)

Set the corner radius of curvature as a fraction of the smaller side.

public voidsetSaturation(float saturation)

sets the saturation of the image; 0 = grayscale, 1 = original, 2 = hyper saturated

public voidsetWarmth(float warmth)

This makes the apparent color temperature of the image warmer or colder.

from AppCompatImageButtondrawableStateChanged, getSupportBackgroundTintList, getSupportBackgroundTintMode, getSupportImageTintList, getSupportImageTintMode, hasOverlappingRendering, setBackgroundDrawable, setBackgroundResource, setImageBitmap, setImageLevel, setImageURI, setSupportBackgroundTintList, setSupportBackgroundTintMode, setSupportImageTintList, setSupportImageTintMode
from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public ImageFilterButton(Context context)

public ImageFilterButton(Context context, AttributeSet attrs)

public ImageFilterButton(Context context, AttributeSet attrs, int defStyleAttr)

Methods

public float getImagePanX()

gts the pan from the center pan of 1 the image is "all the way to the right" if the images width is greater than the screen width, pan = 1 results in the left edge lining up if the images width is less than the screen width, pan = 1 results in the right edges lining up if image width == screen width it does nothing

Returns:

the pan in X. Where 0 is centered = Float. NaN if not set

public float getImagePanY()

gets the pan from the center pan of 1 the image is "all the way to the bottom" if the images width is greater than the screen height, pan = 1 results in the bottom edge lining up if the images width is less than the screen height, pan = 1 results in the top edges lining up if image height == screen height it does nothing

Returns:

pan in Y. Where 0 is centered

public float getImageZoom()

sets the zoom where 1 scales the image just enough to fill the view

Returns:

the zoom

public float getImageRotate()

gets the rotation

Returns:

the rotation in degrees

public void setImagePanX(float pan)

sets the pan from the center pan of 1 the image is "all the way to the right" if the images width is greater than the screen width, pan = 1 results in the left edge lining up if the images width is less than the screen width, pan = 1 results in the right edges lining up if image width == screen width it does nothing

Parameters:

pan: sets the pan in X. Where 0 is centered

public void setImagePanY(float pan)

sets the pan from the center pan of 1 the image is "all the way to the bottom" if the images width is greater than the screen height, pan = 1 results in the bottom edge lining up if the images width is less than the screen height, pan = 1 results in the top edges lining up if image height == screen height it does nothing

Parameters:

pan: sets the pan in Y. Where 0 is centered

public void setImageZoom(float zoom)

sets the zoom where 1 scales the image just enough to fill the view

Parameters:

zoom: the zoom factor

public void setImageRotate(float rotation)

sets the rotation angle of the image in degrees

Parameters:

rotation: the rotation in degrees

public void setImageDrawable(Drawable drawable)

public void setImageResource(int resId)

public void setAltImageResource(int resId)

Set the alternative image used to cross fade to.

Parameters:

resId:

public void setSaturation(float saturation)

sets the saturation of the image; 0 = grayscale, 1 = original, 2 = hyper saturated

Parameters:

saturation:

public float getSaturation()

Returns the currently applied saturation

Returns:

0 = grayscale, 1 = original, 2 = hyper saturated

public void setContrast(float contrast)

This sets the contrast. 1 = unchanged, 0 = gray, 2 = high contrast

Parameters:

contrast:

public float getContrast()

Returns the currently applied contrast

Returns:

1 = unchanged, 0 = gray, 2 = high contrast

public void setWarmth(float warmth)

This makes the apparent color temperature of the image warmer or colder.

Parameters:

warmth: 1 is neutral, 2 is warm, .5 is cold

public float getWarmth()

Returns the currently applied warmth

Returns:

warmth 1 is neutral, 2 is warm, .5 is cold

public void setCrossfade(float crossfade)

Set the current mix between the two images that can be set on this view.

Parameters:

crossfade: a number from 0 to 1

public float getCrossfade()

Returns the currently applied crossfade.

Returns:

a number from 0 to 1

public void setBrightness(float brightness)

sets the brightness of the image; 0 = black, 1 = original, 2 = twice as bright

Parameters:

brightness:

public void setRoundPercent(float round)

Set the corner radius of curvature as a fraction of the smaller side. For squares 1 will result in a circle

Parameters:

round: the radius of curvature as a fraction of the smaller width

public void setRound(float round)

Set the corner radius of curvature

Parameters:

round: the radius of curvature NaN = default meaning roundPercent in effect

public float getRoundPercent()

Get the fractional corner radius of curvature.

Returns:

Fractional radius of curvature with respect to smallest size

public float getRound()

Get the corner radius of curvature NaN = RoundPercent in effect.

Returns:

Radius of curvature

public void draw(Canvas canvas)

public void layout(int l, int t, int r, int b)

Source

/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package androidx.constraintlayout.utils.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Outline;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.constraintlayout.widget.R;

/**
 * An AppCompatImageButton that can display, combine and filter images. <b>Added in 2.0</b>
 * <p>
 * Subclass of AppCompatImageButton to handle various common filtering operations.
 * </p>
 * <h2>ImageFilterButton attributes</h2>
 * <table summary="KeyTrigger attributes">
 * <tr>
 * <td>altSrc</td>
 * <td>Provide and alternative image to the src image to allow cross fading</td>
 * </tr>
 * <tr>
 * <td>saturation</td>
 * <td>Sets the saturation of the image.<br>  0 = grayscale, 1 = original, 2 = hyper saturated</td>
 * </tr
 * <tr>
 * <td>brightness</td>
 * <td>Sets the brightness of the image.<br>  0 = black, 1 = original, 2 = twice as bright
 * </td>
 * </tr>
 * <tr>
 * <td>warmth</td>
 * <td>This adjust the apparent color temperature of the image.<br> 1=neutral, 2=warm, .5=cold</td>
 * </tr>
 * <tr>
 * <td>contrast</td>
 * <td>This sets the contrast. 1 = unchanged, 0 = gray, 2 = high contrast</td>
 * </tr>
 * <tr>
 * <td>crossfade</td>
 * <td>Set the current mix between the two images. <br>  0=src 1= altSrc image</td>
 * </tr>
 * <tr>
 * <td>round</td>
 * <td>(id) call the TransitionListener with this trigger id</td>
 * </tr>
 * <tr>
 * <td>roundPercent</td>
 * <td>Set the corner radius of curvature  as a fraction of the smaller side.
 *    For squares 1 will result in a circle</td>
 * </tr>
 * <tr>
 * <td>overlay</td>
 * <td>Defines whether the alt image will be faded in on top of the original image or if it will be
 * crossfaded with it. Default is true. Set to false for semitransparent objects</td>
 * </tr>
 * </table>
 */
public class ImageFilterButton extends androidx.appcompat.widget.AppCompatImageButton {
    private ImageFilterView.ImageMatrix mImageMatrix = new ImageFilterView.ImageMatrix();
    private float mCrossfade = 0;
    private float mRoundPercent = 0; // rounds the corners as a percent
    private float mRound = Float.NaN; // rounds the corners in dp if NaN RoundPercent is in effect
    private Path mPath;
    ViewOutlineProvider mViewOutlineProvider;
    RectF mRect;

    Drawable[] mLayers = new Drawable[2];
    LayerDrawable mLayer;
    private boolean mOverlay = true;
    private Drawable mAltDrawable = null;
    private Drawable mDrawable = null;

    public ImageFilterButton(Context context) {
        super(context);
        init(context, null);
    }

    public ImageFilterButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public ImageFilterButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        setPadding(0, 0, 0, 0);
        if (attrs != null) {
            TypedArray a = context
                    .obtainStyledAttributes(attrs, R.styleable.ImageFilterView);
            final int count = a.getIndexCount();
            mAltDrawable = a.getDrawable(R.styleable.ImageFilterView_altSrc);

            for (int i = 0; i < count; i++) {
                int attr = a.getIndex(i);
                if (attr == R.styleable.ImageFilterView_crossfade) {
                    mCrossfade = a.getFloat(attr, 0);
                } else if (attr == R.styleable.ImageFilterView_warmth) {
                    setWarmth(a.getFloat(attr, 0));
                } else if (attr == R.styleable.ImageFilterView_saturation) {
                    setSaturation(a.getFloat(attr, 0));
                } else if (attr == R.styleable.ImageFilterView_contrast) {
                    setContrast(a.getFloat(attr, 0));
                } else if (attr == R.styleable.ImageFilterView_round) {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                        setRound(a.getDimension(attr, 0));
                    }
                } else if (attr == R.styleable.ImageFilterView_roundPercent) {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                        setRoundPercent(a.getFloat(attr, 0));
                    }
                } else if (attr == R.styleable.ImageFilterView_overlay) {
                    setOverlay(a.getBoolean(attr, mOverlay));
                } else if (attr == R.styleable.ImageFilterView_imagePanX) {
                    setImagePanX(a.getFloat(attr, mPanX));
                } else if (attr == R.styleable.ImageFilterView_imagePanY) {
                    setImagePanY(a.getFloat(attr, mPanY));
                } else if (attr == R.styleable.ImageFilterView_imageRotate) {
                    setImageRotate(a.getFloat(attr, mRotate));
                } else if (attr == R.styleable.ImageFilterView_imageZoom) {
                    setImageZoom(a.getFloat(attr, mZoom));
                }
            }
            a.recycle();

            mDrawable = getDrawable();
            if (mAltDrawable != null && mDrawable != null) {

                mLayers[0] = mDrawable = getDrawable().mutate();
                mLayers[1] = mAltDrawable.mutate();

                mLayer = new LayerDrawable(mLayers);
                mLayer.getDrawable(1).setAlpha((int) (255 * mCrossfade));
                if (!mOverlay) {
                    mLayer.getDrawable(0).setAlpha((int) (255 * (1 - mCrossfade)));
                }
                super.setImageDrawable(mLayer);
            } else {
                mDrawable = getDrawable();
                if (mDrawable != null) {
                    mLayers[0] = mDrawable = mDrawable.mutate();
                }
            }
        }
    }
    // ======================== support for pan/zoom/rotate =================
    // defined as 0 = center of screen
    // if with < scree with,  1 is the right edge lines up with screen
    // if width > screen width, 1 is thee left edge lines up
    // -1 works similarly
    // zoom 1 = the image fits such that the view is filed

    private float mPanX = Float.NaN;
    private float mPanY = Float.NaN;
    private float mZoom = Float.NaN;
    private float mRotate = Float.NaN;

    /**
     * gts the pan from the center
     * pan of 1 the image is "all the way to the right"
     * if the images width is greater than the screen width,
     *  pan = 1 results in the left edge lining up
     * if the images width is less than the screen width,
     *  pan = 1 results in the right edges lining up
     * if image width == screen width it does nothing
     *
     * @return the pan in X. Where 0 is centered = Float. NaN if not set
     */
    public float getImagePanX() {
        return mPanX;
    }

    /**
     * gets the pan from the center
     * pan of 1 the image is "all the way to the bottom"
     * if the images width is greater than the screen height,
     *  pan = 1 results in the bottom edge lining up
     * if the images width is less than the screen height,
     *  pan = 1 results in the top edges lining up
     * if image height == screen height it does nothing
     *
     * @return pan in Y. Where 0 is centered
     */
    public float getImagePanY() {
        return mPanY;
    }

    /**
     * sets the zoom where 1 scales the image just enough to fill the view
     *
     * @return the zoom
     */
    public float getImageZoom() {
        return mZoom;
    }

    /**
     * gets the rotation
     *
     * @return the rotation in degrees
     */
    public float getImageRotate() {
        return mRotate;
    }

    /**
     * sets the pan from the center
     * pan of 1 the image is "all the way to the right"
     * if the images width is greater than the screen width,
     * pan = 1 results in the left edge lining up
     * if the images width is less than the screen width,
     * pan = 1 results in the right edges lining up
     * if image width == screen width it does nothing
     *
     * @param pan sets the pan in X. Where 0 is centered
     */
    public void setImagePanX(float pan) {
        mPanX = pan;
        updateViewMatrix();
    }

    /**
     * sets the pan from the center
     * pan of 1 the image is "all the way to the bottom"
     * if the images width is greater than the screen height,
     * pan = 1 results in the bottom edge lining up
     * if the images width is less than the screen height,
     * pan = 1 results in the top edges lining up
     * if image height == screen height it does nothing
     *
     * @param pan sets the pan in Y. Where 0 is centered
     */
    public void setImagePanY(float pan) {
        mPanY = pan;
        updateViewMatrix();
    }

    /**
     * sets the zoom where 1 scales the image just enough to fill the view
     *
     * @param zoom the zoom factor
     */
    public void setImageZoom(float zoom) {
        mZoom = zoom;
        updateViewMatrix();
    }

    /**
     * sets the rotation angle of the image in degrees
     *
     * @param rotation the rotation in degrees
     */
    public void setImageRotate(float rotation) {
        mRotate = rotation;
        updateViewMatrix();
    }

    @Override
    public void setImageDrawable(Drawable drawable) {
        if (mAltDrawable != null && drawable != null) {
            mDrawable = drawable.mutate();
            mLayers[0] = mDrawable;
            mLayers[1] = mAltDrawable;
            mLayer = new LayerDrawable(mLayers);
            super.setImageDrawable(mLayer);
            setCrossfade(mCrossfade);
        } else {
            super.setImageDrawable(drawable);
        }
    }

    @Override
    public void setImageResource(int resId) {
        if (mAltDrawable != null) {
            mDrawable = AppCompatResources.getDrawable(getContext(), resId).mutate();
            mLayers[0] = mDrawable;
            mLayers[1] = mAltDrawable;
            mLayer = new LayerDrawable(mLayers);
            super.setImageDrawable(mLayer);
            setCrossfade(mCrossfade);
        } else {
            super.setImageResource(resId);
        }
    }

    /**
     * Set the alternative image used to cross fade to.
     *
     * @param resId
     */
    public void setAltImageResource(int resId) {
        mAltDrawable = AppCompatResources.getDrawable(getContext(), resId).mutate();
        mLayers[0] = mDrawable;
        mLayers[1] = mAltDrawable;
        mLayer = new LayerDrawable(mLayers);
        super.setImageDrawable(mLayer);
        setCrossfade(mCrossfade);
    }

    private void updateViewMatrix() {
        if (Float.isNaN(mPanX)
                && Float.isNaN(mPanY)
                && Float.isNaN(mZoom)
                && Float.isNaN(mRotate)
        ) {
            setScaleType(ScaleType.FIT_CENTER);
            return;
        }
        setMatrix();
    }

    private void setMatrix() {
        if (Float.isNaN(mPanX)
                && Float.isNaN(mPanY)
                && Float.isNaN(mZoom)
                && Float.isNaN(mRotate)
        ) {
            return;
        }
        float panX = Float.isNaN(mPanX) ? 0 : mPanX;
        float panY = Float.isNaN(mPanY) ? 0 : mPanY;
        float zoom = Float.isNaN(mZoom) ? 1 : mZoom;
        float rota = Float.isNaN(mRotate) ? 0 : mRotate;
        Matrix imageMatrix = new Matrix();
        imageMatrix.reset();
        float iw = getDrawable().getIntrinsicWidth();
        float ih = getDrawable().getIntrinsicHeight();
        float sw = getWidth();
        float sh = getHeight();
        float scale = zoom * ((iw * sh < ih * sw) ? sw / iw : sh / ih);
        imageMatrix.postScale(scale, scale);
        float tx = 0.5f * (panX * (sw - scale * iw) + sw - (scale * iw));
        float ty = 0.5f * (panY * (sh - scale * ih) + sh - (scale * ih));
        imageMatrix.postTranslate(tx, ty);
        imageMatrix.postRotate(rota, sw / 2, sh / 2);
        setImageMatrix(imageMatrix);
        setScaleType(ScaleType.MATRIX);
    }
    // ================================================================

    /**
     * Defines whether the alt image will be faded in on top of the original image or if it will be
     * crossfaded with it. Default is true.
     *
     * @param overlay
     */
    private void setOverlay(boolean overlay) {
        mOverlay = overlay;
    }

    /**
     * sets the saturation of the image;
     * 0 = grayscale, 1 = original, 2 = hyper saturated
     *
     * @param saturation
     */

    public void setSaturation(float saturation) {
        mImageMatrix.mSaturation = saturation;
        mImageMatrix.updateMatrix(this);
    }

    /**
     * Returns the currently applied saturation
     *
     * @return 0 = grayscale, 1 = original, 2 = hyper saturated
     */
    public float getSaturation() {
        return mImageMatrix.mSaturation;
    }

    /**
     * This sets the contrast. 1 = unchanged, 0 = gray, 2 = high contrast
     *
     * @param contrast
     */
    public void setContrast(float contrast) {
        mImageMatrix.mContrast = contrast;
        mImageMatrix.updateMatrix(this);
    }

    /**
     * Returns the currently applied contrast
     *
     * @return 1 = unchanged, 0 = gray, 2 = high contrast
     */
    public float getContrast() {
        return mImageMatrix.mContrast;
    }

    /**
     * This makes the apparent color temperature of the image warmer or colder.
     *
     * @param warmth 1 is neutral, 2 is warm, .5 is cold
     */
    public void setWarmth(float warmth) {
        mImageMatrix.mWarmth = warmth;
        mImageMatrix.updateMatrix(this);
    }

    /**
     * Returns the currently applied warmth
     *
     * @return warmth 1 is neutral, 2 is warm, .5 is cold
     */
    public float getWarmth() {
        return mImageMatrix.mWarmth;
    }

    /**
     * Set the current mix between the two images that can be set on this view.
     *
     * @param crossfade a number from 0 to 1
     */
    public void setCrossfade(float crossfade) {

        mCrossfade = crossfade;
        if (mLayers != null) {
            if (!mOverlay) {
                mLayer.getDrawable(0).setAlpha((int) (255 * (1 - mCrossfade)));
            }
            mLayer.getDrawable(1).setAlpha((int) (255 * mCrossfade));
            super.setImageDrawable(mLayer);
        }
    }

    /**
     * Returns the currently applied crossfade.
     *
     * @return a number from 0 to 1
     */
    public float getCrossfade() {
        return mCrossfade;
    }

    /**
     * sets the brightness of the image;
     * 0 = black, 1 = original, 2 = twice as bright
     *
     * @param brightness
     */

    public void setBrightness(float brightness) {
        mImageMatrix.mBrightness = brightness;
        mImageMatrix.updateMatrix(this);
    }

    /**
     * Set the corner radius of curvature  as a fraction of the smaller side.
     * For squares 1 will result in a circle
     *
     * @param round the radius of curvature as a fraction of the smaller width
     */
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    public void setRoundPercent(float round) {
        boolean change = (mRoundPercent != round);
        mRoundPercent = round;
        if (mRoundPercent != 0.0f) {
            if (mPath == null) {
                mPath = new Path();
            }
            if (mRect == null) {
                mRect = new RectF();
            }
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                if (mViewOutlineProvider == null) {
                    mViewOutlineProvider = new ViewOutlineProvider() {
                        @Override
                        public void getOutline(View view, Outline outline) {
                            int w = getWidth();
                            int h = getHeight();
                            float r = Math.min(w, h) * mRoundPercent / 2;
                            outline.setRoundRect(0, 0, w, h, r);
                        }
                    };
                    setOutlineProvider(mViewOutlineProvider);
                }
                setClipToOutline(true);
            }
            int w = getWidth();
            int h = getHeight();
            float r = Math.min(w, h) * mRoundPercent / 2;
            mRect.set(0, 0, w, h);
            mPath.reset();
            mPath.addRoundRect(mRect, r, r, Path.Direction.CW);
        } else {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                setClipToOutline(false);
            }
        }
        if (change) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                invalidateOutline();
            }
        }

    }

    /**
     * Set the corner radius of curvature
     *
     * @param round the radius of curvature  NaN = default meaning roundPercent in effect
     */
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    public void setRound(float round) {
        if (Float.isNaN(round)) {
            mRound = round;
            float tmp = mRoundPercent;
            mRoundPercent = -1;
            setRoundPercent(tmp); // force eval of roundPercent
            return;
        }
        boolean change = (mRound != round);
        mRound = round;

        if (mRound != 0.0f) {
            if (mPath == null) {
                mPath = new Path();
            }
            if (mRect == null) {
                mRect = new RectF();
            }
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                if (mViewOutlineProvider == null) {
                    mViewOutlineProvider = new ViewOutlineProvider() {
                        @Override
                        public void getOutline(View view, Outline outline) {
                            int w = getWidth();
                            int h = getHeight();
                            outline.setRoundRect(0, 0, w, h, mRound);
                        }
                    };
                    setOutlineProvider(mViewOutlineProvider);
                }
                setClipToOutline(true);

            }
            int w = getWidth();
            int h = getHeight();
            mRect.set(0, 0, w, h);
            mPath.reset();
            mPath.addRoundRect(mRect, mRound, mRound, Path.Direction.CW);
        } else {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                setClipToOutline(false);
            }
        }
        if (change) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                invalidateOutline();
            }
        }

    }

    /**
     * Get the fractional corner radius of curvature.
     *
     * @return Fractional radius of curvature with respect to smallest size
     */
    public float getRoundPercent() {
        return mRoundPercent;
    }

    /**
     * Get the corner radius of curvature NaN = RoundPercent in effect.
     *
     * @return Radius of curvature
     */
    public float getRound() {
        return mRound;
    }

    @Override
    public void draw(@NonNull Canvas canvas) {
        boolean clip = false;
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            if (mRound != 0.0f && mPath != null) {
                clip = true;
                canvas.save();
                canvas.clipPath(mPath);
            }
        }
        super.draw(canvas);
        if (clip) {
            canvas.restore();
        }
    }

    @Override
    public void layout(int l, int t, int r, int b) {
        super.layout(l, t, r, b);
        setMatrix();
    }
}