-
Star
(202)
You must be signed in to star a gist -
Fork
(41)
You must be signed in to fork a gist
-
-
Save chrisbanes/9091754 to your computer and use it in GitHub Desktop.
| <?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); | |
| } | |
| } | |
| } |
Thanks suau, that cleared it up!
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);
}@gabrielemariotti Thank you for this solution.
For travelers, see the better solution by @gabrielemariotti in commit f5b5e4d6c87a247aff5b477cea13859338f813fa.
He has since corrected the logic to drawableHotspotChanged(float x, float y).
I had a problem (see http://stackoverflow.com/a/28015226/1696171) in AbsListViews and RecyclerViews.
https://github.com/NightlyNexus/cardslib/blob/master/library-core/src/main/java/it/gmariotti/cardslib/library/view/ForegroundLinearLayout.java
fixes the issue.
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?
The simplest fix would probably be to give the ripple a mask so that it's clipped by the view's bounds.
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 ?
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 = falsebeforedraw(Canvas canvas)was called. ThereforesetBoundsnever gets called on the foregroundDrawable.Here is a simple fix (i think there are no pull requests for gists)
replace line 178:
mForegroundBoundsChanged = changed;