de.mrapp.android.util.view.ScrimInsetsLayout.java Source code

Java tutorial

Introduction

Here is the source code for de.mrapp.android.util.view.ScrimInsetsLayout.java

Source

/*
 * Copyright 2015 - 2017 Michael Rapp
 *
 * 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 de.mrapp.android.util.view;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.AttrRes;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StyleRes;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.widget.FrameLayout;

import de.mrapp.android.util.R;

/**
 * A layout, which allows to show a drawable in the insets, which are passed to its {@link
 * #fitSystemWindows(Rect)} method. This area usually corresponds to the device's status or
 * navigation bar.
 *
 * @author Michael Rapp
 * @since 1.4.6
 */
public class ScrimInsetsLayout extends FrameLayout {

    /**
     * Defines the interface, a class, which should be notified, when the insets, which are passed
     * to the layout's {@link #fitSystemWindows(Rect)} method, have been changed, must implement.
     */
    public interface Callback {

        /**
         * The method, which is invoked, when the insets have been changed.
         *
         * @param insets
         *         The new insets as an instance of the class {@link Rect}. The insets may not be
         *         null
         */
        void onInsetsChanged(@NonNull Rect insets);

    }

    /**
     * The drawable, which is shown in the layout's insets.
     */
    private Drawable insetDrawable;

    /**
     * The layout's current insets.
     */
    private Rect insets;

    /**
     * The callback, which is notified, when the layout's insets have been changed.
     */
    private Callback callback;

    /**
     * Initializes the view.
     *
     * @param attributeSet
     *         The attribute set, the attributes should be obtained from, as an instance of the type
     *         {@link AttributeSet}, or null, if no attributes should be obtained
     * @param defaultStyle
     *         The default style to apply to this view. If 0, no style will be applied (beyond what
     *         is included in the theme). This may either be an attribute resource, whose value will
     *         be retrieved from the current theme, or an explicit style resource
     * @param defaultStyleResource
     *         A resource identifier of a style resource that supplies default values for the view,
     *         used only if the default style is 0 or can not be found in the theme. Can be 0 to not
     *         look for defaults
     */
    private void initialize(@Nullable final AttributeSet attributeSet, @AttrRes final int defaultStyle,
            @StyleRes final int defaultStyleResource) {
        this.insets = null;
        this.callback = null;
        setWillNotDraw(true);
        obtainStyledAttributes(attributeSet, defaultStyle, defaultStyleResource);
    }

    /**
     * Obtains the view's attributes from a specific attribute set.
     *
     * @param attributeSet
     *         The attribute set, the view's attributes should be obtained from, as an instance of
     *         the type {@link AttributeSet} or null, if no attributes should be obtained
     * @param defaultStyle
     *         The default style to apply to this view. If 0, no style will be applied (beyond what
     *         is included in the theme). This may either be an attribute resource, whose value will
     *         be retrieved from the current theme, or an explicit style resource
     * @param defaultStyleResource
     *         A resource identifier of a style resource that supplies default values for the view,
     *         used only if the default style is 0 or can not be found in the theme. Can be 0 to not
     *         look for defaults
     */
    private void obtainStyledAttributes(@Nullable final AttributeSet attributeSet, @AttrRes final int defaultStyle,
            @StyleRes final int defaultStyleResource) {
        if (attributeSet != null) {
            TypedArray typedArray = getContext().obtainStyledAttributes(attributeSet, R.styleable.ScrimInsetsLayout,
                    defaultStyle, defaultStyleResource);

            try {
                obtainInsetForeground(typedArray);
            } finally {
                typedArray.recycle();
            }
        }
    }

    /**
     * Obtains the drawable, which should be shown in the layout's insets, from a specific typed
     * array.
     *
     * @param typedArray
     *         The typed array, the drawable should be obtained from, as an instance of the class
     *         {@link TypedArray}. The typed array may not be null
     */
    private void obtainInsetForeground(@NonNull final TypedArray typedArray) {
        int color = typedArray.getColor(R.styleable.ScrimInsetsLayout_insetDrawable, -1);

        if (color == -1) {
            Drawable drawable = typedArray.getDrawable(R.styleable.ScrimInsetsLayout_insetDrawable);

            if (drawable != null) {
                setInsetDrawable(drawable);
            } else {
                setInsetColor(ContextCompat.getColor(getContext(),
                        R.color.scrim_insets_layout_insets_drawable_default_value));
            }
        } else {
            setInsetColor(color);
        }
    }

    /**
     * Notifies the callback, that the layout's insets have been changed.
     *
     * @param insets
     *         The new insets as an instance of the class {@link Rect}
     */
    private void notifyOnInsetsChanged(@NonNull final Rect insets) {
        if (callback != null) {
            callback.onInsetsChanged(insets);
        }
    }

    /**
     * Creates a new layout, which allows to show a drawable in the insets, which are passed to its
     * {@link #fitSystemWindows(Rect)} method.
     *
     * @param context
     *         The context, which should be used by the view, as an instance of the class {@link
     *         Context}. The context may not be null
     */
    public ScrimInsetsLayout(@NonNull final Context context) {
        this(context, null);
    }

