Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
ForegroundLinearLayout
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ForegroundLinearLayout">
<attr name="android:foreground" />
<attr name="android:foregroundInsidePadding" />
<attr name="android:foregroundGravity" />
</declare-styleable>
</resources>
/*
* Copyright (C) 2006 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 your.package;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.widget.LinearLayout;
import your.package.R;
public class ForegroundLinearLayout extends LinearLayout {
private Drawable mForeground;
private final Rect mSelfBounds = new Rect();
private final Rect mOverlayBounds = new Rect();
private int mForegroundGravity = Gravity.FILL;
protected boolean mForegroundInPadding = true;
boolean mForegroundBoundsChanged = false;
public ForegroundLinearLayout(Context context) {
super(context);
}
public ForegroundLinearLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ForegroundLinearLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ForegroundLinearLayout,
defStyle, 0);
mForegroundGravity = a.getInt(
R.styleable.ForegroundLinearLayout_android_foregroundGravity, mForegroundGravity);
final Drawable d = a.getDrawable(R.styleable.ForegroundLinearLayout_android_foreground);
if (d != null) {
setForeground(d);
}
mForegroundInPadding = a.getBoolean(
R.styleable.ForegroundLinearLayout_android_foregroundInsidePadding, true);
a.recycle();
}
/**
* Describes how the foreground is positioned.
*
* @return foreground gravity.
*
* @see #setForegroundGravity(int)
*/
public int getForegroundGravity() {
return mForegroundGravity;
}
/**
* Describes how the foreground is positioned. Defaults to START and TOP.
*
* @param foregroundGravity See {@link android.view.Gravity}
*
* @see #getForegroundGravity()
*/
public void setForegroundGravity(int foregroundGravity) {
if (mForegroundGravity != foregroundGravity) {
if ((foregroundGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
foregroundGravity |= Gravity.START;
}
if ((foregroundGravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
foregroundGravity |= Gravity.TOP;
}
mForegroundGravity = foregroundGravity;
if (mForegroundGravity == Gravity.FILL && mForeground != null) {
Rect padding = new Rect();
mForeground.getPadding(padding);
}
requestLayout();
}
}
@Override
protected boolean verifyDrawable(Drawable who) {
return super.verifyDrawable(who) || (who == mForeground);
}
@Override
public void jumpDrawablesToCurrentState() {
super.jumpDrawablesToCurrentState();
if (mForeground != null) mForeground.jumpToCurrentState();
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
if (mForeground != null && mForeground.isStateful()) {
mForeground.setState(getDrawableState());
}
}
/**
* Supply a Drawable that is to be rendered on top of all of the child
* views in the frame layout. Any padding in the Drawable will be taken
* into account by ensuring that the children are inset to be placed
* inside of the padding area.
*
* @param drawable The Drawable to be drawn on top of the children.
*/
public void setForeground(Drawable drawable) {
if (mForeground != drawable) {
if (mForeground != null) {
mForeground.setCallback(null);
unscheduleDrawable(mForeground);
}
mForeground = drawable;
if (drawable != null) {
setWillNotDraw(false);
drawable.setCallback(this);
if (drawable.isStateful()) {
drawable.setState(getDrawableState());
}
if (mForegroundGravity == Gravity.FILL) {
Rect padding = new Rect();
drawable.getPadding(padding);
}
} else {
setWillNotDraw(true);
}
requestLayout();
invalidate();
}
}
/**
* Returns the drawable used as the foreground of this FrameLayout. The
* foreground drawable, if non-null, is always drawn on top of the children.
*
* @return A Drawable or null if no foreground was set.
*/
public Drawable getForeground() {
return mForeground;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mForegroundBoundsChanged = changed;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mForegroundBoundsChanged = true;
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mForeground != null) {
final Drawable foreground = mForeground;
if (mForegroundBoundsChanged) {
mForegroundBoundsChanged = false;
final Rect selfBounds = mSelfBounds;
final Rect overlayBounds = mOverlayBounds;
final int w = getRight() - getLeft();
final int h = getBottom() - getTop();
if (mForegroundInPadding) {
selfBounds.set(0, 0, w, h);
} else {
selfBounds.set(getPaddingLeft(), getPaddingTop(),
w - getPaddingRight(), h - getPaddingBottom());
}
Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(),
foreground.getIntrinsicHeight(), selfBounds, overlayBounds);
foreground.setBounds(overlayBounds);
}
foreground.draw(canvas);
}
}
}
@dvdh

This comment has been minimized.

Copy link

commented Mar 6, 2014

Hey what's the purpose of calling getPadding on lines 104 and 155?

@suau

This comment has been minimized.

Copy link

commented Mar 17, 2014

@dvdh it's ported from FrameLayout, which has the methods:

int getPaddingLeftWithForeground()
int getPadding****WithForeground() etc.

it has no use here, you can remove it if you don't plan to add these methods to your custom view.

@suau

This comment has been minimized.

Copy link

commented Mar 17, 2014

Note there is a bug, which prevents this to work in some cases, e.g. in a DrawerLayout.
This happens when onLayout is called with changed = false before draw(Canvas canvas) was called. Therefore setBounds never gets called on the foregroundDrawable.

Here is a simple fix (i think there are no pull requests for gists)
replace line 178:
mForegroundBoundsChanged = changed;

if (changed) {
   mForegroundBoundsChanged = true;
}
@dvdh

This comment has been minimized.

Copy link

commented Mar 24, 2014

Thanks suau, that cleared it up!

@gabrielemariotti

This comment has been minimized.

Copy link

commented Nov 9, 2014

With Android 5.0 the ripple drawable will animate in the foreground, but the ripple will always begin at the center of the view.

A solution can be:

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    @Override
    public boolean onTouchEvent(MotionEvent e) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            if (e.getActionMasked() == MotionEvent.ACTION_DOWN) {
                if (mForeground != null)
                    mForeground.setHotspot(e.getX(), e.getY());
            }
        }
        return super.onTouchEvent(e);
    }
@AndroidDeveloperLB

This comment has been minimized.

Copy link

commented Jan 8, 2015

@gabrielemariotti Thank you for this solution.

@NightlyNexus

This comment has been minimized.

Copy link

commented Jan 18, 2015

For travelers, see the better solution by @gabrielemariotti in commit f5b5e4d6c87a247aff5b477cea13859338f813fa.

https://github.com/gabrielemariotti/cardslib/blob/master/library-core/src/main/java/it/gmariotti/cardslib/library/view/ForegroundLinearLayout.java

He has since corrected the logic to drawableHotspotChanged(float x, float y).

@Pkmmte

This comment has been minimized.

Copy link

commented Jun 8, 2015

Using a RippleDrawable as the background causes it to bleed out of the layout bounds. Setting it to the foreground works fine, however.

Anyone know of a fix for this?

@dluco

This comment has been minimized.

Copy link

commented Dec 17, 2015

The simplest fix would probably be to give the ripple a mask so that it's clipped by the view's bounds.

@altaf933

This comment has been minimized.

Copy link

commented Sep 2, 2016

minSdkVersion 17 it's getting crash and the log cat error "Caused by: java.lang.NoSuchMethodError: android.widget.LinearLayout.".

It might be not support for lower version ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.