mirror of
https://github.com/material-components/material-components-android.git
synced 2026-02-20 08:39:55 +08:00
am a196f6c5: Merge "AppBarLayout improvements" into lmp-mr1-ub-dev
* commit 'a196f6c585e08dd962cb14b659790931a6e92e40': AppBarLayout improvements GitOrigin-RevId=0afaaf631e3c992dc4af9d628444f1eee06758a8 PiperOrigin-RevId: 140545874
This commit is contained in:
parent
b227636e05
commit
d45d59df81
13
Android.mk
13
Android.mk
@ -63,12 +63,23 @@ LOCAL_JAVA_LIBRARIES := android-support-design-res \
|
||||
android-support-v7-appcompat
|
||||
include $(BUILD_STATIC_JAVA_LIBRARY)
|
||||
|
||||
# A helper sub-library that makes direct use of Honeycomb MR1 APIs
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := android-support-design-honeycomb-mr1
|
||||
LOCAL_SDK_VERSION := 12
|
||||
LOCAL_SRC_FILES := $(call all-java-files-under, honeycomb-mr1)
|
||||
LOCAL_STATIC_JAVA_LIBRARIES := android-support-design-honeycomb
|
||||
LOCAL_JAVA_LIBRARIES := android-support-design-res \
|
||||
android-support-v4 \
|
||||
android-support-v7-appcompat
|
||||
include $(BUILD_STATIC_JAVA_LIBRARY)
|
||||
|
||||
# A helper sub-library that makes direct use of Lollipop APIs
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := android-support-design-lollipop
|
||||
LOCAL_SDK_VERSION := 21
|
||||
LOCAL_SRC_FILES := $(call all-java-files-under, lollipop)
|
||||
LOCAL_STATIC_JAVA_LIBRARIES := android-support-design-honeycomb
|
||||
LOCAL_STATIC_JAVA_LIBRARIES := android-support-design-honeycomb-mr1
|
||||
LOCAL_JAVA_LIBRARIES := android-support-design-res \
|
||||
android-support-v4 \
|
||||
android-support-v7-appcompat
|
||||
|
||||
@ -3,18 +3,17 @@ package android.support.design.widget {
|
||||
public class AppBarLayout extends android.widget.LinearLayout {
|
||||
ctor public AppBarLayout(android.content.Context);
|
||||
ctor public AppBarLayout(android.content.Context, android.util.AttributeSet);
|
||||
}
|
||||
|
||||
public static abstract interface AppBarLayout.AppBarLayoutChild {
|
||||
method public abstract int onOffsetUpdate(int);
|
||||
field public static final int STATE_ELEVATED_ABOVE = 1; // 0x1
|
||||
field public static final int STATE_ELEVATED_INLINE = 0; // 0x0
|
||||
method public void addOnOffsetChangedListener(android.support.design.widget.AppBarLayout.OnOffsetChangedListener);
|
||||
method public float getTargetElevation();
|
||||
method public void removeOnOffsetChangedListener(android.support.design.widget.AppBarLayout.OnOffsetChangedListener);
|
||||
method public void setTargetElevation(float);
|
||||
}
|
||||
|
||||
public static class AppBarLayout.Behavior extends android.support.design.widget.ViewOffsetBehavior {
|
||||
ctor public AppBarLayout.Behavior();
|
||||
ctor public AppBarLayout.Behavior(android.content.Context, android.util.AttributeSet);
|
||||
method public boolean onLayoutChild(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, int);
|
||||
method public boolean onNestedFling(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.View, float, float, boolean);
|
||||
method public void onNestedPreScroll(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.View, int, int, int[]);
|
||||
method public void onNestedScroll(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.View, int, int, int, int);
|
||||
method public boolean onStartNestedScroll(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.View, android.view.View, int);
|
||||
@ -39,6 +38,10 @@ package android.support.design.widget {
|
||||
field public static final int SCROLL_FLAG_SCROLL = 1; // 0x1
|
||||
}
|
||||
|
||||
public static abstract interface AppBarLayout.OnOffsetChangedListener {
|
||||
method public abstract void onOffsetChanged(android.support.design.widget.AppBarLayout, int);
|
||||
}
|
||||
|
||||
public static class AppBarLayout.ScrollingViewBehavior extends android.support.design.widget.ViewOffsetBehavior {
|
||||
ctor public AppBarLayout.ScrollingViewBehavior();
|
||||
ctor public AppBarLayout.ScrollingViewBehavior(android.content.Context, android.util.AttributeSet);
|
||||
@ -49,12 +52,11 @@ package android.support.design.widget {
|
||||
method public void setOverlayTop(int);
|
||||
}
|
||||
|
||||
public class CollapsingToolbarLayout extends android.widget.FrameLayout implements android.support.design.widget.AppBarLayout.AppBarLayoutChild {
|
||||
public class CollapsingToolbarLayout extends android.widget.FrameLayout {
|
||||
ctor public CollapsingToolbarLayout(android.content.Context);
|
||||
ctor public CollapsingToolbarLayout(android.content.Context, android.util.AttributeSet);
|
||||
ctor public CollapsingToolbarLayout(android.content.Context, android.util.AttributeSet, int);
|
||||
method public int getForegroundScrimColor();
|
||||
method public int onOffsetUpdate(int);
|
||||
method public void setCollapsedTitleTextAppearance(int);
|
||||
method public void setCollapsedTitleTextColor(int);
|
||||
method public void setExpandedTitleColor(int);
|
||||
|
||||
194
base/android/support/design/widget/ValueAnimatorCompat.java
Normal file
194
base/android/support/design/widget/ValueAnimatorCompat.java
Normal file
@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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 android.support.design.widget;
|
||||
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
/**
|
||||
* This class offers a very small subset of {@code ValueAnimator}'s API, but works pre-v11 too.
|
||||
* <p>
|
||||
* You shouldn't not instantiate this directly. Instead use {@code ViewUtils.createAnimator()}.
|
||||
*/
|
||||
class ValueAnimatorCompat {
|
||||
|
||||
interface AnimatorUpdateListener {
|
||||
/**
|
||||
* <p>Notifies the occurrence of another frame of the animation.</p>
|
||||
*
|
||||
* @param animator The animation which was repeated.
|
||||
*/
|
||||
void onAnimationUpdate(ValueAnimatorCompat animator);
|
||||
}
|
||||
|
||||
/**
|
||||
* An animation listener receives notifications from an animation.
|
||||
* Notifications indicate animation related events, such as the end or the
|
||||
* repetition of the animation.
|
||||
*/
|
||||
interface AnimatorListener {
|
||||
/**
|
||||
* <p>Notifies the start of the animation.</p>
|
||||
*
|
||||
* @param animator The started animation.
|
||||
*/
|
||||
void onAnimationStart(ValueAnimatorCompat animator);
|
||||
/**
|
||||
* <p>Notifies the end of the animation. This callback is not invoked
|
||||
* for animations with repeat count set to INFINITE.</p>
|
||||
*
|
||||
* @param animator The animation which reached its end.
|
||||
*/
|
||||
void onAnimationEnd(ValueAnimatorCompat animator);
|
||||
/**
|
||||
* <p>Notifies the cancellation of the animation. This callback is not invoked
|
||||
* for animations with repeat count set to INFINITE.</p>
|
||||
*
|
||||
* @param animator The animation which was canceled.
|
||||
*/
|
||||
void onAnimationCancel(ValueAnimatorCompat animator);
|
||||
}
|
||||
|
||||
static class AnimatorListenerAdapter implements AnimatorListener {
|
||||
@Override
|
||||
public void onAnimationStart(ValueAnimatorCompat animator) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(ValueAnimatorCompat animator) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(ValueAnimatorCompat animator) {
|
||||
}
|
||||
}
|
||||
|
||||
interface Creator {
|
||||
ValueAnimatorCompat createAnimator();
|
||||
}
|
||||
|
||||
static abstract class Impl {
|
||||
interface AnimatorUpdateListenerProxy {
|
||||
void onAnimationUpdate();
|
||||
}
|
||||
|
||||
interface AnimatorListenerProxy {
|
||||
void onAnimationStart();
|
||||
void onAnimationEnd();
|
||||
void onAnimationCancel();
|
||||
}
|
||||
|
||||
abstract void start();
|
||||
abstract boolean isRunning();
|
||||
abstract void setInterpolator(Interpolator interpolator);
|
||||
abstract void setListener(AnimatorListenerProxy listener);
|
||||
abstract void setUpdateListener(AnimatorUpdateListenerProxy updateListener);
|
||||
abstract void setIntValues(int from, int to);
|
||||
abstract int getAnimatedIntValue();
|
||||
abstract void setFloatValues(float from, float to);
|
||||
abstract float getAnimatedFloatValue();
|
||||
abstract void setDuration(int duration);
|
||||
abstract void cancel();
|
||||
abstract float getAnimatedFraction();
|
||||
abstract void end();
|
||||
}
|
||||
|
||||
private final Impl mImpl;
|
||||
|
||||
ValueAnimatorCompat(Impl impl) {
|
||||
mImpl = impl;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
mImpl.start();
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return mImpl.isRunning();
|
||||
}
|
||||
|
||||
public void setInterpolator(Interpolator interpolator) {
|
||||
mImpl.setInterpolator(interpolator);
|
||||
}
|
||||
|
||||
public void setUpdateListener(final AnimatorUpdateListener updateListener) {
|
||||
if (updateListener != null) {
|
||||
mImpl.setUpdateListener(new Impl.AnimatorUpdateListenerProxy() {
|
||||
@Override
|
||||
public void onAnimationUpdate() {
|
||||
updateListener.onAnimationUpdate(ValueAnimatorCompat.this);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
mImpl.setUpdateListener(null);
|
||||
}
|
||||
}
|
||||
|
||||
public void setListener(final AnimatorListener listener) {
|
||||
if (listener != null) {
|
||||
mImpl.setListener(new Impl.AnimatorListenerProxy() {
|
||||
@Override
|
||||
public void onAnimationStart() {
|
||||
listener.onAnimationStart(ValueAnimatorCompat.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd() {
|
||||
listener.onAnimationEnd(ValueAnimatorCompat.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel() {
|
||||
listener.onAnimationCancel(ValueAnimatorCompat.this);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
mImpl.setListener(null);
|
||||
}
|
||||
}
|
||||
|
||||
public void setIntValues(int from, int to) {
|
||||
mImpl.setIntValues(from, to);
|
||||
}
|
||||
|
||||
public int getAnimatedIntValue() {
|
||||
return mImpl.getAnimatedIntValue();
|
||||
}
|
||||
|
||||
public void setFloatValues(float from, float to) {
|
||||
mImpl.setFloatValues(from, to);
|
||||
}
|
||||
|
||||
public float getAnimatedFloatValue() {
|
||||
return mImpl.getAnimatedFloatValue();
|
||||
}
|
||||
|
||||
public void setDuration(int duration) {
|
||||
mImpl.setDuration(duration);
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
mImpl.cancel();
|
||||
}
|
||||
|
||||
public float getAnimatedFraction() {
|
||||
return mImpl.getAnimatedFraction();
|
||||
}
|
||||
|
||||
public void end() {
|
||||
mImpl.end();
|
||||
}
|
||||
}
|
||||
@ -12,7 +12,7 @@ android {
|
||||
|
||||
sourceSets {
|
||||
main.manifest.srcFile 'AndroidManifest.xml'
|
||||
main.java.srcDirs = ['base', 'eclair-mr1', 'honeycomb', 'lollipop', 'src']
|
||||
main.java.srcDirs = ['base', 'eclair-mr1', 'honeycomb', 'honeycomb-mr1', 'lollipop', 'src']
|
||||
main.res.srcDir 'res'
|
||||
main.assets.srcDir 'assets'
|
||||
main.resources.srcDir 'src'
|
||||
|
||||
@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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 android.support.design.widget;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
/**
|
||||
* A 'fake' ValueAnimator implementation which uses a Runnable.
|
||||
*/
|
||||
class ValueAnimatorCompatImplEclairMr1 extends ValueAnimatorCompat.Impl {
|
||||
|
||||
private static final int HANDLER_DELAY = 10;
|
||||
private static final int DEFAULT_DURATION = 200;
|
||||
|
||||
private static final Handler sHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
private long mStartTime;
|
||||
private boolean mIsRunning;
|
||||
|
||||
private final int[] mIntValues = new int[2];
|
||||
private final float[] mFloatValues = new float[2];
|
||||
|
||||
private int mDuration = DEFAULT_DURATION;
|
||||
private Interpolator mInterpolator;
|
||||
private AnimatorListenerProxy mListener;
|
||||
private AnimatorUpdateListenerProxy mUpdateListener;
|
||||
|
||||
private float mAnimatedFraction;
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
if (mIsRunning) {
|
||||
// If we're already running, ignore
|
||||
return;
|
||||
}
|
||||
|
||||
if (mInterpolator == null) {
|
||||
mInterpolator = new AccelerateDecelerateInterpolator();
|
||||
}
|
||||
|
||||
mStartTime = SystemClock.uptimeMillis();
|
||||
mIsRunning = true;
|
||||
|
||||
if (mListener != null) {
|
||||
mListener.onAnimationStart();
|
||||
}
|
||||
|
||||
sHandler.postDelayed(mRunnable, HANDLER_DELAY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return mIsRunning;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInterpolator(Interpolator interpolator) {
|
||||
mInterpolator = interpolator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setListener(AnimatorListenerProxy listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUpdateListener(AnimatorUpdateListenerProxy updateListener) {
|
||||
mUpdateListener = updateListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIntValues(int from, int to) {
|
||||
mIntValues[0] = from;
|
||||
mIntValues[1] = to;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAnimatedIntValue() {
|
||||
return AnimationUtils.lerp(mIntValues[0], mIntValues[1], getAnimatedFraction());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFloatValues(float from, float to) {
|
||||
mFloatValues[0] = from;
|
||||
mFloatValues[1] = to;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getAnimatedFloatValue() {
|
||||
return AnimationUtils.lerp(mFloatValues[0], mFloatValues[1], getAnimatedFraction());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDuration(int duration) {
|
||||
mDuration = duration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
mIsRunning = false;
|
||||
sHandler.removeCallbacks(mRunnable);
|
||||
|
||||
if (mListener != null) {
|
||||
mListener.onAnimationCancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getAnimatedFraction() {
|
||||
return mAnimatedFraction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
if (mIsRunning) {
|
||||
mIsRunning = false;
|
||||
sHandler.removeCallbacks(mRunnable);
|
||||
|
||||
// Set our animated fraction to 1
|
||||
mAnimatedFraction = 1f;
|
||||
|
||||
if (mUpdateListener != null) {
|
||||
mUpdateListener.onAnimationUpdate();
|
||||
}
|
||||
|
||||
if (mListener != null) {
|
||||
mListener.onAnimationEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void update() {
|
||||
if (mIsRunning) {
|
||||
// Update the animated fraction
|
||||
final long elapsed = SystemClock.uptimeMillis() - mStartTime;
|
||||
final float linearFraction = elapsed / (float) mDuration;
|
||||
mAnimatedFraction = mInterpolator != null
|
||||
? mInterpolator.getInterpolation(linearFraction)
|
||||
: linearFraction;
|
||||
|
||||
// If we're running, dispatch tp the listener
|
||||
if (mUpdateListener != null) {
|
||||
mUpdateListener.onAnimationUpdate();
|
||||
}
|
||||
|
||||
// Check to see if we've passed the animation duration
|
||||
if (SystemClock.uptimeMillis() >= (mStartTime + mDuration)) {
|
||||
mIsRunning = false;
|
||||
|
||||
if (mListener != null) {
|
||||
mListener.onAnimationEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mIsRunning) {
|
||||
// If we're still running, post another delayed runnable
|
||||
sHandler.postDelayed(mRunnable, HANDLER_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
private final Runnable mRunnable = new Runnable() {
|
||||
public void run() {
|
||||
update();
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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 android.support.design.widget;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
class ValueAnimatorCompatImplHoneycombMr1 extends ValueAnimatorCompat.Impl {
|
||||
|
||||
final ValueAnimator mValueAnimator;
|
||||
|
||||
ValueAnimatorCompatImplHoneycombMr1() {
|
||||
mValueAnimator = new ValueAnimator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
mValueAnimator.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return mValueAnimator.isRunning();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInterpolator(Interpolator interpolator) {
|
||||
mValueAnimator.setInterpolator(interpolator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUpdateListener(final AnimatorUpdateListenerProxy updateListener) {
|
||||
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator valueAnimator) {
|
||||
updateListener.onAnimationUpdate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setListener(final AnimatorListenerProxy listener) {
|
||||
mValueAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animator) {
|
||||
listener.onAnimationStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animator) {
|
||||
listener.onAnimationEnd();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animator) {
|
||||
listener.onAnimationCancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIntValues(int from, int to) {
|
||||
mValueAnimator.setIntValues(from, to);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAnimatedIntValue() {
|
||||
return (int) mValueAnimator.getAnimatedValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFloatValues(float from, float to) {
|
||||
mValueAnimator.setFloatValues(from, to);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getAnimatedFloatValue() {
|
||||
return (float) mValueAnimator.getAnimatedValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDuration(int duration) {
|
||||
mValueAnimator.setDuration(duration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
mValueAnimator.cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getAnimatedFraction() {
|
||||
return mValueAnimator.getAnimatedFraction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
mValueAnimator.end();
|
||||
}
|
||||
}
|
||||
30
res/anim/fab_in.xml
Normal file
30
res/anim/fab_in.xml
Normal file
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2015 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.
|
||||
-->
|
||||
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<alpha android:fromAlpha="0.0"
|
||||
android:toAlpha="1.0"/>
|
||||
|
||||
<scale android:fromXScale="0.0"
|
||||
android:fromYScale="0.0"
|
||||
android:toXScale="1.0"
|
||||
android:toYScale="1.0"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%"/>
|
||||
|
||||
</set>
|
||||
30
res/anim/fab_out.xml
Normal file
30
res/anim/fab_out.xml
Normal file
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2015 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.
|
||||
-->
|
||||
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<alpha android:fromAlpha="1.0"
|
||||
android:toAlpha="0.0"/>
|
||||
|
||||
<scale android:fromXScale="1.0"
|
||||
android:fromYScale="1.0"
|
||||
android:toXScale="0.0"
|
||||
android:toYScale="0.0"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%"/>
|
||||
|
||||
</set>
|
||||
@ -21,6 +21,7 @@ import android.content.res.TypedArray;
|
||||
import android.support.annotation.IntDef;
|
||||
import android.support.design.R;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v4.widget.ScrollerCompat;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@ -29,11 +30,14 @@ import android.widget.LinearLayout;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AppBarLayout is a vertical {@link LinearLayout} which implements many of the features of
|
||||
* Material Design's App bar concept, namely scrolling gestures.
|
||||
* material design's app bar concept, namely scrolling gestures.
|
||||
* <p>
|
||||
* Children should provide their desired scrolling behavior through
|
||||
* {@link LayoutParams#setScrollFlags(int)} and the associated layout xml attribute:
|
||||
@ -89,48 +93,19 @@ import java.util.List;
|
||||
public class AppBarLayout extends LinearLayout {
|
||||
|
||||
/**
|
||||
* Interface which allows an implementing child {@link View} of this {@link AppBarLayout} to
|
||||
* receive offset updates, and provide extra information.
|
||||
* Interface definition for a callback to be invoked when an {@link AppBarLayout}'s vertical
|
||||
* offset changes.
|
||||
*/
|
||||
public interface AppBarLayoutChild {
|
||||
|
||||
/** @hide */
|
||||
@IntDef({
|
||||
STATE_ELEVATED_ABOVE,
|
||||
STATE_ELEVATED_INLINE
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface ElevatedState {}
|
||||
|
||||
/**
|
||||
* The {@link AppBarLayout} should be elevated above any scrolling content, and this cast
|
||||
* a shadow.
|
||||
*
|
||||
* @see #onOffsetUpdate(int)
|
||||
*/
|
||||
int STATE_ELEVATED_ABOVE = 1;
|
||||
|
||||
/**
|
||||
* The {@link AppBarLayout} should not be elevated above any scrolling content.
|
||||
*
|
||||
* @see #onOffsetUpdate(int)
|
||||
*/
|
||||
int STATE_ELEVATED_INLINE = 0;
|
||||
|
||||
public interface OnOffsetChangedListener {
|
||||
/**
|
||||
* Called when the {@link AppBarLayout}'s layout offset has been changed. This allows
|
||||
* child views to implement custom behavior based on the offset (for instance pinning a
|
||||
* view at a certain y value).
|
||||
*
|
||||
* <p>You can influence the elevation of the {@link AppBarLayout} by returning one of
|
||||
* {@link #STATE_ELEVATED_INLINE} or {@link #STATE_ELEVATED_ABOVE}.
|
||||
*
|
||||
* @param appBarLayout the {@link AppBarLayout} which offset has changed
|
||||
* @param verticalOffset the vertical offset for the parent {@link AppBarLayout}, in px
|
||||
*
|
||||
* @return one of {@link #STATE_ELEVATED_INLINE} or {@link #STATE_ELEVATED_ABOVE}.
|
||||
*/
|
||||
@ElevatedState
|
||||
int onOffsetUpdate(int verticalOffset);
|
||||
void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset);
|
||||
}
|
||||
|
||||
private static final int INVALID_SCROLL_RANGE = -1;
|
||||
@ -143,6 +118,8 @@ public class AppBarLayout extends LinearLayout {
|
||||
|
||||
private float mTargetElevation;
|
||||
|
||||
private final List<WeakReference<OnOffsetChangedListener>> mListeners;
|
||||
|
||||
public AppBarLayout(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
@ -159,6 +136,45 @@ public class AppBarLayout extends LinearLayout {
|
||||
|
||||
// Use the bounds view outline provider so that we cast a shadow, even without a background
|
||||
ViewUtils.setBoundsViewOutlineProvider(this);
|
||||
|
||||
mListeners = new ArrayList<>();
|
||||
|
||||
ViewCompat.setElevation(this, mTargetElevation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener that will be called when the offset of this {@link AppBarLayout} changes.
|
||||
*
|
||||
* @param listener The listener that will be called when the offset changes.]
|
||||
*
|
||||
* @see #removeOnOffsetChangedListener(OnOffsetChangedListener)
|
||||
*/
|
||||
public void addOnOffsetChangedListener(OnOffsetChangedListener listener) {
|
||||
for (int i = 0, z = mListeners.size(); i < z; i++) {
|
||||
final WeakReference<OnOffsetChangedListener> ref = mListeners.get(i);
|
||||
if (ref != null && ref.get() == listener) {
|
||||
// Listener already added
|
||||
return;
|
||||
}
|
||||
}
|
||||
mListeners.add(new WeakReference<>(listener));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the previously added {@link OnOffsetChangedListener}.
|
||||
*
|
||||
* @param listener the listener to remove.
|
||||
*/
|
||||
public void removeOnOffsetChangedListener(OnOffsetChangedListener listener) {
|
||||
final Iterator<WeakReference<OnOffsetChangedListener>> i = mListeners.iterator();
|
||||
while (i.hasNext()) {
|
||||
final WeakReference<OnOffsetChangedListener> ref = i.next();
|
||||
final OnOffsetChangedListener item = ref.get();
|
||||
if (item == listener || item == null) {
|
||||
// If the item is null, or is our given listener, remove
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -359,9 +375,26 @@ public class AppBarLayout extends LinearLayout {
|
||||
}
|
||||
|
||||
/**
|
||||
* The elevation value to use when {@link AppBarLayout} is elevated above content.
|
||||
* Set the elevation value to use when this {@link AppBarLayout} should be elevated
|
||||
* above content.
|
||||
* <p>
|
||||
* This method does not do anything itself. A typical use for this method is called from within
|
||||
* an {@link OnOffsetChangedListener} when the offset has changed in such a way to require an
|
||||
* elevation change.
|
||||
*
|
||||
* @param elevation the elevation value to use.
|
||||
*
|
||||
* @see ViewCompat#setElevation(View, float)
|
||||
*/
|
||||
final float getTargetElevation() {
|
||||
public void setTargetElevation(float elevation) {
|
||||
mTargetElevation = elevation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the elevation value to use when this {@link AppBarLayout} should be elevated
|
||||
* above content.
|
||||
*/
|
||||
public float getTargetElevation() {
|
||||
return mTargetElevation;
|
||||
}
|
||||
|
||||
@ -515,9 +548,13 @@ public class AppBarLayout extends LinearLayout {
|
||||
* scroll handling with offsetting.
|
||||
*/
|
||||
public static class Behavior extends ViewOffsetBehavior<AppBarLayout> {
|
||||
private int mSiblingOffsetTop;
|
||||
private int mLogicalOffsetTop;
|
||||
|
||||
private boolean mSkipNestedPreScroll;
|
||||
private Runnable mFlingRunnable;
|
||||
private ScrollerCompat mScroller;
|
||||
|
||||
private ValueAnimatorCompat mAnimator;
|
||||
|
||||
public Behavior() {}
|
||||
|
||||
@ -529,8 +566,15 @@ public class AppBarLayout extends LinearLayout {
|
||||
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
|
||||
View directTargetChild, View target, int nestedScrollAxes) {
|
||||
// Return true if we're nested scrolling vertically and we have scrollable children
|
||||
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0
|
||||
final boolean started = (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0
|
||||
&& child.hasScrollableChildren();
|
||||
|
||||
if (started && mAnimator != null) {
|
||||
// Cancel any offset animation
|
||||
mAnimator.cancel();
|
||||
}
|
||||
|
||||
return started;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -575,14 +619,120 @@ public class AppBarLayout extends LinearLayout {
|
||||
mSkipNestedPreScroll = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onNestedFling(final CoordinatorLayout coordinatorLayout,
|
||||
final AppBarLayout child, View target, float velocityX, float velocityY,
|
||||
boolean consumed) {
|
||||
if (!consumed) {
|
||||
// It has been consumed so let's fling ourselves
|
||||
return fling(coordinatorLayout, child, -child.getTotalScrollRange(), 0, -velocityY);
|
||||
} else {
|
||||
// If we're scrolling up and the child also consumed the fling. We'll fake scroll
|
||||
// upto our 'collapsed' offset
|
||||
int targetScroll;
|
||||
if (velocityY < 0) {
|
||||
// We're scrolling down
|
||||
targetScroll = -child.getTotalScrollRange()
|
||||
+ child.getDownNestedPreScrollRange();
|
||||
|
||||
if (getTopBottomOffsetForScrollingSibling() > targetScroll) {
|
||||
// If we're currently expanded more than the target scroll, we'll return false
|
||||
// now. This is so that we don't 'scroll' the wrong way.
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// We're scrolling up
|
||||
targetScroll = -child.getUpNestedPreScrollRange();
|
||||
|
||||
if (getTopBottomOffsetForScrollingSibling() < targetScroll) {
|
||||
// If we're currently expanded less than the target scroll, we'll return
|
||||
// false now. This is so that we don't 'scroll' the wrong way.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (mLogicalOffsetTop != targetScroll) {
|
||||
animateOffsetTo(coordinatorLayout, child, targetScroll);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void animateOffsetTo(final CoordinatorLayout coordinatorLayout,
|
||||
final AppBarLayout child, int offset) {
|
||||
if (mAnimator == null) {
|
||||
mAnimator = ViewUtils.createAnimator();
|
||||
mAnimator.setInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR);
|
||||
mAnimator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimatorCompat animator) {
|
||||
setAppBarTopBottomOffset(coordinatorLayout, child,
|
||||
animator.getAnimatedIntValue());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
mAnimator.cancel();
|
||||
}
|
||||
|
||||
mAnimator.setIntValues(getTopBottomOffsetForScrollingSibling(), offset);
|
||||
mAnimator.start();
|
||||
}
|
||||
|
||||
private boolean fling(CoordinatorLayout coordinatorLayout, AppBarLayout layout, int minOffset,
|
||||
int maxOffset, float velocityY) {
|
||||
if (mFlingRunnable != null) {
|
||||
layout.removeCallbacks(mFlingRunnable);
|
||||
}
|
||||
|
||||
if (mScroller == null) {
|
||||
mScroller = ScrollerCompat.create(layout.getContext());
|
||||
}
|
||||
|
||||
mScroller.fling(
|
||||
0, mLogicalOffsetTop, // curr
|
||||
0, Math.round(velocityY), // velocity.
|
||||
0, 0, // x
|
||||
minOffset, maxOffset); // y
|
||||
|
||||
if (mScroller.computeScrollOffset()) {
|
||||
mFlingRunnable = new FlingRunnable(coordinatorLayout, layout);
|
||||
ViewCompat.postOnAnimation(layout, mFlingRunnable);
|
||||
return true;
|
||||
} else {
|
||||
mFlingRunnable = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private class FlingRunnable implements Runnable {
|
||||
private final CoordinatorLayout mParent;
|
||||
private final AppBarLayout mLayout;
|
||||
|
||||
FlingRunnable(CoordinatorLayout parent, AppBarLayout layout) {
|
||||
mParent = parent;
|
||||
mLayout = layout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (mLayout != null && mScroller != null && mScroller.computeScrollOffset()) {
|
||||
setAppBarTopBottomOffset(mParent, mLayout, mScroller.getCurrY());
|
||||
|
||||
// Post ourselves so that we run on the next animation
|
||||
ViewCompat.postOnAnimation(mLayout, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLayoutChild(CoordinatorLayout parent, AppBarLayout child,
|
||||
int layoutDirection) {
|
||||
boolean handled = super.onLayoutChild(parent, child, layoutDirection);
|
||||
|
||||
// Make sure we update the elevation
|
||||
final int elevationState = dispatchOffsetUpdates(child);
|
||||
checkElevation(child, getTopAndBottomOffset(), elevationState);
|
||||
dispatchOffsetUpdates(child);
|
||||
|
||||
return handled;
|
||||
}
|
||||
@ -590,15 +740,23 @@ public class AppBarLayout extends LinearLayout {
|
||||
private int scroll(CoordinatorLayout coordinatorLayout, AppBarLayout appBarLayout,
|
||||
int dy, int minOffset, int maxOffset) {
|
||||
return setAppBarTopBottomOffset(coordinatorLayout, appBarLayout,
|
||||
mSiblingOffsetTop - dy, minOffset, maxOffset);
|
||||
mLogicalOffsetTop - dy, minOffset, maxOffset);
|
||||
}
|
||||
|
||||
private int setAppBarTopBottomOffset(CoordinatorLayout coordinatorLayout,
|
||||
final int setAppBarTopBottomOffset(CoordinatorLayout coordinatorLayout,
|
||||
AppBarLayout appBarLayout, int newOffset) {
|
||||
return setAppBarTopBottomOffset(coordinatorLayout, appBarLayout, newOffset,
|
||||
Integer.MIN_VALUE, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
final int setAppBarTopBottomOffset(CoordinatorLayout coordinatorLayout,
|
||||
AppBarLayout appBarLayout, int newOffset, int minOffset, int maxOffset) {
|
||||
final int curOffset = mSiblingOffsetTop;
|
||||
final int curOffset = mLogicalOffsetTop;
|
||||
int consumed = 0;
|
||||
|
||||
if (minOffset != 0) {
|
||||
if (minOffset != 0 && curOffset >= minOffset && curOffset <= maxOffset) {
|
||||
// If we have some scrolling range, and we're currently within the min and max
|
||||
// offsets, calculate a new offset
|
||||
newOffset = MathUtils.constrain(newOffset, minOffset, maxOffset);
|
||||
|
||||
if (curOffset != newOffset) {
|
||||
@ -606,10 +764,11 @@ public class AppBarLayout extends LinearLayout {
|
||||
appBarLayout.hasChildWithInterpolator()
|
||||
? interpolateOffset(appBarLayout, newOffset)
|
||||
: newOffset);
|
||||
|
||||
// Update how much dy we have consumed
|
||||
consumed = curOffset - newOffset;
|
||||
// Update the stored sibling offset
|
||||
mSiblingOffsetTop = newOffset;
|
||||
mLogicalOffsetTop = newOffset;
|
||||
|
||||
if (!offsetChanged && appBarLayout.hasChildWithInterpolator()) {
|
||||
// If the offset hasn't changed and we're using an interpolated scroll
|
||||
@ -619,44 +778,29 @@ public class AppBarLayout extends LinearLayout {
|
||||
coordinatorLayout.dispatchDependentViewsChanged(appBarLayout);
|
||||
}
|
||||
|
||||
// Dispatch the updates to any AppBarLayoutChild children
|
||||
final int childState = dispatchOffsetUpdates(appBarLayout);
|
||||
checkElevation(appBarLayout, newOffset, childState);
|
||||
// Dispatch the updates to any listeners
|
||||
dispatchOffsetUpdates(appBarLayout);
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
private void checkElevation(AppBarLayout appBarLayout, int offset, int childState) {
|
||||
if (appBarLayout.getHeight() + offset == 0) {
|
||||
// If we're not visible, clear out the elevation
|
||||
ViewCompat.setElevation(appBarLayout, 0f);
|
||||
} else {
|
||||
if (childState == AppBarLayoutChild.STATE_ELEVATED_ABOVE) {
|
||||
ViewCompat.setElevation(appBarLayout, appBarLayout.getTargetElevation());
|
||||
} else {
|
||||
ViewCompat.setElevation(appBarLayout, 0f);
|
||||
private void dispatchOffsetUpdates(AppBarLayout layout) {
|
||||
final List<WeakReference<OnOffsetChangedListener>> listeners = layout.mListeners;
|
||||
|
||||
// Iterate backwards through the list so that most recently added listeners
|
||||
// get the first chance to decide
|
||||
for (int i = 0, z = listeners.size(); i < z; i++) {
|
||||
final WeakReference<OnOffsetChangedListener> ref = listeners.get(i);
|
||||
final OnOffsetChangedListener listener = ref != null ? ref.get() : null;
|
||||
|
||||
if (listener != null) {
|
||||
listener.onOffsetChanged(layout, getTopAndBottomOffset());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int dispatchOffsetUpdates(AppBarLayout layout) {
|
||||
for (int i = 0, z = layout.getChildCount(); i < z; i++) {
|
||||
View child = layout.getChildAt(i);
|
||||
if (child instanceof AppBarLayoutChild) {
|
||||
final int childState = ((AppBarLayoutChild) child)
|
||||
.onOffsetUpdate(getTopAndBottomOffset());
|
||||
|
||||
if (childState == AppBarLayoutChild.STATE_ELEVATED_INLINE) {
|
||||
return childState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return AppBarLayoutChild.STATE_ELEVATED_ABOVE;
|
||||
}
|
||||
|
||||
private int interpolateOffset(AppBarLayout layout, final int offset) {
|
||||
final int absOffset = Math.abs(offset);
|
||||
|
||||
@ -698,7 +842,7 @@ public class AppBarLayout extends LinearLayout {
|
||||
}
|
||||
|
||||
final int getTopBottomOffsetForScrollingSibling() {
|
||||
return mSiblingOffsetTop;
|
||||
return mLogicalOffsetTop;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -31,6 +31,7 @@ import android.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.Transformation;
|
||||
import android.widget.FrameLayout;
|
||||
@ -71,7 +72,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||
* @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginEnd
|
||||
* @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginBottom
|
||||
*/
|
||||
public class CollapsingToolbarLayout extends FrameLayout implements AppBarLayout.AppBarLayoutChild {
|
||||
public class CollapsingToolbarLayout extends FrameLayout {
|
||||
|
||||
private static final int SCRIM_ANIMATION_DURATION = 600;
|
||||
|
||||
@ -89,6 +90,8 @@ public class CollapsingToolbarLayout extends FrameLayout implements AppBarLayout
|
||||
private int mCurrentForegroundColor;
|
||||
private boolean mScrimIsShown;
|
||||
|
||||
private AppBarLayout.OnOffsetChangedListener mOnOffsetChangedListener;
|
||||
|
||||
public CollapsingToolbarLayout(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
@ -154,6 +157,31 @@ public class CollapsingToolbarLayout extends FrameLayout implements AppBarLayout
|
||||
setWillNotDraw(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
// Add an OnOffsetChangedListener if possible
|
||||
final ViewParent parent = getParent();
|
||||
if (parent instanceof AppBarLayout) {
|
||||
if (mOnOffsetChangedListener == null) {
|
||||
mOnOffsetChangedListener = new OffsetUpdateListener();
|
||||
}
|
||||
((AppBarLayout) parent).addOnOffsetChangedListener(mOnOffsetChangedListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
// Remove our OnOffsetChangedListener if possible and it exists
|
||||
final ViewParent parent = getParent();
|
||||
if (mOnOffsetChangedListener != null && parent instanceof AppBarLayout) {
|
||||
((AppBarLayout) parent).removeOnOffsetChangedListener(mOnOffsetChangedListener);
|
||||
}
|
||||
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addView(View child, int index, ViewGroup.LayoutParams params) {
|
||||
super.addView(child, index, params);
|
||||
@ -234,55 +262,6 @@ public class CollapsingToolbarLayout extends FrameLayout implements AppBarLayout
|
||||
mCollapsingTextHelper.setText(title);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
public int onOffsetUpdate(int verticalOffset) {
|
||||
int pinnedHeight = 0;
|
||||
|
||||
for (int i = 0, z = getChildCount(); i < z; i++) {
|
||||
final View child = getChildAt(i);
|
||||
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
|
||||
final ViewOffsetHelper offsetHelper = getViewOffsetHelper(child);
|
||||
|
||||
switch (lp.mCollapseMode) {
|
||||
case LayoutParams.COLLAPSE_MODE_PIN:
|
||||
if (getHeight() + verticalOffset >= child.getHeight()) {
|
||||
offsetHelper.setTopAndBottomOffset(-verticalOffset);
|
||||
}
|
||||
pinnedHeight += child.getHeight();
|
||||
break;
|
||||
case LayoutParams.COLLAPSE_MODE_PARALLAX:
|
||||
offsetHelper.setTopAndBottomOffset(
|
||||
Math.round(-verticalOffset * lp.mParallaxMult));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Show or hide the scrim if needed
|
||||
if (Color.alpha(mForegroundScrimColor) > 0) {
|
||||
if (getHeight() + verticalOffset < getScrimTriggerOffset()) {
|
||||
showScrim();
|
||||
} else {
|
||||
hideScrim();
|
||||
}
|
||||
}
|
||||
|
||||
// Update the collapsing text's fraction
|
||||
mCollapsingTextHelper.setExpansionFraction(Math.abs(verticalOffset) /
|
||||
(float) (getHeight() - ViewCompat.getMinimumHeight(this)));
|
||||
|
||||
if (pinnedHeight > 0 && (getHeight() + verticalOffset) == pinnedHeight) {
|
||||
// If we have some pinned children, and we're offset to only show those views,
|
||||
// we want to be elevate
|
||||
return STATE_ELEVATED_ABOVE;
|
||||
} else {
|
||||
// Otherwise, we're inline with the content
|
||||
return STATE_ELEVATED_INLINE;
|
||||
}
|
||||
}
|
||||
|
||||
private void showScrim() {
|
||||
if (mScrimIsShown) return;
|
||||
|
||||
@ -542,4 +521,54 @@ public class CollapsingToolbarLayout extends FrameLayout implements AppBarLayout
|
||||
return mParallaxMult;
|
||||
}
|
||||
}
|
||||
|
||||
private class OffsetUpdateListener implements AppBarLayout.OnOffsetChangedListener {
|
||||
@Override
|
||||
public void onOffsetChanged(AppBarLayout layout, int verticalOffset) {
|
||||
int pinnedHeight = 0;
|
||||
|
||||
for (int i = 0, z = getChildCount(); i < z; i++) {
|
||||
final View child = getChildAt(i);
|
||||
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
|
||||
final ViewOffsetHelper offsetHelper = getViewOffsetHelper(child);
|
||||
|
||||
switch (lp.mCollapseMode) {
|
||||
case LayoutParams.COLLAPSE_MODE_PIN:
|
||||
if (getHeight() + verticalOffset >= child.getHeight()) {
|
||||
offsetHelper.setTopAndBottomOffset(-verticalOffset);
|
||||
}
|
||||
pinnedHeight += child.getHeight();
|
||||
break;
|
||||
case LayoutParams.COLLAPSE_MODE_PARALLAX:
|
||||
offsetHelper.setTopAndBottomOffset(
|
||||
Math.round(-verticalOffset * lp.mParallaxMult));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Show or hide the scrim if needed
|
||||
if (Color.alpha(mForegroundScrimColor) > 0) {
|
||||
if (getHeight() + verticalOffset < getScrimTriggerOffset()) {
|
||||
showScrim();
|
||||
} else {
|
||||
hideScrim();
|
||||
}
|
||||
}
|
||||
|
||||
// Update the collapsing text's fraction
|
||||
final int expandRange = getHeight() - ViewCompat.getMinimumHeight(
|
||||
CollapsingToolbarLayout.this);
|
||||
mCollapsingTextHelper.setExpansionFraction(
|
||||
Math.abs(verticalOffset) / (float) expandRange);
|
||||
|
||||
if (pinnedHeight > 0 && (getHeight() + verticalOffset) == pinnedHeight) {
|
||||
// If we have some pinned children, and we're offset to only show those views,
|
||||
// we want to be elevate
|
||||
ViewCompat.setElevation(layout, layout.getTargetElevation());
|
||||
} else {
|
||||
// Otherwise, we're inline with the content
|
||||
ViewCompat.setElevation(layout, 0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,6 +31,7 @@ import android.support.v4.view.ViewCompat;
|
||||
import android.support.v4.view.ViewPropertyAnimatorListener;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.animation.Animation;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import java.util.List;
|
||||
@ -370,40 +371,68 @@ public class FloatingActionButton extends ImageView {
|
||||
private void animateIn(FloatingActionButton button) {
|
||||
button.setVisibility(View.VISIBLE);
|
||||
|
||||
ViewCompat.animate(button)
|
||||
.scaleX(1f)
|
||||
.scaleY(1f)
|
||||
.alpha(1f)
|
||||
.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
|
||||
.withLayer()
|
||||
.setListener(null)
|
||||
.start();
|
||||
if (Build.VERSION.SDK_INT >= 14) {
|
||||
ViewCompat.animate(button)
|
||||
.scaleX(1f)
|
||||
.scaleY(1f)
|
||||
.alpha(1f)
|
||||
.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
|
||||
.withLayer()
|
||||
.setListener(null)
|
||||
.start();
|
||||
} else {
|
||||
Animation anim = android.view.animation.AnimationUtils.loadAnimation(
|
||||
button.getContext(), R.anim.fab_in);
|
||||
anim.setDuration(200);
|
||||
anim.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
|
||||
button.startAnimation(anim);
|
||||
}
|
||||
}
|
||||
|
||||
private void animateOut(FloatingActionButton button) {
|
||||
ViewCompat.animate(button)
|
||||
.scaleX(0f)
|
||||
.scaleY(0f)
|
||||
.alpha(0f)
|
||||
.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
|
||||
.withLayer()
|
||||
.setListener(new ViewPropertyAnimatorListener() {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {
|
||||
mIsAnimatingOut = true;
|
||||
}
|
||||
private void animateOut(final FloatingActionButton button) {
|
||||
if (Build.VERSION.SDK_INT >= 14) {
|
||||
ViewCompat.animate(button)
|
||||
.scaleX(0f)
|
||||
.scaleY(0f)
|
||||
.alpha(0f)
|
||||
.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
|
||||
.withLayer()
|
||||
.setListener(new ViewPropertyAnimatorListener() {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {
|
||||
mIsAnimatingOut = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(View view) {
|
||||
mIsAnimatingOut = false;
|
||||
}
|
||||
@Override
|
||||
public void onAnimationCancel(View view) {
|
||||
mIsAnimatingOut = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {
|
||||
mIsAnimatingOut = false;
|
||||
view.setVisibility(View.GONE);
|
||||
}
|
||||
}).start();
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {
|
||||
mIsAnimatingOut = false;
|
||||
view.setVisibility(View.GONE);
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
Animation anim = android.view.animation.AnimationUtils.loadAnimation(
|
||||
button.getContext(), R.anim.fab_out);
|
||||
anim.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
|
||||
anim.setDuration(200);
|
||||
anim.setAnimationListener(new AnimationUtils.AnimationListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animation animation) {
|
||||
mIsAnimatingOut = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animation animation) {
|
||||
mIsAnimatingOut = false;
|
||||
button.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
button.startAnimation(anim);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,6 +21,16 @@ import android.view.View;
|
||||
|
||||
class ViewUtils {
|
||||
|
||||
static final ValueAnimatorCompat.Creator DEFAULT_ANIMATOR_CREATOR
|
||||
= new ValueAnimatorCompat.Creator() {
|
||||
@Override
|
||||
public ValueAnimatorCompat createAnimator() {
|
||||
return new ValueAnimatorCompat(Build.VERSION.SDK_INT >= 12
|
||||
? new ValueAnimatorCompatImplHoneycombMr1()
|
||||
: new ValueAnimatorCompatImplEclairMr1());
|
||||
}
|
||||
};
|
||||
|
||||
private interface ViewUtilsImpl {
|
||||
void setBoundsViewOutlineProvider(View view);
|
||||
}
|
||||
@ -54,4 +64,8 @@ class ViewUtils {
|
||||
IMPL.setBoundsViewOutlineProvider(view);
|
||||
}
|
||||
|
||||
static ValueAnimatorCompat createAnimator() {
|
||||
return DEFAULT_ANIMATOR_CREATOR.createAnimator();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user