    /**
     * Creates a new layout, which allows to show a drawable in the insets, which are passed to its
     * {@link #fitSystemWindows(Rect)} method.
     *
     * @param context
     *         The context, which should be used by the view, as an instance of the class {@link
     *         Context}. The context may not be null
     * @param attributeSet
     *         The attribute set, the view's attributes should be obtained from, as an instance of
     *         the type {@link AttributeSet} or null, if no attributes should be obtained
     */
    public ScrimInsetsLayout(@NonNull final Context context, @Nullable final AttributeSet attributeSet) {
        super(context, attributeSet);
        initialize(attributeSet, 0, 0);
    }

    /**
     * Creates a new layout, which allows to show a drawable in the insets, which are passed to its
     * {@link #fitSystemWindows(Rect)} method.
     *
     * @param context
     *         The context, which should be used by the view, as an instance of the class {@link
     *         Context}. The context may not be null
     * @param attributeSet
     *         The attribute set, the view's attributes should be obtained from, as an instance of
     *         the type {@link AttributeSet} or null, if no attributes should be obtained
     * @param defaultStyle
     *         The default style to apply to this view. If 0, no style will be applied (beyond what
     *         is included in the theme). This may either be an attribute resource, whose value will
     *         be retrieved from the current theme, or an explicit style resource
     */
    public ScrimInsetsLayout(@NonNull final Context context, @Nullable final AttributeSet attributeSet,
            @AttrRes final int defaultStyle) {
        super(context, attributeSet, defaultStyle);
        initialize(attributeSet, defaultStyle, 0);
    }

    /**
     * Creates a new layout, which allows to show a drawable in the insets, which are passed to its
     * {@link #fitSystemWindows(Rect)} method.
     *
     * @param context
     *         The context, which should be used by the view, as an instance of the class {@link
     *         Context}. The context may not be null
     * @param attributeSet
     *         The attribute set, the view's attributes should be obtained from, as an instance of
     *         the type {@link AttributeSet} or null, if no attributes should be obtained
     * @param defaultStyle
     *         The default style to apply to this view. If 0, no style will be applied (beyond what
     *         is included in the theme). This may either be an attribute resource, whose value will
     *         be retrieved from the current theme, or an explicit style resource
     * @param defaultStyleResource
     *         A resource identifier of a style resource that supplies default values for the view,
     *         used only if the default style is 0 or can not be found in the theme. Can be 0 to not
     *         look for defaults
     */
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public ScrimInsetsLayout(@NonNull final Context context, @Nullable final AttributeSet attributeSet,
            @AttrRes final int defaultStyle, @StyleRes final int defaultStyleResource) {
        super(context, attributeSet, defaultStyle);
        initialize(attributeSet, defaultStyle, defaultStyleResource);
    }

    /**
     * Sets the callback, which should be notified, when the layout's insets have been changed.
     *
     * @param callback
     *         The callback, which should be set, as an instance of the type {@link Callback}, or
     *         null, if no callback should be notified
     */
    public final void setCallback(@Nullable final Callback callback) {
        this.callback = callback;
    }

    /**
     * Returns the drawable, which is shown in the layout's insets.
     *
     * @return The drawable, which is shown in the layout's insets, as an instance of the class
     * {@link Drawable}, or null, if no drawable is shown
     */
    public final Drawable getInsetDrawable() {
        return insetDrawable;
    }

    /**
     * Sets the drawable, which should be shown in the layout's insets.
     *
     * @param insetForeground
     *         The drawable, which should be set, as an instance of the class {@link Drawable}, or
     *         null, if no drawable should be set
     */
    public final void setInsetDrawable(@Nullable final Drawable insetForeground) {
        this.insetDrawable = insetForeground;
        invalidate();
    }

    /**
     * Sets the color, which should be shown in the layout's insets.
     *
     * @param color
     *         The color, which should be set, as an {@link Integer} value
     */
    public final void setInsetColor(@ColorInt final int color) {
        setInsetDrawable(new ColorDrawable(color));
    }

    /**
     * Returns the layout's current insets.
     *
     * @return The layout's current insets or null, if the layout has not been shown yet
     */
    public final Rect getInsets() {
        return insets;
    }

    @Override
    public final void draw(final Canvas canvas) {
        super.draw(canvas);
        int width = getWidth();
        int height = getHeight();

        if (insets != null && insetDrawable != null) {
            int saveCount = canvas.save();
            canvas.translate(getScrollX(), getScrollY());
            insetDrawable.setBounds(0, 0, width, insets.top);
            insetDrawable.draw(canvas);
            insetDrawable.setBounds(0, height - insets.bottom, width, height);
            insetDrawable.draw(canvas);
            insetDrawable.setBounds(0, insets.top, insets.left, height - insets.bottom);
            insetDrawable.draw(canvas);
            insetDrawable.setBounds(width - insets.right, insets.top, width, height - insets.bottom);
            insetDrawable.draw(canvas);
            canvas.restoreToCount(saveCount);
        }
    }

    @SuppressWarnings("deprecation")
    @Override
    protected boolean fitSystemWindows(final Rect insets) {
        this.insets = new Rect(insets);
        setWillNotDraw(insetDrawable == null);
        ViewCompat.postInvalidateOnAnimation(this);
        notifyOnInsetsChanged(insets);
        return true;
    }

    @Override
    protected final void onAttachedToWindow() {
        super.onAttachedToWindow();

        if (insetDrawable != null) {
            insetDrawable.setCallback(this);
        }
    }

    @Override
    protected final void onDetachedFromWindow() {
        super.onDetachedFromWindow();

        if (insetDrawable != null) {
            insetDrawable.setCallback(null);
        }
    }

}