Java tutorial
/* * Copyright (C) 2016 Mobvoi Inc. * 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 ticwear.design.widget; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Typeface; import android.support.annotation.ColorInt; import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.StyleRes; import android.support.v4.view.GravityCompat; import android.support.v4.view.ViewCompat; import android.support.v4.view.WindowInsetsCompat; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.widget.FrameLayout; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import ticwear.design.R; /** * StretchingLayout is a layout can be stretching. * It is designed to be used as a direct child of a {@link AppBarLayout}. * StretchingLayout contains the following features: * * <h3>Stretching size</h3> * If the max height of this layout is set, the layout becomes stretching. As the user pull down * the layout, size of it changed with a resistance. * When the layout size come to max height, the size can not change any more. * You can change the max height by attributes or through the code with call to * {@link #setMaxStretchingHeight(int)}. * * <h3>Collapsing title</h3> * A title which is larger when the layout is fully visible but collapses and becomes smaller as * the layout is scrolled off screen. You can set the title to display via * {@link #setTitle(CharSequence)}. The title appearance can be tweaked via the * {@code collapsedTextAppearance} and {@code expandedTextAppearance} attributes. * * <h3>Parallax scrolling children</h3> * Child views can opt to be scrolled within this layout in a parallax fashion. * See {@link LayoutParams#COLLAPSE_MODE_PARALLAX} and * {@link LayoutParams#setParallaxMultiplier(float)}. * * <h3>Pinned position children</h3> * Child views can opt to be pinned in space globally. This is useful when implementing a * collapsing as it allows the {@link Toolbar} to be fixed in place even though this layout is * moving. See {@link LayoutParams#COLLAPSE_MODE_PIN}. * * @attr ref ticwear.design.R.styleable#CollapsingToolbarLayout_maxStrechingHeight * @attr ref ticwear.design.R.styleable#CollapsingToolbarLayout_collapsedTitleTextAppearance * @attr ref ticwear.design.R.styleable#CollapsingToolbarLayout_expandedTitleTextAppearance * @attr ref ticwear.design.R.styleable#CollapsingToolbarLayout_expandedTitleMargin * @attr ref ticwear.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginStart * @attr ref ticwear.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginEnd * @attr ref ticwear.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginBottom */ public class StretchingLayout extends FrameLayout { private int mExpandedMarginStart; private int mExpandedMarginTop; private int mExpandedMarginEnd; private int mExpandedMarginBottom; private final CollapsingTextHelper mCollapsingTextHelper; private boolean mCollapsingTitleEnabled; private AppBarLayout.OnOffsetChangedListener mOnOffsetChangedListener; private int mCurrentOffset; private WindowInsetsCompat mLastInsets; public StretchingLayout(Context context) { this(context, null); } public StretchingLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public StretchingLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mCollapsingTextHelper = new CollapsingTextHelper(this); mCollapsingTextHelper.setTextSizeInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CollapsingToolbarLayout, defStyleAttr, R.style.Widget_Ticwear_CollapsingToolbar); mCollapsingTextHelper .setExpandedTextGravity(a.getInt(R.styleable.CollapsingToolbarLayout_tic_expandedTitleGravity, GravityCompat.START | Gravity.BOTTOM)); mCollapsingTextHelper .setCollapsedTextGravity(a.getInt(R.styleable.CollapsingToolbarLayout_tic_collapsedTitleGravity, GravityCompat.START | Gravity.CENTER_VERTICAL)); mExpandedMarginStart = mExpandedMarginTop = mExpandedMarginEnd = mExpandedMarginBottom = a .getDimensionPixelSize(R.styleable.CollapsingToolbarLayout_tic_expandedTitleMargin, 0); if (a.hasValue(R.styleable.CollapsingToolbarLayout_tic_expandedTitleMarginStart)) { mExpandedMarginStart = a .getDimensionPixelSize(R.styleable.CollapsingToolbarLayout_tic_expandedTitleMarginStart, 0); } if (a.hasValue(R.styleable.CollapsingToolbarLayout_tic_expandedTitleMarginEnd)) { mExpandedMarginEnd = a .getDimensionPixelSize(R.styleable.CollapsingToolbarLayout_tic_expandedTitleMarginEnd, 0); } if (a.hasValue(R.styleable.CollapsingToolbarLayout_tic_expandedTitleMarginTop)) { mExpandedMarginTop = a .getDimensionPixelSize(R.styleable.CollapsingToolbarLayout_tic_expandedTitleMarginTop, 0); } if (a.hasValue(R.styleable.CollapsingToolbarLayout_tic_expandedTitleMarginBottom)) { mExpandedMarginBottom = a .getDimensionPixelSize(R.styleable.CollapsingToolbarLayout_tic_expandedTitleMarginBottom, 0); } mCollapsingTitleEnabled = a.getBoolean(R.styleable.CollapsingToolbarLayout_tic_titleEnabled, true); setTitle(a.getText(R.styleable.CollapsingToolbarLayout_android_title)); // First load the default text appearances mCollapsingTextHelper.setExpandedTextAppearance(R.style.TextAppearance_Ticwear_CollapsingToolbar_Expanded); mCollapsingTextHelper.setCollapsedTextAppearance(R.style.TextAppearance_Ticwear_TitleBar_Title); // Now overlay any custom text appearances if (a.hasValue(R.styleable.CollapsingToolbarLayout_tic_expandedTitleTextAppearance)) { mCollapsingTextHelper.setExpandedTextAppearance( a.getResourceId(R.styleable.CollapsingToolbarLayout_tic_expandedTitleTextAppearance, 0)); } if (a.hasValue(R.styleable.CollapsingToolbarLayout_tic_collapsedTitleTextAppearance)) { mCollapsingTextHelper.setCollapsedTextAppearance( a.getResourceId(R.styleable.CollapsingToolbarLayout_tic_collapsedTitleTextAppearance, 0)); } a.recycle(); setWillNotDraw(false); ViewCompat.setOnApplyWindowInsetsListener(this, new android.support.v4.view.OnApplyWindowInsetsListener() { @Override public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) { if (isShown()) { mLastInsets = insets; requestLayout(); return insets.consumeSystemWindowInsets(); } else { return insets; } } }); } @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 draw(Canvas canvas) { super.draw(canvas); // Let the collapsing text helper draw it's text if (mCollapsingTitleEnabled) { mCollapsingTextHelper.draw(canvas); } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); // Update the collapsed bounds by getting it's transformed bounds. This needs to be done // before the children are offset below if (mCollapsingTitleEnabled) { final boolean isRtl = ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL; // Update the collapsed bounds mCollapsingTextHelper.setCollapsedBounds(isRtl ? mExpandedMarginEnd : mExpandedMarginStart, bottom - getMinimumHeight(), right - left - (isRtl ? mExpandedMarginStart : mExpandedMarginEnd), bottom); // Update the expanded bounds mCollapsingTextHelper.setExpandedBounds(isRtl ? mExpandedMarginEnd : mExpandedMarginStart, top + mExpandedMarginTop, right - left - (isRtl ? mExpandedMarginStart : mExpandedMarginEnd), bottom - top - mExpandedMarginBottom); // Now recalculate using the new bounds mCollapsingTextHelper.recalculate(); } // Update our child view offset helpers for (int i = 0, z = getChildCount(); i < z; i++) { final View child = getChildAt(i); if (mLastInsets != null && !ViewCompat.getFitsSystemWindows(child)) { final int insetTop = mLastInsets.getSystemWindowInsetTop(); if (child.getTop() < insetTop) { // If the child isn't set to fit system windows but is drawing within the inset // offset it down child.offsetTopAndBottom(insetTop); } } getViewOffsetHelper(child).onViewLayout(); } } private static ViewOffsetHelper getViewOffsetHelper(View view) { ViewOffsetHelper offsetHelper = (ViewOffsetHelper) view.getTag(R.id.tic_view_offset_helper); if (offsetHelper == null) { offsetHelper = new ViewOffsetHelper(view); view.setTag(R.id.tic_view_offset_helper, offsetHelper); } return offsetHelper; } public void setMaxStretchingHeight(int maxHeight) { throw new RuntimeException("Stub!"); } public int getMaxStretchingHeight() { throw new RuntimeException("Stub!"); } /** * Sets the title to be displayed by this view, if enabled. * * @see #setTitleEnabled(boolean) * @see #getTitle() * * @attr ref R.styleable#CollapsingToolbarLayout_title */ public void setTitle(@Nullable CharSequence title) { mCollapsingTextHelper.setText(title); } /** * Returns the title currently being displayed by this view. If the title is not enabled, then * this will return {@code null}. * * @attr ref R.styleable#CollapsingToolbarLayout_title */ @Nullable public CharSequence getTitle() { return mCollapsingTitleEnabled ? mCollapsingTextHelper.getText() : null; } /** * Sets whether this view should display its own title. * * <p>The title displayed by this view will shrink and grow based on the scroll offset.</p> * * @see #setTitle(CharSequence) * @see #isTitleEnabled() * * @attr ref R.styleable#CollapsingToolbarLayout_titleEnabled */ public void setTitleEnabled(boolean enabled) { if (enabled != mCollapsingTitleEnabled) { mCollapsingTitleEnabled = enabled; requestLayout(); } } /** * Returns whether this view is currently displaying its own title. * * @see #setTitleEnabled(boolean) * * @attr ref R.styleable#CollapsingToolbarLayout_titleEnabled */ public boolean isTitleEnabled() { return mCollapsingTitleEnabled; } /** * Sets the text color and size for the collapsed title from the specified * TextAppearance resource. * * @attr ref ticwear.design.R.styleable#CollapsingToolbarLayout_collapsedTitleTextAppearance */ public void setCollapsedTitleTextAppearance(@StyleRes int resId) { mCollapsingTextHelper.setCollapsedTextAppearance(resId); } /** * Sets the text color of the collapsed title. * * @param color The new text color in ARGB format */ public void setCollapsedTitleTextColor(@ColorInt int color) { mCollapsingTextHelper.setCollapsedTextColor(color); } /** * Sets the horizontal alignment of the collapsed title and the vertical gravity that will * be used when there is extra space in the collapsed bounds beyond what is required for * the title itself. * * @attr ref ticwear.design.R.styleable#CollapsingToolbarLayout_collapsedTitleGravity */ public void setCollapsedTitleGravity(int gravity) { mCollapsingTextHelper.setCollapsedTextGravity(gravity); } /** * Returns the horizontal and vertical alignment for title when collapsed. * * @attr ref ticwear.design.R.styleable#CollapsingToolbarLayout_collapsedTitleGravity */ public int getCollapsedTitleGravity() { return mCollapsingTextHelper.getCollapsedTextGravity(); } /** * Sets the text color and size for the expanded title from the specified * TextAppearance resource. * * @attr ref ticwear.design.R.styleable#CollapsingToolbarLayout_expandedTitleTextAppearance */ public void setExpandedTitleTextAppearance(@StyleRes int resId) { mCollapsingTextHelper.setExpandedTextAppearance(resId); } /** * Sets the text color of the expanded title. * * @param color The new text color in ARGB format */ public void setExpandedTitleColor(@ColorInt int color) { mCollapsingTextHelper.setExpandedTextColor(color); } /** * Sets the horizontal alignment of the expanded title and the vertical gravity that will * be used when there is extra space in the expanded bounds beyond what is required for * the title itself. * * @attr ref ticwear.design.R.styleable#CollapsingToolbarLayout_expandedTitleGravity */ public void setExpandedTitleGravity(int gravity) { mCollapsingTextHelper.setExpandedTextGravity(gravity); } /** * Returns the horizontal and vertical alignment for title when expanded. * * @attr ref ticwear.design.R.styleable#CollapsingToolbarLayout_expandedTitleGravity */ public int getExpandedTitleGravity() { return mCollapsingTextHelper.getExpandedTextGravity(); } /** * Set the typeface to use for the collapsed title. * * @param typeface typeface to use, or {@code null} to use the default. */ public void setCollapsedTitleTypeface(@Nullable Typeface typeface) { mCollapsingTextHelper.setCollapsedTypeface(typeface); } /** * Returns the typeface used for the collapsed title. */ @NonNull public Typeface getCollapsedTitleTypeface() { return mCollapsingTextHelper.getCollapsedTypeface(); } /** * Set the typeface to use for the expanded title. * * @param typeface typeface to use, or {@code null} to use the default. */ public void setExpandedTitleTypeface(@Nullable Typeface typeface) { mCollapsingTextHelper.setExpandedTypeface(typeface); } /** * Returns the typeface used for the expanded title. */ @NonNull public Typeface getExpandedTitleTypeface() { return mCollapsingTextHelper.getExpandedTypeface(); } /** * Sets the expanded title margins. * * @param start the starting title margin in pixels * @param top the top title margin in pixels * @param end the ending title margin in pixels * @param bottom the bottom title margin in pixels * * @see #getExpandedTitleMarginStart() * @see #getExpandedTitleMarginTop() * @see #getExpandedTitleMarginEnd() * @see #getExpandedTitleMarginBottom() * @attr ref ticwear.design.R.styleable#CollapsingToolbarLayout_expandedTitleMargin */ public void setExpandedTitleMargin(int start, int top, int end, int bottom) { mExpandedMarginStart = start; mExpandedMarginTop = top; mExpandedMarginEnd = end; mExpandedMarginBottom = bottom; requestLayout(); } /** * @return the starting expanded title margin in pixels * * @see #setExpandedTitleMarginStart(int) * @attr ref ticwear.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginStart */ public int getExpandedTitleMarginStart() { return mExpandedMarginStart; } /** * Sets the starting expanded title margin in pixels. * * @param margin the starting title margin in pixels * @see #getExpandedTitleMarginStart() * @attr ref ticwear.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginStart */ public void setExpandedTitleMarginStart(int margin) { mExpandedMarginStart = margin; requestLayout(); } /** * @return the top expanded title margin in pixels * @see #setExpandedTitleMarginTop(int) * @attr ref ticwear.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginTop */ public int getExpandedTitleMarginTop() { return mExpandedMarginTop; } /** * Sets the top expanded title margin in pixels. * * @param margin the top title margin in pixels * @see #getExpandedTitleMarginTop() * @attr ref ticwear.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginTop */ public void setExpandedTitleMarginTop(int margin) { mExpandedMarginTop = margin; requestLayout(); } /** * @return the ending expanded title margin in pixels * @see #setExpandedTitleMarginEnd(int) * @attr ref ticwear.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginEnd */ public int getExpandedTitleMarginEnd() { return mExpandedMarginEnd; } /** * Sets the ending expanded title margin in pixels. * * @param margin the ending title margin in pixels * @see #getExpandedTitleMarginEnd() * @attr ref ticwear.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginEnd */ public void setExpandedTitleMarginEnd(int margin) { mExpandedMarginEnd = margin; requestLayout(); } /** * @return the bottom expanded title margin in pixels * @see #setExpandedTitleMarginBottom(int) * @attr ref ticwear.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginBottom */ public int getExpandedTitleMarginBottom() { return mExpandedMarginBottom; } /** * Sets the bottom expanded title margin in pixels. * * @param margin the bottom title margin in pixels * @see #getExpandedTitleMarginBottom() * @attr ref ticwear.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginBottom */ public void setExpandedTitleMarginBottom(int margin) { mExpandedMarginBottom = margin; requestLayout(); } @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof LayoutParams; } @Override protected LayoutParams generateDefaultLayoutParams() { return new LayoutParams(super.generateDefaultLayoutParams()); } @Override public FrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) { return new LayoutParams(getContext(), attrs); } @Override protected FrameLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { return new LayoutParams(p); } public static class LayoutParams extends FrameLayout.LayoutParams { private static final float DEFAULT_PARALLAX_MULTIPLIER = 0.5f; /** @hide */ @IntDef({ COLLAPSE_MODE_OFF, COLLAPSE_MODE_PIN, COLLAPSE_MODE_PARALLAX }) @Retention(RetentionPolicy.SOURCE) @interface CollapseMode { } /** * The view will act as normal with no collapsing behavior. */ public static final int COLLAPSE_MODE_OFF = 0; /** * The view will pin in place until it reaches the bottom of the * {@link StretchingLayout}. */ public static final int COLLAPSE_MODE_PIN = 1; /** * The view will scroll in a parallax fashion. See {@link #setParallaxMultiplier(float)} * to change the multiplier used. */ public static final int COLLAPSE_MODE_PARALLAX = 2; int mCollapseMode = COLLAPSE_MODE_OFF; float mParallaxMult = DEFAULT_PARALLAX_MULTIPLIER; public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.CollapsingAppBarLayout_LayoutParams); mCollapseMode = a.getInt(R.styleable.CollapsingAppBarLayout_LayoutParams_tic_layout_collapseMode, COLLAPSE_MODE_OFF); setParallaxMultiplier(a.getFloat( R.styleable.CollapsingAppBarLayout_LayoutParams_tic_layout_collapseParallaxMultiplier, DEFAULT_PARALLAX_MULTIPLIER)); a.recycle(); } public LayoutParams(int width, int height) { super(width, height); } public LayoutParams(int width, int height, int gravity) { super(width, height, gravity); } public LayoutParams(ViewGroup.LayoutParams p) { super(p); } public LayoutParams(MarginLayoutParams source) { super(source); } public LayoutParams(FrameLayout.LayoutParams source) { super(source); } /** * Set the collapse mode. * * @param collapseMode one of {@link #COLLAPSE_MODE_OFF}, {@link #COLLAPSE_MODE_PIN} * or {@link #COLLAPSE_MODE_PARALLAX}. */ public void setCollapseMode(@CollapseMode int collapseMode) { mCollapseMode = collapseMode; } /** * Returns the requested collapse mode. * * @return the current mode. One of {@link #COLLAPSE_MODE_OFF}, {@link #COLLAPSE_MODE_PIN} * or {@link #COLLAPSE_MODE_PARALLAX}. */ @CollapseMode public int getCollapseMode() { return mCollapseMode; } /** * Set the parallax scroll multiplier used in conjunction with * {@link #COLLAPSE_MODE_PARALLAX}. A value of {@code 0.0} indicates no movement at all, * {@code 1.0f} indicates normal scroll movement. * * @param multiplier the multiplier. * * @see #getParallaxMultiplier() */ public void setParallaxMultiplier(float multiplier) { mParallaxMult = multiplier; } /** * Returns the parallax scroll multiplier used in conjunction with * {@link #COLLAPSE_MODE_PARALLAX}. * * @see #setParallaxMultiplier(float) */ public float getParallaxMultiplier() { return mParallaxMult; } } private class OffsetUpdateListener implements AppBarLayout.OnOffsetChangedListener { @Override public void onOffsetChanged(AppBarLayout layout, int verticalOffset) { mCurrentOffset = verticalOffset; final int insetTop = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0; final int scrollRange = layout.getTotalScrollRange(); 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() - insetTop + verticalOffset >= child.getHeight()) { offsetHelper.setTopAndBottomOffset(-verticalOffset); } break; case LayoutParams.COLLAPSE_MODE_PARALLAX: offsetHelper.setTopAndBottomOffset(Math.round(-verticalOffset * lp.mParallaxMult)); break; } } // Update the collapsing text's fraction final int expandRange = getHeight() - ViewCompat.getMinimumHeight(StretchingLayout.this) - insetTop; mCollapsingTextHelper.setExpansionFraction(Math.abs(verticalOffset) / (float) expandRange); if (Math.abs(verticalOffset) == scrollRange) { // 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); } } } }