Skip to content

Instantly share code, notes, and snippets.

@zhanghai
Last active December 1, 2015 09:25
Show Gist options
  • Save zhanghai/9067a68d6e836389933c to your computer and use it in GitHub Desktop.
Save zhanghai/9067a68d6e836389933c to your computer and use it in GitHub Desktop.
RelativeLayout with backward-compatible foreground support.
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2015 Zhang Hai
~
~ 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.
-->
<resources>
<declare-styleable name="ForegroundRelativeLayout">
<attr name="android:foreground" />
<attr name="android:foregroundGravity" />
<attr name="android:foregroundInsidePadding" />
</declare-styleable>
</resources>
/*
* Copyright (c) 2015 Zhang Hai
*
* 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 com.example.yourapplication;
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.Drawable;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.Gravity;
import android.widget.RelativeLayout;
import me.zhanghai.android.douya.R;
public class ForegroundRelativeLayout extends RelativeLayout {
private boolean mHasNativeBackground;
private Drawable mForeground;
private final Rect mSelfBounds = new Rect();
private final Rect mOverlayBounds = new Rect();
private int mForegroundGravity = Gravity.FILL;
private boolean mForegroundInPadding = true;
private boolean mForegroundBoundsChanged = false;
public ForegroundRelativeLayout(Context context) {
super(context);
init(context, null, 0, 0);
}
public ForegroundRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0, 0);
}
public ForegroundRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr, 0);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ForegroundRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs, defStyleAttr, defStyleRes);
}
private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
mHasNativeBackground = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.M;
if (mHasNativeBackground) {
return;
}
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ForegroundRelativeLayout,
defStyleAttr, defStyleRes);
mForegroundGravity = a.getInt(
R.styleable.ForegroundRelativeLayout_android_foregroundGravity, mForegroundGravity);
Drawable foreground = a.getDrawable(
R.styleable.ForegroundRelativeLayout_android_foreground);
if (foreground != null) {
setForeground(foreground);
}
mForegroundInPadding = a.getBoolean(
R.styleable.ForegroundRelativeLayout_android_foregroundInsidePadding, true);
a.recycle();
}
/**
* {@inheritDoc}
*/
public int getForegroundGravity() {
if (mHasNativeBackground) {
return super.getForegroundGravity();
}
return mForegroundGravity;
}
/**
* {@inheritDoc}
*/
@Override
public void setForegroundGravity(int foregroundGravity) {
if (mHasNativeBackground) {
super.setForegroundGravity(foregroundGravity);
return;
}
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;
requestLayout();
}
}
/**
* {@inheritDoc}
*/
@Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
if (mHasNativeBackground) {
return;
}
if (mForeground != null) {
mForeground.setVisible(visibility == VISIBLE, false);
}
}
/**
* {@inheritDoc}
*/
@Override
protected boolean verifyDrawable(Drawable who) {
if (mHasNativeBackground) {
return super.verifyDrawable(who);
}
return super.verifyDrawable(who) || (who == mForeground);
}
/**
* {@inheritDoc}
*/
@Override
public void jumpDrawablesToCurrentState() {
super.jumpDrawablesToCurrentState();
if (mHasNativeBackground) {
return;
}
if (mForeground != null) {
mForeground.jumpToCurrentState();
}
}
/**
* {@inheritDoc}
*/
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
if (mHasNativeBackground) {
return;
}
if (mForeground != null && mForeground.isStateful()) {
mForeground.setState(getDrawableState());
}
}
/**
* {@inheritDoc}
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void drawableHotspotChanged(float x, float y) {
super.drawableHotspotChanged(x, y);
if (mHasNativeBackground) {
return;
}
if (mForeground != null) {
mForeground.setHotspot(x, y);
}
}
/**
* {@inheritDoc}
*/
@Override
public void setForeground(Drawable foreground) {
if (mHasNativeBackground) {
super.setForeground(foreground);
return;
}
if (mForeground != foreground) {
if (mForeground != null) {
mForeground.setCallback(null);
unscheduleDrawable(mForeground);
}
mForeground = foreground;
if (foreground != null) {
setWillNotDraw(false);
foreground.setCallback(this);
// Drawable.setLayoutDirection() is hidden on previous platforms.
//foreground.setLayoutDirection(ViewCompat.getLayoutDirection(this));
if (foreground.isStateful()) {
foreground.setState(getDrawableState());
}
} else {
setWillNotDraw(true);
}
requestLayout();
invalidate();
}
}
/**
* {@inheritDoc}
*/
@Override
public Drawable getForeground() {
if (mHasNativeBackground) {
return super.getForeground();
}
return mForeground;
}
/**
* {@inheritDoc}
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mHasNativeBackground) {
return;
}
mForegroundBoundsChanged = true;
}
/**
* {@inheritDoc}
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (mHasNativeBackground) {
return;
}
mForegroundBoundsChanged = true;
}
/**
* {@inheritDoc}
*/
@Override
public void draw(@NonNull Canvas canvas) {
super.draw(canvas);
if (mHasNativeBackground) {
return;
}
if (mForeground != null) {
final Drawable foreground = mForeground;
if (mForegroundBoundsChanged) {
mForegroundBoundsChanged = false;
Rect selfBounds = mSelfBounds;
Rect overlayBounds = mOverlayBounds;
int w = getRight() - getLeft();
int h = getBottom() - getTop();
if (mForegroundInPadding) {
selfBounds.set(0, 0, w, h);
} else {
selfBounds.set(getPaddingLeft(), getPaddingTop(), w - getPaddingRight(),
h - getPaddingBottom());
}
int layoutDirection = ViewCompat.getLayoutDirection(this);
GravityCompat.apply(mForegroundGravity, foreground.getIntrinsicWidth(),
foreground.getIntrinsicHeight(), selfBounds, overlayBounds,
layoutDirection);
foreground.setBounds(overlayBounds);
}
foreground.draw(canvas);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment