Last active
July 21, 2017 16:37
-
-
Save fullkomnun/9df0d04d91e5be648448f87570d887df to your computer and use it in GitHub Desktop.
StarwarzCollapsingToolbarLayout.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:app="http://schemas.android.com/apk/res-auto" | |
xmlns:tools="http://schemas.android.com/tools" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
android:fitsSystemWindows="true"> | |
<android.support.design.widget.AppBarLayout | |
android:layout_width="match_parent" | |
android:layout_height="225dp" | |
android:theme="@style/AppTheme.AppBarOverlay" | |
android:fitsSystemWindows="true"> | |
<android.support.design.widget.StarwarzCollapsingToolbarLayout | |
android:id="@+id/collapsible_toolbar" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
android:theme="@style/AppTheme.AppBarOverlay" | |
app:layout_scrollFlags="scroll|exitUntilCollapsed" | |
app:titleEnabled="false" | |
app:title="" | |
android:fitsSystemWindows="true" | |
app:statusBarScrim="#00bdf2" | |
app:scrimVisibleHeightTrigger="300dp" | |
android:background="@drawable/appbar_background"> | |
<LinearLayout android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
android:layout_marginTop="?attr/actionBarSize" | |
android:orientation="vertical" | |
android:layout_gravity="bottom"> | |
<TextView android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:layout_marginTop="30dp" | |
android:text="hello" | |
android:textSize="24sp" | |
android:layout_gravity="center_horizontal"/> | |
<TextView android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:text="from the" | |
android:textSize="24sp" | |
android:textColor="@color/colorAccent" | |
android:layout_marginTop="10dp" | |
android:layout_gravity="center_horizontal"/> | |
<TextView android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:text="other side" | |
android:textSize="24sp" | |
android:layout_marginTop="10dp" | |
android:layout_gravity="center_horizontal"/> | |
</LinearLayout> | |
<android.support.v7.widget.Toolbar | |
android:id="@+id/toolbar" | |
android:layout_width="match_parent" | |
android:layout_height="?attr/actionBarSize" | |
app:popupTheme="@style/AppTheme.PopupOverlay" | |
app:layout_collapseMode="pin" | |
app:title="" | |
android:title="" | |
android:layout_gravity="top"/> | |
</android.support.design.widget.StarwarzCollapsingToolbarLayout> | |
</android.support.design.widget.AppBarLayout> | |
<android.support.v4.widget.NestedScrollView | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
app:layout_behavior="@string/appbar_scrolling_view_behavior"> | |
<TextView | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:layout_marginLeft="@dimen/activity_horizontal_margin" | |
android:layout_marginRight="@dimen/activity_horizontal_margin" | |
android:layout_marginTop="@dimen/activity_vertical_margin" | |
android:layout_marginBottom="@dimen/activity_vertical_margin" | |
android:text="@string/loremipsum" /> | |
</android.support.v4.widget.NestedScrollView> | |
</android.support.design.widget.CoordinatorLayout> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package android.graphics.drawable; | |
/* | |
* Copyright (C) 2008 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. | |
*/ | |
import android.annotation.TargetApi; | |
import android.content.res.Resources; | |
import android.graphics.Canvas; | |
import android.graphics.ColorFilter; | |
import android.graphics.Outline; | |
import android.graphics.Rect; | |
import android.os.Build; | |
/** | |
* A Drawable that insets another Drawable by a specified distance. | |
* This is used when a View needs a background that is smaller than | |
* the View's actual bounds. | |
* A modified copy of the original Android InsetDrawable. The original had no method to change the insets, | |
* only the bounds. The problem with the bounds would be that the click region would be horrible. | |
* Original source: http://grepcode.com/file_/repository.grepcode.com/java/ext/com.google.android/android/5.0.2_r1/android/graphics/drawable/InsetDrawable.java/?v=source | |
*/ | |
public class AnimatableInsetDrawable extends Drawable implements Drawable.Callback { | |
private int _drawOffset = 0; | |
private final Rect mTmpRect = new Rect(); | |
private InsetState mInsetState; | |
private boolean mMutated; | |
/*package*/ AnimatableInsetDrawable() { | |
this(null, null); | |
} | |
public AnimatableInsetDrawable(Drawable drawable, int inset) { | |
this(drawable, inset, inset, inset, inset); | |
} | |
public AnimatableInsetDrawable(Drawable drawable, int insetLeft, int insetTop, | |
int insetRight, int insetBottom) { | |
this(null, null); | |
mInsetState.mDrawable = drawable; | |
mInsetState.mInsetLeft = insetLeft; | |
mInsetState.mInsetTop = insetTop; | |
mInsetState.mInsetRight = insetRight; | |
mInsetState.mInsetBottom = insetBottom; | |
if (drawable != null) { | |
drawable.setCallback(this); | |
} | |
} | |
/** | |
* Changes the insets of the drawable to make it animatable. | |
* | |
* @param insetLeft The left inset. | |
* @param insetTop The top inset. | |
* @param insetRight The right inset. | |
* @param insetBottom The bottom inset. | |
*/ | |
public void setInsets(int insetLeft, int insetTop, int insetRight, int insetBottom) { | |
mInsetState.mInsetLeft = insetLeft; | |
mInsetState.mInsetTop = insetTop; | |
mInsetState.mInsetRight = insetRight; | |
mInsetState.mInsetBottom = insetBottom; | |
onBoundsChange(getBounds()); | |
} | |
// TODO add getInsets() | |
@Override | |
public boolean canApplyTheme() { | |
return mInsetState != null && mInsetState.mThemeAttrs != null; | |
} | |
@Override | |
public void invalidateDrawable(Drawable who) { | |
final Callback callback = getCallback(); | |
if (callback != null) { | |
callback.invalidateDrawable(this); | |
} | |
} | |
@Override | |
public void scheduleDrawable(Drawable who, Runnable what, long when) { | |
final Callback callback = getCallback(); | |
if (callback != null) { | |
callback.scheduleDrawable(this, what, when); | |
} | |
} | |
@Override | |
public void unscheduleDrawable(Drawable who, Runnable what) { | |
final Callback callback = getCallback(); | |
if (callback != null) { | |
callback.unscheduleDrawable(this, what); | |
} | |
} | |
@Override | |
public void draw(Canvas canvas) { | |
canvas.save(); | |
canvas.translate(_drawOffset, 0); | |
mInsetState.mDrawable.draw(canvas); | |
canvas.restore(); | |
} | |
@Override | |
public int getChangingConfigurations() { | |
return super.getChangingConfigurations() | |
| mInsetState.mChangingConfigurations | |
| mInsetState.mDrawable.getChangingConfigurations(); | |
} | |
@Override | |
public boolean getPadding(Rect padding) { | |
boolean pad = mInsetState.mDrawable.getPadding(padding); | |
padding.left += mInsetState.mInsetLeft; | |
padding.right += mInsetState.mInsetRight; | |
padding.top += mInsetState.mInsetTop; | |
padding.bottom += mInsetState.mInsetBottom; | |
return pad || (mInsetState.mInsetLeft | mInsetState.mInsetRight | | |
mInsetState.mInsetTop | mInsetState.mInsetBottom) != 0; | |
} | |
@TargetApi(Build.VERSION_CODES.LOLLIPOP) | |
@Override | |
public void setHotspot(float x, float y) { | |
mInsetState.mDrawable.setHotspot(x, y); | |
} | |
@TargetApi(Build.VERSION_CODES.LOLLIPOP) | |
@Override | |
public void setHotspotBounds(int left, int top, int right, int bottom) { | |
mInsetState.mDrawable.setHotspotBounds(left, top, right, bottom); | |
} | |
@Override | |
public boolean setVisible(boolean visible, boolean restart) { | |
mInsetState.mDrawable.setVisible(visible, restart); | |
return super.setVisible(visible, restart); | |
} | |
@Override | |
public void setAlpha(int alpha) { | |
mInsetState.mDrawable.setAlpha(alpha); | |
} | |
@TargetApi(Build.VERSION_CODES.KITKAT) | |
@Override | |
public int getAlpha() { | |
return mInsetState.mDrawable.getAlpha(); | |
} | |
@Override | |
public void setColorFilter(ColorFilter cf) { | |
mInsetState.mDrawable.setColorFilter(cf); | |
} | |
@Override | |
public int getOpacity() { | |
return mInsetState.mDrawable.getOpacity(); | |
} | |
@Override | |
public boolean isStateful() { | |
return mInsetState.mDrawable.isStateful(); | |
} | |
@Override | |
protected boolean onStateChange(int[] state) { | |
boolean changed = mInsetState.mDrawable.setState(state); | |
onBoundsChange(getBounds()); | |
return changed; | |
} | |
@Override | |
protected boolean onLevelChange(int level) { | |
return mInsetState.mDrawable.setLevel(level); | |
} | |
@Override | |
protected void onBoundsChange(Rect bounds) { | |
final Rect r = mTmpRect; | |
r.set(bounds); | |
r.left += mInsetState.mInsetLeft; | |
r.top += mInsetState.mInsetTop; | |
r.right -= mInsetState.mInsetRight; | |
r.bottom -= mInsetState.mInsetBottom; | |
mInsetState.mDrawable.setBounds(r.left, r.top, r.right, r.bottom); | |
} | |
@Override | |
public int getIntrinsicWidth() { | |
return mInsetState.mDrawable.getIntrinsicWidth(); | |
} | |
@Override | |
public int getIntrinsicHeight() { | |
return mInsetState.mDrawable.getIntrinsicHeight(); | |
} | |
@TargetApi(Build.VERSION_CODES.LOLLIPOP) | |
@Override | |
public void getOutline(Outline outline) { | |
mInsetState.mDrawable.getOutline(outline); | |
} | |
@Override | |
public ConstantState getConstantState() { | |
if (mInsetState.canConstantState()) { | |
mInsetState.mChangingConfigurations = getChangingConfigurations(); | |
return mInsetState; | |
} | |
return null; | |
} | |
@Override | |
public Drawable mutate() { | |
if (!mMutated && super.mutate() == this) { | |
mInsetState.mDrawable.mutate(); | |
mMutated = true; | |
} | |
return this; | |
} | |
/** | |
* Returns the drawable wrapped by this InsetDrawable. May be null. | |
* | |
* @return The wrapped drawable. | |
*/ | |
public Drawable getDrawable() { | |
return mInsetState.mDrawable; | |
} | |
public void notifyDrawWithOffset(int offset) { | |
_drawOffset = offset; | |
} | |
final static class InsetState extends ConstantState { | |
int[] mThemeAttrs; | |
int mChangingConfigurations; | |
Drawable mDrawable; | |
int mInsetLeft; | |
int mInsetTop; | |
int mInsetRight; | |
int mInsetBottom; | |
boolean mCheckedConstantState; | |
boolean mCanConstantState; | |
InsetState(InsetState orig, AnimatableInsetDrawable owner, Resources res) { | |
if (orig != null) { | |
mThemeAttrs = orig.mThemeAttrs; | |
mChangingConfigurations = orig.mChangingConfigurations; | |
if (res != null) { | |
mDrawable = orig.mDrawable.getConstantState().newDrawable(res); | |
} else { | |
mDrawable = orig.mDrawable.getConstantState().newDrawable(); | |
} | |
mDrawable.setCallback(owner); | |
mDrawable.setBounds(orig.mDrawable.getBounds()); | |
mDrawable.setLevel(orig.mDrawable.getLevel()); | |
mInsetLeft = orig.mInsetLeft; | |
mInsetTop = orig.mInsetTop; | |
mInsetRight = orig.mInsetRight; | |
mInsetBottom = orig.mInsetBottom; | |
mCheckedConstantState = mCanConstantState = true; | |
} | |
} | |
@Override | |
public Drawable newDrawable() { | |
return new AnimatableInsetDrawable(this, null); | |
} | |
@Override | |
public Drawable newDrawable(Resources res) { | |
return new AnimatableInsetDrawable(this, res); | |
} | |
@Override | |
public int getChangingConfigurations() { | |
return mChangingConfigurations; | |
} | |
boolean canConstantState() { | |
if (!mCheckedConstantState) { | |
mCanConstantState = mDrawable.getConstantState() != null; | |
mCheckedConstantState = true; | |
} | |
return mCanConstantState; | |
} | |
} | |
private AnimatableInsetDrawable(InsetState state, Resources res) { | |
mInsetState = new InsetState(state, this, res); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ornoyman.com.starwarzcollapsingtoolbarlayout.sample; | |
import android.graphics.Color; | |
import android.graphics.drawable.ColorDrawable; | |
import android.os.Bundle; | |
import android.support.v7.app.AppCompatActivity; | |
import android.support.v7.widget.Toolbar; | |
import android.view.WindowManager; | |
public class MainActivity extends AppCompatActivity { | |
@Override | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_main); | |
getWindow().setBackgroundDrawable(new ColorDrawable(Color.WHITE)); | |
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); | |
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); | |
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); | |
toolbar.setTitle("title"); | |
setSupportActionBar(toolbar); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package android.support.design.widget; | |
import android.content.Context; | |
import android.content.res.TypedArray; | |
import android.graphics.Canvas; | |
import android.graphics.LinearGradient; | |
import android.graphics.Paint; | |
import android.graphics.PorterDuff; | |
import android.graphics.PorterDuffXfermode; | |
import android.graphics.Shader; | |
import android.graphics.drawable.AnimatableInsetDrawable; | |
import android.graphics.drawable.ColorDrawable; | |
import android.graphics.drawable.Drawable; | |
import android.support.annotation.ColorInt; | |
import android.support.annotation.Nullable; | |
import android.support.v4.view.ViewCompat; | |
import android.support.v7.widget.Toolbar; | |
import android.util.AttributeSet; | |
import android.view.View; | |
import android.view.ViewParent; | |
import ornoyman.com.starwarzcollapsingtoolbarlayout.R; | |
public class StarwarzCollapsingToolbarLayout extends CollapsingToolbarLayout { | |
private final Paint shaderPaint = new Paint(); | |
private int toolbarId; | |
private Toolbar toolbar; | |
private OffsetUpdateListener onOffsetChangedListener; | |
public StarwarzCollapsingToolbarLayout(Context context) { | |
this(context, null); | |
} | |
public StarwarzCollapsingToolbarLayout(Context context, AttributeSet attrs) { | |
this(context, attrs, 0); | |
} | |
public StarwarzCollapsingToolbarLayout(Context context, AttributeSet attrs, int defStyleAttr) { | |
super(context, attrs, defStyleAttr); | |
TypedArray a = context.obtainStyledAttributes(attrs, | |
R.styleable.CollapsingToolbarLayout, defStyleAttr, | |
R.style.Widget_Design_CollapsingToolbar); | |
toolbarId = a.getResourceId(android.support.design.R.styleable.CollapsingToolbarLayout_toolbarId, -1); | |
a.recycle(); | |
shaderPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); | |
} | |
@Override | |
protected void onAttachedToWindow() { | |
super.onAttachedToWindow(); | |
// Add an OnOffsetChangedListener if possible | |
final ViewParent parent = getParent(); | |
if (parent instanceof AppBarLayout) { | |
if (onOffsetChangedListener == null) { | |
onOffsetChangedListener = new OffsetUpdateListener(); | |
} | |
((AppBarLayout) parent).addOnOffsetChangedListener(onOffsetChangedListener); | |
} | |
} | |
@Override | |
protected void onDetachedFromWindow() { | |
// Remove our OnOffsetChangedListener if possible and it exists | |
final ViewParent parent = getParent(); | |
if (onOffsetChangedListener != null && parent instanceof AppBarLayout) { | |
((AppBarLayout) parent).removeOnOffsetChangedListener(onOffsetChangedListener); | |
} | |
super.onDetachedFromWindow(); | |
} | |
@Override public void setBackgroundDrawable(Drawable background) { | |
super.setBackgroundDrawable(new AnimatableInsetDrawable(background, 0, 0, 0, 0)); | |
} | |
@Override public void setStatusBarScrim(@Nullable Drawable drawable) { | |
if (drawable instanceof ColorDrawable) { | |
final ColorDrawable colorDrawable = (ColorDrawable)drawable; | |
colorDrawable.setColor(darken(colorDrawable.getColor())); | |
} | |
super.setStatusBarScrim(drawable); | |
} | |
@ColorInt int darken(@ColorInt int color) { | |
final double factor = 0.75; | |
return (color & 0xFF000000) | | |
(crimp((int) (((color >> 16) & 0xFF) * factor)) << 16) | | |
(crimp((int) (((color >> 8) & 0xFF) * factor)) << 8) | | |
(crimp((int) (((color) & 0xFF) * factor))); | |
} | |
@ColorInt int crimp(@ColorInt int color) { | |
return Math.min(Math.max(color, 0), 255); | |
} | |
@Override | |
protected void onSizeChanged(int w, int h, int oldw, int oldh) { | |
super.onSizeChanged(w, h, oldw, oldh); | |
setShader(h); | |
} | |
private void setShader(int height) { | |
ensureToolbar(); | |
if (mCurrentOffset == 0) { | |
shaderPaint.setShader(null); | |
ViewCompat.setBackground(toolbar, null); | |
if (getBackground() instanceof AnimatableInsetDrawable) { | |
((AnimatableInsetDrawable) getBackground()).setInsets(0, 0, 0, 0); | |
} | |
} else { | |
final LinearGradient shader = | |
new LinearGradient(0, height, 0, toolbar.getHeight() - mCurrentOffset, 0xff000000, | |
0x00000000, Shader.TileMode.CLAMP); | |
shaderPaint.setShader(shader); | |
if (toolbar.getBackground() == null && getBackground() instanceof AnimatableInsetDrawable) { | |
ViewCompat.setBackground(toolbar, | |
getBackground().getConstantState().newDrawable()); | |
} | |
if (getBackground() instanceof AnimatableInsetDrawable) { | |
((AnimatableInsetDrawable) getBackground()).setInsets(0, getStatusBarHeight() -mCurrentOffset, 0, 0); | |
} | |
if (toolbar.getBackground() instanceof AnimatableInsetDrawable) { | |
((AnimatableInsetDrawable) toolbar.getBackground()).setInsets(0, -getStatusBarHeight(), 0, (toolbar.getHeight() - getHeight()) - mCurrentOffset); | |
} | |
} | |
} | |
@Override | |
protected void dispatchDraw(Canvas canvas) { | |
ensureToolbar(); | |
canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG); | |
super.dispatchDraw(canvas); | |
canvas.drawRect(0, getStatusBarHeight() + toolbar.getHeight() - mCurrentOffset, getWidth(), getHeight(), shaderPaint); | |
canvas.restore(); | |
} | |
private void ensureToolbar() { | |
if (toolbarId != -1) { | |
// If we have an ID set, try and find it and it's direct parent to us | |
toolbar = (Toolbar) findViewById(toolbarId); | |
} | |
if (toolbar == null) { | |
// If we don't have an ID, or couldn't find a Toolbar with the correct ID, try and find | |
// one from our direct children | |
Toolbar toolbar = null; | |
for (int i = 0, count = getChildCount(); i < count; i++) { | |
final View child = getChildAt(i); | |
if (child instanceof Toolbar) { | |
toolbar = (Toolbar) child; | |
break; | |
} | |
} | |
this.toolbar = toolbar; | |
} | |
} | |
public int getStatusBarHeight() { | |
return mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0; | |
} | |
private class OffsetUpdateListener implements AppBarLayout.OnOffsetChangedListener { | |
@Override public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { | |
setShader(getHeight()); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment